diff --git a/smart-wallet/chain-abstraction/swaps.mdx b/smart-wallet/chain-abstraction/swaps.mdx
index d1fac7a7..55b6ec1d 100644
--- a/smart-wallet/chain-abstraction/swaps.mdx
+++ b/smart-wallet/chain-abstraction/swaps.mdx
@@ -1,101 +1,98 @@
---
title: "Swaps"
-description: "Bridge and swap in a single transaction. Use Warp's built-in swaps or supply your own swap calldata."
+description: "Send and receive any token across chains. The orchestrator handles swaps automatically when needed."
---
-Rhinestone supports two swap modes:
+Warp supports arbitrary input and output tokens. You don't need to configure anything — if the token a user wants to send or receive isn't a settlement token, the orchestrator automatically routes through a swap as part of the intent.
-- **Swaps**: Warp handles the swap using solver liquidity. Simpler to integrate, best for common tokens.
-- **Injected swaps**: you supply the calldata for a swap from an external API (1inch, 0x, etc.). More setup, but full token coverage and best-price routing.
+**Settlement tokens** (the tokens Warp bridges natively) are: ETH, WETH, USDC, USDT, USDT0.
+
+- If the user is spending a non-settlement token on the origin chain, the orchestrator swaps it to a settlement token before bridging.
+- If the destination token is a non-settlement token, the orchestrator swaps the bridged settlement token to the requested token on arrival.
+
+
+ Swaps are only available on chains where the orchestrator has quoter support. On settlement-only chains (Plasma, HyperEVM, Soneium), input and output tokens must be settlement tokens.
+
-## Swaps
+## Automatic swaps
+
+Specify any token as the destination token. If it isn't a settlement token, the swap is handled automatically.
-Specify the token you want on the destination chain. If it differs from what the user holds, Warp handles the bridge and swap automatically.
+The example below sends DEGEN (Base) and receives OP (Optimism) — neither is a settlement token.
```ts
-import { getTokenAddress } from '@rhinestone/sdk'
-import { baseSepolia, arbitrumSepolia } from 'viem/chains'
+import { base, optimism } from 'viem/chains'
+import { parseUnits } from 'viem'
-const ethTarget = getTokenAddress('ETH', arbitrumSepolia.id)
-const ethAmount = 2n
-const receiver = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'
+// DEGEN on Base
+const degenBase = '0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed'
+const degenAmount = parseUnits('1000', 18)
+
+// OP token on Optimism
+const opOptimism = '0x4200000000000000000000000000000000000042'
+const opAmount = parseUnits('10', 18)
+
+const receiver = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
const transaction = await rhinestoneAccount.sendTransaction({
- sourceChains: [baseSepolia],
- targetChain: arbitrumSepolia,
+ sourceChains: [base],
+ targetChain: optimism,
calls: [
{
- to: receiver,
- value: ethAmount,
+ to: opOptimism,
+ data: encodeFunctionData({
+ abi: erc20Abi,
+ functionName: 'transfer',
+ args: [receiver, opAmount],
+ }),
},
],
tokenRequests: [
{
- address: ethTarget,
- amount: ethAmount,
+ address: opOptimism,
+ amount: opAmount,
},
],
})
```
-If the user has USDC on Base Sepolia, Warp will swap it to ETH, bridge to Arbitrum Sepolia, and execute the transfer.
+The orchestrator will:
+1. Swap the user's input token to a settlement token on the source chain
+2. Bridge the settlement token to the destination chain
+3. Swap to the requested output token on arrival
+
+The token address in `tokenRequests` must be the address on the _target_ chain.
-The token address in `calls` and `tokenRequests` must correspond to the _target_ chain.
+## Custom swap calldata
-## Injected swaps
+For specific routing logic on the destination chain, you can supply your own swap calldata. Fetch it from a DEX aggregator and include it as calls on the destination chain.
-For full token coverage or best-price routing, supply swap calldata from a DEX aggregator and include it as destination chain calls.
+**Constraint:** the final token received from your custom swap must be a settlement token. The orchestrator needs to settle in a token it recognises — if your swap outputs an arbitrary token, the intent cannot be fulfilled.
-The example below makes a WETH to cbBTC swap on Base using funds from Arbitrum, via 1inch.
+The example below bridges to Base and executes a custom swap from WETH to cbBTC using an external aggregator.
-**1. Initialize the 1inch client:**
+**1. Fetch approval and swap calldata from your aggregator:**
```ts
const walletAddress = rhinestoneAccount.getAddress()
const wethBase = '0x4200000000000000000000000000000000000006'
const wethAmount = parseEther('0.1')
const cbbtcBase = '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf'
-const oneInchApiKey = 'YOUR_1INCH_API_KEY'
-const baseUrl = 'https://api.1inch.dev/swap/v6.0/8453'
-
-async function call1inchAPI(
- endpointPath: string,
- queryParams: Record
-): Promise {
- const url = new URL(baseUrl + endpointPath)
- url.search = new URLSearchParams(queryParams).toString()
- const response = await fetch(url.toString(), {
- headers: { Accept: 'application/json', Authorization: `Bearer ${oneInchApiKey}` },
- })
- if (!response.ok) throw new Error(`1inch API error ${response.status}`)
- return response.json() as Promise
-}
-```
-
-**2. Fetch approval and swap data:**
-
-```ts
-const approveTx = await call1inchAPI('/approve/transaction', {
- tokenAddress: wethBase,
- amount: wethAmount.toString(),
-})
-const swapTx = await call1inchAPI('/swap', {
+// Fetch from your preferred DEX aggregator API
+const { approveTx, swapTx } = await fetchSwapCalldata({
src: wethBase,
dst: cbbtcBase,
- amount: wethAmount.toString(),
+ amount: wethAmount,
from: walletAddress,
- slippage: '1',
- disableEstimate: 'false',
- allowPartialFill: 'false',
})
```
-**3. Submit as a crosschain transaction:**
+**2. Submit as a crosschain transaction:**
```ts
const transaction = await rhinestoneAccount.sendTransaction({
@@ -103,7 +100,7 @@ const transaction = await rhinestoneAccount.sendTransaction({
targetChain: base,
calls: [
{ to: approveTx.to, data: approveTx.data, value: approveTx.value },
- { to: swapTx.tx.to, data: swapTx.tx.data, value: swapTx.tx.value },
+ { to: swapTx.to, data: swapTx.data, value: swapTx.value },
],
tokenRequests: [
{ address: wethBase, amount: wethAmount },
@@ -111,17 +108,17 @@ const transaction = await rhinestoneAccount.sendTransaction({
})
```
-This bridges from Arbitrum to get 0.1 WETH on Base, then executes the 1inch swap to cbBTC.
+This bridges from Arbitrum to get WETH on Base, then executes your custom swap to cbBTC.
-## Swaps
+## How swaps appear in the API response
-When you request a token on the destination chain that differs from the user's source token, Warp routes through the solver market to bridge and swap in a single operation. No additional parameters are required — the quote response tells you what will be spent and what will be received.
+When the input or output token differs from a settlement token, the orchestrator automatically includes a swap in the intent. No extra parameters are needed — the quote response reflects what will actually be spent and received.
-In a swap, the `tokensSpent` and `tokensReceived` will show different tokens:
+A swap intent will show different tokens in `tokensSpent` and `tokensReceived`:
```json
{