For the complete documentation index, see llms.txt. This page is also available as markdown.

Quickstart

Create, submit, and track a cross-chain vault deposit with ZeroDev Earn and viem.

Beta

ZeroDev Earn is an experimental product offered in beta. It supports your integration with third-party protocols. ZeroDev does not provide these protocols or the services they offer, does not custody assets, and does not execute or control transactions; any transactions are solely between you (or your end users) and the applicable third-party protocol. Any APY, rewards, or other amounts are provided by such protocols, not ZeroDev, and all data is supplied by third parties for informational purposes only. Use involves significant risk, including smart contract failures, market volatility, illiquidity, lockup, and loss of principal. ZeroDev is not liable for any losses arising from use of the product or the third-party protocols it surfaces. You are responsible for ensuring your end users understand these risks and for any disclosures required by applicable law. Do your own independent research and proceed at your own risk.

This guide deposits USDC from Base into a Morpho vault on Arbitrum using an EOA.

Mainnet example

This flow uses mainnet assets. Start with a small amount. The account needs USDC and ETH for gas on Base.

For complete EOA, Kernel, Aave, withdrawal, and recovery scripts, see the earn examples in the ZeroDev examples repository.

Prerequisites

  • Node.js 18 or later
  • A ZeroDev project with the caller's origin or IP address allowlisted
  • An EOA funded with USDC and ETH on Base

Install

npm i @zerodev/earn@beta viem
pnpm add @zerodev/earn@beta viem
yarn add @zerodev/earn@beta viem
bun add @zerodev/earn@beta viem

Pin an exact beta version before deploying to production.

Create the clients

import { createEarnClient, TOKENS, type Vault } from "@zerodev/earn";
import {
  createPublicClient,
  createWalletClient,
  http,
  parseUnits,
  type Hex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arbitrum, base } from "viem/chains";

const account = privateKeyToAccount("<PRIVATE_KEY>" as Hex);

const earn = createEarnClient({
  projectId: "<ZERODEV_PROJECT_ID>",
});

const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

createEarnClient is synchronous. It does not create a signer, RPC client, or bundler client.

Select a vault

Use discovery to find Morpho USDC vaults on Arbitrum. Check the current on-chain deposit state before requesting a quote.

const amount = "1";

const { vaults } = await earn.listVaults({
  asset: TOKENS.USDC,
  chains: [arbitrum.id],
  protocol: "morpho",
});

let vault: Vault | undefined;

for (const candidate of vaults.slice(0, 5)) {
  const check = await earn.preflight({
    owner: account.address,
    vaultId: candidate.address,
    destChainId: candidate.chainId,
    // preflight uses base units
    amount: parseUnits(amount, candidate.asset.decimals).toString(),
  });

  if (!check.depositsDisabled) {
    vault = candidate;
    break;
  }
}

if (!vault) {
  throw new Error("No Morpho USDC vault is currently accepting deposits");
}

listVaults returns listed vaults. preflight verifies the target and reads its current deposit capacity. See Supported protocols and vaults for discovery and raw-address constraints.

Request a quote

const quote = await earn.morpho.deposit({
  owner: account.address,
  amount,
  token: TOKENS.USDC,
  srcChainId: base.id,
  into: vault,
  slippage: 100, // 1%
});

if (!quote.sra) {
  throw new Error("Deposit quote did not include an SRA");
}

console.log("SRA:", quote.sra);
console.log("Estimated shares:", quote.estimatedShares);

Passing a Vault object allows the SDK to derive destChainId from vault.chainId. Request the quote when the user is ready to submit; estimates are valid for approximately 60 seconds.

Submit the source-chain calls

for (const call of quote.transaction.calls) {
  const hash = await walletClient.sendTransaction({
    to: call.to,
    data: call.data,
    value: BigInt(call.value),
  });

  await publicClient.waitForTransactionReceipt({ hash });
}

Execute every call in order. Direct-token deposit routes currently contain the SRA funding transfer. The array shape also supports routes that require more than one source-chain call.

For a smart account, submit quote.userOp.callData as one ERC-4337 user operation. See Using a smart account.

Track the deposit

Save quote.sra before submitting the transaction. Status and recovery methods use the SRA address.

const watcher = earn.watchStatus(quote.sra, {
  onStatusChange(status) {
    console.log(status.state);
  },
  onError(error) {
    console.error(error);
  },
});

await watcher.done;

The normal cross-chain sequence is PENDING, BRIDGING, EXECUTING, then COMPLETED. If execution fails after funding, use the recovery flow.

On this page