# Cross-chain swaps (Alpha)

> Convert tokens across different blockchain networks in a single transaction using Wallet APIs, with gas sponsorship support.

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

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

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

<Tip>
  Cross-chain swaps are in alpha. There may be changes in the future
  to simplify the endpoint/sdk. Updates will be communicated if/when that happens.
</Tip>

# 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**

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

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

```tsx
// Mode 1: Swap exact input amount
{
  // Swap exactly 0.01 USDC (10000 in hex, 6 decimals)
  fromAmount: "0x2710";
} 

// Mode 2: Get minimum output amount
{
  // Get at least 0.0001 ETH (18 decimals). The amount you need to spend is calculated to get at least your desired ETH amount.
  minimumToAmount: "0x5AF3107A4000";
}
```

## Prerequisites

Before you begin, ensure you have:

* An [Alchemy API key](https://dashboard.alchemy.com/apps)
* If you're sponsoring gas, then a [Gas Manager](https://dashboard.alchemy.com/gas-manager/policy/create) 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

<Tabs>
  <Tab title="JavaScript" language="typescript">
    <Info>`@alchemy/wallet-apis` (v5.x.x) is currently in beta but is the recommended replacement for `@account-kit/wallet-client` (v4.x.x). If you run into any issues, please [reach out](mailto:support@alchemy.com).</Info>

<Info>
  **Important**: Cross-chain swaps do not support `postCalls`. You cannot batch
  additional actions after a cross-chain swap completes.
</Info>

You'll need the following env variables:

* `ALCHEMY_API_KEY`: An [Alchemy API key](https://dashboard.alchemy.com/apps)
* `ALCHEMY_POLICY_ID`: A [Gas Manager](https://dashboard.alchemy.com/gas-manager/policy/create) policy ID
* `PRIVATE_KEY`: A private key for a signer

<CodeBlocks>
  ```ts title="requestCrossChainQuote.ts"
  import { swapActions } from "@alchemy/wallet-apis/experimental";
  import { client } from "./client";

  // Add the swap actions to the client
  const swapClient = client.extend(swapActions);

  // Request the cross-chain swap quote
  // Note: toChainId specifies the destination chain for the swap
  const { quote, callId, ...calls } = await swapClient.requestQuoteV0({
    toChainId: "0x...", // Destination chain ID
    fromToken: "0x...",
    toToken: "0x...",
    minimumToAmount: "0x...",
  });

  // Display the swap quote, including the minimum amount to receive and the expiry
  console.log(quote);
  console.log(`Cross-chain swap callId: ${callId}`);

  // Assert that the calls are not raw calls.
  // This will always be the case when requestQuoteV0 is used without the `returnRawCalls` option,
  // the assertion is just needed for Typescript to recognize the result type.
  if (calls.rawCalls) {
    throw new Error("Expected user operation calls");
  }

  // Sign the quote using the signer on the client
  // The callId is automatically included in the signed calls
  const signedCalls = await swapClient.signPreparedCalls(calls);

  // Send the prepared calls
  // The callId is passed through automatically
  const { id } = await swapClient.sendPreparedCalls(signedCalls);

  // Wait for the call to resolve
  // Cross-chain swaps may take longer due to cross-chain messaging
  const callStatusResult = await swapClient.waitForCallsStatus({
    id,
  });

  // Filter through success or failure cases
  // Cross-chain swaps have additional status codes:
  // - 120: Cross-Chain In Progress
  // - 410: Cross-chain Refund
  if (
    callStatusResult.status !== "success" ||
    !callStatusResult.receipts ||
    !callStatusResult.receipts[0]
  ) {
    throw new Error(
      `Cross-chain swap failed with status ${callStatusResult.status}, full receipt:\n ${JSON.stringify(callStatusResult, null, 2)}`,
    );
  }

  console.log("Cross-chain swap confirmed!");
  console.log(
    `Transaction hash: ${callStatusResult.receipts[0].transactionHash}`,
  );
  ```

  ```ts title="client.ts"
  import type { Hex } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { base } from "viem/chains";
  import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";

  export const client = createSmartWalletClient({
    transport: alchemyWalletTransport({
      apiKey: "YOUR_API_KEY",
    }),
    chain: base,
    signer: privateKeyToAccount("0xYOUR_PRIVATE_KEY" as const),
    paymaster: { policyId: "YOUR_POLICY_ID" }, // Optional: If you're using a gas manager policy
  });
```
</CodeBlocks>

  </Tab>

  <Tab title="API" language="bash">
    You will need to fill in values wrapped in curly braces like `{SIGNER_ADDRESS}`.

<Steps>

<Step title="Request a cross-chain swap quote">

<Info>
  **Important**: Cross-chain swaps do not support `postCalls`. You cannot batch
  additional actions after a cross-chain swap.
</Info>

Use your signer address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default. Request a cross-chain swap quote by specifying both the source chain (`chainId`) and destination chain (`toChainId`). As with [single-chain swaps](/docs/wallets/transactions/swap-tokens), you can specify either a `minimumToAmount` or a `fromAmount`.

<Info>
  If you're using an EOA or want the raw array of calls returned, pass the
  optional parameter `"returnRawCalls": true`, this will return a `calls` array.
</Info>

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "wallet_requestQuote_v0",
    "params": [
      {
        "from": "{SIGNER_ADDRESS}",
        "chainId": "{SOURCE_CHAIN_ID}",
        "toChainId": "{DESTINATION_CHAIN_ID}",
        "fromToken": "{FROM_TOKEN}",
        "toToken": "{TO_TOKEN}",
        "fromAmount": "{FROM_AMOUNT_HEXADECIMAL}",
        "capabilities": {
          "paymasterService": {
            "policyId": "{PAYMASTER_POLICY_ID}"
          }
        }
      }
    ]
  }'
```

This returns:

```json
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "rawCalls": false,
    "chainId": "...",
    "callId": "0x...",
    "quote": {
      "expiry": "EXPIRY",
      "minimumToAmount": "MINIMUM_TO_AMOUNT",
      "fromAmount": "FROM_AMOUNT"
    },
    "type": "user-operation-v070",
    "data": "USER_OPERATION_DATA",
    "signatureRequest": {
      "type": "personal_sign",
      "data": {
        "raw": "..."
      },
      "rawPayload": "..."
    },
    "feePayment": {
      "sponsored": true,
      "tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
      "maxAmount": "..."
    }
  }
}
```

Note the `callId` in the response! You'll use this to track the cross-chain swap status. Also note the `signatureRequest` - this is what you need to sign, and the returned `data` field is what you'll need to send the transaction.

If the account hasn't been delegated yet, the response type will be `array` containing both an EIP-7702 authorization and a transaction. See [Send Transactions](/docs/wallets/transactions/send-transactions) for handling authorization signing.

</Step>

<Step title="Sign the signature request">

To sign the signature request, sign the `raw` field (note, this is not a string! You need to pass it to your signer as raw bytes, generally like so: `{ raw: "0x..." }`) with your signer of choice.

This should use the `personal_sign` RPC method, as noted by the `type` in the `signatureRequest`.

Alternatively, you can sign the raw payload with a simple `eth_sign` but this RPC method is not favored due to security concerns.

</Step>

<Step title="Send the prepared call">

Pass the `callId` from Step 1 to `sendPreparedCalls`. This makes the response return the same `callId` with additional cross-chain status tracking information:

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "wallet_sendPreparedCalls",
    "params": [
      {
        "callId": "{CALL_ID_FROM_STEP_1}",
        "type": "user-operation-v070",
        "data": "{DATA_FROM_STEP_1}",
        "chainId": "{SOURCE_CHAIN_ID}",
        "signature": {
          "type": "secp256k1",
          "data": "{SIGNATURE_FROM_STEP_2}"
        }
      }
    ],
    "id": 1
  }'
```

