Skip to content

SDK Reference

@earnforge/sdk is the foundation package. Every other EarnForge surface (CLI, React, MCP, bot, skill, studio) is built on top of it.

Terminal window
npm i @earnforge/sdk

The main entry point. Returns an EarnForge instance with namespaced methods.

import { createEarnForge } from '@earnforge/sdk';
const forge = createEarnForge({
composerApiKey: process.env.LIFI_API_KEY, // Optional -- only needed for deposit quotes
cache: { ttl: 60_000, maxSize: 200 }, // Optional -- LRU cache config
});
ParameterTypeDefaultDescription
composerApiKeystringundefinedLI.FI API key. Required only for buildDepositQuote() and optimizeGasRoutes().
earnDataEarnDataClientOptions{}Override the Earn Data API client options (base URL, headers).
composerBaseUrlstring"https://li.quest"Override the Composer API base URL.
cache.ttlnumber60000Cache time-to-live in milliseconds.
cache.maxSizenumber200Maximum number of cached entries.

Fetch a single page of vaults (max 50 per page, cursor-based pagination).

const page = await forge.vaults.list({
chainId: 8453, // Filter by chain (number, NOT chain name)
asset: 'USDC', // Filter by underlying token symbol
minTvl: 1_000_000, // Minimum TVL in USD
sortBy: 'apy', // Sort field
strategy: 'conservative', // Apply strategy preset filters
cursor: undefined, // Pagination cursor from previous response
});
console.log(page.data); // Vault[]
console.log(page.nextCursor); // string | null

Async iterator that auto-paginates through all vaults. Follows nextCursor until exhausted.

const vaults: Vault[] = [];
for await (const vault of forge.vaults.listAll({ chainId: 8453 })) {
vaults.push(vault);
}
console.log(`Found ${vaults.length} vaults on Base`);

Fetch a single vault by its slug. The slug format is <chainId>-<vaultAddress>.

const vault = await forge.vaults.get('8453-0xbeef0e0834849acc03f0089f01f4f1eeb06873c9');
console.log(vault.name, vault.analytics.apy.total);

Fetch the highest-APY vaults with optional filters and strategy presets.

const top = await forge.vaults.top({
asset: 'USDC',
chainId: 8453,
limit: 10,
strategy: 'risk-adjusted',
minTvl: 5_000_000,
});
ParameterTypeDefaultDescription
assetstringallUnderlying token symbol filter
chainIdnumberallEVM chain ID filter
limitnumber10Maximum vaults to return
strategyStrategyPresetnoneApply strategy preset filters
minTvlnumbernoneMinimum TVL in USD

Returns all 16 supported chains.

const chains = await forge.chains.list();
// [{ id: 1, name: 'Ethereum', ... }, { id: 8453, name: 'Base', ... }, ...]

Returns all 11 supported protocols.

const protocols = await forge.protocols.list();
// [{ name: 'aave-v3', url: '...' }, { name: 'morpho-v1', url: '...' }, ...]

Fetch all Earn positions for a wallet address.

const portfolio = await forge.portfolio.get('0xYourWalletAddress');
for (const position of portfolio.positions) {
console.log(position.vault.name, position.balanceUsd);
}

Build an unsigned deposit transaction via the LI.FI Composer API. Requires a composerApiKey.

const result = await forge.buildDepositQuote(vault, {
fromAmount: '100', // Human-readable amount (e.g. "100" for 100 USDC)
wallet: '0xYourWallet',
fromToken: undefined, // Optional: override source token address
fromChain: undefined, // Optional: override source chain ID for cross-chain
slippage: 0.03, // Optional: slippage tolerance (3%)
});
console.log(result.humanAmount); // "100"
console.log(result.rawAmount); // "100000000" (for 6-decimal USDC)
console.log(result.decimals); // 6
console.log(result.quote); // Full LI.FI quote with transactionRequest

Run pre-deposit checks against a vault. Returns a PreflightReport with pass/fail status and any issues found.

const report = forge.preflight(vault, '0xYourWallet', {
walletChainId: 1, // Optional: detect chain mismatch
depositAmount: '100', // Optional: check balance sufficiency
});
if (!report.ok) {
for (const issue of report.issues) {
console.warn(`[${issue.severity}] ${issue.code}: ${issue.message}`);
}
}

Checks performed:

CodeDescription
NOT_TRANSACTIONALVault cannot accept deposits
CHAIN_MISMATCHWallet on wrong chain
NO_GASInsufficient native token for gas
EMPTY_UNDERLYING_TOKENSVault has no underlying tokens listed
NOT_REDEEMABLEVault may not support withdrawals

Compute a composite 0-10 risk score for a vault. Higher score = safer.

