Conversation
…ero support Initialize new bridging-sdk package for cross-chain G$ token transfers. Includes comprehensive type definitions, chain configurations, and utility functions for decimal handling and fee estimation across Celo, Fuse, Ethereum, and XDC networks. Key components: - Bridge protocol types and event interfaces - Supported chain configurations with native currencies - Decimal normalization utilities for multi-chain operations - Fee estimation with GoodServer API integration - Build configuration with TypeScript and tsup setup
…dDollar bridging SDK Add new demo application showcasing GoodDollar bridging functionality with modern React stack. Includes TypeScript configuration, Vite build setup, and integration with Reown wallet adapter.
… app Added complete bridging SDK with Axelar and LayerZero protocol support, including: - Viem-based SDK implementation with bridge, fee estimation, and transaction tracking - Wagmi hooks for React integration (useBridgingSDK, useBridgeFee, useBridgeTransactionStatus) - Demo React application with bridge form, fee estimator, and transaction history components - Transaction status tracking via LayerZero Scan and Axelarscan APIs - Comprehensive documentation and type definitions - Support for Celo, Ethereum, Fuse, and XDC chains with proper decimal handling - Contract ABI definitions for MessagePassingBridge - Enhanced error handling and validation utilities The SDK provides a complete solution for cross-chain G$ token bridging with proper fee estimation, transaction tracking, and React integration.
- Add tokenAddress property to BridgeChain interface and constants - Replace native balance reading with G$ token contract balance using useReadContract - Update fee estimation route keys to use AXL/LZ prefixes instead of full protocol names - Change projectId configuration for wallet connection
There was a problem hiding this comment.
Hey - I've found 4 security issues, 6 other issues, and left some high level feedback:
Security issues:
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
General comments:
- The SDK methods for
bridgeToWithLzandbridgeToWithAxelarinviem-sdk.tsdon’t match the contract ABI: the ABI signatures arebridgeToWithLz(address,uint256,uint256)/bridgeToWithLzAdapterParams(address,uint256,uint256,bytes)andbridgeToWithAxelar(address,uint256,uint256), but the SDK callsbridgeToWithLzwith four args (includingadapterParams) andbridgeToWithAxelarwith agasRefundAddressarg; this should be reconciled to avoid runtime call failures. - The demo app mixes Tailwind via CDN (
<script src="https://cdn.tailwindcss.com">) with@tailwinddirectives inindex.cssbut there is no Tailwind/PostCSS build setup in the Vite config, so the@tailwindstyles won’t be processed; consider either wiring Tailwind into the build or dropping the@tailwindusage and relying solely on the CDN.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The SDK methods for `bridgeToWithLz` and `bridgeToWithAxelar` in `viem-sdk.ts` don’t match the contract ABI: the ABI signatures are `bridgeToWithLz(address,uint256,uint256)` / `bridgeToWithLzAdapterParams(address,uint256,uint256,bytes)` and `bridgeToWithAxelar(address,uint256,uint256)`, but the SDK calls `bridgeToWithLz` with four args (including `adapterParams`) and `bridgeToWithAxelar` with a `gasRefundAddress` arg; this should be reconciled to avoid runtime call failures.
- The demo app mixes Tailwind via CDN (`<script src="https://cdn.tailwindcss.com">`) with `@tailwind` directives in `index.css` but there is no Tailwind/PostCSS build setup in the Vite config, so the `@tailwind` styles won’t be processed; consider either wiring Tailwind into the build or dropping the `@tailwind` usage and relying solely on the CDN.
## Individual Comments
### Comment 1
<location> `packages/bridging-sdk/src/viem-sdk.ts:208-209` </location>
<code_context>
+ {
+ address: contractAddress as Address,
+ abi: MESSAGE_PASSING_BRIDGE_ABI,
+ functionName: "bridgeToWithLz",
+ args: [target, targetChainId, amount, adapterParams || "0x"],
+ value: feeEstimate.fee,
+ },
</code_context>
<issue_to_address>
**issue (bug_risk):** LayerZero bridge call uses the wrong contract function and argument shape for adapterParams.
`bridgeToWithLz` in the ABI only accepts `(target, targetChainId, amount)`, while `bridgeToWithLzAdapterParams` is the 4-arg variant that includes `adapterParams`. This call uses `bridgeToWithLz` but supplies four args, so it will fail against the ABI. Either switch to `bridgeToWithLzAdapterParams` and keep four args, or remove `adapterParams` and use the 3-arg form.
</issue_to_address>
### Comment 2
<location> `packages/bridging-sdk/src/viem-sdk.ts:253-254` </location>
<code_context>
+ {
+ address: contractAddress as Address,
+ abi: MESSAGE_PASSING_BRIDGE_ABI,
+ functionName: "bridgeToWithAxelar",
+ args: [target, targetChainId, amount, gasRefundAddress || target],
+ value: feeEstimate.fee,
+ },
</code_context>
<issue_to_address>
**issue (bug_risk):** Axelar bridge call passes an extra argument that is not present in the ABI.
`bridgeToWithAxelar` in `MESSAGE_PASSING_BRIDGE_ABI` accepts `(target, targetChainId, amount)`, but this call passes a fourth arg `gasRefundAddress`. The mismatched signature will revert. Use a function that supports a gas refund address, or drop the extra parameter and implement refund logic on-chain instead.
</issue_to_address>
### Comment 3
<location> `apps/demo-bridging-app/src/components/TransactionHistory.tsx:51` </location>
<code_context>
+ data: exec,
+ protocol: exec.args.bridge,
+ })),
+ ].sort((a, b) => Number(b.data.blockNumber - a.data.blockNumber))
+
+ setTransactions(allTransactions)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Sorting by blockNumber via Number() risks precision issues for large block heights.
Using `Number` on a bigint difference can silently lose precision once block heights exceed `Number.MAX_SAFE_INTEGER`. Compare the bigints directly instead, e.g.:
```ts
.sort((a, b) =>
a.data.blockNumber === b.data.blockNumber
? 0
: a.data.blockNumber < b.data.blockNumber
? 1
: -1,
)
```
```suggestion
].sort((a, b) =>
a.data.blockNumber === b.data.blockNumber
? 0
: a.data.blockNumber < b.data.blockNumber
? 1
: -1,
)
```
</issue_to_address>
### Comment 4
<location> `apps/demo-bridging-app/src/components/TransactionHistory.tsx:182` </location>
<code_context>
+ </span>
+ <span className="text-slate-300">•</span>
+ <span className="text-slate-400 text-sm font-medium">
+ {formatTimestamp(Number(transaction.data.blockNumber) * 1000)}
+ </span>
+ </div>
</code_context>
<issue_to_address>
**issue (bug_risk):** Using blockNumber as a timestamp will produce incorrect transaction times.
`blockNumber` is the chain height, not a UNIX timestamp, so multiplying it by 1000 and passing it to `formatTimestamp` yields invalid dates. To show the real time, use a timestamp field on the event/log if available, or fetch the block with `publicClient.getBlock({ blockNumber })` and use its `timestamp`.
</issue_to_address>
### Comment 5
<location> `packages/bridging-sdk/src/viem-sdk.ts:129` </location>
<code_context>
+ /**
+ * Generic bridge method that automatically selects the best protocol
+ */
+ async bridgeTo(
+ target: Address,
+ targetChainId: ChainId,
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the shared fee estimation/validation and contract lookup logic from the three `bridgeTo*` methods into a single private helper to DRY them up and make each public method thinner and easier to read.
You can reduce the duplication in the three `bridgeTo*` methods by centralizing the common fee-handling + contract lookup + submission logic into a small private helper, and keeping the public methods as thin wrappers.
For example:
```ts
private async bridgeInternal<TArgs extends any[]>(opts: {
targetChainId: ChainId
protocol: BridgeProtocol
msgValue?: bigint
fn: "bridgeTo" | "bridgeToWithLz" | "bridgeToWithAxelar"
args: TArgs
}): Promise<TransactionReceipt> {
if (!this.walletClient) {
throw new Error("Wallet client not initialized")
}
const feeEstimate = await this.estimateFee(opts.targetChainId, opts.protocol)
const providedValue = opts.msgValue ?? 0n
const feeValidation = validateFeeCoverage(providedValue, feeEstimate.fee)
if (!feeValidation.isValid) {
throw new Error(feeValidation.error)
}
const contractAddress = BRIDGE_CONTRACT_ADDRESSES[this.currentChainId]
if (!contractAddress) {
throw new Error(`Bridge contract not deployed on chain ${this.currentChainId}`)
}
return await this.submitAndWait(
{
address: contractAddress as Address,
abi: MESSAGE_PASSING_BRIDGE_ABI,
functionName: opts.fn,
args: opts.args,
value: feeEstimate.fee,
},
feeEstimate.fee,
)
}
```
Then the three public methods become much smaller and easier to scan:
```ts
async bridgeTo(
target: Address,
targetChainId: ChainId,
amount: bigint,
protocol: BridgeProtocol,
msgValue?: bigint,
): Promise<TransactionReceipt> {
return this.bridgeInternal({
targetChainId,
protocol,
msgValue,
fn: "bridgeTo",
args: [target, targetChainId, amount, protocol === "LAYERZERO" ? 0 : 1],
})
}
async bridgeToWithLz(
target: Address,
targetChainId: ChainId,
amount: bigint,
adapterParams?: `0x${string}`,
msgValue?: bigint,
): Promise<TransactionReceipt> {
return this.bridgeInternal({
targetChainId,
protocol: "LAYERZERO",
msgValue,
fn: "bridgeToWithLz",
args: [target, targetChainId, amount, adapterParams || "0x"],
})
}
async bridgeToWithAxelar(
target: Address,
targetChainId: ChainId,
amount: bigint,
gasRefundAddress?: Address,
msgValue?: bigint,
): Promise<TransactionReceipt> {
return this.bridgeInternal({
targetChainId,
protocol: "AXELAR",
msgValue,
fn: "bridgeToWithAxelar",
args: [target, targetChainId, amount, gasRefundAddress || target],
})
}
```
This keeps all existing behavior (including protocol-specific arguments and fee usage) but removes the repeated fee estimation/validation and contract resolution logic, making future changes to fee handling or contract lookup a single-point edit.
</issue_to_address>
### Comment 6
<location> `packages/bridging-sdk/src/utils/fees.ts:31` </location>
<code_context>
+/**
+ * Parses a fee string from the GoodServer API (e.g., "4.8367843657257685 Celo")
+ */
+export function parseNativeFee(feeString: string, chainId: ChainId): bigint {
+ const [amountStr] = feeString.split(" ")
+ if (!amountStr) {
</code_context>
<issue_to_address>
**issue (complexity):** Consider reusing existing decimal and validation helpers from shared utilities so this fees module delegates to common logic instead of reimplementing bigint parsing/formatting and balance checks.
You can reduce overlap with `utils/decimals.ts` by reusing its helpers instead of re‑implementing bigint/decimal logic here. This keeps functionality the same while collapsing abstractions.
### 1. Reuse shared decimal parsing/formatting
If `utils/decimals.ts` already exposes something like `parseAmount` and `formatAmount`, you can delegate `parseNativeFee` and `formatFee` to those:
```ts
// fees.ts
import { parseAmount, formatAmount } from "../utils/decimals"
import { SUPPORTED_CHAINS } from "../constants"
import type { ChainId } from "../types"
export function parseNativeFee(feeString: string, chainId: ChainId): bigint {
const [amountStr] = feeString.split(" ")
if (!amountStr) {
throw new Error("Invalid fee format")
}
const decimals = SUPPORTED_CHAINS[chainId]?.decimals ?? 18
return parseAmount(amountStr, decimals)
}
export function formatFee(fee: bigint, chainId: ChainId): string {
const decimals = SUPPORTED_CHAINS[chainId]?.decimals ?? 18
return formatAmount(fee, decimals)
}
```
This removes the duplicated divisor/whole/fractional logic and the custom float → bigint handling while preserving behavior.
### 2. Consolidate balance/fee validation
`validateFeeCoverage` and `validateSufficientBalance` follow the same pattern as the balance validation in `utils/decimals.ts`. You can centralize the core comparison and customize only the message:
```ts
// utils/decimals.ts (or a balances helper)
export function validateAtLeast(
available: bigint,
required: bigint,
errorBuilder: (available: bigint, required: bigint) => string,
): { isValid: boolean; error?: string } {
if (available < required) {
return {
isValid: false,
error: errorBuilder(available, required),
}
}
return { isValid: true }
}
```
Then in this module:
```ts
import { validateAtLeast } from "../utils/decimals"
export function validateFeeCoverage(
msgValue: bigint,
requiredFee: bigint,
): { isValid: boolean; error?: string } {
return validateAtLeast(msgValue, requiredFee, (available, required) =>
`Insufficient fee. Required: ${required.toString()}, Provided: ${available.toString()}`,
)
}
export function validateSufficientBalance(
userBalance: bigint,
bridgeAmount: bigint,
fee: bigint,
): { isValid: boolean; error?: string } {
const totalCost = bridgeAmount + fee
return validateAtLeast(userBalance, totalCost, (available, required) =>
`Insufficient balance. Required: ${required.toString()}, Available: ${available.toString()}`,
)
}
```
This keeps your specific error messages and semantics while avoiding multiple “which validation helper do I use?” entry points.
</issue_to_address>
### Comment 7
<location> `packages/bridging-sdk/src/constants.ts:8` </location>
<code_context>
0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 8
<location> `packages/bridging-sdk/src/constants.ts:19` </location>
<code_context>
0x495d133B938596C9984d462F007B676bDc57eCEC
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 9
<location> `packages/bridging-sdk/src/constants.ts:30` </location>
<code_context>
0x67C5870b4A41D4Ebef24d2456547A03F1f3e094B
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>
### Comment 10
<location> `packages/bridging-sdk/src/constants.ts:41` </location>
<code_context>
0xA13625A72Aef90645CfCe34e25c114629d7855e7
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…ling Extract common bridge transaction logic into bridgeInternal method to reduce code duplication across bridge methods. Update fee parsing and formatting to use centralized utility functions. feat(demo-bridging-app): add fee estimate display with loading state fix(demo-bridging-app): correct transaction sorting and timestamp formatting
… transaction display - Replace Tailwind utility classes with raw CSS equivalents in index.css - Add proper type checking for timestamp display in TransactionHistory component - Add gitleaks allow comments for public token contract addresses This removes the Tailwind CSS build dependency while maintaining the same visual appearance and improves type safety for transaction timestamp handling.
…nt for better maintainability
…allow directives Temporarily comment out actual token contract addresses in SUPPORTED_CHAINS and add gitleaks:allow directives to prevent security scanning tools from flagging these public contract addresses as secrets. This maintains the structure while removing sensitive-looking content from the codebase.
|
Hey @Godbrand0 there have not been commits since last tuesday, what is the active status of this PR? |
|
hello @L03TJ3 the sdk is finished up and i want to hear a feedback from you before proceeding with anything else. |
|
Okay, will follow up tomorrow. @Godbrand0 |
|
hello, have you reviewed it @L03TJ3 |
L03TJ3
left a comment
There was a problem hiding this comment.
Seem to have forgotten a reference document.
Read up on: https://docs.gooddollar.org/user-guides/bridge-gooddollars
things like 'approval of spending tokens' is not handled.
Provided code also seems to not been tested, the demo does not work.
If unsure about something, ask questions. here in the PR or on telegram
| @@ -0,0 +1,157 @@ | |||
| import { useEffect, useState } from "react" | |||
There was a problem hiding this comment.
We ship our core sdk's with wagmi hooks and viem core sdk separately. this should be part of the react-hooks package
| BridgeParams, | ||
| BridgeParamsWithLz, | ||
| BridgeParamsWithAxelar, |
There was a problem hiding this comment.
Why is this imported if not used?
| ) | ||
| } | ||
|
|
||
| const fromBlock = options?.fromBlock || 0n |
There was a problem hiding this comment.
when no options are provided it goes to fetch all blocks since genesis.
this is: 1: inefficient, 2: not a range that is allowed in most rpcs.
something like fromBlock = -5000 should be sufficient.
| /** | ||
| * Generates an explorer link for a bridge transaction | ||
| */ | ||
| getExplorerLink(txHash: Hash, protocol: BridgeProtocol): string { |
There was a problem hiding this comment.
Why are all these sdk methods single methods with external utilities?
does not make sense, can just be part of the sdk
| toChainId: ChainId, | ||
| protocol: BridgeProtocol, | ||
| ): Promise<FeeEstimate> { | ||
| const feeData = await fetchFeeEstimates() |
There was a problem hiding this comment.
Fees are static, does not make sense to fetch everytime.
- following other comment, all this should just be part of the sdk
- fees should be fetched upon initializing the sdk and set as class properties
L03TJ3
left a comment
There was a problem hiding this comment.
Can you also provide a demo-video of the flow
| getCurrentChainId(): ChainId { | ||
| return this.currentChainId | ||
| } | ||
|
|
||
| /** | ||
| * Gets the supported chains | ||
| */ | ||
| getSupportedChains(): Record<number, (typeof SUPPORTED_CHAINS)[0]> { | ||
| return SUPPORTED_CHAINS | ||
| } | ||
|
|
||
| /** |
|
i will work on the requested changes, and get back to you. thanks for the feedback |
| ? 0 | ||
| : a.data.blockNumber < b.data.blockNumber | ||
| ? 1 | ||
| : -1, |
There was a problem hiding this comment.
This is exactly what the sdk should support in simplifying.
the complex logic should be handled within the sdk not in the UI
…oving `wagmi-sdk` and `tracking` utils, and add decimal handling tests.
Description
This PR introduces the new Bridging SDK to the GoodSDKs monorepo, enabling cross-chain G$ token transfers using Axelar and LayerZero protocols. The implementation follows the established patterns from other SDK packages in the repository, particularly the citizen-sdk and engagement-sdk, to maintain consistency across the codebase.
The PR includes both the core SDK package and a demo application that showcases its functionality.
closes #26
Implementation Approach
We referenced the structure and patterns from existing SDKs, demo apps and (https://github.com/GoodDollar/GoodBridge/tree/master/packages/bridge-contracts/contracts/messagePassingBridge) :
SDK Package Structure
Following the citizen-sdk and engagement-sdk layout with:
Demo Application Structure
Following the demo-identity-app pattern:
Code Organization
SDK Package (packages/bridging-sdk)
Demo Application (apps/demo-bridging-app)
Key Features
Dependencies
SDK Dependencies
Demo App Dependencies
The demo app includes:
Checklist:
@sirpy
Summary by Sourcery
Add a new cross-chain GoodDollar bridging SDK package and a demo React app showcasing G$ transfers across supported chains via Axelar and LayerZero.
New Features:
Enhancements: