Quickstart
A smart routing address is a deposit address that accepts funds on any supported chain and delivers them to a destination you choose. Your user can send tokens to it from any chain (even from a CEX), and the funds show up in their wallet on the chain your app runs on.
The API is deliberately small: you call createSmartRoutingAddress, then send funds to the address it returns. That's really it.
In this example, we will create a smart routing address that receives funds on Base and delivers them to a wallet on Arbitrum, then send a deposit and check its status.
Complete example
Here's the complete code example for you to reference as you follow along.
You can run it with npx ts-node src/create-sra-direct-mode.ts after you set up the repo per its README.
Install packages
You will be using the following packages:
npm i @zerodev/smart-routing-address viemyarn add @zerodev/smart-routing-address viempnpm add @zerodev/smart-routing-address viembun add @zerodev/smart-routing-address viemCreate a project
- Go to the ZeroDev dashboard and create a project.
- Copy the project ID and create a
.envfile as follows:
ZERODEV_PROJECT_ID=<YOUR_PROJECT_ID_FROM_DASHBOARD>Create a smart routing address
Import the SDK and the chains you want to route between:
import {
createSmartRoutingAddress,
SMART_ROUTING_ADDRESS_V1_0_0,
} from "@zerodev/smart-routing-address";
import { arbitrum, base } from "viem/chains";Then create the address. Funds sent to it on Base will be delivered to recipient on Arbitrum:
// Replace with your own address
const owner = "0xddED85de258cC7a33A61BC6215DD766E87a97070";
const { smartRoutingAddress, estimatedFees } = await createSmartRoutingAddress({
owner,
projectId: process.env.ZERODEV_PROJECT_ID,
destChain: arbitrum,
recipient: owner, // funds land here on Arbitrum
// Source tokens (any ERC20 and ETH on Base)
srcTokens: [
{ tokenType: "ERC20", chain: base },
{ tokenType: "NATIVE", chain: base },
],
slippage: 5000,
version: SMART_ROUTING_ADDRESS_V1_0_0,
});
console.log("Smart routing address:", smartRoutingAddress);The options are:
-
owneris an address that is authorized to recover funds from the smart routing address, in case the smart routing address fails to deliver the funds for whatever reason. Typically you would set this to your user's EOA wallet, but you could also set it to your own address if you want to recover funds for users. -
projectIdis your ZeroDev project ID from the dashboard. -
destChainis the chain on which the funds are delivered. This is presumably the chain that your app runs on. -
recipientis the address that receives the funds ondestChain. It is often the same asowner, but it doesn't have to be. -
srcTokensis a list of tokens that the smart routing address should be able to receive. Only tokens listed insrcTokenswill be routed to the destination. Other tokens sent to the address will have to be manually recovered by theowner. See Supported Chains for what you can list here. -
slippageis the maximum slippage that the user can expect.slippageis an integer, where 1 is equal to 0.01% (so 100 would mean 1% slippage). -
versionis the Smart Routing Address version your address is created against. Always pass the latest constant exported by the SDK, currentlySMART_ROUTING_ADDRESS_V1_0_0. Docs for the previous version, v0.2.1, are here. -
allowPartialRoutesis optional. When you list many source chains, one temporarily unavailable route fails the whole call by default. Set it totrueto create the address anyway and drop the routes that aren't available. -
actionsis optional and replacesrecipientwhen you want to run custom logic on arrival. See Executing calls on arrival below.
The return value also includes estimatedFees, the estimated fee for each source token deposit.
Send funds to the address
Send any ERC20 or ETH on Base to smartRoutingAddress, from any wallet or exchange. No further calls to the SDK are needed. The funds are bridged and delivered to recipient on Arbitrum automatically.
Only send listed tokens
Only tokens on chains listed in srcTokens are routed. Anything else sent to the address sits there until the owner withdraws it.
Check the deposit status
Once a deposit lands, you can track it through the bridge and the delivery on the destination chain:
import { getSmartRoutingAddressStatus } from "@zerodev/smart-routing-address";
const status = await getSmartRoutingAddressStatus({
smartRoutingAddress,
});
console.log(status.deposits);Each entry in deposits includes the source deposit, the bridge transaction, and the execution on the destination chain. See Fetching Status for the full response shape.
Congratulations! You just routed funds across chains with a single deposit. Look up recipient on Arbiscan to see the funds arrive.
Next steps
Executing calls on arrival
If you need to do something with the funds when they arrive (for example deposit into a vault), pass an actions map instead of recipient. Each entry maps a received token type to the calls that run on the destination chain. Build calls with createCall, using FLEX placeholders for the token address and amount, which aren't known until a deposit lands.
import {
createSmartRoutingAddress,
createCall,
FLEX,
SMART_ROUTING_ADDRESS_V1_0_0,
} from "@zerodev/smart-routing-address";
import { erc20Abi } from "viem";
import { arbitrum, base } from "viem/chains";
const owner = "0xddED85de258cC7a33A61BC6215DD766E87a97070";
// Transfer whatever ERC20 arrives, in whatever amount, to `owner`
const erc20Call = createCall({
target: FLEX.TOKEN_ADDRESS,
value: 0n,
abi: erc20Abi,
functionName: "transfer",
args: [owner, FLEX.AMOUNT],
});
// Send whatever native amount arrives to `owner`
const nativeCall = createCall({
target: owner,
value: FLEX.NATIVE_AMOUNT,
});
const { smartRoutingAddress, estimatedFees } = await createSmartRoutingAddress({
owner,
projectId: process.env.ZERODEV_PROJECT_ID,
destChain: arbitrum,
actions: {
USDC: { action: [erc20Call] },
WRAPPED_NATIVE: { action: [erc20Call] },
NATIVE: { action: [nativeCall] },
},
srcTokens: [
{ tokenType: "ERC20", chain: base },
{ tokenType: "NATIVE", chain: base },
],
slippage: 5000,
version: SMART_ROUTING_ADDRESS_V1_0_0,
});FLEX placeholders
Because the exact token and amount aren't known until a deposit lands, createCall lets you leave holes that the router fills in at execution time:
FLEX.TOKEN_ADDRESS— the address of the token that arrived.FLEX.AMOUNT— the ERC20 amount that arrived (net of fees).FLEX.NATIVE_AMOUNT— the native amount that arrived.
Use these anywhere a token address or amount is expected — as a target, a value, or inside args.
Deposit modes
A smart routing address delivers funds on the destination chain in one of two ways: direct mode (pass recipient, as in the steps above) or execute mode (pass actions, as in Executing calls on arrival).
| Direct mode | Execute mode | |
|---|---|---|
| What you pass | recipient | actions |
| Destination logic | none — funds are transferred to recipient | your calls run on arrival |
| Gas cost | lower | higher (runs your calls) |
| Use when | you just want funds delivered | you need to do something with the funds |
recipient and owner are often the same address, but they don't have to be — owner recovers stuck funds, while recipient receives routed funds.
Keep going
- Drop in the React UI to show users a deposit flow.
- Sponsor fees so users don't pay for routing.
- Coming from v0.2.1? Read the Migration Guide.