Skip to content

Bridging-sdk#32

Open
Godbrand0 wants to merge 17 commits intoGoodDollar:mainfrom
Godbrand0:godbrand
Open

Bridging-sdk#32
Godbrand0 wants to merge 17 commits intoGoodDollar:mainfrom
Godbrand0:godbrand

Conversation

@Godbrand0
Copy link
Copy Markdown

@Godbrand0 Godbrand0 commented Feb 16, 2026

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:

  • Similar package.json configuration with matching scripts, exports, and dependency structure
  • Consistent tsup.config.ts build configuration with ESM/CJS dual output
  • Standardized directory organization with src/index.ts as the main entry point

Demo Application Structure

Following the demo-identity-app pattern:

  • Similar Vite configuration with React plugin and path aliases
  • Consistent package.json structure with workspace dependencies
  • Standardized component organization in src/components/
  • Matching TypeScript configuration

Code Organization

SDK Package (packages/bridging-sdk)

  • Core SDK: viem-sdk.ts for blockchain interactions
  • React Integration: wagmi-sdk.ts for wagmi-compatible hooks
  • Utilities: Modular utilities in dedicated folders:
    • utils/decimals.ts: Amount formatting and conversion
    • utils/fees.ts: Fee estimation and calculation
    • utils/tracking.ts: Transaction status monitoring
  • Types: Comprehensive TypeScript definitions in types.ts
  • Constants: Chain configurations and contract addresses in constants.ts

Demo Application (apps/demo-bridging-app)

  • Main Components:
    • BridgeForm.tsx: Complete bridge interface with amount input and chain selection
    • FeeEstimator.tsx: Real-time fee calculation display
    • TransactionHistory.tsx: Transaction tracking and status monitoring
  • Configuration: config.tsx with wagmi and app configuration
  • Styling: Consistent CSS following the demo app patterns

Key Features

  • Multi-Protocol Support: Implements both Axelar and LayerZero bridging protocols
  • Fee Estimation: Comprehensive fee calculation including gas fees, protocol fees, and server fees
  • Transaction Tracking: Real-time status monitoring with polling mechanism
  • Type Safety: Full TypeScript support with comprehensive type definitions
  • React Integration: Wagmi-compatible hooks for seamless React integration
  • User-Friendly Interface: Clean demo app with intuitive bridge flow

Dependencies

SDK Dependencies

  • viem: For Ethereum client interactions
  • wagmi: For React integration
  • React: As a peer dependency for React components
  • tsup: For building the package

Demo App Dependencies

  • @goodsdks/bridging-sdk: The bridging SDK (workspace dependency)
  • @reown/appkit: For wallet connection
  • React & React DOM: UI framework
  • viem & wagmi: Ethereum interaction
  • Vite: Build tool

The demo app includes:

  • Bridge form with amount input and chain selection
  • Fee estimation display with breakdown
  • Transaction history with status tracking
  • Error handling and user feedback
  • Wallet connection via Reown AppKit

Checklist:

  • PR title matches follow: (Feature|Bug|Chore) Task Name
  • My code follows the style guidelines of this project
  • I have followed all the instructions described in the initial task (check Definitions of Done)
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • [ ] New and existing unit tests pass locally with my changes
  • I have added reference to a related issue in the repository
  • I have added a detailed description of the changes proposed in the pull request. I am as descriptive as possible, assisting reviewers as much as possible.
  • I have added screenshots related to my pull request (for frontend tasks)
  • I have pasted a gif showing the feature.
  • @mentions of the person or team responsible for reviewing proposed changes
image

@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:

  • Introduce @goodsdks/bridging-sdk package providing a viem-based BridgingSDK class, React/wagmi hooks, and utilities for cross-chain G$ bridging.
  • Support multi-chain configuration for Celo, Ethereum, Fuse, and XDC with chain metadata, bridge contract addresses, and decimal normalization utilities.
  • Expose fee estimation via GoodServer, fee validation, and transaction status tracking through LayerZero and Axelar explorer APIs.
  • Provide a demo-bridging-app React/Vite application with wallet connection, bridge form, fee estimator, and transaction history UI to demonstrate SDK usage.

Enhancements:

  • Add TypeScript types, constants, and ABI definitions to standardize bridging interactions and event handling across chains.
  • Set up build configuration for the bridging SDK with tsup and TypeScript to emit ESM/CJS bundles and type declarations.
  • Configure demo app tooling (Vite, Tailwind CSS import, wagmi/Reown AppKit setup) for local development and showcasing the SDK.

demo video: https://www.loom.com/share/100f40fb7fc04afa8abe20dbb50bc6ac

…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
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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.
…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.
@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Feb 23, 2026

Hey @Godbrand0 there have not been commits since last tuesday, what is the active status of this PR?

