Summary
Plenty of apps can hold money that just sits there: the balance in a fintech account, the stablecoins a user has not spent yet, or the onchain capital sitting idle in a wallet. Users increasingly expect that money to earn something, the way a good savings account does.
Adding yield to a product sounds simple until you start counting what it actually takes. For onchain apps, it can mean writing and auditing smart contracts, integrating different protocols, tracking positions, and maintaining the infrastructure behind deposits and withdrawals. That is a lot to build before onboarding a single user.
Earn Kit changes that. Earn Kit is a single TypeScript SDK, part of the App Kits suite, for integrating USDC and EURC earning opportunities, offered by third-party protocols, into wallets, exchanges, fintech apps, and other products. There is no smart contract code to write and no custody to take on. Earn Kit connects users to supported onchain lending opportunities, submits transactions through the integrator’s wallet, and reads back position and rate data while the wallet stays in control of funds.
Earn Kit is built for the integrator: the wallet, exchange, fintech app, or custody platform embedding earning into its own product. The path is direct: discover a supported opportunity, deposit on behalf of a user’s wallet, track the position and accrued yield or loss, then withdraw when the user wants to exit. The SDK provides the signed execution instructions and onchain adapter contract. The integrator brings its own wallet and decides which supported opportunities to offer.
Below, we walk through the USDC depositor flow on Arc Testnet, from discovering a supported opportunity to tracking the position and withdrawing USDC. The current example uses a Morpho-based vault, so the SDK methods and response fields retain their vault-oriented names. For project setup, package names, and the full set of wallet-adapter options, see the Earn Kit quickstart.
The earn flow
Earn Kit gives you four operations to compose: discover supported earning opportunities, deposit USDC, track the position as yield or losses accrue, and withdraw USDC back to the associated wallet.

