Skip to content

Conversation

@joaquim-verges
Copy link
Member

@joaquim-verges joaquim-verges commented Dec 2, 2025


PR-Codex overview

This PR introduces support for erc5792 batch transactions in the thirdweb library, enhancing the Bridge API with a slippage option for transactions. It also includes updates to the SuccessScreen component and the useStepExecutor hook for better transaction handling.

Detailed summary

  • Added support for erc5792 batch transactions in the useStepExecutor hook.
  • Introduced slippageToleranceBps option in Buy.ts and Sell.ts for transaction slippage control.
  • Enhanced SuccessScreen to track payment events and invalidate queries upon success.
  • Updated useStepExecutor to handle batch execution and send calls if supported.
  • Added a gas buffer of 50000n for transaction preparation.
  • Implemented a function to check if atomic transactions are supported for accounts.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • EIP-5792 batch transaction support for swaps, enabling more efficient/atomic swap execution where supported.
    • Optional slippage tolerance parameter for Bridge Buy and Sell to improve price protection.
    • Transfer success tracking on payment success screens for analytics.
  • Improvements

    • Token balance queries now always refresh on component mount for fresher balances.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs-v2 Ready Ready Preview Comment Dec 2, 2025 11:47pm
nebula Ready Ready Preview Comment Dec 2, 2025 11:47pm
thirdweb_playground Ready Ready Preview Comment Dec 2, 2025 11:47pm
thirdweb-www Ready Ready Preview Comment Dec 2, 2025 11:47pm
wallet-ui Ready Ready Preview Comment Dec 2, 2025 11:47pm

@changeset-bot
Copy link

changeset-bot bot commented Dec 2, 2025

🦋 Changeset detected

Latest commit: 3d2c961

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
thirdweb Minor
@thirdweb-dev/nebula Patch
@thirdweb-dev/wagmi-adapter Patch
wagmi-inapp Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Walkthrough

This PR adds ERC-5792 batch (sendCalls) support to the transaction executor, threads an optional slippageToleranceBps through Bridge Buy/Sell prepare flows, adds success-screen tracking and cache invalidation, and changes token balance queries to always refetch on mount.

Changes

Cohort / File(s) Summary
Bridge API slippage support
packages/thirdweb/src/bridge/Buy.ts, packages/thirdweb/src/bridge/Sell.ts
Added optional slippageToleranceBps?: number to prepare options and included it in POST payloads sent to the bridge API for Buy and Sell flows.
ERC-5792 batch transaction integration
packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
Integrated EIP-5792 call/batch execution (executeSendCalls), added supportsAtomic capability checks, added gas buffer usage (50000n), receipt polling via waitForCallsReceipt, branching between batch/sendCalls and fallback flows, and new error paths for missing receipts or unsupported modes.
Success screen enhancements
packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
Added useQueryClient usage, emits ub:ui:success_screen for transfer-type prepared quotes, and invalidates bridge/wallets, wallet balance, and payment-methods queries.
Token query refetch behavior
packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
Changed token balance query options to refetchOnMount: "always" (removed prior refetchOnMount:false, retry:false, refetchOnWindowFocus:false).
Changeset
.changeset/thirty-banks-itch.md
Version bump and notes documenting ERC-5792 batch transaction support and Bridge API slippage option.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Executor as StepExecutor
    participant Account as AccountCapabilities
    participant Blockchain
    participant Poller as ReceiptPoller

    Client->>Executor: execute step
    Executor->>Account: check supportsAtomic(chainId)
    alt supportsAtomic == true
        Account-->>Executor: atomic available
        Executor->>Executor: apply gas buffer (50000n)
        Executor->>Blockchain: sendCalls (batch)
        Blockchain-->>Executor: batch accepted (tx submitted)
        Executor->>Poller: waitForCallsReceipt(batchId)
        Poller->>Blockchain: poll receipts
        Blockchain-->>Poller: receipts returned
        Poller->>Executor: final receipt/status
        Executor-->>Client: success (with preparedQuote.type)
    else supportsAtomic == false
        Account-->>Executor: atomic unavailable
        Executor->>Executor: apply gas buffer (50000n)
        Executor->>Blockchain: send single tx (executeBatch/normal)
        Blockchain-->>Executor: receipt
        Executor-->>Client: success
    end
    alt failure paths
        Executor-->>Client: Error: No receipts found / No supported execution mode
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts — branching logic between batch/sendCalls, capability checks, gas buffer math, and receipt handling.
    • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx — event payload correctness and query invalidation keys.
    • packages/thirdweb/src/bridge/Buy.ts and packages/thirdweb/src/bridge/Sell.ts — ensure slippageToleranceBps is validated and correctly serialized for backend expectations.
    • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts — verify refetch behavior doesn't introduce excessive network or UX regressions.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete and does not follow the required template structure with proper sections for notes and testing instructions. Add missing Linear issue tag, organize notes for reviewer, and explicitly document how to test (unit tests, playground). Follow the template with proper section headers.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main changes: adding ERC5792 batch transaction support and slippage option to the Bridge API.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch _SDK_Support_ERC5792_batch_transactions_for_swaps_add_slippage_option_to_Bridge_API

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (3)
  • TEAM-0000: Entity not found: Issue - Could not find referenced Issue.
  • ERC-5792: Entity not found: Issue - Could not find referenced Issue.
  • EIP-5792: Entity not found: Issue - Could not find referenced Issue.

