---
url: /docs/yield.md
description: >-
  Turn yield on for a managed wallet so its idle USDC balance earns while it
  sits there, and read what it earned.
---

Yield lets a [managed wallet](/docs/wallets) earn on the stablecoins it holds. Turn it on once per wallet. From then on BlindPay moves the idle balance into a lending vault, keeps it earning, and pulls it back whenever a payout or transfer needs it. Turn it off and the whole position returns to the wallet.

Yield is off by default. Ask BlindPay to enable it on your instance before calling the endpoints below. Yield is not a deposit product. The rate is variable, can be zero, and the funds in the vault carry smart contract and market risk.

## How it works

1. You call **Enable Wallet Yield**. The wallet's `yield_status` becomes `enabled` and a `wallet.update` webhook fires.
2. BlindPay sweeps the idle balance into the vault. The first sweep runs right away, then every hour, so new deposits start earning within the hour.
3. The vault balance grows over time. Read it with **Retrieve Wallet Yield** or through `earning_amount` on the [wallet balance](/docs/wallets#check-the-balance).
4. A payout or transfer that needs more than the idle balance triggers a redeem from the vault. You do not do anything different.
5. You call **Disable Wallet Yield**. The wallet moves to `disabling`, BlindPay redeems the position and collects any accrued fee, then `yield_status` settles to `disabled` and `wallet.update` fires again.

**Abstracted:**

You never touch the vault. The wallet keeps behaving like a balance: deposits land in it, payouts draw from it, and the earning part shows up as a separate number.

**Advanced:**

The vault is an ERC-4626 lending vault on the wallet's network. BlindPay signs the deposit and redeem transactions from the managed wallet and sponsors the gas. The wallet holds the vault shares directly, so the position is always visible on-chain at the wallet `address`.

## Supported networks and tokens

| Network | Token | Sweep minimum |
| --- | --- | --- |
| `base` | USDC | 100 USDC |
| `polygon` | USDC | 250 USDC |

A wallet on any other network returns `400 yield_not_available_for_network` on enable. Balances in other tokens stay idle in the wallet and are not affected. Idle balances below the sweep minimum stay in the wallet and do not earn: 100 USDC on Base, 250 USDC on Polygon.

## Fees

BlindPay does not charge on the balance. It charges a **performance fee on gains**, 15% by default, reported as `fee_bps` on the yield object (`1500` means 15%). The fee accrues only on new gains above the wallet's previous high point, so a drop in vault value is never charged twice. Every APY the API reports is already net of the fee.

Accrued fees are collected from the vault on the first day of each month and when you disable yield. A fee under 1 USDC at disable time is waived. Collections appear as `fee` rows in the yield transactions list.

If your instance has a [partner share](#partner-share) configured, it is included in `fee_bps` and collected the same way.

## Prerequisites

You need a managed wallet on a supported network. Yield uses the same `wallets_and_transfers` subscription feature as wallets, plus the `yield` feature. Enable returns `403 yield_not_enabled_for_instance` until BlindPay turns it on for your instance.

## Enable yield

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/yield/enable \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

The response is the wallet, not the yield object. Read `yield_status` here and use [Retrieve Wallet Yield](#retrieve-yield) for positions.

```json
{
  "id": "bl_000000000000",
  "network": "base",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "name": "Customer Balance",
  "external_id": null,
  "yield_status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z"
}
```

Calling enable on a wallet that already has yield on returns the same wallet with `200`. Calling it while the wallet is `disabling` returns `409 yield_status_transition_in_progress`; wait for the `wallet.update` webhook and retry. The endpoint accepts an `Idempotency-Key` header.

## Retrieve yield

```bash [cURL]
curl --request GET \
  --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/yield \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

```json
{
  "status": "enabled",
  "as_of": "2026-09-01T12:00:00Z",
  "fee_bps": 1500,
  "positions": [
    {
      "token": "USDC",
      "vault": {
        "address": "0xC768c589647798a6EE01A91FdE98EF2ed046DBD6",
        "apy": 0.0342
      },
      "apy": 0.0291,
      "gross_amount": 1000.42,
      "fee_accrued": 1.7,
      "earning_amount": 998.72,
      "max_withdrawable": 998.72,
      "total_deposited": 950,
      "total_withdrawn": 0,
      "lifetime_earned_amount": 48.72
    }
  ]
}
```

| Field | Meaning |
| --- | --- |
| `status` | `disabled`, `enabled`, or `disabling` |
| `fee_bps` | Performance fee on gains, in basis points |
| `positions[].apy` | Current rate after the fee, as a fraction (`0.0291` is 2.91%). `null` when the vault rate is unavailable |
| `positions[].vault.apy` | The vault's rate before the fee |
| `positions[].earning_amount` | The balance earning right now, net of accrued fees. This is the number to show your customer |
| `positions[].gross_amount` | Vault value before fees |
| `positions[].fee_accrued` | Fee accrued on gains and not yet collected |
| `positions[].max_withdrawable` | What the vault can return right now |
| `positions[].total_deposited`, `total_withdrawn` | Lifetime flows between the wallet and the vault, excluding fee collections |
| `positions[].lifetime_earned_amount` | Net profit after fees across every time yield was on for this wallet |

Amounts are in token units, the same as the wallet balance endpoint. `positions` is empty when yield is off and the wallet holds nothing in a vault.

## Show it on the balance

The [wallet balance](/docs/wallets#check-the-balance) endpoint splits every token into the idle part and the earning part, so you can show your customer one total without calling the yield endpoint:

```json
{
  "USDC": {
    "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "id": "usdc",
    "symbol": "USDC",
    "amount": 12.5,
    "earning_amount": 998.72,
    "total_amount": 1011.22
  }
}
```

`amount` is idle in the wallet, `earning_amount` is in the vault net of accrued fees, and `total_amount` is the sum. Both extra fields are `0` when yield is off.

## Daily history

```bash [cURL]
curl --request GET \
  --url 'https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/yield/history?from=2026-09-01&to=2026-09-30' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

```json
{
  "data": [
    { "date": "2026-09-01", "token": "USDC", "earning_amount": 998.72, "earned_day": 0.09, "apy": 0.0291 },
    { "date": "2026-09-02", "token": "USDC", "earning_amount": 998.81, "earned_day": 0.09, "apy": 0.0290 }
  ]
}
```

One row per token per day, from a snapshot taken shortly after midnight UTC. `earned_day` is the net yield earned since the previous snapshot, with deposits and redeems of that day excluded, so a top-up never looks like profit. Defaults to the last 30 days. Filter with `token`, `from`, and `to` (UTC dates as `YYYY-MM-DD`, both inclusive). The window is capped at 366 days.

## Transactions

```bash [cURL]
curl --request GET \
  --url 'https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/yield/transactions?limit=10' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

```json
{
  "data": [
    {
      "id": "yt_000000000000",
      "token": "USDC",
      "type": "redeem",
      "trigger": "payout",
      "reference": { "type": "payout", "id": "po_000000000000" },
      "status": "confirmed",
      "amount": 250,
      "transaction_hash": "0xabc…",
      "created_at": "2026-09-01T10:02:00Z"
    }
  ],
  "pagination": { "has_more": false, "next_page": null, "prev_page": null }
}
```

| Field | Values |
| --- | --- |
| `type` | `deposit` moves idle balance into the vault, `redeem` moves it back to the wallet, `fee` is a performance fee collection |
| `trigger` | `sweep`, `payout`, `transfer`, `disable`, or `fee_collection` |
| `reference` | The payout or transfer a redeem funded, `null` otherwise |
| `status` | `pending`, `confirmed`, or `failed` |

Newest first. Uses the standard [pagination](/docs/learn/pagination) parameters plus an optional `token` filter. This list always returns the paginated envelope.

## Disable yield

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/yield/disable \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

Returns `202` with the wallet in `yield_status: disabling`. BlindPay redeems the whole position back to the wallet, collects any accrued fee, and then sets the wallet to `disabled`. This usually takes a few minutes. Poll the wallet or wait for the `wallet.update` webhook to see it settle. Calling disable on a wallet that is already off returns `200` with the same wallet.

A wallet cannot be deleted while yield is on or still settling. Delete returns `400 wallet_yield_not_empty` until `yield_status` is `disabled` and nothing is left in the vault.

## Payouts and transfers with yield on

Nothing changes in your calls. Pass the wallet's `address` as `sender_wallet_address` on a payout, or use the wallet on a transfer, exactly as before. If the idle balance is short, BlindPay redeems the difference from the vault before collecting the funds. The redeem shows up in the transactions list with `trigger: payout` or `trigger: transfer` and a `reference` pointing at the payout or transfer it funded.

Expect the redeem to add about a minute before the payout collects. A redeem waits for an in-flight sweep to finish first, which can add another minute.

## Webhooks

`wallet.update` fires on every `yield_status` change, with the wallet as the payload:

```json [wallet.update]
{
  "webhook_event": "wallet.update",
  "id": "bl_000000000000",
  "network": "base",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "name": "Customer Balance",
  "external_id": null,
  "yield_status": "disabled",
  "created_at": "2026-01-01T00:00:00.000Z"
}
```

There are no per-deposit or per-redeem events. Use the transactions list for that.

## Partner share

You can take a share of the gains on top of BlindPay's fee. BlindPay configures it on your instance: a share in basis points and an EVM address on the same network to receive it. The partner share is added to `fee_bps`, accrues under the same high-water mark rule, and is paid to your address when fees are collected. Contact BlindPay to set it up.

## Errors

| Error | Status | Meaning |
| --- | --- | --- |
| `yield_not_enabled_for_instance` | 403 | Yield is not turned on for your instance |
| `yield_not_available_for_network` | 400 | The wallet's network has no vault |
| `yield_status_transition_in_progress` | 409 | The wallet is `disabling`; wait for `wallet.update` and retry |
| `wallet_yield_not_empty` | 400 | Delete attempted while yield is on or funds remain in the vault |
| `yield_position_unavailable` | 503 | The vault could not be read; retry shortly |
| `wallets_and_transfers_not_enabled` | 400 | Managed wallets are not enabled on your instance |

## Frequently asked questions

**How does yield on a managed wallet work?** Turn yield on for the wallet. BlindPay moves the idle stablecoin balance into a lending vault, where it earns a variable rate. When a payout or transfer needs the funds, BlindPay pulls them back automatically. Turn yield off and the full balance returns to the wallet.

**What does yield cost?** There is no fee on the balance. BlindPay charges a performance fee on gains only, 15% by default, and only on new gains above the previous high point. The APY the API reports is already net of that fee.

**Can I pay out from a wallet with yield on?** Yes. Payouts and transfers work the same way. If the idle balance is short, BlindPay redeems the difference from the vault first, which adds about a minute before the payout collects the funds.

**Is yield guaranteed?** No. The rate is variable and set by the lending market, and funds in a vault carry smart contract and market risk. Your customer can lose money. The rate can be zero.

## Related

* [Managed wallets](/docs/wallets): create the wallet and check its balance
* [Payouts](/docs/payouts): pay out from a wallet with yield on
* [Webhooks events](/docs/learn/webhooks-events): `wallet.update`
* [Pagination](/docs/learn/pagination): paging through yield transactions
