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
stETH
that 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/defi
API
Creating a DeFi client
All DeFi APIs are exposed through a "DeFi client":
import { createKernelDefiClient } from "@zerodev/defi"
const defiClient = createKernelDefiClient(kernelClient, projectId)
Where:
kernelClient
is the account client object.projectId
is 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:
fromToken
is the input token.fromAmount
is abigint
representing the input token amount. Note that this uses the smallest unit for the token, e.g. Wei for Ether.toToken
is 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:
fromToken
is the input token.fromAmount
is abigint
representing the input token amount. Note that this uses the smallest unit for the token, e.g. Wei for Ether.toToken
is 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
Address
for 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:
getSwapUserOp
instead ofsendSwapUserOp
getSwapUserOpCrossChain
instead 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
.