diff --git a/docs.json b/docs.json
index 7bebf9ba..894dc8ac 100644
--- a/docs.json
+++ b/docs.json
@@ -443,7 +443,20 @@
]
},
"features/transaction-management/balances",
- "features/transaction-management/fiat-on-ramp"
+ "features/transaction-management/fiat-on-ramp",
+ {
+ "group": "Earn",
+ "pages": [
+ "features/transaction-management/earn",
+ "features/transaction-management/earn/vault-catalog",
+ "features/transaction-management/earn/deploy-wrapper",
+ "features/transaction-management/earn/deploy-wrapper-2",
+ "features/transaction-management/earn/deposit",
+ "features/transaction-management/earn/positions",
+ "features/transaction-management/earn/end-to-end-example"
+ ],
+ "tag": "Early Access"
+ }
]
},
{
diff --git a/features/transaction-management/earn.mdx b/features/transaction-management/earn.mdx
new file mode 100644
index 00000000..e1d99cf5
--- /dev/null
+++ b/features/transaction-management/earn.mdx
@@ -0,0 +1,123 @@
+---
+title: "Overview"
+description: "Deposit into DeFi yield vaults from Turnkey wallets, with onchain fee collection for your organization."
+---
+
+Earn lets wallets in your organization deposit into DeFi yield vaults, query position data to track their positions, and withdraw, all through Turnkey activities with the corresponding audit trail and policy controls. You can take your own fee on the yield your users earn, paid onchain to a wallet you control.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## What is Earn
+
+Earn connects Turnkey wallets to ERC-4626 yield vaults. The vaults are deployed and operated by independent third-party protocols; Turnkey provides the API infrastructure to interact with them but does not operate, manage, or guarantee them. Morpho vaults are supported today and Aave support is coming soon.
+
+Users don't deposit into vaults directly. They deposit into a fee wrapper, a smart contract that holds the position in the underlying vault. A vault may be enabled via either the dashboard or the API. Once you enable a vault, a wrapper is automatically deployed with your fee parameters. The wrapper collects fees as yield accrues. Fees come out of yield only, never principal, and users never sign a separate fee transaction.
+
+A wrapper is deployed once per vault and fee configuration. After that, deposits, withdrawals, and position queries are each a single API call.
+
+
+ If your application presents Earn functionality to end users, you should clearly disclose that: (1) yield is generated by independent third-party DeFi protocols and do not claim or imply that yield is generated by your application or by Turnkey; (2) deposited assets are subject to risks including smart-contract risk, liquidity risk, and potential loss of principal; (3) yield is variable and not guaranteed; and (4) the user is directing the deposit action and retaining control of their assets. Depending on your jurisdiction and the nature of the assets involved, additional regulatory disclosures may apply.
+
+
+
+ Vault deposits interact with smart contracts deployed on public blockchains. Deposited assets are subject to smart-contract risk, including the possibility of bugs, exploits, or protocol governance changes that could result in partial or total loss of deposited funds. Turnkey does not audit or guarantee the security of any third-party vault contract.
+
+
+## How it works
+
+1. Query [`list_earn_vaults`](/features/transaction-management/earn/vault-catalog) for the vaults available for an asset, with live TVL and APY.
+2. Run [`earn_deploy_wrapper`](/features/transaction-management/earn/deploy-wrapper) once per vault to enable it for your organization and set your fee. Turnkey pays the deployment gas.
+3. Call [`earn_deposit`](/features/transaction-management/earn/deposit) to initiate a transfer of assets from a wallet into the vault.
+4. Poll the matching status endpoint until the transaction confirms. Deposits, withdrawals, and deployments all confirm asynchronously.
+5. Query [`list_earn_positions`](/features/transaction-management/earn/positions) for a wallet’s current value and lifetime totals.
+6. Call [`earn_withdraw`](/features/transaction-management/earn/withdraw) for a partial amount, the yield only, or the full position.
+
+## Supported protocols and chains
+
+| Chain | CAIP-2 | Providers |
+| :-- | :-- | :-- |
+| Ethereum | `eip155:1` | Morpho (Aave upcoming) |
+| Base | `eip155:8453` | Morpho (Aave upcoming) |
+| Arbitrum | `eip155:42161` | Morpho (Aave upcoming) |
+| Polygon | `eip155:137` | Morpho (Aave upcoming) |
+
+Earn is EVM-only in V1. The vault catalog only includes vaults with at least \$100k TVL.
+
+## Fees
+
+Earn fees are charged only on yield: a percentage of the yield a position earns. Principal is never charged. Two fees apply, both in basis points of gross yield:
+
+- **Your fee (the "Client Fee")**: you set it per wrapper at deploy time (`clientFeeBps`), along with the payout wallet (`clientFeeWallet`, a wallet account owned by your organization). Fees accrue onchain and are released to that wallet when you claim them.
+- **Turnkey's fee (The "Turnkey fee")**: resolved automatically when you deploy. The Turnkey Fee is up to 10% of yield (1,000 bps).
+
+Client fees are capped at 4,000 bps (40% of yield). Deployments above this cap are rejected. Both Client fees and Turnkey’s fee are taken from yield as wrapper shares minted to a payment splitter contract deployed alongside the wrapper, which splits them onchain between you and Turnkey.
+
+The net yield your users earn is `grossApy × (1 - totalFeeBps / 10000)`. The [`list_earn_enabled_vaults`](/features/transaction-management/earn/vault-catalog#list-your-enabled-vaults) endpoint returns gross APY, net APY, and your fee rate for every wrapper you've deployed, along with `claimableClientFee`: the fee amount accrued to your organization that is actively claimable. Claim it onchain with the [`claim_earn_fees`](/api-reference/activities/claim-earn-fees) activity; the claimed amount is paid out to your fee wallet.
+
+
+ The fee configuration is fixed per wrapper. To change your fee, deploy a new wrapper for the same vault. Existing positions in the old wrapper remain fully withdrawable, and new deposits go to the new wrapper. See [Deploy a vault wrapper](/features/transaction-management/earn/deploy-wrapper#choose-your-fee-configuration).
+
+
+**Important:** The Client Fee and the Turnkey Fee are infrastructure fees charged on yield generated by third-party vault protocols. Neither fee constitutes a fee for investment advice, asset management, or discretionary portfolio allocation. Turnkey does not exercise discretion over how vault assets are deployed, allocated, or managed. All allocation decisions are made by the vault's curator or governing protocol.
+
+## API surface
+
+Earn adds five activities:
+
+| Activity | Endpoint | Purpose |
+| :-- | :-- | :-- |
+| [`ACTIVITY_TYPE_EARN_DEPLOY_WRAPPER`](/api-reference/activities/deploy-earn-wrapper) | `POST /public/v1/submit/earn_deploy_wrapper` | Enable a vault for your org by deploying its fee wrapper |
+| [`ACTIVITY_TYPE_EARN_DEPOSIT`](/api-reference/activities/deposit-into-earn-vault) | `POST /public/v1/submit/earn_deposit` | Deposit assets from a wallet into an enabled vault |
+| [`ACTIVITY_TYPE_EARN_WITHDRAW`](/api-reference/activities/withdraw-from-earn-vault) | `POST /public/v1/submit/earn_withdraw` | Withdraw assets or exit a position |
+| [`ACTIVITY_TYPE_EARN_SET_WRAPPER_STATE`](/api-reference/activities/set-earn-wrapper-state) | `POST /public/v1/submit/earn_set_wrapper_state` | Pause or resume deposits to a wrapper (withdrawals are never blocked) |
+| [`ACTIVITY_TYPE_CLAIM_EARN_FEES`](/api-reference/activities/claim-earn-fees) | `POST /public/v1/submit/claim_earn_fees` | Claim your accrued fees for a wrapper |
+
+and seven queries:
+
+| Query | Endpoint | Purpose |
+| :-- | :-- | :-- |
+| [Vault catalog](/api-reference/queries/get-earn-vault-catalog) | `POST /public/v1/query/list_earn_vaults` | All wrappable vaults for an asset, with live TVL/APY |
+| [Enabled vaults](/api-reference/queries/get-earn-enabled-vaults) | `POST /public/v1/query/list_earn_enabled_vaults` | Your org's deployed wrappers (management view) |
+| [Positions](/api-reference/queries/get-earn-positions) | `POST /public/v1/query/list_earn_positions` | A wallet's active positions |
+| [Deploy status](/api-reference/queries/get-earn-deploy-status) | `POST /public/v1/query/get_earn_deploy_status` | Poll a wrapper deployment |
+| [Deposit status](/api-reference/queries/get-earn-deposit-status) | `POST /public/v1/query/get_earn_deposit_status` | Poll a deposit until it lands onchain |
+| [Withdraw status](/api-reference/queries/get-earn-withdraw-status) | `POST /public/v1/query/get_earn_withdraw_status` | Poll a withdrawal until it lands onchain |
+| [Claim fees status](/api-reference/queries/get-claim-earn-fees-status) | `POST /public/v1/query/get_claim_earn_fees_status` | Poll a claim fee until it lands onchain |
+
+
+ Earn requests are stamped and submitted like any other Turnkey request. See [Stamps](/api-reference/overview/stamps) and [Submissions](/api-reference/activities/overview). Full request/response schemas and cURL examples live in the API reference pages linked above.
+
+
+## Explore
+
+
+
+ — Discover vaults and check your enabled wrappers.
+
+
+
+ — Enable a vault and set your fee.
+
+
+
+ — One transaction, optionally gas-sponsored.
+
+
+
+ — Partial, yield-only, or full exit.
+
+
+
+ — Current value, lifetime totals, and yield.
+
+
+
+ — Deploy, deposit, and withdraw USDC on Base.
+
+
+
+**Important:** Turnkey does not operate, control, or audit the DeFi vaults or underlying protocols accessible through the Earn feature. Vault data, including APY and TVL figures, is sourced from third-party providers and may be inaccurate, delayed, or subject to change without notice. Yield is generated by independent third-party protocols, not by Turnkey. Yield is variable, not guaranteed, and past rates are not indicative of future performance.
+
+Depositing into DeFi vaults involves risk, including the potential loss of deposited assets due to smart-contract vulnerabilities, market conditions, liquidity constraints, or protocol failures. These materials are provided for informational and technical integration purposes only. Nothing in this documentation constitutes investment, financial, legal, or tax advice, or a recommendation or solicitation to engage in any particular transaction. You and your end users are solely responsible for evaluating the suitability and risks of any vault.
\ No newline at end of file
diff --git a/features/transaction-management/earn/deploy-wrapper-2.mdx b/features/transaction-management/earn/deploy-wrapper-2.mdx
new file mode 100644
index 00000000..252bcbbc
--- /dev/null
+++ b/features/transaction-management/earn/deploy-wrapper-2.mdx
@@ -0,0 +1,66 @@
+---
+title: "Manage wrappers"
+description: "Pause deposits, claim your accrued fees, and change or tier your fee by deploying additional wrappers."
+sidebarTitle: "Manage wrappers"
+mode: "wide"
+---
+
+A deployed wrapper is immutable. It points at the same underlying vault, its fee configuration cannot change, and the deploy abdicates the contract's deposit, withdrawal, and transfer gates, so no one, including Turnkey, can ever restrict user access to funds.
+
+What you control after deployment:
+
+- whether new deposits are accepted
+- when you claim accrued fees
+- which wrapper each user deposits into.
+
+Management operations are parent-organization only; sub-organization wallets can deposit and withdraw but cannot manage wrappers.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## Pause or resume deposits
+
+[`earn_set_wrapper_state`](/api-reference/activities/set-earn-wrapper-state) sets `depositsDisabled` on a wrapper. While true, earn\_deposit against that wrapper is rejected. Withdrawals, position queries, and fee claims are unaffected, so a paused wrapper can never trap funds. Set it back to false to resume deposits.
+
+Pausing gates deposits submitted through the Turnkey API; it is not an onchain lock. The wrapper remains a permissionless ERC-4626 contract that anyone can interact with directly onchain.
+
+Read the current state from `depositsDisabled` on [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults), or on each row of [`list_earn_positions`](/api-reference/queries/get-earn-positions) so your UI can hide the deposit button for paused wrappers.
+
+Common uses: stopping new deposits into a vault that looks compromised while users exit at their own pace, and winding down a wrapper after replacing it with a new fee configuration.
+
+## Claim your fees
+
+Fees accrue onchain as wrapper shares held by the wrapper's payment splitter. They accumulate without any action from you and never expire. Claiming converts them into the underlying asset.
+
+1. Check the claimable amount. `claimableClientFee` on [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults) is the releasable fee for that wrapper, denominated in the underlying asset with USD and asset display values. This field and `clientFeeWallet` are only returned to parent-organization callers.
+2. Submit [`claim_earn_fees`](/api-reference/activities/claim-earn-fees) with the `wrapperAddress`. One transaction releases your fee shares from the splitter to your `clientFeeWallet` and redeems them into the underlying asset, so the payout arrives as the asset (for example USDC), not as vault shares.
+3. Poll [`get_claim_earn_fees_status`](/api-reference/queries/get-claim-earn-fees-status) with the returned `claimRequestId`. As with every Earn transaction, `COMPLETED` on the activity means submitted, not confirmed onchain.
+
+Details:
+
+- The claim releases exactly what is claimable. If your fee wallet also holds a deposit position in the same wrapper, the claim never touches that principal.
+- The claim signs from your `clientFeeWallet`, so your organization's policies apply to it.
+- Claim transactions require gas sponsorship. Gas is covered at transaction time and billed on your next invoice.
+- Claiming with nothing accrued fails with an explicit error.
+- Fees are per wrapper. If you run more than one wrapper for a vault, each has its own splitter and claimable balance; iterate over every wrapper in [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults).
+
+## Change or tier your fee
+
+A wrapper is unique to its vault and fee configuration (`clientFeeBps` and `clientFeeWallet`). To change your fee or payout wallet, or tier fees for the same underlying vault, deploy a new wrapper for the same vault. The two wrappers coexist permanently: neither invalidates the other, each has its own configuration and claimable balance, and each is an independent depositor in the underlying vault. This one mechanic covers both replacing your fee and offering different rates to different audiences.
+
+To replace your fee:
+
+1. Deploy the new wrapper with the new `clientFeeBps` and/or `clientFeeWallet`. You get a new `wrapperAddress` and `splitterAddress`, and Turnkey pays the gas as with the first deployment.
+2. Point new deposits at the new `wrapperAddress`. Deposits are addressed by wrapper, not by vault.
+3. Pause the old wrapper so nothing else lands in it. Withdrawals stay open.
+4. Optionally migrate existing positions. There is no in-place migration: [`earn_withdraw`](/features/transaction-management/earn/withdraw) the full position (`MAX`) from the old wrapper, then [`earn_deposit`](/features/transaction-management/earn/deposit) the proceeds into the new one, per wallet. Both legs can be gas-sponsored.
+5. Keep claiming from the old wrapper. Balances left behind keep accruing fees at the old rate to the old `clientFeeWallet`.
+
+Things to keep in mind:
+
+- Fee changes are forward-only. Yield already accrued settles at the old rate, and a position keeps paying its wrapper's rate until it is migrated.
+- Position history is per wrapper. [`list_earn_positions`](/features/transaction-management/earn/positions) returns one row per wrapper, so a wallet with balances in two wrappers for the same vault shows two rows; aggregate by `vaultAddress` if your UI shows a single balance. A `MAX` exit zeroes a row's totals, and the row disappears once the share balance is zero.
+- The catalog's `enabled` flag is per vault, not per wrapper. It is true once any wrapper exists and won't tell you which is current. Use `wrapperAddress` from [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults) or your own stored mapping as the source of truth.
+- The fee caps apply to every deployment.
+- Redeploying with identical parameters is idempotent: it re-derives the same addresses and skips the broadcast, so retries never create duplicates.
\ No newline at end of file
diff --git a/features/transaction-management/earn/deploy-wrapper.mdx b/features/transaction-management/earn/deploy-wrapper.mdx
new file mode 100644
index 00000000..b70c1b0f
--- /dev/null
+++ b/features/transaction-management/earn/deploy-wrapper.mdx
@@ -0,0 +1,53 @@
+---
+title: "Deploy a vault wrapper"
+description: "Enable a yield vault for your organization by deploying its fee wrapper, a one-time setup step that also sets your fee."
+sidebarTitle: "Deploy a vault wrapper"
+mode: "wide"
+---
+
+Before your users can deposit into a vault, your organization must deploy a **fee wrapper** for it. The wrapper is the contract users actually deposit into. It routes funds to the underlying vault and collects the applicable fees out of the yield, by minting fee shares to a payment splitter as interest accrues.. Deploying it is a one-time activity per vault, with gas sponsored by Turnkey.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## When to deploy
+
+Deploy once per vault you want to offer, per fee configuration. Deposits into a vault with no deployed wrapper fail with `EARN_SETUP_REQUIRED` (see [Deposit into a vault](/features/transaction-management/earn/deposit#prerequisites)). Pick vaults from the [vault catalog](/features/transaction-management/earn/vault-catalog); the catalog's `enabled` flag tells you which vaults your organization has already enabled.
+
+## Choose your fee configuration
+
+The deploy intent carries your fee:
+
+- `clientFeeBps`: your fee on gross yield, in basis points (`"2000"` = 20%). Combined with Turnkey's fee (10% of yield by default), the total cannot exceed 5,000 bps (50% of yield); deployments above the cap are rejected.
+- `clientFeeWallet`: the address that receives your fee payouts onchain. It must be a wallet account owned by your organization; addresses outside your org are rejected.
+
+
+ The fee configuration is bound into the wrapper's deterministic (CREATE2) address. Deploying the same vault with a different `clientFeeBps` or `clientFeeWallet` produces a new wrapper at a new address. Positions in the old wrapper remain fully withdrawable; new deposits should target the new wrapper. To change your fee, redeploy and point deposits at the new address.
+
+
+## Submit the activity
+
+Submit an [`ACTIVITY_TYPE_EARN_DEPLOY_WRAPPER`](/api-reference/activities/deploy-earn-wrapper) activity with the vault's address (from the catalog), its CAIP-2 chain, and your fee configuration. See [Deploy Earn wrapper](/api-reference/activities/deploy-earn-wrapper) in the API reference for the full request/response schema and cURL example.
+
+The activity result returns the deployed addresses immediately; they are derived deterministically before the transaction confirms:
+
+- `wrapperAddress`: the deposit target for this vault.
+- `splitterAddress`: the payment splitter that distributes fees between you and Turnkey.
+- `deployRequestId`: poll handle for the deployment transaction.
+
+## Poll deployment status
+
+The activity completes when the deployment transaction is broadcast, not when it confirms. Poll [`get_earn_deploy_status`](/api-reference/queries/get-earn-deploy-status) with the `deployRequestId` until it reports `COMPLETED` before accepting deposits. `status` is `PENDING`, `COMPLETED`, or `FAILED`; on `COMPLETED` the response carries the `deployTxHash`, and on `FAILED` it includes an `error` field with the reason.
+
+## Gas and idempotency
+
+
+ Wrapper deployment gas costs (roughly 7.1M gas per deployment) are currently covered by Turnkey’s infrastructure and are not charged to you or your users. Deployments are also idempotent: resubmitting the activity with identical parameters re-derives the same wrapper and splitter addresses and skips the broadcast if the contracts already exist, so retries are safe.
+
+
+## Next steps
+
+- [Browse the vault catalog](/features/transaction-management/earn/vault-catalog) to pick vaults to enable
+- [Deposit into a vault](/features/transaction-management/earn/deposit) once the deployment is `COMPLETED`
+- Review the [fee model](/features/transaction-management/earn#fees)
\ No newline at end of file
diff --git a/features/transaction-management/earn/deposit.mdx b/features/transaction-management/earn/deposit.mdx
new file mode 100644
index 00000000..040bb53d
--- /dev/null
+++ b/features/transaction-management/earn/deposit.mdx
@@ -0,0 +1,51 @@
+---
+title: "Deposit into a vault"
+description: "Move assets from a Turnkey wallet into an enabled yield vault in one atomic transaction, optionally gas-sponsored."
+mode: "wide"
+---
+
+When a user initiates a deposit into a vault, you pass through their intents via our API where we then form and broadcast the transaction to deposit those assets into your organization's fee wrapper for a given vault. The token approval and the vault deposit execute as a single atomic batch transaction, so there is no separate approval step to manage.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## Prerequisites
+
+- Your organization has [deployed a wrapper](/features/transaction-management/earn/deploy-wrapper) for the vault, and its deployment status is `COMPLETED`. Deposits targeting an address with no deployed wrapper fail with `EARN_SETUP_REQUIRED`.
+- Deposits to the wrapper are not [paused](/features/transaction-management/earn/deploy-wrapper#manage-a-deployed-wrapper): check `depositsDisabled` on [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults).
+- The `signWith` wallet holds enough of the vault's underlying asset. For non-sponsored deposits it also needs the chain's native token for gas.
+
+
+ Sub-organization wallets can deposit into (and withdraw from) wrappers deployed by their parent organization. The wrapper configuration lives on the parent, the sub-org wallet signs, and no per-sub-org deployment is needed.
+
+
+## Submit the deposit
+
+Submit an [`ACTIVITY_TYPE_EARN_DEPOSIT`](/api-reference/activities/deposit-into-earn-vault) activity with:
+
+- the `wrapperAddress` to deposit into, from [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults)
+- the `signWith` wallet account to deposit from and sign with
+- the amount in `assets`, in raw onchain units of the underlying asset (e.g. `"1000000"` for 1 USDC at 6 decimals)
+- the CAIP-2 chain in `chainCaip2`, and optionally `sponsor` for gas sponsorship
+
+See [Deposit into Earn vault](/api-reference/activities/deposit-into-earn-vault) in the API reference for the full request/response schema and cURL example. The activity result contains only a poll handle, `depositRequestId`.
+
+## Gas: sponsored vs self-funded
+
+With `sponsor: true`, network fees are covered through Turnkey’s Gas Station and the batch executes as an EIP-7702 sponsored transaction. The `signWith` wallet does not need a gas token.
+
+With `sponsor: false`, the `signWith` wallet pays gas itself, and the user initiating the deposit will need the relevant chain’s gas token.
+
+## Poll deposit status (required)
+
+
+ A `COMPLETED` activity means the transaction was enqueued for broadcast, not that it landed onchain. A transaction that later fails (for example, from an insufficient token balance) is invisible in the activity result. Poll [`get_earn_deposit_status`](/api-reference/queries/get-earn-deposit-status) until it reports `COMPLETED` (included onchain) or `FAILED`.
+
+
+Poll with the `depositRequestId` from the activity result. `status` is `PENDING`, `COMPLETED`, or `FAILED`; on `COMPLETED` the response carries the `depositTxHash`, and on `FAILED` it includes an `error` field with the reason. See [Submissions](/api-reference/activities/overview) for general activity semantics.
+
+## Next steps
+
+- [Track positions](/features/transaction-management/earn/positions) once the deposit is `COMPLETED`
+- [Withdraw from a vault](/features/transaction-management/earn/withdraw)
\ No newline at end of file
diff --git a/features/transaction-management/earn/end-to-end-example.mdx b/features/transaction-management/earn/end-to-end-example.mdx
new file mode 100644
index 00000000..0ef1ca79
--- /dev/null
+++ b/features/transaction-management/earn/end-to-end-example.mdx
@@ -0,0 +1,284 @@
+---
+title: "End-to-end example: earn on Base"
+description: "Deploy a wrapper for a Morpho USDC vault on Base, deposit, track the position, and withdraw: the full Earn lifecycle."
+sidebarTitle: "End-to-end example: earn on Base"
+mode: "wide"
+---
+
+This walkthrough runs the complete Earn lifecycle against Base mainnet: find a Morpho USDC vault (we’ll use Steakhouse USDC), enable it, deposit 100 USDC from a Turnkey wallet, check the position, and withdraw everything.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+**Prerequisites**
+
+- Earn Early access enabled for your organization (and a Pro plan or higher if you want gas sponsorship)
+- A Turnkey API key pair
+- A wallet account holding USDC on Base, plus ETH on Base for gas if you don’t sponsor
+- An org-owned wallet address to receive your fee payouts
+
+
+
+ All requests go through the generic `request` method of `TurnkeyClient`, stamped by your API key. Deposits, withdrawals, and deployments all confirm asynchronously, so define a small polling helper here as well.
+
+ ```javascript
+ import { TurnkeyClient } from "@turnkey/http";
+ import { ApiKeyStamper } from "@turnkey/api-key-stamper";
+
+ const organizationId = "";
+
+ const client = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ new ApiKeyStamper({
+ apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
+ apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
+ }),
+ );
+
+ // Poll an earn status endpoint until the transaction lands onchain.
+ async function pollEarnStatus(path, idField, id) {
+ for (;;) {
+ const res = await client.request(path, {
+ organizationId,
+ [idField]: id,
+ });
+ if (res.status === "COMPLETED") return res;
+ if (res.status === "FAILED") {
+ throw new Error(`${path} failed: ${res.error}`);
+ }
+ await new Promise((r) => setTimeout(r, 3000));
+ }
+ }
+ ```
+
+
+ Query the catalog for USDC vaults on Base. The chain comes from the CAIP-19 asset identifier; results are sorted by TVL.
+
+
+
+ ```javascript title="JavaScript"
+ const { vaults } = await client.request("/public/v1/query/list_earn_vaults", {
+ organizationId,
+ caip19: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ provider: "EARN_PROVIDER_MORPHO",
+ });
+
+ // Pick the vault you want to offer. Here we use Steakhouse USDC.
+ const vault = vaults[0];
+ console.log(vault.vaultAddress, vault.apyPct, vault.display.usd);
+ ```
+
+ ```bash title="cURL"
+ curl --request POST \
+ --url https://api.turnkey.com/public/v1/query/list_earn_vaults \
+ --header 'Accept: application/json' \
+ --header 'Content-Type: application/json' \
+ --header "X-Stamp: (see Stamps)" \
+ --data '{
+ "organizationId": "",
+ "caip19": "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "provider": "EARN_PROVIDER_MORPHO"
+ }'
+ ```
+
+
+
+
+ Enable the vault for your organization with a 1% fee on yield (`"100"` bps) paid to your fee wallet. Turnkey pays the deployment gas. This step is safe to re-run: identical parameters return the same addresses without redeploying.
+
+
+
+ ```javascript title="JavaScript"
+ const { activity: deploy } = await client.request(
+ "/public/v1/submit/earn_deploy_wrapper",
+ {
+ type: "ACTIVITY_TYPE_EARN_DEPLOY_WRAPPER",
+ timestampMs: String(Date.now()),
+ organizationId,
+ parameters: {
+ vaultAddress: vault.vaultAddress,
+ chainCaip2: "eip155:8453",
+ clientFeeBps: "100",
+ clientFeeWallet: "",
+ },
+ },
+ );
+
+ const { deployRequestId, wrapperAddress } =
+ deploy.result.earnDeployWrapperResult;
+ ```
+
+ ```bash title="cURL"
+ curl --request POST \
+ --url https://api.turnkey.com/public/v1/submit/earn_deploy_wrapper \
+ --header 'Accept: application/json' \
+ --header 'Content-Type: application/json' \
+ --header "X-Stamp: (see Stamps)" \
+ --data '{
+ "type": "ACTIVITY_TYPE_EARN_DEPLOY_WRAPPER",
+ "timestampMs": " (e.g. 1745474677453)",
+ "organizationId": "",
+ "parameters": {
+ "vaultAddress": "",
+ "chainCaip2": "eip155:8453",
+ "clientFeeBps": "100",
+ "clientFeeWallet": ""
+ }
+ }'
+ ```
+
+
+
+
+ Poll until the wrapper is live onchain.
+
+ ```javascript
+ await pollEarnStatus(
+ "/public/v1/query/get_earn_deploy_status",
+ "deployRequestId",
+ deployRequestId,
+ );
+ ```
+
+
+ USDC has 6 decimals, so 100 USDC is `"100000000"` raw units. The approval and deposit run as one atomic transaction. With `sponsor: true`, Gas Station pays the gas (Pro plan or higher); set it to `false` to have the wallet pay with its own ETH.
+
+
+
+ ```javascript title="JavaScript"
+ const { activity: deposit } = await client.request(
+ "/public/v1/submit/earn_deposit",
+ {
+ type: "ACTIVITY_TYPE_EARN_DEPOSIT",
+ timestampMs: String(Date.now()),
+ organizationId,
+ parameters: {
+ wrapperAddress,
+ signWith: "",
+ assets: "100000000",
+ chainCaip2: "eip155:8453",
+ sponsor: true,
+ },
+ },
+ );
+
+ const { depositRequestId } = deposit.result.earnDepositResult;
+ ```
+
+ ```bash title="cURL"
+ curl --request POST \
+ --url https://api.turnkey.com/public/v1/submit/earn_deposit \
+ --header 'Accept: application/json' \
+ --header 'Content-Type: application/json' \
+ --header "X-Stamp: (see Stamps)" \
+ --data '{
+ "type": "ACTIVITY_TYPE_EARN_DEPOSIT",
+ "timestampMs": " (e.g. 1745474677453)",
+ "organizationId": "",
+ "parameters": {
+ "wrapperAddress": "",
+ "signWith": "",
+ "assets": "100000000",
+ "chainCaip2": "eip155:8453",
+ "sponsor": true
+ }
+ }'
+ ```
+
+
+
+
+ The activity completing only means the transaction was enqueued. Poll until it's included onchain.
+
+ ```javascript
+ const { depositTxHash } = await pollEarnStatus(
+ "/public/v1/query/get_earn_deposit_status",
+ "depositRequestId",
+ depositRequestId,
+ );
+ console.log("deposited:", depositTxHash);
+ ```
+
+
+ Query the wallet's positions and compute the yield earned so far from the raw fields, using `BigInt` rather than floats or the `display` values.
+
+ ```javascript
+ const { positions } = await client.request(
+ "/public/v1/query/list_earn_positions",
+ {
+ organizationId,
+ walletAddress: "",
+ },
+ );
+
+ const p = positions.find((p) => p.wrapperAddress === wrapperAddress);
+
+ const yieldEarned =
+ BigInt(p.currentValue) -
+ BigInt(p.totalDeposited) +
+ BigInt(p.totalWithdrawn);
+
+ console.log(`current value: ${p.display.currentValueUsd} USD`);
+ console.log(`yield earned: ${yieldEarned} raw units`);
+ ```
+
+
+ Exit the full position with `"MAX"`, which redeems the exact live share balance, then poll the withdrawal to confirmation.
+
+ ```javascript
+ const { activity: withdraw } = await client.request(
+ "/public/v1/submit/earn_withdraw",
+ {
+ type: "ACTIVITY_TYPE_EARN_WITHDRAW",
+ timestampMs: String(Date.now()),
+ organizationId,
+ parameters: {
+ wrapperAddress,
+ signWith: "",
+ amountValue: "MAX",
+ chainCaip2: "eip155:8453",
+ sponsor: true,
+ },
+ },
+ );
+
+ const { withdrawRequestId } = withdraw.result.earnWithdrawResult;
+
+ const { withdrawTxHash } = await pollEarnStatus(
+ "/public/v1/query/get_earn_withdraw_status",
+ "withdrawRequestId",
+ withdrawRequestId,
+ );
+ console.log("withdrawn:", withdrawTxHash);
+ ```
+
+
+
+## Dive deeper
+
+
+
+ — Fee model, chains, and the full API surface.
+
+
+
+ — Catalog and enabled-vault queries in detail.
+
+
+
+ — Fee configuration, idempotency, and status polling.
+
+
+
+ — Gas options and deposit semantics.
+
+
+
+ — Partial and yield-only withdrawals.
+
+
+
+ — Position fields, units, and precision.
+
+
\ No newline at end of file
diff --git a/features/transaction-management/earn/positions.mdx b/features/transaction-management/earn/positions.mdx
new file mode 100644
index 00000000..27266513
--- /dev/null
+++ b/features/transaction-management/earn/positions.mdx
@@ -0,0 +1,47 @@
+---
+title: "Track positions"
+description: "Query a wallet’s active Earn positions: current value, lifetime deposits and withdrawals, and the yield earned."
+mode: "wide"
+---
+
+[`list_earn_positions`](/api-reference/queries/get-earn-positions) returns the active positions for a single wallet address, one entry per wrapper the wallet holds shares in, with live onchain values. It takes your `organizationId` (or the sub-organization that owns the wallet) and the `walletAddress` to return positions for; positions are scoped per wallet, not org-wide. See [Get Earn positions](/api-reference/queries/get-earn-positions) in the API reference for the full request/response schema and cURL example.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## Understanding the fields
+
+| Field | Units | Meaning |
+| :-- | :-- | :-- |
+| `currentValue` | raw onchain units of the underlying asset | Live value of the position, already net of the wrapper's fees. This is what a full withdrawal would return right now |
+| `totalDeposited` | raw onchain units | Lifetime amount deposited into this position since it was opened (or since the last full `MAX` exit) |
+| `totalWithdrawn` | raw onchain units | Lifetime amount withdrawn over the same window |
+| `display.*` | formatted strings | USD and asset-denominated renderings for UI display only |
+| `depositsDisabled` | boolean | When `true`, new deposits to this wrapper are currently [paused](/features/transaction-management/earn/deploy-wrapper#manage-a-deployed-wrapper); withdrawals are unaffected |
+
+Raw fields are exact base-10 integers in the asset's smallest unit (e.g. `"100512340"` = 100.51234 USDC at 6 decimals). The totals accumulate from your deposit and withdrawal amounts; a [`MAX` withdrawal](/features/transaction-management/earn/withdraw#full-exit-with-max) closes the position and resets both totals to zero.
+
+
+ Don't do arithmetic with `display` values; they are formatted, rounded strings for presentation. Compute with the raw fields using `BigInt` (or your language's arbitrary-precision integers) rather than floats.
+
+
+## Computing yield
+
+Yield earned to date is:
+
+```text
+yield = currentValue - totalDeposited + totalWithdrawn
+```
+
+For example, a position with `totalDeposited = "100000000"` (100 USDC), `totalWithdrawn = "0"`, and `currentValue = "100512340"` has earned `512340` raw units (0.51234 USDC), net of all fees. To pay out just the yield, see [Claiming yield only](/features/transaction-management/earn/withdraw#claiming-yield-only).
+
+## Access notes
+
+- Positions are per wallet address: deposits made by a sub-organization wallet appear under that wallet's address, queried with the sub-organization's ID.
+- Some Earn management reads are restricted to the parent organization; position queries work for both parent and sub-organizations.
+
+## Next steps
+
+- [Withdraw from a vault](/features/transaction-management/earn/withdraw) for a partial, yield-only, or `MAX` exit
+- [Deposit into a vault](/features/transaction-management/earn/deposit) to grow a position
\ No newline at end of file
diff --git a/features/transaction-management/earn/vault-catalog.mdx b/features/transaction-management/earn/vault-catalog.mdx
new file mode 100644
index 00000000..49f184a6
--- /dev/null
+++ b/features/transaction-management/earn/vault-catalog.mdx
@@ -0,0 +1,59 @@
+---
+title: "Browse the vault catalog"
+description: "Discover the yield vaults available for an asset with live TVL and APY, and list the vaults your organization has enabled."
+sidebarTitle: "Browse the vault catalog"
+mode: "wide"
+---
+
+Two queries cover vault discovery: [`list_earn_vaults`](/api-reference/queries/get-earn-vault-catalog) returns the market of wrappable vaults for an asset, and [`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults) returns the wrappers your organization has already deployed. Full request/response schemas and cURL examples are in the API reference.
+
+
+ Earn is currently an Early Access Product. [Contact us](https://www.turnkey.com/contact-us) to enable it for your organization.
+
+
+## Discover vaults with list\_earn\_vaults
+
+[`list_earn_vaults`](/api-reference/queries/get-earn-vault-catalog) takes a required CAIP-19 asset identifier (e.g. `eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` for USDC on Base) and returns every wrappable vault for that asset. The chain is derived from the identifier, and you can optionally filter by provider.
+
+Reading the results:
+
+- The catalog is sorted by TVL in USD, descending, and only includes vaults with at least **\$100k TVL**. Sort order does not reflect a recommendation, ranking, or endorsement of any vault.
+- `tvl` is in raw onchain units of the underlying asset; `apyPct` is a decimal fraction (`"0.0812"` = 8.12% gross APY, before fees).
+- `display` values are formatted strings for presentation only. Don't do arithmetic with them.
+- `enabled: true` means your organization already has a wrapper deployed for the vault.
+- `name` and `curator` carry the provider's human-readable vault name and curator(s), for building vault pickers.
+
+
+ `apyPct` reflects the vault's gross annual percentage yield at the time of query. Yield is variable, driven by borrower demand, market utilization, and the vault curator's allocation strategy, and may change significantly between the time of query and the time of deposit or withdrawal. Do not present APY or yield figures to end users as fixed or guaranteed rates.
+
+
+### Paging through the catalog
+
+Results are cursor-paginated via `paginationOptions` (`limit` defaults to 10, max 100). Each response includes a `pageInfo` block:
+
+- `hasNextPage` / `hasPreviousPage` tell you whether more results exist in either direction.
+- Pass `pageInfo.endCursor` as the next request's `after` cursor (or `startCursor` as `before`) to keep paging.
+- Cursors are opaque and versioned; don't construct or parse them by hand. They encode the vault's position in the TVL ranking, so scans resume deterministically even as live TVL shifts between requests.
+
+## List your enabled vaults
+
+[`list_earn_enabled_vaults`](/api-reference/queries/get-earn-enabled-vaults) is the management view of every wrapper your organization has deployed, with onchain totals and the fee breakdown. Optional filters narrow by provider or CAIP-19 asset.
+
+Key fields:
+
+- `wrapperAddress` is the deposit target to pass to [`earn_deposit`](/features/transaction-management/earn/deposit); `vaultAddress` is the underlying vault it wraps.
+- `apyPct` is the gross APY; `netApyPct` is what depositors earn after both fees: `netApy = grossApy × (1 - totalFeeBps / 10000)`.
+- `clientFeeBps` is your fee for the wrapper, and `totalDeposited` is the wrapper's TVL in raw units of the underlying asset.
+- `depositsDisabled: true` means deposits to the wrapper are currently paused via [`earn_set_wrapper_state`](/api-reference/activities/set-earn-wrapper-state); withdrawals are unaffected.
+- `claimableClientFee` is your accrued fee that is releasable right now, claimable with [`claim_earn_fees`](/api-reference/activities/claim-earn-fees). It is only returned when the parent organization queries; sub-organizations don't see it.
+
+## Providers
+
+Morpho vaults are available today. Aave support is upcoming; the API shape is identical, so no integration changes will be needed. See the [chain support table](/features/transaction-management/earn#supported-protocols-and-chains).
+
+**Important:** The vaults accessible through Earn are operated by independent third-party protocols. These providers are not subcontractors, agents, or affiliates of Turnkey. Turnkey does not manage, audit, or guarantee any vault, its strategy, its curator, or its returns. Turnkey's role is limited to providing the API infrastructure that enables wallet-level interaction with these protocols.
+
+## Next steps
+
+- [Deploy a vault wrapper](/features/transaction-management/earn/deploy-wrapper) for a vault from the catalog
+- [Get Earn vault catalog](/api-reference/queries/get-earn-vault-catalog) and [Get Earn enabled vaults](/api-reference/queries/get-earn-enabled-vaults) in the API reference
\ No newline at end of file