# Same-chain swaps (Alpha)

> Convert any token to any other token onchain using Wallet APIs, with gas sponsorship and post-swap action support.

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

Swaps let you convert any token to any other token onchain. They're built natively into Wallet APIs and you can integrate in minutes.

You can also add actions after the swap completes. For example, you can deposit your newly swapped tokens into a DeFi protocol.

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

[Cross-chain swaps are live! Send your first cross-chain swap now!](/docs/wallets/transactions/cross-chain-swap-tokens)

<Tip>
  Swaps are in alpha. There may be changes in the future to simplify
  the endpoint/SDK.
</Tip>

# The swap flow

## **Flow**

1. Request a swap quote
2. Sign the prepared swap calls (including any post swap action)
3. Send prepared calls
4. Wait for onchain confirmation

## **Swap options**

When requesting a swap quote, specify either an amount in, or a minimum amount out.

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

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

## Prerequisites

Before you begin, ensure you have:

* An [API key](https://dashboard.alchemy.com/apps)
* If you're sponsoring gas, a [Gas Manager](https://dashboard.alchemy.com/gas-manager/policy/create) policy
* A small amount of USDC for testing (~$1 worth is enough)
  * **Important**: Send these tokens to your smart wallet address before swapping.
* An owner to 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>

You need the following environment variables:

* `ALCHEMY_API_KEY`: An [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 an owner

<CodeBlocks>
  ```ts title="requestQuote.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 swap quote
  const { quote, ...calls } = await swapClient.requestQuoteV0({
    fromToken: "0x...",
    toToken: "0x...",
    minimumToAmount: "0x...",
  });

  // Display the swap quote, including the minimum amount to receive and the expiry
  console.log(quote);

  // 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
  const signedCalls = await swapClient.signPreparedCalls(calls);

  // Send the prepared calls
  const { id } = await swapClient.sendPreparedCalls(signedCalls);

  // Wait for the call to resolve
  const callStatusResult = await swapClient.waitForCallsStatus({
    id,
  });

  // Filter through success or failure cases
  if (
    callStatusResult.status !== "success" ||
    !callStatusResult.receipts ||
    !callStatusResult.receipts[0]
  ) {
    throw new Error(
      `Transaction failed with status ${callStatusResult.status}, full receipt:\n ${JSON.stringify(callStatusResult, null, 2)}`,
    );
  }

  console.log("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">
    Fill in values wrapped in curly braces like `{SIGNER_ADDRESS}`.

<Steps>

<Step title="Request a swap quote">

Use your owner address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default. Note that `postCalls` are optional and allow you to batch an array of calls after the swap.

<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": "{CHAIN_ID}",
        "fromToken": "{FROM_TOKEN}",
        "toToken": "{TO_TOKEN}",
        "fromAmount": "{FROM_AMOUNT_HEXADECIMAL}",
        "postCalls": [{
          "to": "{POSTCALL_TO_ADDRESS}",
          "data": "{POSTCALL_DATA}",
          "value": "{POSTCALL_VALUE}"
        }],
        "capabilities": {
          "paymasterService": {
            "policyId": "{PAYMASTER_POLICY_ID}"
          }
        }
      }
    ]
  }'
```

This returns:

```json
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "rawCalls": false,
    "chainId": "...",
    "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 `signatureRequest`. This is what you now sign, and the returned `data` field is what you need to send the transaction.

If the account hasn't been delegated yet, the response type is `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 -- pass it as raw bytes, generally like so: `{ raw: "0x..." }`).

    Use the `personal_sign` RPC method, as noted by the `type` in the `signatureRequest`.

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

</Step>

<Step title="Send the prepared call">

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

This returns:

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

Note the `CALL_ID`! You need this to track call status in the next step.

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

</Step>

<Step title="Track the prepared call">

Use the `wallet_getCallsStatus` endpoint to check the transaction's status.

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

This returns:

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

Note that the status codes match the following:
| Code | Title |
|------|--------------------------|
| 100 | Pending |
| 200 | Confirmed |
| 400 | Offchain Failure |
| 500 | Onchain Failure |
| 600 | Partial Onchain Failure |

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

For more details, see 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?

Chains supported (for now) are: Ethereum, Arbitrum, Base, Berachain, BSC/BNB, Ink, Monad, Optimism, Polygon, Unichain and World Chain mainnets.

## Does the Swap API support cross-chain swaps?

Currently, the Swap API supports only single-chain swaps. Cross-chain swaps are coming soon!

## How do you encode values?

Values are passed as hexadecimal strings. The Swap API does not 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.