@circle-fin/earn-kit
v1.6.0
Published
SDK for DeFi lending vault deposits, withdrawals, and rewards
Keywords
Readme
Earn Kit
Note: Earn Kit is coming soon. The APIs documented here are published for early integration and feedback; vault availability and production readiness will be announced ahead of general availability.
Earn Kit is a Circle SDK for DeFi lending vault earn flows. It provides typed operations for discovering vaults, fetching wallet positions, previewing deposits, withdrawals, and reward claims, and executing deposit, withdraw, and claim rewards transactions.
Installation
npm install @circle-fin/earn-kit
# or
yarn add @circle-fin/earn-kitInstall an adapter for the wallet stack you use. For EVM flows:
npm install @circle-fin/adapter-viem-v2 viem
# or
yarn add @circle-fin/adapter-viem-v2 viemQuick Start
import { Blockchain, EarnChain, EarnKit } from '@circle-fin/earn-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
const kit = new EarnKit()
const adapter = createViemAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY,
})
const quote = await kit.getDepositQuote({
from: { adapter, chain: EarnChain.Arc_Testnet },
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
amount: '100.50',
})
console.log(`Expected shares: ${quote.expectedShares.amount}`)
const depositResult = await kit.deposit({
from: { adapter, chain: EarnChain.Arc_Testnet },
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
amount: '100.50',
})
console.log(`Deposit submitted: ${depositResult.txHash}`)Deposits
Same-chain deposits keep the existing approval and Earn action flow. Cross-chain
deposits are selected only when to is present and to.chain differs from
from.chain.
const bridgeDeposit = await kit.deposit({
from: { adapter, chain: Blockchain.Ethereum_Sepolia },
to: {
chain: EarnChain.Arc_Testnet,
recipientAddress: '0x1234567890123456789012345678901234567890',
},
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
amount: '100.50',
})
console.log(`Bridge deposit submitted: ${bridgeDeposit.execId}`)For cross-chain deposits, to.recipientAddress is the destination wallet that
receives the vault position and from.address can be used as an optional
source wallet override. The returned result contains the bridge execution ID,
submit status, source chain, destination chain, vault address, amount, and
prepared bundle expiry timestamp. The SDK generates the bridge execution ID
and retry() resumes the same bridge execution after a failure.
Cross-chain Earn deposits currently support Ethereum Sepolia, Arbitrum Sepolia, and Base Sepolia as source chains, with Arc Testnet as the Earn destination chain.
Withdrawal Fees
Withdrawal quotes return operation fees in quote.fees. Circle withdrawal fees
are tagged with type: 'circle' so integrators can show the fee preview before
calling withdraw.
const quote = await kit.getWithdrawalQuote({
from: { adapter, chain: EarnChain.Arc_Testnet },
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
amount: '50.00',
})
const circleFee = quote.fees.find((fee) => fee.type === 'circle')
if (circleFee === undefined) {
console.log('Circle fee: 0')
} else {
console.log(`Circle fee: ${circleFee.amount} ${circleFee.symbol}`)
}When no Circle fee applies, including when the backend fee flag is disabled,
quote.fees has no type: 'circle' entry. EarnKit formats fee amounts as
human-readable decimal strings, consistent with the rest of the kit-level quote
surface. Read the returned fee entry's symbol rather than assuming a fixed
fee token.
Batched operations (SCA wallets)
Same-chain deposit and withdraw each normally submit two sequential
transactions: an ERC-20 / vault-share approve followed by the Earn execute.
When the connected adapter supports atomic batching, the SDK bundles the
approve and execute into one batch instead. The adapter may submit that
batch through EIP-5792, a Circle wallet challenge, or an ERC-4337 UserOperation.
This removes the partial-state window where a user approves but never executes
and confirms in a single wallet interaction.
Batching is automatic and requires no code change — it engages only when all of the following hold:
- The operation is a same-chain
depositorwithdraw(cross-chain deposits use an EIP-3009 signature, notapprove+execute, so batching does not apply). - The
from.chainis an EVM chain. - The signed payload contains a positive token pull and approval has not been skipped.
- The wallet adapter reports atomic-batch support for the chain and address.
The SDK reads the current allowance before submission. When a top-up is needed,
it batches [approve, execute]; when the allowance already covers the amount,
it submits [execute] through the same batch API without an unnecessary
approval. Operations with no approval-bearing token input, EOA wallets, and
wallets without batch support keep the existing sequential behavior unchanged.
To force the sequential flow even on a batch-capable wallet, set
batchTransactions: false in the operation config:
await kit.deposit({
from: { adapter, chain: EarnChain.Arc_Testnet },
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
amount: '100.50',
config: { batchTransactions: false }, // always use sequential approve -> execute
})Discovering Vaults
Iterate every vault available on a chain without managing pagination:
for await (const vault of kit.exploreVaultsIterator({
chain: 'Arc_Testnet',
sortBy: 'apy',
})) {
console.log(`${vault.name}: ${(vault.currentApy * 100).toFixed(2)}% APY`)
}For paged UIs that need totals, fetch one page at a time instead:
const { vaults, pagination } = await kit.exploreVaults({
chain: 'Arc_Testnet',
page: 1,
})
console.log(`Page 1 of ${pagination.totalPages} (${pagination.totalCount} vaults)`)Supported Chains
Use kit.getSupportedChains() or the functional getSupportedChains(context)
helper to read the supported chain definitions from configured earn providers.
Core Operations
getVaults: fetch vault metadata for one or more vaults.exploreVaultsIterator: lazily iterate every vault available on a chain with optional protocol, asset, APY, and TVL filters. Pages are fetched on demand, so there is no page arithmetic to manage.exploreVaults: fetch a single page of vault discovery results with pagination metadata, for paged UIs.getPosition: fetch a wallet position for a vault.getDepositQuote: preview a deposit.getWithdrawalQuote: preview a withdrawal.getClaimRewardsQuote: preview claimable rewards.deposit: execute a deposit transaction.withdraw: execute a withdrawal transaction.claimRewards: claim rewards, or return without a transaction when none are claimable.getCrossChainDepositStatus: read the current bridge status of a cross-chain deposit byexecId.waitForCrossChainDeposit: poll a cross-chain deposit until it reaches a terminal bridge state (or the wait budget elapses).
Configuration
Earn Kit works without an API key. An API key can be passed per operation for permissioned access and attribution:
await kit.getVaults({
vaults: [
{
chain: 'Arc_Testnet',
vaultAddress: '0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88',
},
],
config: {
apiKey: process.env.CIRCLE_API_KEY,
},
})The API key is a server-only secret — supplying it from a browser throws. Hold it on your server and forward the prepared transaction to the client.
Migrating from
kitKey:config.kitKeyis deprecated in favor ofconfig.apiKey. It keeps working, and legacyKIT_KEY:<keyId>:<keySecret>values are still accepted, so you can move over at your own pace. When both fields are set,apiKeywins.
License
This project is licensed under the Apache 2.0 License. Contact support for details.
