Swaps let you convert any token to any other token onchain. They're built natively into Smart Wallets and you can integrate in minutes.
Smart Wallets also allow you to add actions after the swap completes. For example, you can deposit your newly swapped tokens into a DeFi protocol.
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 live! Send your first cross-chain swap now!
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.
- Request a swap quote
- Sign the prepared swap calls (including any post swap action)
- Send prepared calls
- Wait for onchain confirmation
When requesting a swap quote, you can specify either an amount in, or a minimum amount out.
// 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.Before you begin, ensure you have:
- An Alchemy API Key
- If you're sponsoring gas, then a Gas Manager policy
- A small amount of USDC 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
@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.You'll need the following env variables:
ALCHEMY_API_KEY: An Alchemy API KeyALCHEMY_POLICY_ID: A Gas Manager policy IDPRIVATE_KEY: A private key for a signer
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}`,
);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:
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 (
fromoraccount). In v5.x.x, the client automatically uses the signer's address as the account address via EIP-7702. - Chain imports come directly from
@account-kit/infrainstead ofviem/chains. - Numeric values use hex strings:
value: "0x0"instead ofvalue: BigInt(0). - In v4.x.x, the paymaster capability on
prepareCallsorsendCallsis calledpaymasterServiceinstead ofpaymaster, or you can set thepolicyIddirectly on the client. - Signers use
LocalAccountSigner/WalletClientSignerfrom@aa-sdk/core. In v5.x.x, a viemLocalAccountorWalletClientis used directly.
See the full migration guide for a complete cheat sheet.
Chains supported (for now) are: Ethereum, Arbitrum, Base, Berachain, BSC/BNB, Ink, Monad, Optimism, Polygon, Unichain and World Chain mainnets.
Currently, the Swap API supports only single-chain swaps. Cross-chain swaps are coming soon!
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.
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.