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.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.
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.
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.
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.
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}');
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.
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.
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.
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.
deposit builds the payload, execute sends it. amount is human-readable, so '12.5' deposits 12.5 USDC.
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.
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.
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.
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.
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.
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
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.