> ## 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 Node SDK.

## Overview

Earn lets a wallet deposit into a yield vault, track what the position is worth, claim reward campaigns, and withdraw. Every server wallet client exposes the feature in two layers:

* `client.earn` reads vaults, positions, earnings, and rewards, and builds unsigned deposit, withdrawal, and claim payloads.
* `client.depositToEarnVault`, `client.withdrawFromEarnVault`, and `client.claimEarnRewards` prepare and execute those payloads with the wallet in one call. `client.executeEarnTransaction` executes a payload you prepared yourself.

For how vaults, shares, and yield work, see the [Earn overview](https://www.dynamic.xyz/docs/earn/overview.md).

## Prerequisites

* [Set up your authenticated client](https://www.dynamic.xyz/docs/node/quickstart.md). `client.earn` and the write methods are available on `DynamicEvmWalletClient` and `DynamicSvmWalletClient`.
* A wallet on the vault's chain family, created with [createWalletAccount](https://www.dynamic.xyz/docs/node/reference/evm/create-wallet-account.md). EVM vaults take an EVM wallet and Solana vaults take an SVM wallet.
* Earn enabled for your environment in the Dynamic dashboard. Until it is, every `client.earn` call fails with an HTTP 403 error.
* Persisted `walletMetadata` (and `externalServerKeyShares` when you manage shares yourself) for the wallet that owns the position. The SDK is stateless, so every write call needs them.

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

```typescript theme={"system"}
import { authenticatedEvmClient } from './client';

const evmClient = await authenticatedEvmClient();

const vaults = await evmClient.earn.listVaults();

for (const vault of vaults) {
  console.log(`${vault.assetSymbol} by ${vault.curator} on chain ${vault.chainId}`);
  console.log(`Net APY: ${vault.netApy}, TVL: ${vault.tvlUsd}`);
}

const vault = await evmClient.earn.getVaultDetails(vaults[0].vaultId);
```

`netApy` and `tvlUsd` come from live market data on a best-effort basis, so both can be 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.

```typescript theme={"system"}
const position = await evmClient.earn.getPosition({
  vaultId: vault.vaultId,
  ownerAddress: walletMetadata.accountAddress,
});

const assets = BigInt(position.assets);
console.log(`Balance: ${assets} raw units of ${vault.assetSymbol}`);
```

`getPerformance` returns the indexed performance of the position, which trails the chain head. `getEarnings` aggregates an owner's earnings across every vault on a chain, with USD totals and a per-vault breakdown.

```typescript theme={"system"}
const earnings = await evmClient.earn.getEarnings({
  ownerAddress: walletMetadata.accountAddress,
  chainId: 8453,
});
```

## 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.

```typescript theme={"system"}
const rewards = await evmClient.earn.getRewards({
  vaultId: vault.vaultId,
  ownerAddress: walletMetadata.accountAddress,
});

for (const reward of rewards) {
  console.log(`${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. See [Provider support](https://www.dynamic.xyz/docs/earn/overview.md#provider-support).

## Deposit on EVM

`depositToEarnVault` builds the payload and executes it: it sends the ERC-20 approval first and waits for it to confirm whenever the vault's allowance is short, then sends the vault transaction. `amount` is human-readable, so `"12.5"` deposits 12.5 USDC.

```typescript theme={"system"}
const { transactionHash } = await evmClient.depositToEarnVault({
  vaultId: vault.vaultId,
  amount: '12.5',
  walletMetadata,
  externalServerKeyShares,
  password: 'user-password',
  rpcUrl: 'https://mainnet.base.org',
  onStepChange: (step) => console.log(`Step: ${step}`),
});
```

`onStepChange` reports `approval` before `transaction` when an approval is needed. When the current allowance already covers the amount, the approval is skipped. Withdrawals redeem shares the wallet already holds, so `withdrawFromEarnVault` never needs an approval.

To keep the prepare and execute steps separate, call `evmClient.earn.prepareDeposit` and pass the result to `executeEarnTransaction`.

## Deposit on Solana

Solana payloads return an ordered `transactions` sequence instead of a single transaction. `depositToEarnVault` and `executeEarnTransaction` sign and send each transaction one at a time, because a later transaction can depend on the state an earlier one wrote.

```typescript theme={"system"}
import { authenticatedSvmClient } from './client';

const svmClient = await authenticatedSvmClient();

const { transactionHash } = await svmClient.depositToEarnVault({
  vaultId: vault.vaultId,
  amount: '12.5',
  walletMetadata,
  externalServerKeyShares,
  password: 'user-password',
  onTransactionStepChange: ({ ordinal, total }) =>
    console.log(`Sent transaction ${ordinal + 1} of ${total}`),
});
```

`transactionHash` is the signature of the last transaction in the sequence. Each payload transaction carries an `expiryBlockHeight`: the blockhash was fixed when the payload was built, so prepare and execute promptly, and prepare again if a step reports it expired.

If a transaction in the sequence fails after earlier ones confirmed, the method throws an `EarnSolanaStepError` whose `ordinal` reports which step failed, so the action may be partially applied.

## Claim rewards

```typescript theme={"system"}
const { transactionHash } = await evmClient.claimEarnRewards({
  vaultId: vault.vaultId,
  walletMetadata,
  externalServerKeyShares,
  password: 'user-password',
  rpcUrl: 'https://mainnet.base.org',
});
```

## Gas sponsorship

Pass `sponsored: true` to pay a vault action's gas through Dynamic gas sponsorship instead of the wallet.

On EVM, a sponsored execution relays the ERC-20 approval and the vault call as one gasless batch through the relayer. See [EVM Gas Sponsorship](https://www.dynamic.xyz/docs/node/wallets/server-wallets/gas-sponsorship-evm.md) for how to enable it.

```typescript theme={"system"}
const { transactionHash } = await evmClient.depositToEarnVault({
  vaultId: vault.vaultId,
  amount: '12.5',
  walletMetadata,
  externalServerKeyShares,
  password: 'user-password',
  sponsored: true,
});
```

On Solana, `sponsored: true` sponsors every transaction in the sequence. Vault transactions are built with the owner as fee payer, unlike the browser SDK which sponsors automatically. See [SVM Gas Sponsorship](https://www.dynamic.xyz/docs/node/wallets/server-wallets/gas-sponsorship-svm.md).

## Amount units

Amounts cross the wire as strings in two different units:

* The `amount` you pass to `prepareDeposit`, `depositToEarnVault`, `prepareWithdrawal`, and `withdrawFromEarnVault` 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 JavaScript `number`, so keep them as strings or `bigint`.

> [!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 `client.earn` call surfaces API failures as HTTP errors:

| Condition | What it means |
| - | - |
| HTTP 403 | Earn is not enabled for your environment |
| HTTP 422 on claim | The wallet has no claimable rewards in the vault |
| HTTP 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 |

The write methods also throw when `walletMetadata.chainName` does not match the client (`'EVM'` on the EVM client, `'SVM'` on the SVM client), when the payload carries no transaction for that chain, when an EVM approval reverts, or when a Solana sequence step expires or fails. The `chain` you pass must match the payload's `chainId`, and either `chain` or `rpcUrl` is required on EVM.

## Next steps

* [Earn overview](https://www.dynamic.xyz/docs/earn/overview.md) for vault, share, and yield concepts
* [Sign EVM Transactions](https://www.dynamic.xyz/docs/node/evm/sign-transactions.md) for the wallet signing model Earn builds on
* [Earn reference](https://www.dynamic.xyz/docs/node/reference/earn/list-vaults.md) for every `client.earn` method


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