# Earn Kit

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

> **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

```bash
npm install @circle-fin/earn-kit
# or
yarn add @circle-fin/earn-kit
```

Install an adapter for the wallet stack you use. For EVM flows:

```bash
npm install @circle-fin/adapter-viem-v2 viem
# or
yarn add @circle-fin/adapter-viem-v2 viem
```

## Quick Start

```typescript
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`.

```typescript
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`.

```typescript
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** `deposit` or `withdraw` (cross-chain deposits
  use an EIP-3009 signature, not `approve` + `execute`, so batching does not
  apply).
- The `from.chain` is 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`:

```typescript
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:

```typescript
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:

```typescript
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 by `execId`.
- `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:

```typescript
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.kitKey` is deprecated in favor of
> `config.apiKey`. It keeps working, and legacy `KIT_KEY:<keyId>:<keySecret>`
> values are still accepted, so you can move over at your own pace. When both
> fields are set, `apiKey` wins.

## License

This project is licensed under the Apache 2.0 License. Contact [support](https://help.circle.com/s/submit-ticket) for details.
