Skip to content

Commit 4555d99

Browse files
authored
Merge pull request #1085 from tkhq/radu/with-yield-xyz-update
added policies for non root user
2 parents fe5b9fe + d083659 commit 4555d99

File tree

9 files changed

+173
-62
lines changed

9 files changed

+173
-62
lines changed

examples/oauth/.env.local.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ API_PUBLIC_KEY="<Turnkey API Public Key (that starts with 02 or 03)>"
22
API_PRIVATE_KEY="<Turnkey API Private Key>"
33
NEXT_PUBLIC_ORGANIZATION_ID="<Turnkey organization ID>"
44
NEXT_PUBLIC_GOOGLE_CLIENT_ID="<Google OIDC client ID>"
5-
NEXT_PUBLIC_BASE_URL="https://api.turnkey.com"
5+
NEXT_PUBLIC_BASE_URL="https://api.turnkey.com"

examples/with-yield-xyz/.env.local.example

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@ TURNKEY_API_PUBLIC_KEY="<Turnkey API Public Key of the root user (that starts wi
22
TURNKEY_API_PRIVATE_KEY="<Turnkey API Private Key of the root user>"
33
TURNKEY_ORGANIZATION_ID="<Turnkey organization ID>"
44
TURNKEY_BASE_URL="https://api.turnkey.com"
5-
SIGN_WITH= "<Turnkey organization wallet Base address 0x...>"
5+
6+
NONROOT_USER_ID="<userId of the non-root user>"
7+
NONROOT_API_PUBLIC_KEY="<API Public Key of the non-root user (that starts with 02 or 03)>"
8+
NONROOT_API_PRIVATE_KEY="<API Private Key of the non-root user>"
9+
10+
11+
SIGN_WITH="<Turnkey organization wallet Base address 0x...>"
12+
RPC_URL="<RPC URL for Base mainnet>"
13+
614
YIELD_ID="base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault"
7-
YIELD_API_KEY="<Yield.xyz project API Key>"
8-
RPC_URL="<RPC URL for Base mainnet>"
15+
YIELD_API_KEY="<Yield project API Key>"
16+
17+
USDC_ADDRESS="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
18+
gtUSDCf_VAULT_ADDRESS="0x236919F11ff9eA9550A4287696C2FC9e18E6e890"

examples/with-yield-xyz/README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ This example shows how to sign transactions to Yield.xyz vaults on Base Mainnet
88
- `balance.ts` is checking your balance in any yield and fetches current yield stats.
99
- `exit.ts` is withdrawing an amount from the yield.
1010

11+
On top of it we showcase the power of the Turnkey policy engine by allowing a non-root Turnkey user to sign only the specific transactions required to interact with Yield.xyz’s Base USDC vault (which internally supplies to Morpho):
12+
13+
- `createPolicies.ts` uses an organization root user (RootQuorum) to create precise policy conditions for a non-root user, restricting their signing permissions to:
14+
- the USDC contract (`USDC_ADDRESS`), and
15+
- Yield.xyz’s Base USDC vault (`gtUSDCf_VAULT_ADDRESS`), which corresponds to Yield.xyz’s identifier for Morpho’s Base USDC vault (`base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault`).
16+
- 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.
17+
1118
## Getting started
1219

1320
### 1/ Cloning the example
@@ -30,7 +37,9 @@ The first step is to set up your Turnkey organization and account. By following
3037
- A root user with a public/private API key pair within the Turnkey parent organization
3138
- An organization ID
3239