@Godbrand0
Copy link
Copy Markdown
Author

hello @L03TJ3 the sdk is finished up and i want to hear a feedback from you before proceeding with anything else.
can you take a look at the pr and let me know your feedback.

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Feb 23, 2026

Okay, will follow up tomorrow. @Godbrand0
to not get PR's stalled, please use the 'request review' buttons. this can also be done for reviews when its not 100% finalized (just share in a comment that its a in-between review or something)

@Godbrand0
Copy link
Copy Markdown
Author

hello, have you reviewed it @L03TJ3

Copy link
Copy Markdown
Collaborator

@L03TJ3 L03TJ3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We ship our core sdk's with wagmi hooks and viem core sdk separately. this should be part of the react-hooks package

Comment on lines +25 to +27
BridgeParams,
BridgeParamsWithLz,
BridgeParamsWithAxelar,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this imported if not used?

)
}

const fromBlock = options?.fromBlock || 0n
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fees are static, does not make sense to fetch everytime.

  1. following other comment, all this should just be part of the sdk
  2. fees should be fetched upon initializing the sdk and set as class properties

Copy link
Copy Markdown
Collaborator

@L03TJ3 L03TJ3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also provide a demo-video of the flow

Comment on lines +347 to +358
getCurrentChainId(): ChainId {
return this.currentChainId
}

/**
* Gets the supported chains
*/
getSupportedChains(): Record<number, (typeof SUPPORTED_CHAINS)[0]> {
return SUPPORTED_CHAINS
}

/**
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no single line helpers

@Godbrand0
Copy link
Copy Markdown
Author

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,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Mar 2, 2026

@Godbrand0 Don't forget to re-request review (using the button top-right of the pullrequest)
once all changes have been done :)

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Mar 6, 2026

is this ready for review? @Godbrand0

@Godbrand0
Copy link
Copy Markdown
Author

hello @L03TJ3 i am done with the changes, but im tryto to get celo to test the sdk.

@Godbrand0
Copy link
Copy Markdown
Author

is there a way to test out the bridge using test tokens? @L03TJ3

…ages

- Replace custom balance fetching with Wagmi useBalance hook for cross-chain balance display
- Add fee caching with 60s TTL to reduce API calls and improve performance
- Implement CORS proxy for GoodServer API to prevent browser blocking
- Enhance bridge limit error messages with detailed minimum amount information
- Add automatic source/destination chain swapping to prevent same-chain selection
- Update LayerZero API endpoint and response structure for transaction status tracking
- Prevent unnecessary SDK re-initialization when wallet client changes
@Godbrand0
Copy link
Copy Markdown
Author

here is a link to the demo video: https://www.loom.com/share/100f40fb7fc04afa8abe20dbb50bc6ac

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Mar 18, 2026

When testing the demo I get invalid route?
image

@Godbrand0

Comment on lines +45 to +84
* Formats a bigint amount to a human-readable string with the specified decimals
*/
export function formatAmount(amount: bigint, decimals: number): string {
const divisor = 10n ** BigInt(decimals)
const whole = amount / divisor
const fractional = amount % divisor

if (fractional === 0n) {
return whole.toString()
}

const fractionalStr = fractional.toString().padStart(decimals, "0")
const trimmedFractional = fractionalStr.replace(/0+$/, "")

return `${whole}.${trimmedFractional}`
}