The underlying position can vary by protocol. In this Arc Testnet walkthrough, depositing USDC into the selected Morpho vault gives the user vault shares: a claim on the vault’s underlying USDC. As the position accrues yield, the USDC value of those shares grows. Earn Kit keeps the integration in familiar USDC terms while returning the implementation-specific data the app needs to read the position.
Each operation is a single typed call. Discovering opportunities and checking a position fetch data, with no transaction to sign. Depositing and withdrawing submit onchain transactions: the SDK fetches signed parameters from the service and handles the USDC approval. Earn Kit can be used permissionlessly, so no API key is needed.
The snippets below all build on one connected instance:
import { 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 as `0x${string}`,
});Discovering a vault
The first question an integrator faces is: “Which opportunity should the app offer?” In the current SDK, exploreVaults is the discovery method. It returns a paginated list of supported vault-based opportunities on a chain, with optional filters, so the app can find the relevant address instead of hardcoding it.
const { vaults, pagination } = await kit.exploreVaults({
chain: "Arc_Testnet",
});
Each entry carries the information an app needs to present a choice. If the app already has a current vault address, getVaults reads its full metadata.
{
vaultAddress: '0xaabbef1d3971c710276ed41ec791bbe14cdb8e88',
chain: 'Arc_Testnet',
name: 'EarnKit USDC Vault (Arc Testnet)',
protocol: 'MORPHO',
asset: 'USDC',
assetAddress: '0x3600000000000000000000000000000000000000',
currentApy: 0.042,
nativeApy: 0,
vaultFee: 0,
rewards: [],
collateral: [],
status: 'active',
circleGuarded: false,
address: '0xaabbef1d3971c710276ed41ec791bbe14cdb8e88',
asOf: '2026-09-14T03:44:07Z',
manager: null,
apyProfile: {
current: 0.042,
native: 0,
d7: null,
d30: null,
d90: null,
rewardShare: 1,
source: 'morpho:avgNetApy',
asOf: '2026-09-14T03:44:07Z'
},
fee: {
performance: null,
management: null
},
riskSignals: {
circleSentinel: false,
warnings: [],
earnKitWarnings: []
},
productType: 'vault',
totalDeposits: '0.0',
liquidity: '1000000.0',
liquidityProfile: {
totalDeposits: '0.0',
available: '1000000.0',
totalSupply: '0.0',
status: 'active'
}
}
(currentApy is a decimal, so 0.042 is a 4.2% APY) If you already have a vault address, getVaults reads its full metadata.
Earn Kit does not curate, recommend, or rank vaults. exploreVaults lists what is available, and the integrator decides what to offer.
Depositing USDC
With the address of a supported opportunity in hand, the app can preview a deposit before submitting anything onchain. For the current vault-based implementation, getDepositQuote shows the expected shares, share price, current APY, and estimated protocol and gas fees for a given deposit amount.
const quote = await kit.getDepositQuote({
from: { adapter, chain: "Arc_Testnet" },
vaultAddress,
amount: "1",
});{
"vaultAddress": "0xaabbef1d3971c710276ed41ec791bbe14cdb8e88",
"vaultName": "EarnKit USDC Vault (Arc Testnet)",
"deposit": { "symbol": "USDC", "amount": "1.0" },
"expectedShares": {
"symbol": "shares",
"address": "0xaabbef1d3971c710276ed41ec791bbe14cdb8e88",
"amount": "0.9993833804542597"
},
"sharePrice": "1.000617",
"currentApy": 0.042,
"fees": [],
"gasFees": [
{ "name": "Approve", "token": "USDC", "blockchain": "Arc_Testnet",
"fees": { "gas": "52000", "gasPrice": "35150201648", "fee": "1827810485696000" } },
{ "name": "Deposit", "token": "USDC", "blockchain": "Arc_Testnet",
"fees": { "gas": "362478", "gasPrice": "35150201648", "fee": "12741174792963744" } }
]
}
The deposit itself is one call. deposit fetches signed execution parameters from the service and submits them through the integrator’s wallet, handling the USDC approval along the way so the wallet stays in control throughout. Once the transaction is confirmed, it returns the transfer kind, the selected opportunity, the amount, and transaction details.
const result = await kit.deposit({
from: { adapter, chain: "Arc_Testnet" },
vaultAddress, // chosen from exploreVaults
amount: "1", // USDC, as a decimal string
});{
"kind": "same-chain",
"txHash": "0x82a55d1f4c3d0b368d8354472245bc5e7b96d4dc9b75f7d82bc8e66e35708f49",
"explorerUrl": "https://testnet.arcscan.app/tx/0x82a55d1f4c3d0b368d8354472245bc5e7b96d4dc9b75f7d82bc8e66e35708f49",
"vaultAddress": "0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88",
"amount": "1"
}Tracking the position
Once a user has deposited, getPosition is the read an app can surface as “here is your earn position.” It returns the current USDC-denominated balance, implementation-specific position data, and a principal-versus-yield breakdown where available. For the Morpho vault in this example, that implementation-specific data includes vault shares.
const position = await kit.getPosition({ from: { adapter, chain: "Arc_Testnet" }, vaultAddress });
A position that has earned yield can look like this:
{
"wallet": "0x212681a4bceff3eedad57956b13e986bf53d53b1",
"chain": "Arc_Testnet",
"vaultAddress": "0xaabbef1d3971c710276ed41ec791bbe14cdb8e88",
"vaultName": "EarnKit USDC Vault (Arc Testnet)",
"asset": "USDC",
"currentApy": 0.042,
"currentBalance": "1.000000",
"shares": "0.999382963854979361",
"pnl": {
"status": "available",
"principalDeposited": "0.999999",
"totalYieldEarned": "0.000001"
},
"accruedRewards": []
}
Yield accrues directly into the position, so there is nothing extra to claim in this flow. The balance simply grows.
Withdrawing back to USDC
Withdrawing mirrors depositing. withdraw exits the selected position and returns USDC to the same wallet on the same chain. getWithdrawalQuote previews the expected USDC out, the amount of the position redeemed, and any fee beforehand.
const result = await kit.withdraw({
from: { adapter, chain: "Arc_Testnet" },
vaultAddress,
amount: "1",
});
The result carries the same transaction details, without the kind field:
{
"txHash": "0xcc002e9cb3b8020d1126a06b33884b23fffe792b5042db1506bedebb7129810a",
"explorerUrl": "https://testnet.arcscan.app/tx/0xcc002e9cb3b8020d1126a06b33884b23fffe792b5042db1506bedebb7129810a",
"vaultAddress": "0xAabbeF1D3971c710276ed41eC791BbE14CdB8E88",
"amount": "1"
}
The USDC lands back in the wallet that deposited it.
Handling errors
The reads and writes above can fail for ordinary reasons: a network blip, an amount larger than the wallet's balance, an unsupported chain. Earn Kit surfaces these as a typed KitError, so you can branch on the error instead of parsing strings. Each one carries a numeric code, a name (like NETWORK_MISMATCH), a type (INPUT, BALANCE, ONCHAIN, RPC, NETWORK, RATE_LIMIT, SERVICE, LIQUIDITY, or UNKNOWN), and a recoverability flag (RETRYABLE, RESUMABLE, or FATAL).
Use the isKitError guard rather than instanceof, then decide what to do from the structured fields:
import { EarnKit, isKitError, isRetryableError } from "@circle-fin/earn-kit";
try {
await kit.deposit({ from: { adapter, chain: "Arc_Testnet" }, vaultAddress, amount: "1" });
} catch (err) {
if (isKitError(err)) {
if (isRetryableError(err)) {
// transient (network or rate limit): back off and retry
} else {
// FATAL (bad input or insufficient balance): surface it to the user
console.error(err.code, err.name, err.type, err.message);
}
} else {
throw err;
}
}
Previewing with getDepositQuote or getWithdrawalQuote before submitting catches most input and balance problems early, leaving the write path to handle network and onchain failures.
That is all it takes to embed USDC earning opportunities into an app: discover an opportunity, deposit, track the position, and withdraw, each through a single typed call. The wallet keeps custody throughout, while Earn Kit provides the integration layer for supported third-party protocols.
Start building today by visiting the Earn Kit docs, or clone the Fintech Starter App to see the complete flow running end to end.
USDC is issued by regulated affiliates of Circle. See Circle’s list of regulatory authorizations.
Arc is an open L1 blockchain launched by Arc Network Services LLC ("Arc LLC") and operated by a permissioned validator set. Arc LLC provides software services only and does not offer regulated financial or advisory services. Arc has not been reviewed or approved by the New York State Department of Financial Services or any other regulatory authority.
The Arc network is provided "as is" and "as available." Use of Arc involves inherent risks associated with blockchain technology, including smart contract vulnerabilities, network disruptions, and the absence of recourse for transaction errors or losses. The ability to transact on Arc depends on the ability to obtain and use USDC to pay gas fees. Neither Arc LLC nor any permissioned validator is responsible for the content, accuracy, legality, or functionality of third-party applications, protocols, or services built on or integrated with Arc. You are solely responsible for features or services you provide to users, including obtaining any necessary licenses or approvals and otherwise complying with applicable laws.
All Arc features may be modified, delayed, or cancelled at any time without notice. Nothing herein constitutes a commitment, warranty, guarantee or legal, regulatory, tax, or investment advice.
Earn Kit is intended to provide software infrastructure and technical tooling that may enable third parties to build or offer access to certain vault-related functionality for their own users. Each integrator is solely responsible for ensuring that its use of the Earn Kit, including how it structures, offers, markets, monetizes, or supports related products and services, complies with all applicable laws and regulations. By providing the Earn Kit, Circle is providing software infrastructure and technical tools only and does not provide legal, tax, or regulatory advice. Integrators should consult their own advisors before launching or offering any related product or service.