33-
Make sure you have a [wallet](https://app.turnkey.com/dashboard/wallets) with an Ethereum wallet account created within this organization and have it funded with some ETH and USDC on Base Mainnet.
40+
The next step is to create another user within the organization with a different API key and 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`.
41+
42+
Finally, make sure you have a [wallet](https://app.turnkey.com/dashboard/wallets) with an Ethereum wallet account created within this organization and have it funded with some ETH and USDC on Base Mainnet.
3443

3544
Once you've gathered these values, add them to a new `.env.local` file. Notice that your private key should be securely managed and **_never_** be committed to git.
3645

@@ -44,30 +53,41 @@ Now open `.env.local` and add the missing environment variables:
4453
- `TURNKEY_API_PRIVATE_KEY`
4554
- `TURNKEY_ORGANIZATION_ID`
4655
- `TURNKEY_BASE_URL`
56+
- `NONROOT_USER_ID`
57+
- `NONROOT_API_PUBLIC_KEY`
58+
- `NONROOT_API_PRIVATE_KEY`
4759
- `SIGN_WITH`
4860
- `YIELD_ID`
4961
- `YIELD_API_KEY`
5062
- `RPC_URL`
63+
- `USDC_ADDRESS`
64+
- `gtUSDCf_VAULT_ADDRESS`
65+
66+
### 3/ Setting up the policies for the non-root user
67+
68+
```bash
69+
pnpm createPolicies
70+
```
5171

52-
### 3/ Discover a yield (with metadata)
72+
### 4/ Discover a yield (with metadata)
5373

5474
```bash
5575
pnpm discover
5676
```
5777

58-
### 4/ Enter the yield (deposit via Yield.xyz)
78+
### 5/ Enter the yield (deposit via Yield.xyz)
5979

6080
```bash
6181
pnpm enter
6282
```
6383

64-
### 5/ Check user balance
84+
### 6/ Check user balance
6585

6686
```bash
6787
pnpm balance
6888
```
6989

70-
### 6/ Exit the yield (withdraw funds)
90+
### 7/ Exit the yield (withdraw funds)
7191

7292
```bash
7393
pnpm exit

examples/with-yield-xyz/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": "true",
55
"scripts": {
66
"build": "pnpm -w run build-all",
7+
"createPolicies": "tsx src/createPolicies.ts",
78
"enter": "tsx src/enter.ts",
89
"exit": "tsx src/exit.ts",
910
"balance": "tsx src/balance.ts",
@@ -12,9 +13,8 @@
1213
"typecheck": "tsc --noEmit"
1314
},
1415
"dependencies": {
15-
"@turnkey/api-key-stamper": "workspace:*",
1616
"@turnkey/ethers": "workspace:*",
17-
"@turnkey/http": "workspace:*",
17+
"@turnkey/sdk-server": "workspace:*",
1818
"dotenv": "^16.0.3",
1919
"ethers": "^6.10.0"
2020
}

examples/with-yield-xyz/src/balance.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ import * as dotenv from "dotenv";
33
// Load environment variables from `.env.local`
44
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
55

6-
const turnkeyAccount = {
7-
address: process.env.SIGN_WITH!,
8-
};
9-
console.log(turnkeyAccount);
10-
116
async function main() {
127
const balanceRes = await fetch(
138
`https://api.yield.xyz/v1/yields/${process.env.YIELD_ID}/balances`,
@@ -17,7 +12,7 @@ async function main() {
1712
"Content-Type": "application/json",
1813
"x-api-key": process.env.YIELD_API_KEY!,
1914
},
20-
body: JSON.stringify({ address: turnkeyAccount.address }),
15+
body: JSON.stringify({ address: process.env.SIGN_WITH! }),
2116
},
2217
);
2318
const balances = await balanceRes.json();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Turnkey } from "@turnkey/sdk-server";
2+
import * as dotenv from "dotenv";
3+
import * as path from "path";
4+
5+
// Load environment variables from `.env.local`
6+
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
7+
8+
async function main() {
9+
const turnkeyClient = new Turnkey({
10+
apiBaseUrl: "https://api.turnkey.com",
11+
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
12+
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
13+
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!,
14+
}).apiClient();
15+
16+
// The id of the non-root user that you'll be using to sign the Yield related transactions
17+
const userId = process.env.NONROOT_USER_ID!;
18+
19+
//approval policy
20+
const approvalPolicy = {
21+
policyName:
22+
"Allow API key user to call the approve function on the USDC_ADDRESS",
23+
effect: "EFFECT_ALLOW" as const,
24+
consensus: `approvers.any(user, user.id == '${userId}')`,
25+
condition: `eth.tx.to == '${process.env.USDC_ADDRESS}' && eth.tx.data[0..10] == '0x095ea7b3'`,
26+
notes: "",
27+
};
28+
29+
const { policyId: approvalPolicyId } =
30+
await turnkeyClient.createPolicy(approvalPolicy);
31+
32+
console.log(
33+
[
34+
`Created approval policy:`,
35+
`- Name: ${approvalPolicy.policyName}`,
36+
`- Policy ID: ${approvalPolicyId}`,
37+
`- Effect: ${approvalPolicy.effect}`,
38+
`- Consensus: ${approvalPolicy.consensus}`,
39+
`- Condition: ${approvalPolicy.condition}`,
40+
``,
41+
].join("\n"),
42+
);
43+
44+
//deposit policy
45+
const depositPolicy = {
46+
policyName:
47+
"Allow API key user to call the deposit function on the gtUSDCf_VAULT_ADDRESS",
48+
effect: "EFFECT_ALLOW" as const,
49+
consensus: `approvers.any(user, user.id == '${userId}')`,
50+
condition: `eth.tx.to == '${process.env.gtUSDCf_VAULT_ADDRESS}' && eth.tx.data[0..10] == '0x6e553f65'`,
51+
notes: "",
52+
};
53+
54+
const { policyId: depositPolicyId } =
55+
await turnkeyClient.createPolicy(depositPolicy);
56+
57+
console.log(
58+
[
59+
`Created deposit policy:`,
60+
`- Name: ${depositPolicy.policyName}`,
61+
`- Policy ID: ${depositPolicyId}`,
62+
`- Effect: ${depositPolicy.effect}`,
63+
`- Consensus: ${depositPolicy.consensus}`,
64+
`- Condition: ${depositPolicy.condition}`,
65+
``,
66+
].join("\n"),
67+
);
68+
69+
//withdraw policy
70+
const withdrawPolicy = {
71+
policyName:
72+
"Allow API key user to call the withdraw function on the gtUSDCf_VAULT_ADDRESS",
73+
effect: "EFFECT_ALLOW" as const,
74+
consensus: `approvers.any(user, user.id == '${userId}')`,
75+
condition: `eth.tx.to == '${process.env.gtUSDCf_VAULT_ADDRESS}' && eth.tx.data[0..10] == '0xba087652'`,
76+
notes: "",
77+
};
78+
79+
const { policyId: withdrawPolicyId } =
80+
await turnkeyClient.createPolicy(withdrawPolicy);
81+
82+
console.log(
83+
[
84+
`Created withdraw policy:`,
85+
`- Name: ${withdrawPolicy.policyName}`,
86+
`- Policy ID: ${withdrawPolicyId}`,
87+
`- Effect: ${withdrawPolicy.effect}`,
88+
`- Consensus: ${withdrawPolicy.consensus}`,
89+
`- Condition: ${withdrawPolicy.condition}`,
90+
``,
91+
].join("\n"),
92+
);
93+
}
94+
95+
main().catch((error) => {
96+
console.error(error);
97+
process.exit(1);
98+
});