/**
* Parses a human-readable amount string to a bigint with the specified decimals
*/
export function parseAmount(amount: string, decimals: number): bigint {
if (!amount || amount === "0") return 0n

const parts = amount.split(".")
let wholeStr = parts[0] || "0"
let fractionalStr = parts[1] || ""

// Pad or truncate fractional part to match decimals
if (fractionalStr.length > decimals) {
fractionalStr = fractionalStr.slice(0, decimals)
} else {
fractionalStr = fractionalStr.padEnd(decimals, "0")
}

const whole = BigInt(wholeStr)
const fractional = BigInt(fractionalStr || "0")
const divisor = 10n ** BigInt(decimals)

return whole * divisor + fractional
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Viem provides formatUnit/parseUnits already

@@ -0,0 +1,132 @@
import { SUPPORTED_CHAINS, NORMALIZED_DECIMALS } from "../constants"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helpers that are not used

"react": "^18",
"typescript": "latest",
"viem": "latest",
"wagmi": "latest"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sdk itself should be headless.
thats why we don't include wagmi in the sdks but have the package react-hooks.
should be removed from deps

test-decimals.ts Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testing scripts like this should not be included in final PR

Comment on lines +304 to +312
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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fees are static. once they are fetched they don't change very often if at all.
seems a bit redundant to both do it on the UI level, and then have it pass down to do the same check again.

The SDK should make integration and logic done on the UI level simple.
fee-estimates etc, all should be handled from within the sdk.

In UI you would just want to see:

  • fetch fees (I don't know why we would need a specific hook for this if it can be provided by the sdk itself?)
  • canBridge (for validation)
  • call sdk.bridgeTo(params)


// Protocol support validation
if (protocol === "AXELAR" && (sourceChain === 50 || sourceChain === 122 || targetChainId === 50 || targetChainId === 122)) {
throw new Error(`Axelar bridging is not supported for ${SUPPORTED_CHAINS[sourceChain].name} or ${SUPPORTED_CHAINS[targetChainId].name}`)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if chainId passed down is unsupported it will throw a type error

Suggested change
throw new Error(`Axelar bridging is not supported for ${SUPPORTED_CHAINS[sourceChain].name} or ${SUPPORTED_CHAINS[targetChainId].name}`)
throw new Error(`Axelar bridging is not supported for ${SUPPORTED_CHAINS[sourceChain]?.name} or ${SUPPORTED_CHAINS[targetChainId]?.name}`)

Comment on lines +459 to +461
const response = await fetch(`${API_ENDPOINTS.LAYERZERO_SCAN}/messages/tx/${txHash}`)
const json = await response.json()
const data: LayerZeroScanResponse = json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before parsing json you want to check response.ok

if (!response.ok) { // throw error}

AXELARSCAN: "https://api.axelarscan.io",
} as const

export const EXPLORER_URLS = {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove helpers/methods that are not being used.

@Godbrand0 Godbrand0 requested review from L03TJ3 March 19, 2026 13:26
@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented Mar 25, 2026

To make it easier to review changes you did:

  • Make the commit clarative to what is being updated
  • reply to comments with what is fixed and where (or what has been not fixed for reason X)
    @Godbrand0

- SDK: refactored to use native viem utilities and added multi-chain event retrieval
- SDK: reduced block query range to 10k for RPC compatibility and added response.ok checks
- UI: implemented unified cross-chain history view across all supported networks
- UI: improved status tracking logic and resolved history rendering crash
- Config: optimized polling intervals and switched to more stable Ethereum RPC
@Godbrand0
Copy link
Copy Markdown
Author

🛠️ Bridging SDK Enhancements
Robust Refactor: Migrated to native viem utilities (formatUnits, parseUnits) for cleaner code and smaller bundle size.
RPC Stability: Reduced the event query range to 10,000 blocks to ensure compatibility with free-tier RPC providers and prevent "block range exceeded" errors.
API Reliability: Added response.ok validation for all external API calls (LayerZero/Axelar status trackers).
Multi-Chain Support: Implemented getAllHistory static method to aggregate bridge events across all supported networks simultaneously.

React Hooks & Config
Orchestrated History: Updated useBridgeHistory to handle multi-client queries, enabling a global view of all user transactions.
Stable RPC Providers: Switched the Ethereum provider to Cloudflare to mitigate 429 Too Many Requests errors.
Optimized Polling: Balanced UI responsiveness and RPC load by increasing balance/allowance polling intervals to 30 seconds.

Demo App UI Refinements
Unified Transaction History: All historical bridge transactions across Celo, Fuse, Ethereum, and XDC are now consolidated into a single list.
Dynamic Status Tracking: "Initiated" transactions now automatically update to "Completed" (green indicator) once verified on the destination chain.
Crash Fix: Resolved a critical timestamp rendering error in the history component.
Verified Badge: Added a "Verified" status badge for tracked cross-chain transfers.

@@ -0,0 +1,1111 @@
export const MESSAGE_PASSING_BRIDGE_ABI = [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can take example how we use ABI's in our other sdks'.
we only need the methods that we are going to use, not a full ABI.

export const identityV2ABI = parseAbi([

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. packages/bridging-sdk/src/abi.ts — Use parseAbi with only needed methods

Reviewer: Only include methods that are actually used, following the pattern in citizen-sdk.

Fix: Rewrote abi.ts to use parseAbi from viem with only the 5 functions and 2 events the SDK actually calls:

  • canBridge, bridgeLimits (reads)
  • bridgeTo, bridgeToWithAxelar, bridgeToWithLzAdapterParams (writes)
  • BridgeRequest, ExecutedTransfer (events)


### React Integration

```tsx
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong example. useBridgingSDK is part of the react-hooks package right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. packages/bridging-sdk/README.md — Wrong useBridgingSDK package

Reviewer: useBridgingSDK is from @goodsdks/react-hooks, not @goodsdks/bridging-sdk.

Fix: Updated the React Integration example to import from @goodsdks/react-hooks.

@@ -0,0 +1,420 @@
# GoodDollar Bridging SDK
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readme seems out of date with what is available

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useBridgingSDK from wrong package, denormalizeAmount, getExplorerLink, extra bridge param

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3. packages/bridging-sdk/README.md — Out-of-date content (denormalizeAmount, getExplorerLink, extra bridge param)

Reviewer: README is out of date — denormalizeAmount doesn't exist in exports, getExplorerLink is wrong (method is explorerLink), and bridgeTo was shown with an extra fee param it doesn't take.

Fix:

  • Removed denormalizeAmount (not exported; only normalizeAmount is).
  • Renamed getExplorerLinkexplorerLink throughout.
  • Removed the extra feeEstimate.fee argument from all bridgeTo/bridgeToWithLz/bridgeToWithAxelar examples — the SDK handles fee internally.

@@ -0,0 +1,33 @@
{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please align package versions with demo-identity-app (where possible).
this will reduce the changes in yarn.lock massively.

Also make sure to only add packages you actually need/use

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4. apps/demo-bridging-app/package.json — Align versions with demo-identity-app

Reviewer: Align package versions with demo-identity-app where possible to reduce yarn.lock churn, and remove unused packages.

Fix:

  • Aligned @reown/appkit and @reown/appkit-adapter-wagmi to ^1.7.2.
  • Aligned react/react-dom to ^18.3.1.
  • Aligned devDependencies: typescript^5.8.2, vite6.3.5, @typescript-eslint/*^5.62.0, @vitejs/plugin-react^4.3.4, eslint^8.57.1.
  • Added @goodsdks/react-hooks (used in BridgeForm.tsx) and @tanstack/react-query (used in config.tsx) which were missing.
  • viem and wagmi are kept at v2 since the bridging SDK requires wagmi v2 APIs (vs v1 in the identity app).

const allHistoryFiles = await Promise.all(historyPromises)
const combined = allHistoryFiles.flat()

return combined.sort((a, b) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are combining all histories cross-chain, and then finaly sort the combined list on blocknumbers.
blocknumbers are chain-specific and this will result in a wrong order

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5. packages/bridging-sdk/src/viem-sdk.ts — Cross-chain history sorted by chain-specific block numbers

Reviewer: Block numbers are chain-specific — combining all chains then sorting by blockNumber gives a wrong order.

Fix: Extracted a private static _fetchChainHistory(address, publicClient, chainId, options) helper. Each chain's events are sorted by block number within that chain. getAllHistory no longer applies a cross-chain sort — it flattens per-chain results in place.

Comment on lines +344 to +346
const chainId = Number(chainIdStr) as ChainId
const sdk = new BridgingSDK(client, undefined, chainId)
return await sdk.getHistory(address, options)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why initializing a new chainSdk, and not just make getHistory accept a param chainId?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6. packages/bridging-sdk/src/viem-sdk.tsgetAllHistory creates new SDK instances per chain

Reviewer: Why initialize a new BridgingSDK per chain instead of passing chainId as a param?

Fix: getAllHistory now calls the private static _fetchChainHistory directly with the client and chainId from the clients map — no new BridgingSDK instances are create

this.lastFeeFetchTime = Date.now()
}

setWalletClient(walletClient: WalletClient) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You override here this.currentChainId to wallet-client chainId

When you do reads using publicClient, chainId will no longer be aligned

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7. packages/bridging-sdk/src/viem-sdk.tssetWalletClient overrides currentChainId

Reviewer: Overriding this.currentChainId to the wallet client's chain misaligns it with the publicClient used for reads.

Fix: Removed this.currentChainId = walletClient.chain.id from setWalletClient. currentChainId is now exclusively driven by the publicClient (set in the constructor).

}
}

const handleSwapChains = () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic does not make sense.
Display logic should affect fee fetching and display, balance fetching and output calculations.

If a user is not connected to the right chain, it should trigger switch chain when choosing to bridge.

This current flow breaks the demo-app. if I click switch I am no longer able to find a working route.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8. apps/demo-bridging-app/src/components/BridgeForm.tsx — Broken swap logic

Reviewer: handleSwapChains set toChain = fromChain, breaking routing. The swap button should trigger a wallet chain switch, and balance/fee display should follow.

Fix: handleSwapChains now calls switchChain({ chainId: toChain }) via wagmi's useSwitchChain. When the wallet switches, fromChain (derived from walletChainId) updates automatically, toChain resets via the existing useEffect, and all fee/balance hooks re-fetch against the correct chains.

…fee balance check

- Deduplicate bridge history by id: drop BridgeRequest when a matching ExecutedTransfer exists
- Add per-chain HISTORY_BLOCK_LOOKBACK constants (~7 days) to surface older transactions
- Add native balance preflight check in BridgeForm to show a clear error when wallet lacks funds for bridge fee
- Rebuild bridging-sdk dist
@Godbrand0 Godbrand0 requested a review from a team April 1, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MessagePassingBridge SDK Package

2 participants