DeFi Integrations
ZeroDev partners with Enso to support seamless token swaps and DeFi integrations, even across chains.
The API deals with two types of tokens:
- Base tokens are normal tokens that do not represent a DeFi position. Examples are ETH, USDC, etc.
- DeFi tokens are ERC20 tokens that represent a DeFi position, such as in a ERC-4626 vault. For example, depositing ETH into Lido gets you
stETHthat represents staked ETH.
By allowing you to swap between base tokens and DeFi tokens, you can easily:
- Swap between any token pairs.
- Entering and exiting DeFi positions (staking, lending, etc.)
ZeroDev leverages batching and delegatecall internally to ensure that even complex routes are executed in one atomic UserOp, providing the user with low latency, low gas cost, and high safety.
Supported Tokens
See the full lists of supported base tokens and DeFi tokens:
Installation
npm i @zerodev/defiAPI
Creating a DeFi client
All DeFi APIs are exposed through a "DeFi client":
import { createKernelDefiClient } from "@zerodev/defi"
const defiClient = createKernelDefiClient(kernelClient, projectId)Where:
kernelClientis the account client object.projectIdis your ZeroDev project ID, obtained from the dashboard.
Swapping Tokens
Suppose you want to swap 100 USDC to USDT:
import { baseTokenAddresses } from "@zerodev/defi"
import { parseUnits } from "viem"
import { arbitrum } from "viem/chains"
// Replace this with your network
const chain = arbitrum
const userOpHash = await defiClient.sendSwapUserOp({
fromToken: baseTokenAddresses[chain.id].USDC,
fromAmount: parseUnits('100', 6), // USDC uses 6 decimals
toToken: baseTokenAddresses[chain.id].USDT,
gasToken: 'sponsored',
})Where:
fromTokenis the input token.fromAmountis abigintrepresenting the input token amount. Note that this uses the smallest unit for the token, e.g. Wei for Ether.toTokenis the output token.toAddress: defaults to the account's own address. If specified, it will send the output token to that address instead.gasToken: see below.
Entering / Exiting DeFi positions
Entering a DeFi position simply means swapping a token into a DeFi token.
You can get a DeFi token address through the defiTokenAddresses constant, which is a map with three keys: chainId => tokenName => protocolName. For example, the DeFi token representing the USDC vault on AAVE v3 on Arbitrum would be defiTokenAddresses[arbitrum.id]['USDC']['aave-v3']. So, to enter the vault:
import { defiTokenAddresses } from "@zerodev/defi"
import { arbitrum } from "viem/chains"
const chain = arbitrum
const userOpHash = await defiClient.sendSwapUserOp({
fromToken: baseTokenAddresses[chain.id].USDC,
fromAmount: 1_000_000,
toToken: defiTokenAddresses[chain.id]['USDC']['aave-v3'],
gasToken: 'sponsored',
})Similarly, exiting a DeFi position is just swapping a DeFi token into another token.
Cross-chain Swaps
To swap tokens across chains, use sendSwapUserOpCrossChain. For example, to swap USDC on Arbitrum to DAI on Polygon:
// Convert mainnet DAI to USDC, and lend it through AAVE on Arbitrum
const userOpHash = await defiClient.sendSwapUserOpCrossChain({
fromToken: baseTokenAddresses[mainnet.id].DAI,
fromAmount: 1_000_000,
toToken: defiTokenAddresses[arbitrum.id]['USDC']['aave-v3'],
toChainId: arbitrum.id,
// Pay gas with input token
gasToken: "fromToken"
})Where:
fromTokenis the input token.fromAmountis abigintrepresenting the input token amount. Note that this uses the smallest unit for the token, e.g. Wei for Ether.toTokenis the output token.toChainId: the chain fortoToken,toAddress: defaults to the account's own address. If specified, it will send the output token to that address instead.gasToken: see below.
Listing Tokens
You can list all ERC20 tokens an account owns with the listTokenBalances function:
const accountBalances = await defiClient.listTokenBalances({
account: account.address,
chainId: chain.id,
})Gas Tokens
The gasToken flag specifies how gas is paid for the UserOp. It can be one of the following values:
sponsored: sponsor the UserOp.fromToken: pays gas in the input token, using a ERC20 paymaster.toToken: pays gas in the output token, using a ERC20 paymaster.native: pays gas in the native token, using the account's balance.- You can also specify an
Addressfor a ERC20 token, to pay gas with that token using a ERC20 paymaster.
Getting the UserOp without sending
If you want to just construct a UserOp but not send it immediately, use:
getSwapUserOpinstead ofsendSwapUserOpgetSwapUserOpCrossChaininstead ofsendSwapUserOpCrossChain
If you want to get regular transaction data instead of UserOps (presumably because you want to send the transaction through a EOA), use getSwapUserOpCrossChain.