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

# Earn

> Deposit into yield vaults, track positions and rewards, and withdraw with the Dynamic Flutter SDK.

## Overview

Earn lets a user's EVM wallet deposit into a yield vault, track what the position is worth, claim reward campaigns, and withdraw. The `dynamic_sdk_earn` package wraps the Earn API and signs and broadcasts the transactions it returns.

For how vaults, shares, and yield work, see the [Earn overview](/docs/overview/yield).

Every Earn call returns an unsigned payload. You sign and broadcast it with `execute`, which sends the ERC-20 approval first and waits for it to confirm whenever the payload carries one.

## Prerequisites

* Dynamic SDK initialized (see [Quickstart](/docs/flutter/quickstart))
* User authenticated (see [Authentication](/docs/flutter/authentication))
* An EVM wallet (see [Wallet Creation](/docs/flutter/wallet-creation)). Earn is EVM only.
* Earn enabled for your environment in the Dynamic dashboard. Until it is, every call throws an `EarnException` with `statusCode` 403.

## Install

```yaml theme={"system"}
dependencies:
  dynamic_sdk: ^1.17.0
  dynamic_sdk_earn: ^1.17.0
```

Earn is available on the SDK instance as `DynamicSDK.instance.earn`:

```dart theme={"system"}
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_earn/dynamic_sdk_earn.dart';

final earn = DynamicSDK.instance.earn;
```

## Browse vaults

`listVaults` returns the vaults your environment can access. `getVaultDetails` adds the on-chain fields you need to display and transact against one, including `assetDecimals`.

```dart theme={"system"}
final vaults = await earn.listVaults();

for (final vault in vaults) {
  print('${vault.assetSymbol} by ${vault.curator} on chain ${vault.chainId}');
  print('Net APY: ${vault.netApy}, TVL: ${vault.tvlUsd}');
}

final vault = await earn.getVaultDetails(vaultId: vaults.first.vaultId);
```

`netApy` and `tvlUsd` come from live market data on a best-effort basis, so both are null when that data is unavailable.

## Read a position

A position holds `shares` and `assets`, both in raw token units. Show `assets`: it grows as yield accrues, while `shares` stays flat.

```dart theme={"system"}
final position = await earn.getVaultPosition(
  vaultId: vault.vaultId,
  ownerAddress: wallet.address,
);

final assets = BigInt.parse(position.assets);
final balance = assets / BigInt.from(10).pow(vault.assetDecimals);

print('Balance: $balance ${vault.assetSymbol}');
```

## Read rewards

Rewards are a separate bonus some vaults pay in another token. They sit unclaimed until you claim them, unlike base yield, which shows up as `assets` growing.

```dart theme={"system"}
final rewards = await earn.getVaultRewards(
  vaultId: vault.vaultId,
  ownerAddress: wallet.address,
);

for (final reward in rewards) {
  print('${reward.accrued} raw units of ${reward.tokenSymbol}');
}
```

The list is empty when the wallet has accrued nothing in the vault.

## Deposit

`deposit` builds the payload, `execute` sends it. `amount` is human-readable, so `'12.5'` deposits 12.5 USDC.

```dart theme={"system"}
final deposit = await earn.deposit(
  vaultId: vault.vaultId,
  request: EarnVaultActionRequest(
    ownerAddress: wallet.address,
    amount: '12.5',
  ),
);

final transactionHash = await earn.execute(
  payload: deposit.signingPayload,
  wallet: wallet,
  onStepChange: (step) {
    switch (step) {
      case EarnExecutionStep.approval:
        print('Approving the vault to spend the deposit');
      case EarnExecutionStep.transaction:
        print('Sending the vault transaction');
    }
  },
);
```

`execute` returns the vault transaction hash. When the payload carries an `evmApproval`, it sends that approval first and waits for its receipt, so `onStepChange` reports `approval` before `transaction`.

To send the shares to a different wallet, set `receiverAddress` on the request. It defaults to `ownerAddress`.

## Withdraw

Withdrawals redeem shares the wallet already holds, so they never need an approval.

```dart theme={"system"}
final withdrawal = await earn.withdraw(
  vaultId: vault.vaultId,
  request: EarnVaultActionRequest(
    ownerAddress: wallet.address,
    amount: '12.5',
  ),
);

final transactionHash = await earn.execute(
  payload: withdrawal.signingPayload,
  wallet: wallet,
);
```

## Claim rewards

```dart theme={"system"}
try {
  final claim = await earn.claim(
    vaultId: vault.vaultId,
    request: EarnVaultClaimRequest(ownerAddress: wallet.address),
  );

  final transactionHash = await earn.execute(
    payload: claim.signingPayload,
    wallet: wallet,
  );
} on EarnException catch (e) {
  if (e.nothingToClaim) {
    print('Nothing to claim in this vault');
  }
}
```

## Amount units

Amounts cross the wire as strings in two different units:

* `EarnVaultActionRequest.amount` is human-readable: `'12.5'` means 12.5 USDC.
* `EarnVaultPosition`, `EarnVaultReward`, and the amounts inside a signing payload are raw token units. They can exceed a Dart `int`, so parse them with `BigInt.parse`.

<Warning>
  Mixing the two units up silently transacts the wrong value. Human-readable amounts also need a
  leading zero: `'0.01'` works, `'.01'` is rejected by the API.
</Warning>

## Handle errors

Every failed Earn call throws an `EarnException` carrying the HTTP `statusCode`:

| Condition                       | What it means                                                          |
| ------------------------------- | ---------------------------------------------------------------------- |
| `statusCode` 403                | Earn is not enabled for your environment                               |
| `nothingToClaim` (422 on claim) | The wallet has no claimable rewards in the vault                       |
| Other status codes              | The API rejected the request, for example an invalid amount or address |

```dart theme={"system"}
try {
  final vaults = await earn.listVaults();
} on EarnException catch (e) {
  print('Earn failed (${e.statusCode}): ${e.message}');
}
```

`execute` also throws an `EarnException` when the wallet is not EVM, when the payload carries no transaction, or when the approval reverts or is not confirmed in time.

## Next steps

* [ERC-20 Token Transfers](/docs/flutter/wallets/evm/erc20-transfers) to move the vault asset in and out of the wallet
* [Token Balances](/docs/flutter/wallets/general/token-balances) to show what the wallet holds before a deposit
* [Earn overview](/docs/overview/yield) for vault, share, and yield concepts
