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

Reference

ZeroDev Earn client configuration, methods, types, defaults, exports, and errors.

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.

Client

import { createEarnClient } from "@zerodev/earn";

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

createEarnClient is synchronous.

OptionTypeRequiredDefaultDescription
projectIdstringYes—Sent as x-project-id on each request
serverUrlstringNoDEFAULT_SERVER_URLOverride for staging or self-hosting
fetchtypeof fetchNoGlobal fetchCustom fetch implementation
timeoutMsnumberNo30000Per-request timeout
maxRetriesnumberNo2Retry limit for idempotent GET requests

Quote-building POST requests are not retried automatically because each request can create an SRA. GET requests retry after 429, 502, 503, network failures, and timeouts.

Deposit methods

All deposit methods return Promise<Quote>.

MethodParamsConstraints
earn.aave.deposit(params)AaveDepositParamsdestChainId required; into optional
earn.morpho.deposit(params)VaultDepositParamsinto required
earn.fluid.deposit(params)VaultDepositParamsinto required
earn.yearn.deposit(params)VaultDepositParamsinto required
earn.erc4626.deposit(params)VaultDepositParamsinto required; target must pass ERC-4626 validation
earn.depositIntoVault(params)VaultDepositParams & { protocol: string }Registered protocol required
type DepositParams = {
  owner: Address;
  amount: number | string;      // display units
  token: TokenSymbol | Address;
  srcChainId: number;
  destChainId?: number;
  into?: string | Vault;
  slippage?: number;            // 1–5000 bps; default 100
};

type AaveDepositParams = DepositParams & {
  destChainId: number;
};

type VaultDepositParams = DepositParams & {
  into: string | Vault;
};

When into is a Vault, destChainId is derived from vault.chainId. Providing a different explicit destChainId returns INVALID_REQUEST.

See Supported protocols and vaults for adapter behavior and custom-vault constraints.

Discovery methods

MethodReturnsNotes
earn.listVaults(params?)Promise<VaultPage>Lists supported Aave, Morpho, Fluid, and Yearn vaults
earn.getVault(vaultId, chainId?)Promise<VaultDetails>Pass chainId for direct lookup
earn.getChains()Promise<ChainInfo[]>Reads the SRA chain registry
earn.getTokens({ chainId? })Promise<TokenInfo[]>Reads supported SRA token addresses
earn.preflight(params)Promise<PreflightResult>Resolves a vault by address and reads current capacity
earn.listOpportunities(params?)Promise<VaultPage>Legacy discovery method with a different filter shape
type ListVaultsParams = {
  asset?: TokenSymbol | Address;
  chains?: number[];
  protocol?: string;
  minTvl?: number; // USD; omitted means $100,000
  minApy?: number; // percent
  page?: number;   // zero-based
};

type VaultPage = {
  vaults: Vault[];
  nextPage: number | null;
};

type ListOpportunitiesParams = {
  chainId?: number;
  category?: "lend" | "liquid-staking" | "fixed-yield";
  protocol?: string;
  minTvl?: number;
  minApy?: number;
  page?: number;
};

listOpportunities accepts chainId, category, protocol, minTvl, minApy, and page. It does not accept the asset or chains filters used by listVaults.

Listed vaults have both an id and a contract address. getVault and withdrawFromVault accept either value as vaultId; pass chainId to identify the network. preflight.vaultId accepts only the contract address.

Status and SRA methods

MethodReturnsNotes
earn.getStatus(sra)Promise<EarnStatus>Single status read
earn.watchStatus(sra, options)WatcherPolls until terminal state, manual stop, or error
earn.getSraInfo({ sra })Promise<SraInfoResult>Owner, chain, source tokens, slippage, and stored actions
earn.getSraFeeEstimates({ sra })Promise<SraFeeEstimatesResult>Per-chain fee data and sponsorship flags
earn.getWithdrawCalls({ sra, tokens })Promise<WithdrawCallsResult>Owner-authorized SRA recovery calls
type WatchStatusOptions = {
  interval?: number;    // default 4000 ms
  timeout?: number;     // default 600000 ms; 0 disables timeout
  maxRetries?: number;  // default 5 consecutive failures
  onStatusChange: (status: EarnStatus) => void;
  onError?: (error: unknown) => void;
};

type Watcher = {
  (): void;
  stop(): void;
  done: Promise<void>;
};

type EarnState =
  | "PENDING"
  | "BRIDGING"
  | "EXECUTING"
  | "COMPLETED"
  | "FAILED"
  | "ABANDONED";

type WithdrawCallsResult = {
  data: {
    chainId: number;
    calls: {
      to: Address;
      data?: Hex;
      value: string;
    }[];
  }[];
  receiver: Address;
};

watcher.done resolves after a terminal state or manual stop. It rejects after persistent polling failure or WATCH_TIMEOUT.

Withdrawal method

earn.withdrawFromVault(params: VaultWithdrawParams): Promise<VaultWithdrawResult>;

type VaultWithdrawParams = {
  owner: Address;
  vaultId: string;
  chainId: number;
  amount?: number | string;
  max?: boolean;
};

Pass:

  • amount for a partial withdrawal.
  • max: true for a full exit.
  • Neither for a preview with no calls.