Comment @coderabbitai help to get the list of available commands and usage tips.

@joaquim-verges joaquim-verges marked this pull request as ready for review December 2, 2025 23:02
@joaquim-verges joaquim-verges requested review from a team as code owners December 2, 2025 23:02
@github-actions github-actions bot added packages SDK Involves changes to the thirdweb SDK labels Dec 2, 2025
Copy link
Member Author

joaquim-verges commented Dec 2, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (1)

636-647: Missing executeSendCalls in dependency array.

The execute callback uses executeSendCalls (line 575) but it's not included in the dependency array. This will cause stale closure issues if executeSendCalls is recreated.

   }, [
     executionState,
     wallet,
     currentTxIndex,
     flatTxs,
     executeSingleTx,
     executeBatch,
+    executeSendCalls,
     onrampStatus,
     executeOnramp,
     onComplete,
     preparedQuote,
   ]);
🧹 Nitpick comments (5)
packages/thirdweb/src/bridge/Sell.ts (1)

355-355: Consider adding client-side validation for slippageToleranceBps.

The slippageToleranceBps parameter is correctly threaded through the implementation. However, adding client-side validation would improve user experience by catching invalid values early (e.g., negative numbers or values exceeding 10,000 bps).

Consider adding validation before the API call:

 export async function prepare(
   options: prepare.Options,
 ): Promise<prepare.Result> {
   const {
     originChainId,
     originTokenAddress,
     destinationChainId,
     destinationTokenAddress,
     amount,
     sender,
     receiver,
     client,
     purchaseData,
     maxSteps,
     paymentLinkId,
     slippageToleranceBps,
   } = options;
+
+  if (slippageToleranceBps !== undefined && (slippageToleranceBps < 0 || slippageToleranceBps > 10000)) {
+    throw new Error("slippageToleranceBps must be between 0 and 10000 (0% to 100%)");
+  }

   const clientFetch = getClientFetch(client);

Also applies to: 374-374, 453-454

packages/thirdweb/src/bridge/Buy.ts (1)

369-369: Consider adding client-side validation for slippageToleranceBps.

The slippageToleranceBps parameter is correctly threaded through the implementation. However, adding client-side validation would improve user experience by catching invalid values early (e.g., negative numbers or values exceeding 10,000 bps).

Consider adding validation before the API call:

 export async function prepare(
   options: prepare.Options,
 ): Promise<prepare.Result> {
   const {
     originChainId,
     originTokenAddress,
     destinationChainId,
     destinationTokenAddress,
     sender,
     receiver,
     client,
     amount,
     purchaseData,
     maxSteps,
     paymentLinkId,
     slippageToleranceBps,
   } = options;
+
+  if (slippageToleranceBps !== undefined && (slippageToleranceBps < 0 || slippageToleranceBps > 10000)) {
+    throw new Error("slippageToleranceBps must be between 0 and 10000 (0% to 100%)");
+  }

   const clientFetch = getClientFetch(client);

Also applies to: 388-388, 465-466

packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (2)

15-15: Inconsistent import pattern: lazy-load waitForCallsReceipt inside executeSendCalls.

Other heavy dependencies (prepareTransaction, sendCalls, status) are dynamically imported inside async functions. This top-level import breaks that pattern. As per coding guidelines, lazy-load heavy dependencies inside async paths to keep the initial bundle lean.

Move the import inside executeSendCalls:

-import { waitForCallsReceipt } from "../../../wallets/eip5792/wait-for-calls-receipt.js";

Then in executeSendCalls (around line 385):

+      const { waitForCallsReceipt } = await import(
+        "../../../wallets/eip5792/wait-for-calls-receipt.js"
+      );
       const callsStatus = await waitForCallsReceipt(result);

716-726: Add explicit return type annotation.

Per coding guidelines, async functions should have explicit return types for clarity and type safety.

-async function supportsAtomic(account: Account, chainId: number) {
+async function supportsAtomic(account: Account, chainId: number): Promise<boolean> {
   const capabilitiesFn = account.getCapabilities;
packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (1)

64-65: Success-screen side-effect query and cache invalidation behavior

Using useQueryClient() plus queryClient.invalidateQueries on success to refresh bridge/v1/wallets, walletBalance, and payment-methods is a reasonable way to ensure post-payment state is fresh. One thing to double-check is whether it’s acceptable for this useQuery to potentially re-run on refocus or remount (depending on the global React Query config), which would re-trigger the tracking + invalidations multiple times rather than strictly once-per-success.

If you want strictly one-shot behavior, consider either:

  • Tying enabled/staleTime explicitly to the lifecycle of this screen, or
  • Converting this into a useEffect with an internal guard, and using queryClient.invalidateQueries from there.

Also applies to: 66-100

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 22cea5b and 315351c.

📒 Files selected for processing (6)
  • .changeset/thirty-banks-itch.md (1 hunks)
  • packages/thirdweb/src/bridge/Buy.ts (3 hunks)
  • packages/thirdweb/src/bridge/Sell.ts (3 hunks)
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (18 hunks)
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (3 hunks)
  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/bridge/Sell.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
  • packages/thirdweb/src/bridge/Buy.ts
packages/thirdweb/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/**/*.{ts,tsx}: Comment only ambiguous logic in SDK code; avoid restating TypeScript in prose
Load heavy dependencies inside async paths to keep initial bundle lean (e.g. const { jsPDF } = await import("jspdf");)

Lazy-load heavy dependencies inside async paths to keep the initial bundle lean (e.g., const { jsPDF } = await import('jspdf');)

Files:

  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/bridge/Sell.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
  • packages/thirdweb/src/bridge/Buy.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/bridge/Sell.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
  • packages/thirdweb/src/bridge/Buy.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/bridge/Sell.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
  • packages/thirdweb/src/bridge/Buy.ts
🧬 Code graph analysis (1)
packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (3)
packages/thirdweb/src/exports/thirdweb.ts (1)
  • prepareTransaction (181-181)
packages/thirdweb/src/bridge/Onramp.ts (1)
  • status (13-13)
packages/thirdweb/src/react/core/hooks/useBridgePrepare.ts (1)
  • BridgePrepareResult (23-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Size
  • GitHub Check: Lint Packages
  • GitHub Check: Build Packages
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Unit Tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (7)
.changeset/thirty-banks-itch.md (1)

1-5: Changeset format and versioning look good.

The YAML front matter is correctly formatted for the Changesets tool, and the minor version bump is appropriate for the new features being introduced (ERC-5792 batch transaction support and slippage tolerance parameters to the Bridge API). The description accurately summarizes the key changes in this PR.

packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (3)

332-419: LGTM!

The executeSendCalls implementation correctly handles ERC-5792 batch calls. Using lastReceipt.transactionHash for status polling is appropriate since the batch is atomic, and the chain validation in the batching logic (line 557) ensures firstTx.chainId is valid for all transactions.


551-595: LGTM!

The batching logic correctly prioritizes sendBatchTransaction over ERC-5792 sendCalls, and the defensive else branch at lines 582-585 provides a safety net (though logically unreachable given the canBatch || canSendCalls condition).


204-204: LGTM!

Consistent 50k gas buffer applied across all transaction execution paths.

packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1)

119-119: Aggressive refetch is complementary to invalidation; verify if necessary for UX.

Query invalidation is properly implemented in SuccessScreen.tsx (line 88–90), which targets the ["bridge/v1/wallets"] query after transactions. However, refetchOnMount: 'always' serves a different purpose: it ensures fresh token balances when users navigate back to the bridge widget, regardless of how they reached it.

This may be intentional for a bridge/swap context where balance accuracy is critical. However, consider whether:

  • refetchOnMount: true (default) with a staleTime config would be sufficient instead of forcing every mount
  • Testing confirms the aggressive refetch improves UX without causing excessive API load
  • Pagination-driven query instances (via page/limit in the queryKey) already create separate cache entries
packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (2)

3-4: React Query hook import looks consistent with existing patterns

Importing useQuery/useQueryClient from @tanstack/react-query is aligned with how the rest of the file uses the query cache; nothing to change here.


78-87: Transfer payment tracking payload appears internally consistent

The new preparedQuote.type === "transfer" branch sends chainId/toChainId as preparedQuote.intent.chainId and fromToken/toToken as preparedQuote.intent.tokenAddress, which makes sense for a same-chain, same-asset transfer. This keeps the analytics shape consistent with buy/sell events.

Please confirm that the BridgePrepareResult transfer intent shape indeed uses chainId and tokenAddress (and not, for example, originChainId/destinationChainId) so this won’t drift if the intent type evolves.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

size-limit report 📦

Path Size
@thirdweb-dev/nexus (esm) 104.88 KB (0%)
@thirdweb-dev/nexus (cjs) 316.6 KB (0%)

@codecov
Copy link

codecov bot commented Dec 2, 2025

Codecov Report

❌ Patch coverage is 4.51128% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.73%. Comparing base (1056696) to head (3d2c961).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...s/thirdweb/src/react/core/hooks/useStepExecutor.ts 0.94% 105 Missing ⚠️
...ct/web/ui/Bridge/payment-success/SuccessScreen.tsx 4.54% 21 Missing ⚠️
.../src/react/web/ui/Bridge/swap-widget/use-tokens.ts 0.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (4.51%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8484      +/-   ##
==========================================
- Coverage   54.83%   54.73%   -0.11%     
==========================================
  Files         920      920              
  Lines       60891    61013     +122     
  Branches     4141     4145       +4     
==========================================
+ Hits        33390    33395       +5     
- Misses      27399    27516     +117     
  Partials      102      102              
Flag Coverage Δ
packages 54.73% <4.51%> (-0.11%) ⬇️
Files with missing lines Coverage Δ
packages/thirdweb/src/bridge/Buy.ts 92.30% <100.00%> (+0.12%) ⬆️
packages/thirdweb/src/bridge/Sell.ts 93.07% <100.00%> (+0.10%) ⬆️
.../src/react/web/ui/Bridge/swap-widget/use-tokens.ts 8.57% <0.00%> (+0.23%) ⬆️
...ct/web/ui/Bridge/payment-success/SuccessScreen.tsx 9.23% <4.54%> (-1.78%) ⬇️
...s/thirdweb/src/react/core/hooks/useStepExecutor.ts 1.15% <0.94%> (-0.04%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (1)

78-97: Transfer analytics + cache invalidation are reasonable; consider minor cleanup and confirm key semantics

  • The new preparedQuote.type === "transfer" branch mirrors the buy/sell analytics shape and uses the same "ub:ui:success_screen" event, which is consistent. Please double‑check that using the same token and chain for both from* and to* fields matches what your analytics pipeline expects for a transfer (especially if BridgePrepareResult.intent ever distinguishes source vs destination chain/token more explicitly).
  • The three invalidateQueries calls will run once per success-screen mount and should refresh bridge wallets, balances, and payment methods. Make sure these queryKeys match the prefixes actually used by the corresponding hooks/selectors (e.g., if keys are tuples like ["walletBalance", walletAddress, chainId], this relies on React Query’s partial key matching behavior in your version).
  • Optional: there’s a bit of duplication between the buy/sell and transfer trackPayEvent payloads; you could factor out a shared baseEvent ({ client, event: "ub:ui:success_screen" }) or a small helper function to keep this easier to maintain if more types are added later.
packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (2)

204-204: Extract gas buffer to a named constant.

The gas buffer value 50000n is hardcoded in three locations (lines 204, 281, 372). Consider extracting this to a named constant at the top of the file for better maintainability.

+// Gas buffer for transaction execution
+const TX_GAS_BUFFER = 50000n;
+
 import { useCallback, useEffect, useMemo, useRef, useState } from "react";

Then use extraGas: TX_GAS_BUFFER in all three locations.

Also applies to: 281-281, 372-372


250-330: Consider refactoring to reduce duplication between executeBatch and executeSendCalls.

The two functions share significant structural similarity (~60+ lines of common logic):

  • Transaction preparation and mapping
  • Error handling patterns
  • Status polling logic

While the current implementation works correctly, extracting shared logic into helper functions would improve maintainability and reduce the risk of divergent bug fixes.

Example refactoring approach:

// Shared transaction preparation
async function prepareBatchTransactions(txs: FlattenedTx[]) {
  const { prepareTransaction } = await import(
    "../../../transaction/prepare-transaction.js"
  );
  
  return Promise.all(
    txs.map(async (tx) => {
      return prepareTransaction({
        chain: tx.chain,
        client: tx.client,
        data: tx.data,
        to: tx.to,
        value: tx.value,
        extraGas: TX_GAS_BUFFER,
      });
    }),
  );
}

// Shared status polling
async function pollForBatchCompletion(
  firstTx: FlattenedTx,
  transactionHash: string,
  preparedQuote: BridgePrepareResult,
  completedStatusResults: CompletedStatusResult[],
  poller: (...) => ...,
  abortSignal: AbortSignal,
) {
  const { status } = await import("../../../bridge/Status.js");
  await poller(async () => {
    const statusResult = await status({
      chainId: firstTx.chainId,
      client: firstTx.client,
      transactionHash,
    });

    if (statusResult.status === "COMPLETED") {
      const typedStatusResult = {
        type: preparedQuote.type,
        ...statusResult,
      };
      completedStatusResults.push(typedStatusResult);
      return { completed: true };
    }

    if (statusResult.status === "FAILED") {
      throw new Error("Payment failed");
    }

    return { completed: false };
  }, abortSignal);
}

Also applies to: 332-419

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 315351c and 3d2c961.

📒 Files selected for processing (6)
  • .changeset/thirty-banks-itch.md (1 hunks)
  • packages/thirdweb/src/bridge/Buy.ts (3 hunks)
  • packages/thirdweb/src/bridge/Sell.ts (3 hunks)
  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (8 hunks)
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (3 hunks)
  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  • packages/thirdweb/src/bridge/Sell.ts
  • packages/thirdweb/src/bridge/Buy.ts
  • .changeset/thirty-banks-itch.md
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
packages/thirdweb/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/thirdweb/src/**/*.{ts,tsx}: Comment only ambiguous logic in SDK code; avoid restating TypeScript in prose
Load heavy dependencies inside async paths to keep initial bundle lean (e.g. const { jsPDF } = await import("jspdf");)

Lazy-load heavy dependencies inside async paths to keep the initial bundle lean (e.g., const { jsPDF } = await import('jspdf');)

Files:

  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • packages/thirdweb/src/react/core/hooks/useStepExecutor.ts
  • packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx
🧬 Code graph analysis (1)
packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (2)
packages/thirdweb/src/exports/thirdweb.ts (1)
  • prepareTransaction (181-181)
packages/thirdweb/src/bridge/Onramp.ts (1)
  • status (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Unit Tests
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Size
  • GitHub Check: Build Packages
  • GitHub Check: Lint Packages
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx (1)

3-3: React Query client wiring looks good

Importing useQueryClient and instantiating queryClient at the top of the component is idiomatic and correctly scoped; no changes needed here.

Also applies to: 64-64

packages/thirdweb/src/react/core/hooks/useStepExecutor.ts (3)

12-12: LGTM!

The import of waitForCallsReceipt is correctly added to support ERC-5792 batch transaction receipts.


717-727: LGTM!

The supportsAtomic helper correctly checks account capabilities for ERC-5792 atomic execution support. The logic properly handles missing capabilities and checks for both "supported" and "ready" statuses.


543-585: LGTM!

The execution selection logic correctly:

  1. Checks for both sendBatchTransaction and ERC-5792 sendCalls support
  2. Prefers native batch transactions over sendCalls when both are available
  3. Includes defensive error handling for the theoretically unreachable else case

The logic properly optimizes batch execution by grouping consecutive same-chain transactions.

Comment on lines +384 to +391
// get tx hash
const callsStatus = await waitForCallsReceipt(result);
const lastReceipt =
callsStatus.receipts?.[callsStatus.receipts.length - 1];

if (!lastReceipt) {
throw new Error("No receipts found");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find waitForCallsReceipt implementation
fd -e ts -e tsx | xargs grep -l "waitForCallsReceipt" | head -20

Repository: thirdweb-dev/js

Length of output: 498


🏁 Script executed:

#!/bin/bash
# Search for waitForCallsReceipt definition
rg -n "export.*waitForCallsReceipt|function waitForCallsReceipt" --type=ts

Repository: thirdweb-dev/js

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Look at useStepExecutor.ts to see context around lines 384-391
cat -n packages/thirdweb/src/react/core/hooks/useStepExecutor.ts | sed -n '370,410p'

Repository: thirdweb-dev/js

Length of output: 1514


🏁 Script executed:

#!/bin/bash
# Search for other receipt handling patterns in the codebase
rg -n "receipts\[" --type=ts -C3 | head -50

Repository: thirdweb-dev/js

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if there's a receipt status field used anywhere
rg -n "receipt.*status|status.*receipt" --type=ts -C2 | head -40

Repository: thirdweb-dev/js

Length of output: 4254


🏁 Script executed:

#!/bin/bash
# Read the waitForCallsReceipt implementation
cat -n packages/thirdweb/src/wallets/eip5792/wait-for-calls-receipt.ts | head -100

Repository: thirdweb-dev/js

Length of output: 3892


🏁 Script executed:

#!/bin/bash
# Look at executeBatch function in useStepExecutor.ts for comparison
cat -n packages/thirdweb/src/react/core/hooks/useStepExecutor.ts | sed -n '250,330p'

Repository: thirdweb-dev/js

Length of output: 3209


🏁 Script executed:

#!/bin/bash
# Check if there's validation inside waitForCallsReceipt
rg -n "status.*reverted|reverted.*status" packages/thirdweb/src/wallets/eip5792/wait-for-calls-receipt.ts -C5

Repository: thirdweb-dev/js

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check GetCallsStatusResponse type definition
rg -n "type GetCallsStatusResponse|interface GetCallsStatusResponse" packages/thirdweb/src/wallets/eip5792/ -A 20

Repository: thirdweb-dev/js

Length of output: 1633


🏁 Script executed:

#!/bin/bash
# Check what status values receipts can have
rg -n "receiptStatuses|receipt.*status" packages/thirdweb/src/wallets/eip5792/get-calls-status.ts -B3 -A3

Repository: thirdweb-dev/js

Length of output: 430


🏁 Script executed:

#!/bin/bash
# Check if individual receipt status is ever validated anywhere
rg -n "receipt\.status.*==|receipt\.status.*===|reverted" packages/thirdweb/src/wallets/eip5792/ --type=ts

Repository: thirdweb-dev/js

Length of output: 266


🏁 Script executed:

#!/bin/bash
# Check if bundle.status === "success" means all receipts are success
cat -n packages/thirdweb/src/wallets/eip5792/get-calls-status.ts | sed -n '1,110p'

Repository: thirdweb-dev/js

Length of output: 4365


🏁 Script executed:

#!/bin/bash
# Check EIP-5792 spec or comments about bundle vs receipt status
rg -n "atomic|bundle|receipt.*reverted" packages/thirdweb/src/wallets/eip5792/ -B2 -A2 | head -60

Repository: thirdweb-dev/js

Length of output: 145


Check individual receipt statuses for non-atomic bundles.

The code only uses lastReceipt.transactionHash for polling without verifying receipt success. ERC-5792 supports both atomic and non-atomic call bundles. For atomic bundles, waitForCallsReceipt returning bundle.status === "success" guarantees all receipts succeeded. However, for non-atomic bundles, individual calls can fail independently—their receipt statuses should be validated.

Consider adding validation for non-atomic cases:

 // get tx hash
 const callsStatus = await waitForCallsReceipt(result);
 const lastReceipt =
   callsStatus.receipts?.[callsStatus.receipts.length - 1];

 if (!lastReceipt) {
   throw new Error("No receipts found");
 }

+// For non-atomic bundles, verify individual receipt success
+if (!callsStatus.atomic) {
+  for (const receipt of callsStatus.receipts) {
+    if (receipt.status === "reverted") {
+      throw new Error("Transaction in batch failed");
+    }
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// get tx hash
const callsStatus = await waitForCallsReceipt(result);
const lastReceipt =
callsStatus.receipts?.[callsStatus.receipts.length - 1];
if (!lastReceipt) {
throw new Error("No receipts found");
}
// get tx hash
const callsStatus = await waitForCallsReceipt(result);
const lastReceipt =
callsStatus.receipts?.[callsStatus.receipts.length - 1];
if (!lastReceipt) {
throw new Error("No receipts found");
}
// For non-atomic bundles, verify individual receipt success
if (!callsStatus.atomic) {
for (const receipt of callsStatus.receipts) {
if (receipt.status === "reverted") {
throw new Error("Transaction in batch failed");
}
}
}
🤖 Prompt for AI Agents
In packages/thirdweb/src/react/core/hooks/useStepExecutor.ts around lines 384 to
391, the code only grabs lastReceipt.transactionHash without validating
individual receipt success for non-atomic bundles; update the logic to detect
non-atomic bundles (e.g., bundle.status !== "success" or appropriate flag from
waitForCallsReceipt result) and iterate over callsStatus.receipts to check each
receipt's status (or reverted/failed fields), and if any receipt indicates
failure, throw or return a descriptive error containing the failing call index
and its transactionHash/revert reason; otherwise continue using the appropriate
successful receipt.transactionHash for polling.

refetchOnMount: false,
retry: false,
refetchOnWindowFocus: false,
refetchOnMount: "always",
Copy link

Choose a reason for hiding this comment

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

The refetch behavior change may have unintended side effects. By removing the retry: false and refetchOnWindowFocus: false options, token balance queries will now retry on failure and refetch on window focus, which could increase API calls beyond what was intended.

View Details
📝 Patch Details
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
index 19521d200..de9c92b31 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
+++ b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
@@ -117,5 +117,7 @@ export function useTokenBalances(options: {
       return json.result;
     },
     refetchOnMount: "always",
+    retry: false,
+    refetchOnWindowFocus: false,
   });
 }

Analysis

Unintended query behavior change in useTokenBalances() after refactor

What fails: useTokenBalances() in packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts now retries failed requests 3 times and refetches on window focus, contradicting the explicit defensive configuration set in previous commits.

How to reproduce:

  1. Examine git history: git log --oneline packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts
  2. Compare commit b8afa98 (which explicitly added retry: false) vs commit 3d2c961 (which removed it)
  3. Verify React Query defaults using TanStack Query documentation:
    • retry defaults to 3 (not false)
    • refetchOnWindowFocus defaults to true (not false)

Result: Removed three explicit query options without updating the feature:

  • Commit b8afa98 (Sep 29) explicitly set retry: false to "reduce loading times and improve UI responsiveness" by disabling retries on failed token balance queries
  • Commit 3d2c961 (Dec 3) removed retry: false, refetchOnWindowFocus: false, and changed refetchOnMount: false to refetchOnMount: "always"
  • Now useTokenBalances() will automatically:
    • Retry 3 times on API failure (vs previous: never retry)
    • Refetch when window regains focus (vs previous: never refetch on focus)

Expected: Query options should maintain the explicit defensive configuration (retry: false, refetchOnWindowFocus: false) while still supporting the new refetchOnMount: "always" behavior. This preserves the intentional choice to minimize unnecessary API calls for token balance queries while ensuring fresh data on component mount.

@joaquim-verges joaquim-verges merged commit 9e43da4 into main Dec 2, 2025
24 of 25 checks passed
@joaquim-verges joaquim-verges deleted the _SDK_Support_ERC5792_batch_transactions_for_swaps_add_slippage_option_to_Bridge_API branch December 2, 2025 23:49
@joaquim-verges joaquim-verges mentioned this pull request Dec 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

packages SDK Involves changes to the thirdweb SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants