> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trails.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Earn

> DeFi yield and staking deposits with the focused Earn component

## Overview

Earn mode enables users to deposit into DeFi protocols for yield generation and staking. It supports both pre-integrated protocols (Aave, Morpho) and custom contract integrations with arbitrary calldata.

<Note>
  Earn mode includes both deposit and withdrawal tabs. To deep-link users directly into withdrawals, use [Withdraw mode](/sdk/modes/withdraw).
</Note>

## Quick start

```tsx theme={null}
import { Earn } from '0xtrails/widget'

// Open protocol selection — user picks everything
<Earn apiKey="YOUR_API_KEY" />
```

Pre-configure the destination protocol:

```tsx theme={null}
<Earn
  apiKey="YOUR_API_KEY"
  to={{
    recipient: "0xYearnVault",
    currency: "USDC",
    chain: "katana",
    calldata: encodedDepositCalldata,
  }}
  onEarnSuccess={({ sessionId }) => console.log("deposited", sessionId)}
/>
```

## Props

### Required

| Prop     | Type     | Description    |
| -------- | -------- | -------------- |
| `apiKey` | `string` | Trails API key |

### Destination (optional)

| Prop              | Type               | Description                                        |
| ----------------- | ------------------ | -------------------------------------------------- |
| `to.recipient`    | `string`           | Protocol contract address                          |
| `to.token`        | `string`           | Token to deposit — symbol or contract address      |
| `to.chain`        | `ChainIdentifier`  | Protocol chain — name, ID, or viem Chain           |
| `to.defaultToken` | `string`           | Default token — user can change                    |
| `to.defaultChain` | `ChainIdentifier`  | Default chain — user can change                    |
| `to.amount`       | `string \| number` | Fixed deposit amount                               |
| `to.calldata`     | `string`           | ABI-encoded function call to execute after deposit |

### Source (optional)

The `from` shape is a discriminated union on `paymentMethod`.

**Crypto source** (`paymentMethod` is `"CONNECTED_WALLET"`, `"EXCHANGE"`, `"CRYPTO_TRANSFER"`, or omitted):

| Prop                 | Type               | Description                                                 |
| -------------------- | ------------------ | ----------------------------------------------------------- |
| `from.token`         | `string`           | Source ERC-20 token — symbol or contract address            |
| `from.chain`         | `ChainIdentifier`  | Source chain                                                |
| `from.amount`        | `string \| number` | Source amount                                               |
| `from.walletAddress` | `string`           | Source wallet (defaults to connected wallet)                |
| `from.exchange`      | `string`           | Mesh exchange key (e.g. `"coinbase"`). `EXCHANGE` arm only. |

**Fiat source** (`paymentMethod` is `"CREDIT_DEBIT_CARD"`):

| Prop            | Type               | Description                                        |
| --------------- | ------------------ | -------------------------------------------------- |
| `from.currency` | `string`           | ISO 4217 fiat code, e.g. `"USD"`, `"EUR"`, `"GBP"` |
| `from.amount`   | `string \| number` | Fiat amount to charge                              |

### Payment method (optional)

`paymentMethod` controls how the user sources funds:

| Value                 | Method                     |
| --------------------- | -------------------------- |
| `"CONNECTED_WALLET"`  | Connected wallet (default) |
| `"CRYPTO_TRANSFER"`   | QR code / address deposit  |
| `"CREDIT_DEBIT_CARD"` | Fiat on-ramp               |
| `"EXCHANGE"`          | CEX transfer               |

### Lifecycle callbacks

| Callback        | Signature                        | When it fires                  |
| --------------- | -------------------------------- | ------------------------------ |
| `onEarnStart`   | `({ sessionId }) => void`        | User begins the deposit flow   |
| `onEarnSuccess` | `({ sessionId }) => void`        | Deposit completes successfully |
| `onEarnError`   | `({ sessionId, error }) => void` | Deposit encounters an error    |

## Examples

### Protocol selection — user picks everything

```tsx theme={null}
import { Earn } from '0xtrails/widget'

<Earn
  apiKey="YOUR_API_KEY"
  onEarnSuccess={({ sessionId }) => console.log("deposited", sessionId)}
/>
```

