Quickstart
Archived version
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 transfers them to a wallet on Arbitrum, then send a deposit and check its status.
Install packages
You will be using the following packages:
npm i @zerodev/smart-routing-address@0.2.6 viemyarn add @zerodev/smart-routing-address@0.2.6 viempnpm add @zerodev/smart-routing-address@0.2.6 viembun add @zerodev/smart-routing-address@0.2.6 viemCreate a project
In v0.2.1 a project is only needed for fee sponsorship. You can skip this step and leave out config.baseUrl below.
- 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,
createCall,
FLEX,
SMART_ROUTING_ADDRESS_SERVER_URL,
SMART_ROUTING_ADDRESS_V0_2_1,
} from "@zerodev/smart-routing-address";
import { erc20Abi } from "viem";
import { arbitrum, base } from "viem/chains";Define the calls that run on Arbitrum when funds arrive. Build them with createCall, using FLEX placeholders for the token address and amount, which aren't known until a deposit lands:
// Replace with your own address
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,
});Then create the address. Funds sent to it on Base trigger the matching actions on Arbitrum:
const { smartRoutingAddress, estimatedFees } = await createSmartRoutingAddress({
owner,
destChain: arbitrum,
// Source tokens (any ERC20 and ETH on Base)
srcTokens: [
{ tokenType: "ERC20", chain: base },
{ tokenType: "NATIVE", chain: base },
],
// Which calls run on Arbitrum for each token that arrives
actions: {
USDC: { action: [erc20Call], fallBack: [] },
WRAPPED_NATIVE: { action: [erc20Call], fallBack: [] },
NATIVE: { action: [nativeCall], fallBack: [] },
},
slippage: 5000,
config: {
version: SMART_ROUTING_ADDRESS_V0_2_1,
// Only needed for fee sponsorship
baseUrl: `${SMART_ROUTING_ADDRESS_SERVER_URL}/${process.env.ZERODEV_PROJECT_ID}`,
},
});
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 execute theactionsfor 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. -
destChainis the chain on which theactionsrun. This is presumably the chain that your app runs on. -
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. -
actionsmaps each token type to the calls that run ondestChainwhen that token arrives. Every entry needs anactionlist and afallBacklist; pass an emptyfallBackif you don't need one. -
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). -
config.versionis the Smart Routing Address version your address is created against. PassSMART_ROUTING_ADDRESS_V0_2_1. For the latest version, see the current docs. -
config.baseUrlis only needed for fee sponsorship. Leave it out otherwise. -
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.
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 the matching actions run 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 recovers it through the portal.
Check the deposit status
Once a deposit lands, you can track it through the bridge and the execution 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 owner on Arbiscan to see the funds arrive.
Next steps
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.
Keep going
- Sponsor fees so users don't pay for routing.
- Ready to upgrade? Read the Migration Guide to move to v1.