Cross-chain swaps (Alpha)

Cross-chain swaps let you convert tokens across different blockchain networks in a single transaction. They’re built natively into Smart Wallets and you can integrate in minutes.

Cross-chain swaps work just like any other Smart Wallet transaction, so you can sponsor gas to do gasless swaps, or pay for gas in an ERC-20 token.

Cross-chain swaps are in alpha. Note that there may be changes in the future to simplify the endpoint/sdk. We will let you know if/when that happens.

The Cross-chain Swap flow

Flow

  1. Request a cross-chain swap quote
  2. Sign the prepared swap calls
  3. Send prepared calls
  4. Wait for cross-chain confirmation

Swap options

Important: Cross-chain swaps do not support postCalls. You cannot batch additional actions after a cross-chain swap completes (for now).

When requesting a cross-chain swap quote, you can specify either a fromAmount , or a minimumToAmount.

1// Mode 1: Swap exact input amount
2{
3 fromAmount: "0x2710";
4} // Swap exactly 0.01 USDC (10000 in hex, 6 decimals)
5
6// Mode 2: Get minimum output amount
7{
8 minimumToAmount: "0x5AF3107A4000";
9} // Get at least 0.0001 ETH (18 decimals). The amount you need to spend is calculated to get at least your desired ETH amount.

Prerequisites

Before you begin, ensure you have:

  • An Alchemy API Key
  • If you’re sponsoring gas, then a Gas Manager policy
  • A small amount of tokens for testing (~$1 worth is enough!)
    • Important: You’ll need to send these tokens to your smart wallet address to be able to swap!
  • A signer to own the account and sign messages

Note that Cross-chain Swaps are currently supported via direct APIs and the SDK. React support coming soon!

Required SDK version: ^v4.70.0

Important: Cross-chain swaps do not support postCalls. You cannot batch additional actions after a cross-chain swap completes.

Use the usePrepareSwap hook with the toChainId parameter to request cross-chain swap quotes and the useSignAndSendPreparedCalls hook to execute token swaps across different chains.

Prerequisites

crossChainSwap.tsx
1import {
2 useSmartAccountClient,
3 usePrepareSwap,
4 useSignAndSendPreparedCalls,
5 useWaitForCallsStatus,
6 useUser,
7} from "@account-kit/react";
8
9// Chain IDs
10const CHAIN_IDS = {
11 ARBITRUM: "0xa4b1",
12 BASE: "0x2105",
13} as const;
14
15// Token addresses
16const TOKENS = {
17 NATIVE: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEee", // ETH
18 BASE_USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
19} as const;
20
21export default function CrossChainSwap() {
22 const user = useUser();
23 const { client } = useSmartAccountClient({
24 accountParams: { mode: "7702" },
25 });
26
27 const { prepareSwapAsync, isPreparingSwap } = usePrepareSwap({
28 client,
29 });
30
31 const {
32 signAndSendPreparedCallsAsync,
33 isSigningAndSendingPreparedCalls,
34 signAndSendPreparedCallsResult,
35 } = useSignAndSendPreparedCalls({ client });
36
37 const {
38 data: statusResult,
39 isLoading: isWaitingForConfirmation,
40 error,
41 } = useWaitForCallsStatus({
42 client,
43 id: signAndSendPreparedCallsResult?.preparedCallIds[0],
44 });
45
46 const handleCrossChainSwap = async () => {
47 if (!client?.account.address) {
48 throw new Error("No account connected");
49 }
50
51 try {
52 // Step 1: Request cross-chain swap quote
53 const result = await prepareSwapAsync({
54 from: client.account.address,
55 fromToken: TOKENS.NATIVE,
56 toChainId: CHAIN_IDS.BASE, // Destination chain
57 toToken: TOKENS.BASE_USDC,
58 fromAmount: "0x5af3107a4000", // 0.0001 ETH
59 });
60
61 const { quote, ...calls } = result;
62 console.log("Cross-chain swap quote:", quote);
63
64 // Ensure we have prepared calls
65 if (calls.rawCalls) {
66 throw new Error("Expected prepared calls");
67 }
68
69 // Step 2: Sign and send the prepared calls
70 const callIds = await signAndSendPreparedCallsAsync(calls);
71
72 console.log("Cross-chain swap initiated");
73 console.log("Call ID:", callIds?.preparedCallIds[0]);
74 } catch (error) {
75 console.error("Cross-chain swap failed:", error);
76 }
77 };
78
79 if (!user) {
80 return <div>Please log in to use swap functionality</div>;
81 }
82
83 return (
84 <div>
85 <button onClick={handleCrossChainSwap} disabled={!client}>
86 {isPreparingSwap
87 ? "Requesting quote..."
88 : isSigningAndSendingPreparedCalls
89 ? "Signing and sending..."
90 : "Swap ETH Arbitrum → Base USDC"}
91 </button>
92
93 {signAndSendPreparedCallsResult && (
94 <p>
95 {isWaitingForConfirmation
96 ? "Waiting for cross-chain confirmation..."
97 : error
98 ? `Error: ${error}`
99 : statusResult?.statusCode === 200
100 ? "Cross-chain swap confirmed!"
101 : `Status: ${statusResult?.statusCode}`}
102 </p>
103 )}
104 </div>
105 );
106}

