From d95b4f15edf690c7e434af1286dc22517055593a Mon Sep 17 00:00:00 2001 From: Radu Sandor Date: Thu, 30 Oct 2025 17:46:57 +0200 Subject: [PATCH 1/5] added yield.xyx cookbook integration --- cookbook/yieldxyz.mdx | 284 ++++++++++++++++++++++++++++++++++++++++++ docs.json | 3 +- 2 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 cookbook/yieldxyz.mdx diff --git a/cookbook/yieldxyz.mdx b/cookbook/yieldxyz.mdx new file mode 100644 index 00000000..4b94c170 --- /dev/null +++ b/cookbook/yieldxyz.mdx @@ -0,0 +1,284 @@ +--- +title: 'Building on Yield.xyz with Turnkey' +sidebarTitle: "Yield.xyz integration" +--- + +## Overview + +[Yield.xyz](https://yield.xyz/) is the ultimate yield infrastructure for Web3, providing one unified API for every yield across 75+ networks. Instead of building and maintaining your own vaults or integrating directly with individual DeFi protocols like Morpho, Aave, or Lido, you can integrate Yield.xyz once and unlock 1,000+ standardized opportunities across staking, lending, and DeFivaults. + +Yield.xyz’s API is self-custodial by design: it constructs ready-to-sign transactions for yield actions but leaves execution and signing entirely in your control. You fetch opportunities and action flows from Yield.xyz, and have Turnkey sign and broadcast them under your policy-controlled keys. + +### Why Yield.xyz instead direct protocol integrations? + +- **No protocol maintenance** – skip deploying/managing your own vaults or building dozens of one-off integrations. +- **Unified API** – discover, enter, exit, manage, and track positions across 75+ chains and 1,500+ yields with one interface. +- **Web2 Style UX** — full abstraction of Web3 complexity via Optimized Allocator Vaults (OAVs) which automatically offramp and reinvest any incentive token issued by any protocol. Additional features include charging of deposit, management, and/or performance fees, as well as modules for e.g. cross chain routing. +- **Pre-configured OAVs** – instantly offer access to 10–20 standardized and pre-configured Optimized Allocator Vaults (“grab-off-the-shelf”) across popular assets. +- **Custom OAVs** – request dedicated OAVs for specific strategies or branded products. +- **Monetization** – ability to layer on additional user-level fees per OAV as well as revenue sharing from validators on all staking integrations + - *Note: Yield.xyz enforces a minimum 10% fee* + - *Note: the validator revenue share is applicable only to PVN members (>30 of the largest validator service providers)* +- **Security & control** – Turnkey enforces transaction policies so client keys can only interact with the exact vaults/tokens you allow. + +By combining Yield.xyz and Turnkey, you deliver a production-ready yield experience in your app with minimal effort: your users can discover, deposit, track, manage, and withdraw – all signed securely with Turnkey. + +## Getting started + +Follow the [Turnkey Quickstart](https://docs.turnkey.com/getting-started/quickstart) to set up: + +- **Turnkey Root API Keys & Org ID** – a root user with API key pair and your Organization ID. +- **Turnkey Non-Root User & Wallet** – a secondary user (with its own API key) outside the root quorum. +- **Turnkey Client & Signer** – initialize a Turnkey client (`TurnkeyClient`) with your API key pair and connect a signer (`TurnkeySigner`) from `@turnkey/ethers` to your RPC provider. + +Once configured, you’ll be able to: + +1. **Discover** available yields with `GET /v1/yields`. +2. **Act** on them with `POST /v1/actions/{enter|exit|manage}`. +3. **Track** balances and status with `GET /v1/yields/{id}/balances`  + +--- + +## **Discover a yield (with metadata)** + +Discovery is just a read to the Yield.xyz API — Turnkey doesn’t change this step. You’ll typically do it server-side (to keep your Yield API key secret), then surface the results to your app where you’ll later enter using your Turnkey signer. + +**Example: USDC yields on Base** + +```tsx +const res = await fetch( + 'https://api.yield.xyz/v1/yields?network=base&token=USDC&limit=10', + { headers: { 'X-API-KEY': process.env.YIELD_API_KEY! } } +); +const items: any[] = await res.json(); + +// pick one yield +const selected = items[0]; +const YIELD_ID = selected.id; + +// metadata is included in the object +const apy = selected.rewardRate; +const token = selected.token; +const metadata = selected.metadata; +``` + +--- + +## Exemplary Yields + +To simplify integration, we provide a set of **ready-to-use yields** across both **Stablecoins**/**DeFi** and **staking** — designed to offer clean UX, consistent APIs, and monetization support. These yields are already live and have seen significant adoption across our clients. + +--- + +**Stablecoin/DeFi Yields** + +The Stablecoin/DeFi yields provided here are wrapped using **Optimized Allocator Vaults (OAVs)**, which automatically handle: + +- Incentive off-ramping & reinvestment +- Asset wrapping (where applicable) +- Fee charging (performance, management, deposit) + +This makes OAVs unique: they **enable fee capture for partners while simultaneously simplifying the user experience**, ensuring yield products feel as seamless as mainstream fintech offerings. + +We’ve deployed two sets of OAVs with pre-configured fees — available via API Keys: + +- 10% fee OAVs: `4bea9274-7cef-4e43-995b-f35147469ede` +- 20% fee OAVs: `e35de2d4-93e2-4cf8-b016-b0838fec1f20` + +For reference, the expanded list of yields most popular among existing clients (already wrapped into OAVs) can be found **[here](https://docs.yield.xyz/docs/off-the-shelf-oavs#/).** + +| Yield ID | Yield Name | Protocol | Monetization | +| --- | --- | --- | --- | +| arbitrum-usdc-gtusdcc-0x7e97fa6893871A2751B5fE961978DCCb2c201E65-4626-vault | Gauntlet USDC Core | Morpho | Available deposit, performance, management fees | +| ethereum-usds-susds-0xa3931d71877c0e7a3148cb7eb4463524fec27fbd-4626-vault | USDS Sky Savings Rate | Sky | Available deposit, performance, management fees | +| ethereum-usdc-fusdc-0x9Fb7b4477576Fe5B32be4C1843aFB1e55F251B33-4626-vault | Fluid USDC Vault | Fluid | Available deposit, performance, management fees | +| base-usdc-smusdc-0x616a4e1db48e22028f6bbf20444cd3b8e3273738-4626-vault | Seamless USDC Vault | Morpho | Available deposit, performance, management fees | +| base-usdc-aave-v3-lending | USDC Aave Lending | Aave | Available deposit, performance, management fees | + +**Staking Yields** + +These staking yields are accessible via Yield.xyz, which: + +- Enables delegations to any active validator +- Supports **validator revenue sharing** (50–85% of validator fees) with preferred validators +- Standardizes interactions across networks (undelegation, cooldowns, reward tracking) + +| Yield ID | Network | Exemplary Validator | Validator Address | Monetization | +| --- | --- | --- | --- | --- | +| solana-sol-native-multivalidator-staking | Solana | Meria | H2tJNyMHnRF6ahCQLQ1sSycM4FGchymuzyYzUqKEuydk | Deposit fees and revenue share | +| tron-trx-native-staking | Tron | Luganodes | TGyrSc9ZmTdbYziuk1SKEmdtCdETafewJ9 | Revenue share | +| bsc-bnb-native-staking | Binance | Figment | 0x477cB5d87144b2a6d93f72e32f5E01a459260D68 | Revenue share | +| ethereum-eth-everstake-staking | Ethereum | Everstake | N/A | Revenue share | +| ethereum-eth-figment-staking | Ethereum | Figment | N/A | Revenue share | +| hyperevm-hype-native-staking | Hyperliquid | Meria | 0x950f8dd5e5030e1fa6ad2cdc4295809d185925d0 | Revenue share | +| cosmos-atom-native-staking | Cosmos | Chorus One | cosmosvaloper15urq2dtp9qce4fyc85m6upwm9xul3049e02707 | Deposit fees and revenue share | + +The full list of staking yields can be found [in the Yield.xyz docs](https://docs.yield.xyz/docs/staking-yields#/). + +**Full Yield List** + +For the full list of live yields (DeFi + staking), you can: + +→ View the [Yield.xyz Dashboard](https://yields-dashboard.yield.xyz/) + +→ Query via the [Yield API](https://docs.yield.xyz/reference/yieldscontroller_getyield) as showcased in the above “Discover a yield” section. + +--- + +## Enter the yield (deposit via Yield.xyz) + +First, we’ll call the Yield.xyz `POST /v1/actions/enter` endpoint to get the transactions required to enter the Morpho USDC vault on Base. We need to specify the yield ID for this vault, the amount to deposit, and our wallet address. For example, Yield.xyz’s identifier for Morpho’s Base USDC vault might be `"base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault"`(you can discover yield IDs via the Yield.xyz API or docs). We’ll deposit 0.5 USDC in this example. + +The code below calls the Yield.xyz API (using a fetch request; you could also use Yield.xyz’s SDK) and retrieves the transactions. It then uses the Turnkey signer (connectedSigner from `@turnkey/ethers`) to send each transaction in sequence: + +```tsx +import { ethers } from "ethers"; +import { TurnkeySigner } from "@turnkey/ethers"; +import { TurnkeyClient } from "@turnkey/http"; +import { ApiKeyStamper } from "@turnkey/api-key-stamper"; + +// initialize Turnkey client and signer +const turnkeyClient = new TurnkeyClient( + { baseUrl: "https://api.turnkey.com" }, + new ApiKeyStamper({ + apiPublicKey: process.env.TURNKEY_PUBLIC_KEY!, + apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY!, + }) +); + +const turnkeySigner = new TurnkeySigner({ + client: turnkeyClient, + organizationId: process.env.TURNKEY_ORG_ID!, + signWith: turnkeyAccount.address, +}); + +const provider = new ethers.providers.JsonRpcProvider(""); +const connectedSigner = turnkeySigner.connect(provider); + +// Prepare entry via Yield.xyz +const depositAmount = "0.5"; +const enterPayload = { + yieldId: '', + address: turnkeyAccount.address, + arguments: { amount: depositAmount }, +}; + +const enterRes = await fetch("https://api.yield.xyz/v1/actions/enter", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.YIELD_API_KEY!, + }, + body: JSON.stringify(enterPayload), +}); +const action = await enterRes.json(); + +// Sign and broadcast each transaction step +for (const tx of action.transactions) { + const sent = await connectedSigner.sendTransaction(tx); + console.log("Broadcasted tx:", sent.hash); +} +``` + +In the above flow, the first transaction is an ERC-20 approval allowing the vault contract to spend 0.5 USDC on our behalf, and the second is the actual deposit into the vault. Yield.xyz figured out these steps for us – we didn’t have to manually craft the token `approve` or vault `deposit` call (no need to find contract ABIs or addresses ourselves). After executing these, our USDC is now deposited and earning yield in the vault. + +## Check user balance + +After depositing, we can query the Yield.xyz API to confirm our position and even fetch the current yield stats. Yield.xyz provides a portfolio endpoint to get a unified view of a user’s balances in any yield. + +```tsx +const balanceRes = await fetch( + `https://api.yield.xyz/v1/yields/${yieldId}/balances`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.YIELD_API_KEY!, + }, + body: JSON.stringify({ address: turnkeyAccount.address }), + } +); +const balances = await balanceRes.json(); +console.log("Vault balances:", JSON.stringify(balances, null, 2)); +``` + +The response will include the amount of USDC we have active in the vault (and any pending yield or rewards). For instance, it might show an active balance of ~0.5 USDC, and possibly any claimable rewards if applicable, all in one payload. Yield.xyz normalizes these outputs across all yields, so even if we were querying a staking position or another DeFi protocol, the format would be consistent. + +## Exit the yield (withdraw funds) + +Yield.xyz’s `POST /v1/actions/exit` endpoint returns the transaction(s) needed to withdraw. Like with enter, we provide the yield ID, our address, and how much we want to withdraw. + +```tsx +const exitPayload = { + yieldId, + address: turnkeyAccount.address, + arguments: { amount: "0.1" }, +}; + +const exitRes = await fetch("https://api.yield.xyz/v1/actions/exit", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.YIELD_API_KEY!, + }, + body: JSON.stringify(exitPayload), +}); +const exitAction = await exitRes.json(); + +for (const tx of exitAction.transactions) { + const sent = await connectedSigner.sendTransaction(tx); + console.log("Withdraw tx:", sent.hash); +} +``` + +In the first step, we requested to withdraw 0.1 USDC. Yield.xyz returned the appropriate transaction (a call to the vault’s `withdraw` function under the hood). We sent it with our Turnkey signer, and once mined, ~0.1 USDC would be back in our wallet. + +--- + +## Monetization + +Monetization of yield interactions is possible on multiple levels depending on the yield type: + +- **Staking.** + - **Deposit Fees**: Leverage **FeeWrapper contracts** (EVM) and **atomic fee transfer mechanisms** (non-EVM) to deduct a percentage of the deposit and transfer it to the recipient in the same transaction. + - **Revenue Share:** [Yield.xyz](http://yield.xyz/) has agreements with 30+ validator service providers, entitling clients to a portion of validator revenues (typically 50%–85%). + - [Validator revenue share overview](https://www.notion.so/2695318e934e80cea8b1ccb00ac835c0?pvs=21) +- **DeFi.** + - **Revenue Share:** Some major protocols and curators provide revenue share on deposits via the [Yield.xyz](http://yield.xyz/) API. + - [DeFi revenue share overview](https://www.notion.so/25b5318e934e803eab5cccfc941a6ba8?pvs=21) + - **User-Facing Fees via OAVs:** + - **Optimized Allocator Vaults (OAVs)** allow deposit, performance, and management fees to be configured at the user level. + - **Performance Fees:** Charged on realized profits. + - **Management Fees:** Flat annualized rate applied on AUM. + - **Deposit Fees:** Automatically deducted at the time of deposit. + +You can identify which monetization options apply to a given yield in the yield metadata, specifically the `possibleFeeTakingMechanisms` object → [API Reference](https://docs.yield.xyz/reference/yieldscontroller_getyield#/) + +### **Web2 UX via OAVs** + +Beyond monetization, **OAVs deliver a Web2-grade experience for Web3 yields.** + +- **Automatic Off-Ramping & Reinvestment:** Incentive tokens are automatically off-ramped into the deposit asset and reinvested, ensuring users earn pure yield in their deposit token. +- **Automatic Wrapping/Swapping:** Complex asset flows are handled automatically, reducing friction and mirroring the simplicity of Web2 fintech apps. +- **Unified UX:** End-users interact only with their deposit asset — no juggling of dozens of reward tokens, no manual reinvestments. + +This makes OAVs unique: they **enable fee capture for partners while simultaneously simplifying the user experience**, ensuring yield products feel as seamless as mainstream fintech offerings.For reference, the yields most popular among existing clients (already wrapped into OAVs) can be found [**here](https://docs.google.com/spreadsheets/d/1_GphMivV3ebN4-SHlviYx9eyweceI7ple6KmpAajcMY/edit?gid=524025543#gid=524025543).** We’ve deployed two sets of OAVs with pre-configured fees — available via API Keys: + +- 10% fee OAVs: `4bea9274-7cef-4e43-995b-f35147469ede` +- 20% fee OAVs: `e35de2d4-93e2-4cf8-b016-b0838fec1f20` + +--- + +## **Conclusion** + +With this integration you can: + +- **Discover** yield opportunities across 70+ networks. +- **Enter** and **Exit** with standardized transactions. +- **Track** balances and rewards. + +All transactions are constructed by Yield.xyz for the selected yield opportunities and signed with Turnkey — no need to write custom contract logic or manage ABIs. + +Partners can start with baseline access to public validator delegation and curated OAVs, then request custom OAVs with client-specific fee structures (in addition to the minimum 10% platform fee). + +This lets you go to market with yield in days, not months — with more revenue, less engineering overhead, and stronger security guarantees than building and maintaining protocol integrations yourself. \ No newline at end of file diff --git a/docs.json b/docs.json index cf8cfc41..78409982 100644 --- a/docs.json +++ b/docs.json @@ -215,7 +215,8 @@ "cookbook/breeze", "cookbook/jupiter", "cookbook/lifi", - "cookbook/0x" + "cookbook/0x", + "cookbook/yieldxyz" ] } ] From 499df6e5c36f85d734870e9bd81341886d829502 Mon Sep 17 00:00:00 2001 From: Radu Sandor Date: Wed, 5 Nov 2025 17:02:38 +0200 Subject: [PATCH 2/5] nits --- cookbook/yieldxyz.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cookbook/yieldxyz.mdx b/cookbook/yieldxyz.mdx index 598a4f9d..f745f959 100644 --- a/cookbook/yieldxyz.mdx +++ b/cookbook/yieldxyz.mdx @@ -151,12 +151,12 @@ const turnkeyClient = new TurnkeyClient( ); const turnkeyAccount = { - address: process.env.TURNKEY_WALLET_ADDRESS!, + address: process.env.SIGN_WITH!, }; const turnkeySigner = new TurnkeySigner({ client: turnkeyClient, - organizationId: process.env.TURNKEY_ORG_ID!, + organizationId: process.env.TURNKEY_ORGANIZATION_ID!, signWith: turnkeyAccount.address, }); @@ -225,7 +225,7 @@ Yield.xyz’s `POST /v1/actions/exit` endpoint returns the transaction(s) need ```tsx const exitPayload = { - yieldId, + yieldId: process.env.YIELD_ID! , address: turnkeyAccount.address, arguments: { amount: "0.1" }, }; From 832565910dd7a583af5d6f63db45ea4b4eb8d2e4 Mon Sep 17 00:00:00 2001 From: Radu Sandor Date: Thu, 6 Nov 2025 13:47:36 +0200 Subject: [PATCH 3/5] add yield api key info to getting started --- cookbook/yieldxyz.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cookbook/yieldxyz.mdx b/cookbook/yieldxyz.mdx index f745f959..60ab17ba 100644 --- a/cookbook/yieldxyz.mdx +++ b/cookbook/yieldxyz.mdx @@ -31,9 +31,13 @@ The working example can be found [here](https://github.com/tkhq/sdk/tree/main/ex Follow the [Turnkey Quickstart](https://docs.turnkey.com/getting-started/quickstart) to set up: - **Turnkey Root API Keys & Org ID** – a root user with API key pair and your Organization ID. -- **Turnkey Non-Root User & Wallet** – a secondary user (with its own API key) outside the root quorum. +- **[Turnkey Non-Root User](https://docs.turnkey.com/concepts/overview#users) & Wallet** – a secondary user (with its own API key) outside the root quorum. - **Turnkey Client & Signer** – initialize a Turnkey client (`TurnkeyClient`) with your API key pair and connect a signer (`TurnkeySigner`) from `@turnkey/ethers` to your RPC provider. +Follow the [Yield Project Setup](https://docs.yield.xyz/docs/creating-an-api-key) to create: + +- **Yield API KEY** – to make authenticated requests to the Yield.xyz API. + Once configured, you’ll be able to: 1. **Discover** available yields with `GET /v1/yields`. From 562995411449a47ec272672237af240f69fd91d2 Mon Sep 17 00:00:00 2001 From: Radu Sandor Date: Mon, 10 Nov 2025 16:14:46 +0200 Subject: [PATCH 4/5] added create policies example and switched to using @turnkey/sdk-server --- .../policies/delegated-access-overview.mdx | 2 - cookbook/yieldxyz.mdx | 243 +++++++++++++----- 2 files changed, 184 insertions(+), 61 deletions(-) diff --git a/concepts/policies/delegated-access-overview.mdx b/concepts/policies/delegated-access-overview.mdx index 242f7226..8769bc7c 100644 --- a/concepts/policies/delegated-access-overview.mdx +++ b/concepts/policies/delegated-access-overview.mdx @@ -3,8 +3,6 @@ title: "Overview" description: "With Turnkey you can create multi-user accounts with flexible co-ownership controls. This primitive enables you to establish delegated access to a user’s wallet, reducing or removing the need for them to manually approve each action. You can provide a smoother user experience while ensuring that end-users maintain full control over their wallets." --- -## Overview - Delegated access works by creating a specialized business-controlled user within each end-user’s sub-organization that has carefully scoped permissions to perform only specific actions, such as signing transactions to designated addresses. This can enable your backend to do things like: - Automate onchain actions such as staking, redemptions, or limit orders diff --git a/cookbook/yieldxyz.mdx b/cookbook/yieldxyz.mdx index 60ab17ba..113460a1 100644 --- a/cookbook/yieldxyz.mdx +++ b/cookbook/yieldxyz.mdx @@ -22,6 +22,7 @@ Yield.xyz’s API is self-custodial by design: it constructs ready-to-sign tran - **Security & control** – Turnkey enforces transaction policies so client keys can only interact with the exact vaults/tokens you allow. By combining Yield.xyz and Turnkey, you deliver a production-ready yield experience in your app with minimal effort: your users can discover, deposit, track, manage, and withdraw – all signed securely with Turnkey. + The working example can be found [here](https://github.com/tkhq/sdk/tree/main/examples/with-yield-xyz). --- @@ -30,10 +31,16 @@ The working example can be found [here](https://github.com/tkhq/sdk/tree/main/ex Follow the [Turnkey Quickstart](https://docs.turnkey.com/getting-started/quickstart) to set up: -- **Turnkey Root API Keys & Org ID** – a root user with API key pair and your Organization ID. -- **[Turnkey Non-Root User](https://docs.turnkey.com/concepts/overview#users) & Wallet** – a secondary user (with its own API key) outside the root quorum. +- **Organization ID** – logical grouping of resources( e.g. users, wallets, policies). +- **Root quorum user** – a root user with an API key pair. +- **[Non-Root User](https://docs.turnkey.com/concepts/overview#users)** – a secondary user (with its own API key) outside the root quorum. +- **Wallet** – hierarchical deterministic (HD) wallet. - **Turnkey Client & Signer** – initialize a Turnkey client (`TurnkeyClient`) with your API key pair and connect a signer (`TurnkeySigner`) from `@turnkey/ethers` to your RPC provider. +After creating a non-root user with a different API key, the next step is to remove it from the root quorum. You can do this from the Turnkey [dashboard](https://app.turnkey.com/dashboard/security/updateRootQuorum) or [API](https://docs.turnkey.com/api-reference/activities/update-root-quorum). Here’s a simple [script](https://github.com/tkhq/sdk/blob/main/examples/kitchen-sink/src/sdk-server/updateRootQuorum.ts) that shows how to update the root quorum using `@turnkey/sdk-server`. + +Also, make sure you have a wallet with an Ethereum account created within this organization and have it funded with some ETH and USDC on Base Mainnet. + Follow the [Yield Project Setup](https://docs.yield.xyz/docs/creating-an-api-key) to create: - **Yield API KEY** – to make authenticated requests to the Yield.xyz API. @@ -46,6 +53,116 @@ Once configured, you’ll be able to: --- +## Setting up the policies for the non-root user + +Now, we want to use the non-root user for signing transactions to Yield.xyz and restrict it to only be able to interact with the USDC and Yield vault smart contracts. We'll define a new API client that uses the organization’s root user to create the required policy: + +```tsx +import { Turnkey } from "@turnkey/sdk-server"; +import * as dotenv from "dotenv"; +import * as path from "path"; + +// Load environment variables from `.env.local` +dotenv.config({ path: path.resolve(process.cwd(), ".env.local") }); + +async function main() { + const turnkeyClient = new Turnkey({ + apiBaseUrl: "https://api.turnkey.com", + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, + defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!, + }).apiClient(); + + // The id of the non-root user that you'll be using to sign the Yield related transactions + const userId = process.env.NONROOT_USER_ID!; + + //approval policy + + const approvalPolicy = { + policyName: + "Allow API key user to call the approve function on the USDC_ADDRESS", + effect: "EFFECT_ALLOW" as const, + consensus: `approvers.any(user, user.id == '${userId}')`, + condition: `eth.tx.to == '${process.env.USDC_ADDRESS}' && eth.tx.data[0..10] == '0x095ea7b3'`, + notes: "", + }; + + const { policyId: approvalPolicyId } = + await turnkeyClient.createPolicy(approvalPolicy); + + console.log( + [ + `Created approval policy:`, + `- Name: ${approvalPolicy.policyName}`, + `- Policy ID: ${approvalPolicyId}`, + `- Effect: ${approvalPolicy.effect}`, + `- Consensus: ${approvalPolicy.consensus}`, + `- Condition: ${approvalPolicy.condition}`, + ``, + ].join("\n"), + ); + + //deposit policy + + const depositPolicy = { + policyName: + "Allow API key user to call the deposit function on the gtUSDCf_VAULT_ADDRESS", + effect: "EFFECT_ALLOW" as const, + consensus: `approvers.any(user, user.id == '${userId}')`, + condition: `eth.tx.to == '${process.env.gtUSDCf_VAULT_ADDRESS}' && eth.tx.data[0..10] == '0x6e553f65'`, + notes: "", + }; + + const { policyId: depositPolicyId } = + await turnkeyClient.createPolicy(depositPolicy); + + console.log( + [ + `Created deposit policy:`, + `- Name: ${depositPolicy.policyName}`, + `- Policy ID: ${depositPolicyId}`, + `- Effect: ${depositPolicy.effect}`, + `- Consensus: ${depositPolicy.consensus}`, + `- Condition: ${depositPolicy.condition}`, + ``, + ].join("\n"), + ); + + //withdraw policy + + const withdrawPolicy = { + policyName: + "Allow API key user to call the withdraw function on the gtUSDCf_VAULT_ADDRESS", + effect: "EFFECT_ALLOW" as const, + consensus: `approvers.any(user, user.id == '${userId}')`, + condition: `eth.tx.to == '${process.env.gtUSDCf_VAULT_ADDRESS}' && eth.tx.data[0..10] == '0xba087652'`, + notes: "", + }; + + const { policyId: withdrawPolicyId } = + await turnkeyClient.createPolicy(withdrawPolicy); + + console.log( + [ + `Created withdraw policy:`, + `- Name: ${withdrawPolicy.policyName}`, + `- Policy ID: ${withdrawPolicyId}`, + `- Effect: ${withdrawPolicy.effect}`, + `- Consensus: ${withdrawPolicy.consensus}`, + `- Condition: ${withdrawPolicy.condition}`, + ``, + ].join("\n"), + ); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +--- + ## **Discover a yield (with metadata)** Discovery is just a read to the Yield.xyz API — Turnkey doesn’t change this step. You’ll typically do it server-side (to keep your Yield API key secret), then surface the results to your app where you’ll later enter using your Turnkey signer. @@ -74,7 +191,7 @@ const metadata = selected.metadata; ## Exemplary Yields -To simplify integration, we provide a set of **ready-to-use yields** across both **Stablecoins**/**DeFi** and **staking** — designed to offer clean UX, consistent APIs, and monetization support. These yields are already live and have seen significant adoption across our clients. +To simplify integration, Yield.xyz provides a set of **ready-to-use yields** across both **Stablecoins**/**DeFi** and **staking** — designed to offer clean UX, consistent APIs, and monetization support. These yields are already live and have seen significant adoption across our clients. --- @@ -137,66 +254,74 @@ For the full list of live yields (DeFi + staking), you can: First, we’ll call the Yield.xyz `POST /v1/actions/enter` endpoint to get the transactions required to enter the Morpho USDC vault on Base. We need to specify the yield ID for this vault, the amount to deposit, and our wallet address. For example, Yield.xyz’s identifier for Morpho’s Base USDC vault might be `"base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault"`(you can discover yield IDs via the Yield.xyz API or docs). We’ll deposit 0.5 USDC in this example. -The code below calls the Yield.xyz API (using a fetch request; you could also use Yield.xyz’s SDK) and retrieves the transactions. It then uses the Turnkey signer (connectedSigner from `@turnkey/ethers`) to send each transaction in sequence: +The code below calls the Yield.xyz API (using a fetch request; you could also use Yield.xyz’s [SDK](https://www.npmjs.com/package/@stakekit/api-hooks)) and retrieves the transactions. It then uses the Turnkey signer (`connectedSigner` from `@turnkey/ethers`) to send each transaction in sequence: ```tsx +import * as path from "path"; +import * as dotenv from "dotenv"; import { ethers } from "ethers"; import { TurnkeySigner } from "@turnkey/ethers"; -import { TurnkeyClient } from "@turnkey/http"; -import { ApiKeyStamper } from "@turnkey/api-key-stamper"; - -// initialize Turnkey client and signer -const turnkeyClient = new TurnkeyClient( - { baseUrl: process.env.TURNKEY_BASE_URL! }, - new ApiKeyStamper({ - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, - apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, - }) -); - -const turnkeyAccount = { - address: process.env.SIGN_WITH!, - }; - -const turnkeySigner = new TurnkeySigner({ - client: turnkeyClient, - organizationId: process.env.TURNKEY_ORGANIZATION_ID!, - signWith: turnkeyAccount.address, -}); - -const provider = new ethers.JsonRpcProvider(""); -const connectedSigner = turnkeySigner.connect(provider); - -// Prepare entry via Yield.xyz -const depositAmount = "0.5"; -const enterPayload = { - yieldId: process.env.YIELD_ID!, - address: turnkeyAccount.address, - arguments: { amount: depositAmount }, -}; +import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server"; + +// Load environment variables from `.env.local` +dotenv.config({ path: path.resolve(process.cwd(), ".env.local") }); + +async function main() { + // Initialize the Turnkey client + const turnkeyClient = new TurnkeyServerSDK({ + apiBaseUrl: "https://api.turnkey.com", + apiPublicKey: process.env.NONROOT_API_PUBLIC_KEY!, + apiPrivateKey: process.env.NONROOT_API_PRIVATE_KEY!, + defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!, + }); + + // Initialize the Turnkey Signer + const turnkeySigner = new TurnkeySigner({ + client: turnkeyClient.apiClient(), + organizationId: process.env.TURNKEY_ORGANIZATION_ID!, + signWith: process.env.SIGN_WITH!, + }); + + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!); + const connectedSigner = turnkeySigner.connect(provider); + + // Prepare entry via Yield.xyz + const depositAmount = "0.5"; + const enterPayload = { + yieldId: process.env.YIELD_ID!, // e.g."base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault" + address: process.env.SIGN_WITH!, + arguments: { amount: depositAmount }, + }; + + const enterRes = await fetch("https://api.yield.xyz/v1/actions/enter", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.YIELD_API_KEY!, + }, + body: JSON.stringify(enterPayload), + }); + const action = await enterRes.json(); + console.log("Yield API response:", JSON.stringify(action, null, 2)); + + // Sign and broadcast each transaction step + for (const tx of action.transactions) { + const unsignedTx = JSON.parse(tx.unsignedTransaction); + const sent = await connectedSigner.sendTransaction({ + to: unsignedTx.to, + data: unsignedTx.data, + value: unsignedTx.value ?? "0x0", + chainId: unsignedTx.chainId, + }); + console.log("Broadcasted tx:", sent.hash); + await sent.wait(); //waiting for the approve transaction to be mined + } +} -const enterRes = await fetch("https://api.yield.xyz/v1/actions/enter", { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": process.env.YIELD_API_KEY!, - }, - body: JSON.stringify(enterPayload), +main().catch((err) => { + console.error("Error running Yield deposit example:", err); + process.exit(1); }); -const action = await enterRes.json(); - -// Sign and broadcast each transaction step -for (const tx of action.transactions) { - const unsignedTx = JSON.parse(tx.unsignedTransaction); - const sent = await connectedSigner.sendTransaction({ - to: unsignedTx.to, - data: unsignedTx.data, - value: unsignedTx.value ?? '0x0', - chainId: unsignedTx.chainId - }); - console.log("Broadcasted tx:", sent.hash); - await sent.wait(); //waiting for the approve transaction to be mined -} ``` In the above flow, the first transaction is an ERC-20 approval allowing the vault contract to spend 0.5 USDC on our behalf, and the second is the actual deposit into the vault. Yield.xyz figured out these steps for us – we didn’t have to manually craft the token `approve` or vault `deposit` call (no need to find contract ABIs or addresses ourselves). After executing these, our USDC is now deposited and earning yield in the vault. @@ -225,12 +350,12 @@ The response will include the amount of USDC we have active in the vault (and an ## Exit the yield (withdraw funds) -Yield.xyz’s `POST /v1/actions/exit` endpoint returns the transaction(s) needed to withdraw. Like with enter, we provide the yield ID, our address, and how much we want to withdraw. +Yield.xyz’s `POST /v1/actions/exit` endpoint returns the transaction data required to perform a withdrawal. Similar to the enter action, you specify the yield ID, your wallet address, and the withdrawal amount. ```tsx const exitPayload = { yieldId: process.env.YIELD_ID! , - address: turnkeyAccount.address, + address: process.env.SIGN_WITH!, arguments: { amount: "0.1" }, }; From e479345a808b2704d6b0360db5280d43ab66bb0e Mon Sep 17 00:00:00 2001 From: Radu Sandor Date: Mon, 10 Nov 2025 18:19:42 +0200 Subject: [PATCH 5/5] small updates --- cookbook/yieldxyz.mdx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cookbook/yieldxyz.mdx b/cookbook/yieldxyz.mdx index 113460a1..9407936b 100644 --- a/cookbook/yieldxyz.mdx +++ b/cookbook/yieldxyz.mdx @@ -34,7 +34,7 @@ Follow the [Turnkey Quickstart](https://docs.turnkey.com/getting-started/quicks - **Organization ID** – logical grouping of resources( e.g. users, wallets, policies). - **Root quorum user** – a root user with an API key pair. - **[Non-Root User](https://docs.turnkey.com/concepts/overview#users)** – a secondary user (with its own API key) outside the root quorum. -- **Wallet** – hierarchical deterministic (HD) wallet. +- **Wallet** – hierarchical deterministic (HD) wallet having at least an Ethereum wallet account. - **Turnkey Client & Signer** – initialize a Turnkey client (`TurnkeyClient`) with your API key pair and connect a signer (`TurnkeySigner`) from `@turnkey/ethers` to your RPC provider. After creating a non-root user with a different API key, the next step is to remove it from the root quorum. You can do this from the Turnkey [dashboard](https://app.turnkey.com/dashboard/security/updateRootQuorum) or [API](https://docs.turnkey.com/api-reference/activities/update-root-quorum). Here’s a simple [script](https://github.com/tkhq/sdk/blob/main/examples/kitchen-sink/src/sdk-server/updateRootQuorum.ts) that shows how to update the root quorum using `@turnkey/sdk-server`. @@ -55,7 +55,8 @@ Once configured, you’ll be able to: ## Setting up the policies for the non-root user -Now, we want to use the non-root user for signing transactions to Yield.xyz and restrict it to only be able to interact with the USDC and Yield vault smart contracts. We'll define a new API client that uses the organization’s root user to create the required policy: +Now, we want to use the non-root user for signing transactions to Yield.xyz and restrict it to only be able to interact with the USDC and Yield vault smart contracts. We'll define a new API client that uses the organization’s root user to create the required policies. +Each policy uses the `eth.tx.data` field to identify which smart contract function is being called. The first four bytes of this field represent the function selector. For the `approve` function, the selector is `0x095ea7b3`; for `deposit` it is `0x6e553f65`; and for `withdraw`, it’s `0xba087652`. This allows the policies to precisely restrict the non-root user to only those permitted contract calls. ```tsx import { Turnkey } from "@turnkey/sdk-server"; @@ -77,7 +78,6 @@ async function main() { const userId = process.env.NONROOT_USER_ID!; //approval policy - const approvalPolicy = { policyName: "Allow API key user to call the approve function on the USDC_ADDRESS", @@ -103,7 +103,6 @@ async function main() { ); //deposit policy - const depositPolicy = { policyName: "Allow API key user to call the deposit function on the gtUSDCf_VAULT_ADDRESS", @@ -129,7 +128,6 @@ async function main() { ); //withdraw policy - const withdrawPolicy = { policyName: "Allow API key user to call the withdraw function on the gtUSDCf_VAULT_ADDRESS", @@ -163,7 +161,7 @@ main().catch((error) => { --- -## **Discover a yield (with metadata)** +## Discover a yield (with metadata) Discovery is just a read to the Yield.xyz API — Turnkey doesn’t change this step. You’ll typically do it server-side (to keep your Yield API key secret), then surface the results to your app where you’ll later enter using your Turnkey signer. @@ -254,7 +252,7 @@ For the full list of live yields (DeFi + staking), you can: First, we’ll call the Yield.xyz `POST /v1/actions/enter` endpoint to get the transactions required to enter the Morpho USDC vault on Base. We need to specify the yield ID for this vault, the amount to deposit, and our wallet address. For example, Yield.xyz’s identifier for Morpho’s Base USDC vault might be `"base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault"`(you can discover yield IDs via the Yield.xyz API or docs). We’ll deposit 0.5 USDC in this example. -The code below calls the Yield.xyz API (using a fetch request; you could also use Yield.xyz’s [SDK](https://www.npmjs.com/package/@stakekit/api-hooks)) and retrieves the transactions. It then uses the Turnkey signer (`connectedSigner` from `@turnkey/ethers`) to send each transaction in sequence: +The code below calls the Yield.xyz API (using a fetch request; you could also use Yield.xyz’s [SDK](https://www.npmjs.com/package/@stakekit/api-hooks)) and retrieves the transactions. It then uses the non-root Turnkey user to sign and send each transaction in sequence: ```tsx import * as path from "path"; @@ -426,6 +424,7 @@ With this integration you can: - **Discover** yield opportunities across 70+ networks. - **Enter** and **Exit** with standardized transactions. - **Track** balances and rewards. +- Use Turnkey’s **policy engine** to ensure signing operations can only interact with the intended smart contracts. All transactions are constructed by Yield.xyz for the selected yield opportunities and signed with Turnkey — no need to write custom contract logic or manage ABIs.