amount and max cannot be used together. Withdrawal is same-chain. The vault must be indexed by vaults.fyi and use an allowlisted protocol, but it does not need to match the listVaults discovery filters.

type VaultWithdrawResult = {
  chainId: number;
  protocol: string;
  target: Address;
  asset: Address;
  assetSymbol: string;
  decimals: number;
  amount: string;
  exitAll: boolean;
  available: string;
  receiver: Address;
  calls: OnChainCall[];
};

Disabled method

earn.bridgeAndSwap(params) rejects locally with FEATURE_DISABLED. Standalone bridge-and-swap is not available. Token conversion required by a deposit can still be handled by an SRA-supported route.

Quote

All deposit methods return the same Quote shape.

type Quote = {
  quoteId: string;
  expiresAt: string;
  sra: Address | null;
  transaction: {
    chainId: number;
    calls: OnChainCall[];
  };
  userOp: {
    callData: Hex;
    calls: OnChainCall[];
    chainId: number;
  };
  estimatedFees: {
    totalFeeAmount: string | null;
    totalFeeToken: Address | null;
    perChain: {
      chainId: number;
      feeAmount: string | null;
      feeToken: Address | null;
    }[];
  };
  estimatedReceiveAmount: string;
  estimatedShares?: string;
  vaultApy?: number;
  route?: {
    bridgeTokenType: string | null;
    bridgeTokenSrc?: Address;
    bridgeTokenDest?: Address;
    sameChain: boolean;
  };
};

type OnChainCall = {
  to: Address;
  data: Hex;
  value: string;
};

Amounts in API responses are base-unit decimal strings. Convert them with BigInt before passing them to on-chain clients.

totalFeeAmount and totalFeeToken are both null when fees use more than one denomination. Render perChain in that case. Sponsored fee entries are excluded from charged totals.

Errors

Rejected SDK requests use EarnError or a code-specific subclass.

import {
  EarnError,
  VaultCapExceededError,
} from "@zerodev/earn";

try {
  await earn.morpho.deposit(params);
} catch (error) {
  if (error instanceof VaultCapExceededError) {
    suggestSmallerAmount();
  } else if (error instanceof EarnError) {
    reportError(error.code, error.message, error.requestId);
  }
}

Include requestId in bug reports. Some codes, including SLIPPAGE_TOO_LOW, use the base EarnError without a dedicated subclass.

CodeMeaningSuggested action
INVALID_REQUESTMissing, malformed, or conflicting parameterFix request parameters
UNSUPPORTED_TOKENFunding token is not supported on the source chainSelect a supported token or route
UNKNOWN_PROTOCOLNo adapter is registered for protocolUse a supported facade or erc4626
VAULT_TYPE_MISMATCHTarget does not match the selected adapterCorrect the protocol or target
ASSET_MISMATCHTarget asset differs from the resolved deposit assetCorrect token or target
VAULT_NOT_ALLOWLISTEDVault lookup failed or its protocol is not allowlistedVerify the address, chain, and protocol
CHAIN_NOT_SUPPORTEDSource or destination chain is unavailableSelect a supported chain
VAULT_CAP_EXCEEDEDAmount exceeds current deposit capacityReduce amount or select another vault
VAULT_DEPOSITS_DISABLEDOn-chain maxDeposit is zeroSelect another vault
INSUFFICIENT_AMOUNTAmount does not cover route minimum or feesIncrease amount or slippage as described in details
SLIPPAGE_TOO_LOWSlippage does not cover route feesRetry using details.minSlippageBps
SWAP_ROUTE_NOT_FOUNDRequired cross-token route is unavailableUse another token, vault, or chain
QUOTE_EXPIREDQuote is no longer accepted by the endpointRequest another quote
SANCTIONED_ADDRESSOwner failed sanctions screeningDo not retry with the same owner
VAULT_BLOCKEDTarget is blocked by service policySelect another vault
ACCESS_DENIEDOrigin or IP is not allowlisted for the projectUpdate project allowlist
RATE_LIMITEDRequest rate exceededRetry after backoff
PAYLOAD_TOO_LARGERequest body exceeds the server limitReduce request size
IDEMPOTENCY_KEY_CONFLICTIdempotency key was reused with another bodyUse a new key
FEATURE_DISABLEDRequested flow is disabledUse an enabled deposit flow
SRA_NOT_FOUNDSRA address is unknownVerify the address and network
SRA_UNAVAILABLESRA service failedRetry by requesting a new quote
QUOTER_UNAVAILABLERoute quote provider failedRetry later
RPC_UNAVAILABLEDestination-chain RPC failedRetry later
SERVICE_UNAVAILABLERequired service is unavailableRetry later
INTERNAL_ERRORUnexpected server failureReport requestId
WATCH_TIMEOUTClient-only watcher timeoutRead status again or increase timeout

Exports

import {
  createEarnClient,
  DEFAULT_SERVER_URL,
  TOKENS,
  EarnError,
  VaultCapExceededError,
  VaultDepositsDisabledError,
  WatchTimeoutError,
} from "@zerodev/earn";

Public types include Quote, OnChainCall, Vault, VaultDetails, DepositParams, VaultDepositParams, VaultWithdrawParams, VaultWithdrawResult, EarnStatus, EarnState, Watcher, and method-specific parameter and result types.

On this page