How it works

  1. Request quote: usePrepareSwap requests a cross-chain swap quote with the toChainId parameter
  2. Destructure result: Extract quote for display and calls for signing
  3. Sign and send: useSignAndSendPreparedCalls signs and submits the transaction (callId automatically preserved)
  4. Track status: useWaitForCallsStatus monitors the transaction with cross-chain status codes

Cross-chain swaps take longer than single-chain swaps due to cross-chain messaging and confirmation requirements.

Cross-chain status codes

Cross-chain swaps have additional status codes to reflect the cross-chain nature:

CodeStatus
100Pending
120Cross-Chain In Progress
200Confirmed
400Offchain Failure
410Cross-chain Refund
500Onchain Failure
600Partial Onchain Failure

Swap options

You can specify either an exact input amount or a minimum output amount:

1// Mode 1: Swap exact input amount
2await prepareSwapAsync({
3 from: address,
4 toChainId: "0x2105", // Base
5 fromToken: "0x...",
6 toToken: "0x...",
7 fromAmount: "0x2710", // Swap exactly 0.01 USDC
8});
9
10// Mode 2: Get minimum output amount
11await prepareSwapAsync({
12 from: address,
13 toChainId: "0x2105", // Base
14 fromToken: "0x...",
15 toToken: "0x...",
16 minimumToAmount: "0x5AF3107A4000", // Get at least 0.0001 ETH
17});

FAQs

What chains are supported for cross-chain swaps?

Chains supported (for now) are: Arbitrum, Arbitrum Nova, Base, Berachain, Boba Network, BSC/BNB, Celo, Ethereum, Hyperliquid, Ink, Optimism, Plasma, Polygon, Shape, Soneium, Story, Unichain, World Chain, and Zora mainnets.

Can I batch additional calls after a cross-chain swap?

No, postCalls are not supported for cross-chain swaps (for now). You can only perform the swap itself across chains.

How long do cross-chain swaps take?

Cross-chain swaps typically take longer than single-chain swaps due to the need for cross-chain messaging and confirmation. The exact time depends on the source and destination chains involved in the swap.

How do you encode values?

Values are simply passed as hexadecimal strings. The Swap API doesn’t add complexity to consider decimals, so 0x01 is always the smallest amount of a given asset. 1 ETH, or DAI (18 decimals) is 0xDE0B6B3A7640000 1 USDC (6 decimals) is 0xF4240 This removes any ambiguity— if it’s numerical, it’s a hex.

What is the expiry?

The expiry is an informational indicator of when you can expect to be able to process the swap request. If you’re at/near the expiry, it might be a good time to request a new quote.

What are the different status codes for cross-chain swaps?

Cross-chain swaps may have additional status codes beyond standard transaction statuses to reflect the cross-chain nature of the transaction. These are:

  • 120: Cross-chain in progress
  • 410: Cross-chain refund

When is a CallId returned from wallet_requestQuote_v0?

Any time you’re requesting a cross-chain quote via wallet_requestQuote_v0 , a callId is returned. This callId includes important data for cross-chain tracking. You can use this just like any other callId in wallet_getCallsStatus!