const risk = forge.riskScore(vault);
console.log(risk.score); // 7.8
console.log(risk.label); // "low"
console.log(risk.breakdown); // { tvl: 9, apyStability: 8, protocol: 9, redeemability: 10, assetType: 9 }

See the full Risk Scoring Guide for dimension details, weights, and thresholds.


Get a risk-adjusted portfolio allocation across multiple vaults.

const result = await forge.suggest({
amount: 10_000, // Total USD to allocate
asset: 'USDC', // Filter by asset
maxChains: 3, // Max chains to spread across
maxVaults: 5, // Max vaults in the allocation
strategy: 'diversified', // Optional strategy preset
});
console.log(result.expectedApy); // Weighted-average APY
for (const alloc of result.allocations) {
console.log(`${alloc.vault.name}: $${alloc.amount} (${alloc.percentage}%) APY ${alloc.apy}%`);
}
ParameterTypeDefaultDescription
amountnumberrequiredTotal USD amount to allocate
assetstringallFilter by underlying token symbol
maxChainsnumber5Maximum chains in the allocation
maxVaultsnumber5Maximum vaults in the allocation
strategyStrategyPresetnoneApply strategy preset

The allocation engine scores each vault using apy * (riskScore / 10) and distributes funds proportionally by that score, enforcing the maxChains diversification constraint.


Four built-in presets for common yield strategies:

PresetDescriptionTVL FloorProtocolsTags
conservativeStablecoin, blue-chip, high TVL$50Maave-v3, morpho-v1, euler-v2, pendle, maplestablecoin
max-apyHighest APY, no restrictionsnoneallall
diversifiedMulti-chain, multi-protocol spread$1Mallall
risk-adjustedRisk score >= 7, then sort by APYnoneallall
import { STRATEGIES, getStrategy } from '@earnforge/sdk';
const config = getStrategy('conservative');
console.log(config.description); // "Stablecoin-tagged, TVL > $50M, APY 3-7%, blue-chip protocols only"
console.log(config.filters); // { tags: ['stablecoin'], minTvlUsd: 50_000_000, protocols: [...] }

Compare deposit costs from multiple source chains to find the cheapest route.

const routes = await forge.optimizeGasRoutes(vault, {
fromAmount: '100',
wallet: '0xYourWallet',
fromChains: [1, 10, 8453], // Compare Ethereum, Optimism, Base
});
for (const route of routes) {
console.log(`${route.fromChainName}: gas=$${route.gasCostUsd} fee=$${route.feeCostUsd} total=$${route.totalCostUsd}`);
}
// Routes are sorted by totalCostUsd ascending -- cheapest first

Watch a vault for APY and TVL changes. Returns an async generator of events.

const watcher = forge.watch('8453-0xbeef...', {
apyDropPercent: 20, // Alert when APY drops 20%+
tvlDropPercent: 30, // Alert when TVL drops 30%+
});
for await (const event of watcher) {
console.log(`[${event.type}] APY: ${event.current.apy}% (was ${event.previous.apy}%)`);
}

Fetch 30-day APY history from DeFiLlama yields API.

const history = await forge.getApyHistory('0xVaultAddress', 8453);
for (const point of history) {
console.log(`${point.date}: ${point.apy}%`);
}

Parse the string-typed TVL value from the API into usable formats.

import { parseTvl } from '@earnforge/sdk';
const tvl = parseTvl(vault.analytics.tvl);
console.log(tvl.raw); // "12345678.90" (original string)
console.log(tvl.parsed); // 12345678.9 (number)
console.log(tvl.bigint); // 12345678n (bigint, truncated)

Get the best available APY using the fallback chain: apy.total -> apy30d -> apy7d -> apy1d -> 0.

import { getBestApy } from '@earnforge/sdk';
const apy = getBestApy(vault.analytics);

Convert between human-readable amounts and on-chain smallest-unit amounts.

import { toSmallestUnit, fromSmallestUnit } from '@earnforge/sdk';
toSmallestUnit('100', 6); // "100000000" (100 USDC)
fromSmallestUnit('100000000', 6); // "100"

The SDK exports typed error classes for precise error handling:

Error ClassCodeWhen
EarnForgeErrorvariesBase class for all SDK errors
EarnApiErrorEARN_API_ERRORHTTP error from earn.li.fi
ComposerErrorCOMPOSER_ERRORHTTP error from li.quest (Composer)
PreflightErrorPREFLIGHT_ERRORPreflight checks found blocking issues
RateLimitErrorRATE_LIMITRate limit exceeded (429)
import { EarnApiError, ComposerError, RateLimitError } from '@earnforge/sdk';
try {
await forge.vaults.get('invalid-slug');
} catch (err) {
if (err instanceof EarnApiError) {
console.error(`API error ${err.status}: ${err.message} (${err.url})`);
} else if (err instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${err.retryAfter}ms`);
}
}