### Fixed amount protocol deposit

Deposit 1 USDC into Aave V3 on Arbitrum:

```tsx theme={null}
import { Earn } from '0xtrails/widget'
import { encodeFunctionData } from 'viem'

const AAVE_POOL = '0x794a61358D6845594F94dc1DB02A252b5b4814aD'
const USDC_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'

const supplyCalldata = encodeFunctionData({
  abi: [{
    name: 'supply',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'asset', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'onBehalfOf', type: 'address' },
      { name: 'referralCode', type: 'uint16' },
    ],
    outputs: [],
  }],
  functionName: 'supply',
  args: [USDC_ADDRESS, 1000000n, '0xUserAddress', 0],
})

<Earn
  apiKey="YOUR_API_KEY"
  to={{
    recipient: AAVE_POOL,
    currency: "USDC",
    chain: "arbitrum",
    amount: "1",
    calldata: supplyCalldata,
  }}
>
  <button>Deposit 1 USDC to Aave</button>
</Earn>
```

### ERC-4626 vault with dynamic amount

Use `TRAILS_ROUTER_PLACEHOLDER_AMOUNT` when the deposit amount is a parameter and the user selects it:

```tsx theme={null}
import { Earn } from '0xtrails/widget'
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'

const depositCalldata = encodeFunctionData({
  abi: [{
    name: 'deposit',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'assets', type: 'uint256' },
      { name: 'receiver', type: 'address' },
    ],
    outputs: [{ name: 'shares', type: 'uint256' }],
  }],
  functionName: 'deposit',
  args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, '0xUserAddress'],
})

<Earn
  apiKey="YOUR_API_KEY"
  to={{
    recipient: "0xVaultContract",
    currency: "USDC",
    chain: "base",
    calldata: depositCalldata,
  }}
>
  <button>Deposit to Vault</button>
</Earn>
```

<Note>
  Use `TRAILS_ROUTER_PLACEHOLDER_AMOUNT` when:

  * Deposit amount is a function parameter
  * User selects the deposit amount
  * Amount needs to reflect post-swap/bridge output

  Do not use it for functions that read balance internally (e.g. `depositAll()`).
</Note>

### Composable actions (pre-integrated protocols)

For Aave, Compound, Morpho, Yearn, and other natively supported protocols, use composable actions instead of manual calldata encoding:

```tsx theme={null}
import { useTrailsSendTransaction, lend, erc20Utils } from '0xtrails'

export function AaveLendButton({ recipient }: { recipient: `0x${string}` }) {
  const { sendTransaction, isPending } = useTrailsSendTransaction({
    actions: [
      lend({
        marketId: 'base-usdc-aave-v3-lending',
        amount: '100',
      }),
    ],
  })

  return (
    <button
      disabled={isPending}
      onClick={() =>
        sendTransaction({
          to: recipient,
          tokenAddress: erc20Utils.USDC.addressOn('base'),
          tokenAmount: '100000000',
        })
      }
    >
      {isPending ? 'Sending...' : 'Supply 100 USDC to Aave'}
    </button>
  )
}
```

Use `useEarnMarkets` to discover available market IDs. See [Markets and Providers](/sdk/composable-actions/markets-and-providers).

## Calldata reference

### When to use `TRAILS_ROUTER_PLACEHOLDER_AMOUNT`

```tsx theme={null}
// Correct — dynamic amount in calldata
encodeFunctionData({
  functionName: 'deposit',
  args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT, userAddress],
})

// Correct — function reads balance directly, no placeholder needed
encodeFunctionData({
  functionName: 'depositAll',
  args: [userAddress],
})
```

`TRAILS_ROUTER_PLACEHOLDER_AMOUNT` is `uint256.max` internally. Trails replaces it with the actual output amount at execution time.

## See also

* [Earn use cases](/use-cases/earn) — DeFi integration patterns
* [Composable actions](/sdk/composable-actions/overview) — programmatic protocol deposits
* [Fund mode](/sdk/modes/fund) — similar input-driven flow
* [Configuration reference](/sdk/configuration) — full prop list