examples/with-yield-xyz/src/enter.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,25 @@ import * as path from "path";
22
import * as dotenv from "dotenv";
33
import { ethers } from "ethers";
44
import { TurnkeySigner } from "@turnkey/ethers";
5-
import { TurnkeyClient } from "@turnkey/http";
6-
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
5+
import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server";
76

87
// Load environment variables from `.env.local`
98
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
109

1110
async function main() {
12-
// Initialize Turnkey client and signer
13-
const turnkeyClient = new TurnkeyClient(
14-
{ baseUrl: process.env.TURNKEY_BASE_URL! },
15-
new ApiKeyStamper({
16-
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
17-
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
18-
}),
19-
);
20-
21-
const turnkeyAccount = {
22-
address: process.env.SIGN_WITH!,
23-
};
11+
// Initialize the Turnkey client
12+
const turnkeyClient = new TurnkeyServerSDK({
13+
apiBaseUrl: "https://api.turnkey.com",
14+
apiPublicKey: process.env.NONROOT_API_PUBLIC_KEY!,
15+
apiPrivateKey: process.env.NONROOT_API_PRIVATE_KEY!,
16+
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!,
17+
});
2418

19+
// Initialize the Turnkey Signer
2520
const turnkeySigner = new TurnkeySigner({
26-
client: turnkeyClient,
21+
client: turnkeyClient.apiClient(),
2722
organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
28-
signWith: turnkeyAccount.address,
23+
signWith: process.env.SIGN_WITH!,
2924
});
3025

3126
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
@@ -35,7 +30,7 @@ async function main() {
3530
const depositAmount = "0.5";
3631
const enterPayload = {
3732
yieldId: process.env.YIELD_ID!, // e.g."base-usdc-gtusdcf-0x236919f11ff9ea9550a4287696c2fc9e18e6e890-4626-vault"
38-
address: turnkeyAccount.address,
33+
address: process.env.SIGN_WITH!,
3934
arguments: { amount: depositAmount },
4035
};
4136

@@ -48,6 +43,7 @@ async function main() {
4843
body: JSON.stringify(enterPayload),
4944
});
5045
const action = await enterRes.json();
46+
console.log("Yield API response:", JSON.stringify(action, null, 2));
5147

5248
// Sign and broadcast each transaction step
5349
for (const tx of action.transactions) {

examples/with-yield-xyz/src/exit.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,35 @@ import * as path from "path";
22
import * as dotenv from "dotenv";
33
import { ethers } from "ethers";
44
import { TurnkeySigner } from "@turnkey/ethers";
5-
import { TurnkeyClient } from "@turnkey/http";
6-
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
5+
import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server";
76

87
// Load environment variables from `.env.local`
98
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
109

1110
async function main() {
12-
// Initialize Turnkey client and signer
13-
const turnkeyClient = new TurnkeyClient(
14-
{ baseUrl: process.env.TURNKEY_BASE_URL! },
15-
new ApiKeyStamper({
16-
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
17-
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
18-
}),
19-
);
20-
21-
// Replace with your wallet address or fetched wallet from Turnkey
22-
const turnkeyAccount = {
23-
address: process.env.SIGN_WITH!,
24-
};
11+
// Initialize the Turnkey client
12+
const turnkeyClient = new TurnkeyServerSDK({
13+
apiBaseUrl: "https://api.turnkey.com",
14+
apiPublicKey: process.env.NONROOT_API_PUBLIC_KEY!,
15+
apiPrivateKey: process.env.NONROOT_API_PRIVATE_KEY!,
16+
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!,
17+
});
2518

19+
// Initialize the Turnkey Signer
2620
const turnkeySigner = new TurnkeySigner({
27-
client: turnkeyClient,
21+
client: turnkeyClient.apiClient(),
2822
organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
29-
signWith: turnkeyAccount.address,
23+
signWith: process.env.SIGN_WITH!,
3024
});
3125

3226
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
3327
const connectedSigner = turnkeySigner.connect(provider);
3428
const exitPayload = {
35-
yieldId: process.env.YIELD_ID!,
36-
address: turnkeyAccount.address,
29+
yieldId: process.env.YIELD_ID,
30+
address: process.env.SIGN_WITH!,
3731
arguments: { amount: "0.1" },
3832
};
39-
33+
// Prepare withdrawal via Yield.xyz
4034
const exitRes = await fetch("https://api.yield.xyz/v1/actions/exit", {
4135
method: "POST",
4236
headers: {
@@ -46,6 +40,7 @@ async function main() {
4640
body: JSON.stringify(exitPayload),
4741
});
4842
const exitAction = await exitRes.json();
43+
console.log("Yield API response:", JSON.stringify(exitAction, null, 2));
4944

5045
for (const tx of exitAction.transactions) {
5146
const unsignedTx = JSON.parse(tx.unsignedTransaction);

0 commit comments

Comments
 (0)