> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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 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, providers, and yield work, see the [Earn overview](https://docs.dynamic.xyz/docs/earn/overview.md).

Every Earn call returns an unsigned payload. You sign and broadcast it with `execute`. An EVM payload goes out as one transaction, preceded by its ERC-20 approval when present. A Solana payload is a sequence of transactions that `execute` signs, sends, and confirms one at a time, in order.

## Prerequisites

* Dynamic SDK initialized (see [Quickstart](https://docs.dynamic.xyz/docs/flutter/quickstart.md))
* User authenticated (see [Authentication](https://docs.dynamic.xyz/docs/flutter/authentication.md))
* A wallet on the vault's chain family (see [Wallet Creation](https://docs.dynamic.xyz/docs/flutter/wallet-creation.md)): an EVM wallet for Morpho and Aave vaults, a Solana wallet for vaults on Solana.
* Earn enabled for your environment in the Dynamic Console. Until it is, every call throws an `EarnException` with `statusCode` 403.

## Install

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

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. Each vault reports the `provider` serving it (`morpho`, `aave`, or `kamino`) and the `chainId` and `chainName` of the chain it runs on. `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} via ${vault.provider.value}');
  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`, because it grows as yield accrues. Depending on the vault's provider, `shares` either stays flat or always equals `assets`, so it is not meaningful to show a user.

```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}');
```

## Track performance and earnings

`getVaultPerformance` returns a wallet's indexed earnings in one vault: `netEarningsAssets` in raw token units and a time-weighted `returnOnCapital` that is null when there is too little history. `getEarnings` aggregates a wallet's vault positions on one EVM chain into USD totals plus a per-vault breakdown.

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

print('Earned ${BigInt.parse(performance.netEarningsAssets)} raw units');

final earnings = await earn.getEarnings(
  chainId: vault.chainId,
  ownerAddress: wallet.address,
);

print('Total assets: \$${earnings.totalAssetsUsd}');
```

Both reads depend on the vault's provider. `getVaultPerformance` works only for providers that report indexed performance, and `getEarnings` covers only EVM positions. For anything else, the call rejects with an HTTP 400 error, which surfaces as an `EarnException`. See [Provider support](https://docs.dynamic.xyz/docs/earn/overview.md#provider-support).

## 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. Rewards are supported only for vaults from some providers. For a vault from any other provider, the API rejects the rewards and claim requests with an HTTP 400 error, which surfaces as an `EarnException`. See [Provider support](https://docs.dynamic.xyz/docs/earn/overview.md#provider-support).

## 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`. Solana vaults credit the depositor's own accounts, so `receiverAddress` must be omitted or equal to `ownerAddress` for them.

### Solana payloads

A Solana action returns a `transactions` sequence rather than a single transaction. `execute` signs, sends, and confirms each transaction in order, reporting each one as it begins through `onTransactionStepChange`.

```dart theme={"system"}
final transactionHash = await earn.execute(
  payload: deposit.signingPayload,
  wallet: solanaWallet,
  onTransactionStepChange: (step) {
    print('Sending ${step.label ?? 'transaction'} '
        '(${step.ordinal + 1} of ${step.total})');
  },
);
```

`execute` returns the last transaction's signature. When a step fails or expires, it throws an `EarnSolanaStepException` whose `code`, `ordinal`, and `signature` identify the failed step. Steps before it can already be confirmed on-chain, so check the wallet's position before retrying.

## 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');
  }
}
```

## Gas sponsorship

The `sponsored` parameter on `execute` picks how an EVM payload's transactions pay gas:

* `true` requires gas sponsorship and throws `EarnSponsorException` when it is unavailable.
* `false` always pays the fee from the wallet.
* `null` (the default) sponsors when the environment enables it and falls back to a wallet-paid transaction otherwise.

```dart theme={"system"}
final transactionHash = await earn.execute(
  payload: deposit.signingPayload,
  wallet: wallet,
  sponsored: true,
);
```

A sponsored EVM execution batches the ERC-20 approval and the vault call into one gasless transaction. On Solana, sponsorship works through [SVM Gas Sponsorship](https://docs.dynamic.xyz/docs/flutter/wallets/solana/svm-gas-sponsorship.md). See [EVM Gas Sponsorship](https://docs.dynamic.xyz/docs/flutter/wallets/evm/gas-sponsorship.md) for how to enable it.

## 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`, `EarnVaultPositionPerformance.netEarningsAssets`, 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.

## 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 |
| `statusCode` 400 | The vault's provider does not support the request, such as rewards |
| 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 on the wrong chain family for the payload, when the payload carries no transaction, or when the approval reverts or is not confirmed in time. On Solana, a step that fails or expires throws an `EarnSolanaStepException`, which is an `EarnException` carrying the failed step's `code`, `ordinal`, and `signature`.

## Next steps

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.