Skip to content

Commit cc90919

Browse files
committed
Merge pull-request #446
2 parents df1b6e7 + 12cded4 commit cc90919

File tree

10 files changed

+690
-8
lines changed

10 files changed

+690
-8
lines changed

examples/with-solana/README.md

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This example walks through the following:
77
- Construction of a transaction sending the funds out with the `@turnkey/solana` signer
88
- Creating, minting, and transferring a SPL token using Turnkey
99

10-
You can try this example quickly on Stackblitz. Follow the instructions below --> [Stackblitz Instructions](#6-stackblitz-example)
10+
You can try this example quickly on Stackblitz. Follow the instructions below --> [Stackblitz Instructions](#7-stackblitz-example)
1111

1212
## Getting started
1313

@@ -166,7 +166,138 @@ Token balance for warchest: 0.0001
166166

167167
Enjoy!
168168

169-
### 6/ Stackblitz Example
169+
### 6/ Running the "Create SPL token transfer with policy" example
170+
171+
This example shows the flow of using our policy engine's support for SPL token transfers! Specifically we show how to allow a user (non root user) to send a Solana SPL token transfer by calculating the "associated token address" of the receiving account, given the mint address of the token that we are transferring.
172+
173+
To best see how policies can be used effectively, we will go through example in the following steps
174+
175+
1. Setup - this step creates the on-chain state and SOME of the Turnkey setup state required to make this transfer (notably, it leaves out the creation of the policy that allows the created non root user to sign the SPL transfer)
176+
2. Attempt Transfer - since we're attempting the transfer WITHOUT having created the policy allowing it, this will fail
177+
3. Create Token Policy - this will create a policy that allows Solana transactions initiated by the correct user to be signed if they contain a single instruction which is an SPL token transfer to the correct token account
178+
4. Attempt Transfer (again) - this will succeed, now that the appropriate policy has been created!
179+
180+
NOTE: A very key piece of understanding to glean from this example is the use of "associated token addresses (or "associated token account address") in the creation of SPL related policies. For further context on ATA's look here: https://spl.solana.com/associated-token-account
181+
182+
#### Step 1. Setup
183+
184+
First we will run the setup command
185+
186+
```bash
187+
$ pnpm token-transfer-policy setup
188+
```
189+
190+
This will create a Solana wallet address that you'll have to fund with devnet SOL via a faucet or cli. A commonly used SOL devnet faucet is available here --> https://faucet.solana.com/
191+
192+
Funding your devnet and hitting `y` in the terminal to proceed will set up the necessary on-chain state and Turnkey setup to start, and will output the following information to be used in coming steps
193+
194+
- Turnkey Solana wallet address
195+
- Token Mint public key
196+
- Non root user created with user id
197+
198+
```bash
199+
✔ Ready to Continue? … yes
200+
Broadcasting token creation transaction...
201+
Transaction broadcast and confirmed! 🎉
202+
https://explorer.solana.com/tx/35SJqgdy2nWBvuECXrqRVkuYdRf6kkmWCuxNFd3TcmdMsXfrEns6nLjQqhKx5zvUwYPZ8xHnsiYCRtuhS8a9a5XX?cluster=devnet
203+
204+
Broadcasting token account creation transaction...
205+
Transaction broadcast and confirmed! 🎉
206+
https://explorer.solana.com/tx/3ffN8Goe4P3TSbJZGrmBADfGZWVdnrHNTsbNqce8AAPbANFmhWVHhd5MMhinXhppccMMvjqPqj8vz1PbFQUaV8Ns?cluster=devnet
207+
208+
Broadcasting token account creation transaction...
209+
Transaction broadcast and confirmed! 🎉
210+
https://explorer.solana.com/tx/4sTgMDSMvf1oTJenJWiGRapf6mhoghv85juqdTv5VmZuyQ48ykgf22RZPxdCACSEEg8ige6GarSEQc2Nr4NkfzY1?cluster=devnet
211+
212+
Broadcasting mint transaction...
213+
Transaction broadcast and confirmed! 🎉
214+
https://explorer.solana.com/tx/FMBBXrXYGb2nGQqbHs1j17GJG5owzSMnZ1RqN9R7cSVn8Q2JMbZKMik9MLzvG1LNmdrsmSV16yvFdyLHukZw5KJ?cluster=devnet
215+
216+
New user created!
217+
- Name: Non Root User
218+
- User ID: 13c4609b-8818-40be-aeac-c8b63da0de4a
219+
220+
Setup complete -- token mint and token accounts created, non root user created
221+
Turnkey Solana wallet address: 2bwUkGZ72SNyzBWFwfzKVrVWJ9JNZggjqFPDM9X9mrp9
222+
Token Mint public key: FA7umztm6eYcENE22ndEZhd8MCf9C7myJ2KjfCwobj8F
223+
Non root user created with user id: 13c4609b-8818-40be-aeac-c8b63da0de4a
224+
```
225+
226+
#### Step 2. Attempt to Transfer Tokens (This is expected to FAIL)
227+
228+
We will now attempt to transfer these SPL tokens that we've created
229+
230+
```bash
231+
$ pnpm token-transfer-policy attempt-transfer
232+
```
233+
234+
This will prompt you first to enter in the Solana wallet address that is originating the transfer (use the address labeled "Turnkey Solana wallet address" output at the end of the setup step)
235+
236+
```bash
237+
? Enter Solana wallet address originating transfer (created during setup stage):
238+
```
239+
240+
Once you've entered your wallet address you will then be prompted to enter in the Mint address/public key for the token being transferred (use the address labeled "Token Mint public key" output at the end of the setup step)
241+
242+
```bash
243+
? Enter Mint account address of token being transferred (created during setup stage):
244+
```
245+
246+
Since this is being initiated by a non root user, and the policy allowing this has not been created, this is EXPECTED TO FAIL. You will get an error that includes the following information saying that no policies evaluated to the outcome: Allow
247+
248+
```bash
249+
details: [
250+
{
251+
'@type': 'type.googleapis.com/errors.v1.PolicyEnginePermissionError',
252+
message: 'No policies evaluated to outcome: Allow',
253+
policyEvaluations: []
254+
}
255+
],
256+
code: 7
257+
```
258+
259+
#### Step 3. Create Policy allowing the correct user to make SPL transfers to receiving Associated Token Address
260+
261+
This is the most important step! In this step we are taking our receiving address (the Turnkey Warchest), calculating it's "associated token address", given the Mint account address for the token we've created, and creating a policy that allows the correct non root user to sign Solana transactions if and only if they contain a single instruction that is an SPL token transfer to this associated token address.
262+
263+
```bash
264+
$ pnpm token-transfer-policy create-token-policy
265+
```
266+
267+
This will prompt you first to enter in the user ID of the non root user who's credentials are being used to sign the transfer (use the value labeled "Non root user created with user id" output at the end of the setup step)
268+
269+
```bash
270+
? Enter non-root user ID originating the transfer (created during setup stage):
271+
```
272+
273+
Once you've entered this user id, you will then be prompted to enter in the Mint address/public key for the token being transferred (use the address labeled "Token Mint public key" output at the end of the setup step)
274+
275+
```bash
276+
? Enter Mint account address of token being transferred (created during setup stage):
277+
```
278+
279+
This should successfully create your policy!
280+
281+
```bash
282+
New policy created!
283+
- Name: Let non root user send SPL transfers to the associated token account of WARCHEST: 6nDvKk6emwskFLtEpbQgFX6rsMtzbNY4LkvaNnzkvBaq given the mint address for the token just created
284+
- Policy ID: f715fda0-2236-4904-b4be-9098f7c192b8
285+
- Effect: EFFECT_ALLOW
286+
- Consensus: approvers.any(user, user.id == '96a10eb7-0389-447e-bc50-b768a76c0d7f')
287+
- Condition: solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.any(transfer, transfer.to == '6nDvKk6emwskFLtEpbQgFX6rsMtzbNY4LkvaNnzkvBaq')
288+
```
289+
290+
#### Step 4. Attempt to Transfer Tokens (This is expected to SUCCEED)
291+
292+
Following the instructions for step 2 above should find success this time -- showing that the policy has successfully allowed the user to transfer tokens!
293+
294+
```bash
295+
Broadcasting token transfer transaction...
296+
Transaction broadcast and confirmed! 🎉
297+
https://explorer.solana.com/tx/GQa4uqoS2zU9uGsGuzMBAq4UmR4dYPXAWgfS4cHQ5Lmqdwb3pKwXrEc4jGHEaZLCyZmCZWErsaPWecN8udkBXaT?cluster=devnet
298+
```
299+
300+
### 7/ Stackblitz Example
170301

171302
Example Link: https://stackblitz.com/edit/stackblitz-starters-xeb93i
172303

examples/with-solana/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"start": "pnpm tsx src/index.ts",
77
"advanced": "pnpm tsx src/advanced.ts",
88
"token-transfer": "pnpm tsx src/tokenTransfer.ts",
9+
"token-transfer-policy": "pnpm tsx src/tokenTransferPolicy.ts",
910
"faucet": "pnpm tsx src/faucet.ts",
1011
"jupiter-swap": "pnpm tsx src/jupiterSwap.ts",
1112
"with-fee-payer": "pnpm tsx src/withFeePayer.ts",

examples/with-solana/src/keys.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// NOTE: All of these keys should be considered compromised!!!!
2+
// NOTE: Only used for demo purposes
3+
4+
const keys: { [key: string]: { [key: string]: string } } = {
5+
nonRootUser: {
6+
publicKey:
7+
"03178b1cb727bd1ab23c3c5725d633f9ad3c7c0502efda2371e4e4bd88aea8a94d",
8+
privateKey:
9+
"26e53957146d5d5b5612f09518b8bb40deddcf28d378270101227cb0a231969e",
10+
},
11+
};
12+
13+
export default keys;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
type TurnkeyServerClient,
3+
TurnkeyActivityError,
4+
} from "@turnkey/sdk-server";
5+
6+
import { refineNonNull } from "../utils";
7+
8+
export default async function createPolicy(
9+
turnkeyClient: TurnkeyServerClient,
10+
policyName: string,
11+
effect: "EFFECT_ALLOW" | "EFFECT_DENY",
12+
consensus: string,
13+
condition: string,
14+
notes: ""
15+
): Promise<string> {
16+
try {
17+
const { policyId } = await turnkeyClient.createPolicy({
18+
policyName,
19+
condition,
20+
consensus,
21+
effect,
22+
notes,
23+
});
24+
25+
const newPolicyId = refineNonNull(policyId);
26+
27+
// Success!
28+
console.log(
29+
[
30+
`New policy created!`,
31+
`- Name: ${policyName}`,
32+
`- Policy ID: ${newPolicyId}`,
33+
`- Effect: ${effect}`,
34+
`- Consensus: ${consensus}`,
35+
`- Condition: ${condition}`,
36+
``,
37+
].join("\n")
38+
);
39+
40+
return newPolicyId;
41+
} catch (error) {
42+
// If needed, you can read from `TurnkeyActivityError` to find out why the activity didn't succeed
43+
if (error instanceof TurnkeyActivityError) {
44+
throw error;
45+
}
46+
47+
throw new TurnkeyActivityError({
48+
message: "Failed to create a new policy",
49+
cause: error as Error,
50+
});
51+
}
52+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
type TurnkeyServerClient,
3+
TurnkeyActivityError,
4+
} from "@turnkey/sdk-server";
5+
6+
import { refineNonNull } from "../utils";
7+
8+
export default async function createUser(
9+
turnkeyClient: TurnkeyServerClient,
10+
userName: string,
11+
apiKeyName: string,
12+
publicKey: string
13+
): Promise<string> {
14+
let userTags: string[] = new Array();
15+
try {
16+
const { userIds } = await turnkeyClient.createApiOnlyUsers({
17+
apiOnlyUsers: [
18+
{
19+
userName,
20+
userTags,
21+
apiKeys: [
22+
{
23+
apiKeyName,
24+
publicKey,
25+
},
26+
],
27+
},
28+
],
29+
});
30+
31+
const userId = refineNonNull(userIds?.[0]);
32+
33+
// Success!
34+
console.log(
35+
[
36+
`New user created!`,
37+
`- Name: ${userName}`,
38+
`- User ID: ${userId}`,
39+
``,
40+
].join("\n")
41+
);
42+
43+
return userId;
44+
} catch (error) {
45+
// If needed, you can read from `TurnkeyActivityError` to find out why the activity didn't succeed
46+
if (error instanceof TurnkeyActivityError) {
47+
throw error;
48+
}
49+
50+
throw new TurnkeyActivityError({
51+
message: "Failed to create a new user",
52+
cause: error as Error,
53+
});
54+
}
55+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as createUser } from "./createUser";
2+
export { default as createPolicy } from "./createPolicy";

examples/with-solana/src/tokenTransfer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
createNewSolanaWallet,
1717
createToken,
1818
createTokenAccount,
19-
createTokenTransfer,
19+
createTokenTransferAddSignature,
2020
solanaNetwork,
2121
TURNKEY_WAR_CHEST,
2222
} from "./utils";
@@ -125,7 +125,7 @@ async function main() {
125125
);
126126

127127
// Transfer token from primary to Warchest
128-
await createTokenTransfer(
128+
await createTokenTransferAddSignature(
129129
turnkeySigner,
130130
connection,
131131
solAddress,

0 commit comments

Comments
 (0)