This returns:

```json
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "id": "CALL_ID"
  }
}
```

The response returns the same `callId` you passed in, which you'll use to track the cross-chain swap status in the next step.

For other potential responses, [check out the API reference!](/docs/wallets/api-reference/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-send-prepared-calls)

</Step>

<Step title="Track the cross-chain swap">

Use the `wallet_getCallsStatus` endpoint to check the status of your cross-chain swap. Cross-chain swaps may take longer than single-chain swaps due to cross-chain messaging.

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "wallet_getCallsStatus",
    "params": [
      [
        "{CALL_ID_FROM_STEP_1_OR_STEP_3}"
      ]
    ],
    "id": 1
  }'
```

This returns:

```json
{
  "id": "1",
  "jsonrpc": "2.0",
  "result": {
    "id": "CALL_ID",
    "chainId": "SOURCE_CHAIN_ID",
    "atomic": true,
    "status": 200,
    "receipts": [...]
  }
}
```

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

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

To get your transaction hash, you can access `result.receipts[0].transactionHash`.

For more details, check out [the API reference!](/docs/wallets/api-reference/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-get-calls-status)

</Step>

</Steps>

  </Tab>
</Tabs>

<Accordion title="Using @account-kit/wallet-client (v4.x.x)?">
The examples on this page use `@alchemy/wallet-apis` (v5.x.x). If you're using `@account-kit/wallet-client` (v4.x.x), the client setup looks like this:

```ts title="client.ts (v4.x.x)"
import { LocalAccountSigner } from "@aa-sdk/core";
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { alchemy, sepolia } from "@account-kit/infra";

const signer = LocalAccountSigner.privateKeyToAccountSigner("0xYOUR_PRIVATE_KEY" as const);

export const client = createSmartWalletClient({
  transport: alchemy({ apiKey: "YOUR_API_KEY" }),
  chain: sepolia,
  signer,
  account: signer.address, // can also be passed per action as `from` or `account`
  // Optional: sponsor gas for your users (see "Sponsor gas" guide)
  policyId: "YOUR_POLICY_ID", 
});
```

Key v4.x.x differences:
* **Account address** must be specified on the client or per action (`from` or `account`). In v5.x.x, the client automatically uses the owner's address as the account address via [EIP-7702](/docs/wallets/transactions/using-eip-7702).
* **Chain imports** come directly from `@account-kit/infra` instead of `viem/chains`.
* **Numeric values** use hex strings: `value: "0x0"` instead of `value: BigInt(0)`.
* In v4.x.x, the **paymaster capability** on `prepareCalls` or `sendCalls` is called `paymasterService` instead of `paymaster`, or you can set the `policyId` directly on the client.
* **Owners** use `LocalAccountSigner` / `WalletClientSigner` from `@aa-sdk/core`. In v5.x.x, a viem `LocalAccount` or `WalletClient` is used directly.

See the [full migration guide](/docs/wallets/resources/migration-v5) for a complete cheat sheet.
</Accordion>


# FAQs

## What chains are supported for cross-chain swaps?

Chains supported (for now) are: Arbitrum, 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 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 request a cross-chain quote via `wallet_requestQuote_v0`, a `callId` is returned. This `callId` includes important data for cross-chain tracking. Use this like any other `callId` in `wallet_getCallsStatus`.