Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Turnkey signer: @turnkey/solana #195

Merged
merged 6 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"packages/cosmjs",
"packages/ethers",
"packages/http",
"packages/solana",
"packages/viem",
"packages/webauthn-stamper",
"packages/api-key-stamper",
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ typings/
.env
.env.*
!.env.local.example
!.env.test.example
!.env.example

# parcel-bundler cache (https://parceljs.org/)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ API Docs: https://docs.turnkey.com/
| [`@turnkey/ethers`](/packages/ethers) | [![npm](https://img.shields.io/npm/v/@turnkey/ethers?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/ethers) | Turnkey Signer for Ethers | [CHANGELOG](/packages/ethers/CHANGELOG.md) |
| [`@turnkey/viem`](/packages/viem) | [![npm](https://img.shields.io/npm/v/@turnkey/viem?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/viem) | (Experimental) Turnkey Helpers to work with Viem | [CHANGELOG](/packages/viem/CHANGELOG.md) |
| [`@turnkey/cosmjs`](/packages/cosmjs) | [![npm](https://img.shields.io/npm/v/@turnkey/cosmjs?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/cosmjs) | (Experimental) Turnkey Cosmos Signer for CosmJS | [CHANGELOG](/packages/cosmjs/CHANGELOG.md) |
| [`@turnkey/solana`](/packages/solana) | [![npm](https://img.shields.io/npm/v/@turnkey/solana?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/solana) | (Experimental) Turnkey Signer for Solana | [CHANGELOG](/packages/solana/CHANGELOG.md) |
| [`@turnkey/http`](/packages/http) | [![npm](https://img.shields.io/npm/v/@turnkey/http?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/http) | Lower-level, fully typed HTTP client for interacting with Turnkey API | [CHANGELOG](/packages/http/CHANGELOG.md) |
| [`@turnkey/api-key-stamper`](/packages/api-key-stamper) | [![npm](https://img.shields.io/npm/v/@turnkey/api-key-stamper?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/api-key-stamper) | Provide API key signatures over Turnkey requests | [CHANGELOG](/packages/api-key-stamper/CHANGELOG.md) |
| [`@turnkey/iframe-stamper`](/packages/iframe-stamper) | [![npm](https://img.shields.io/npm/v/@turnkey/iframe-stamper?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/iframe-stamper) | Provide API key signatures over Turnkey requests within iframe contexts | [CHANGELOG](/packages/iframe-stamper/CHANGELOG.md) |
Expand Down
2 changes: 1 addition & 1 deletion examples/with-solana/.env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ API_PUBLIC_KEY="<Turnkey API Public Key (that starts with 02 or 03)>"
API_PRIVATE_KEY="<Turnkey API Private Key>"
BASE_URL="https://api.turnkey.com"
ORGANIZATION_ID="<Turnkey organization ID>"
PRIVATE_KEY_ID="<(optional) Turnkey (crypto) Private Key ID>"
SOLANA_ADDRESS="<(optional) If you want to use an existing Solana address in your organization, put it here!>"
32 changes: 16 additions & 16 deletions examples/with-solana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

This example walks through the following:

- Creation of a new Turnkey private key
- Derivation of a new Solana address
- Creation of a new Turnkey wallet with a new Solana account
- Monitoring of devnet tokens landing on that address
- Construction of a transaction sending the funds out
- Construction of a transaction sending the funds out with the `@turnkey/solana` signer

## Getting started

Expand All @@ -24,7 +23,7 @@ $ cd examples/with-solana/

### 2/ Setting up Turnkey

The first step is to set up your Turnkey organization and account. By following the [Quickstart](https://docs.turnkey.com/getting-started/quickstart) guide, you should have:
The first step is to set up your Turnkey organization. By following the [Quickstart](https://docs.turnkey.com/getting-started/quickstart) guide, you should have:

- A public/private API key pair for Turnkey
- An organization ID
Expand All @@ -42,13 +41,13 @@ Now open `.env.local` and add the missing environment variables:
- `BASE_URL`
- `ORGANIZATION_ID`

You can also add a Turnkey Private Key ID if you have one already:
You can specify an existing Turnkey Solana address if you have one already:

```
PRIVATE_KEY_ID=<your Turnkey Private Key ID>
SOLANA_ADDRESS=<your Turnkey Solana address>
```

This is optional: the script will create a new one if you don't specify one in your `.env.local` file
Note that this is optional: the script gives you a fresh one if you don't specify one in your `.env.local` file

### 3/ Running the script

Expand All @@ -59,23 +58,24 @@ $ pnpm start
You should see output similar to the following:

```
creating a new Solana private key on your Turnkey organization...
creating a new Solana wallet in your Turnkey organization...

New Solana private key created!
- Name: Solana Key ff73
- Private key ID: e082a9c4-046a-422c-9836-1910615d8100
New Solana wallet created!
- Name: Solana Wallet 9dab
- Wallet ID: 28da5d1a-4d8d-57db-926a-ca36e1c31f63
- Solana address: ARWHYAx8aiNrMkNfCMJxA7FpBBxRdrRbi7Biuzcssxjs

Your Solana address: "6ziT1tk8YhQx8nEiJHAEM5eh9g4DnLwdek7Zfx7KYGAo"
Your new Solana address: "ARWHYAx8aiNrMkNfCMJxA7FpBBxRdrRbi7Biuzcssxjs"

💸 To continue this demo you'll need some devnet funds. You can use:
💸 Your onchain balance is at 0! To continue this demo you'll need devnet funds! You can use:
- The faucet in this example: `pnpm run faucet`
- The official Solana CLI: `solana airdrop 1 6ziT1tk8YhQx8nEiJHAEM5eh9g4DnLwdek7Zfx7KYGAo`
- The official Solana CLI: `solana airdrop 1 ARWHYAx8aiNrMkNfCMJxA7FpBBxRdrRbi7Biuzcssxjs`
- Any online faucet (e.g. https://faucet.triangleplatform.com/solana/devnet)

To check your balance: https://explorer.solana.com/address/6ziT1tk8YhQx8nEiJHAEM5eh9g4DnLwdek7Zfx7KYGAo?cluster=devnet
To check your balance: https://explorer.solana.com/address/ARWHYAx8aiNrMkNfCMJxA7FpBBxRdrRbi7Biuzcssxjs?cluster=devnet

--------
? Do you have devnet funds in 6ziT1tk8YhQx8nEiJHAEM5eh9g4DnLwdek7Zfx7KYGAo? (Y/n)Y
? Do you have devnet funds in ARWHYAx8aiNrMkNfCMJxA7FpBBxRdrRbi7Biuzcssxjs? (Y/n)Y

? Amount (in Lamports) to send to tkhqC9QX2gkqJtUFk2QKhBmQfFyyqZXSpr73VFRi35C: 6000000
New signature: dac3995b81a464fdcd5f914f0264695380562f432387e8240422f6e591b4cf5465c12390042889f8d4890242d289b98fd9a29f808cdee11a745c27c497b2fe0d
Expand Down
1 change: 1 addition & 0 deletions examples/with-solana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@turnkey/http": "workspace:*",
"@turnkey/solana": "workspace:*",
"@turnkey/api-key-stamper": "workspace:*",
"dotenv": "^16.0.3",
"bs58": "^5.0.0",
Expand Down
72 changes: 0 additions & 72 deletions examples/with-solana/src/createSolanaKey.ts

This file was deleted.

31 changes: 6 additions & 25 deletions examples/with-solana/src/createSolanaTransfer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
import type { TurnkeyClient } from "@turnkey/http";
import { recentBlockhash } from "./solanaNetwork";
import base58 from "bs58";
import { TurnkeySigner } from "@turnkey/solana";

/**
* Creates a Solana transfer and signs it with Turnkey.
Expand All @@ -17,15 +17,15 @@ export async function createAndSignTransfer(input: {
toAddress: string;
amount: number;
turnkeyOrganizationId: string;
turnkeyPrivateKeyId: string;
turnkeySolAddress: string;
}): Promise<Buffer> {
const {
client,
fromAddress,
toAddress,
amount,
turnkeyOrganizationId,
turnkeyPrivateKeyId,
turnkeySolAddress,
} = input;
const fromKey = new PublicKey(fromAddress);
const toKey = new PublicKey(toAddress);
Expand All @@ -43,29 +43,10 @@ export async function createAndSignTransfer(input: {
// Set the signer
transferTransaction.feePayer = fromKey;

const messageToSign = transferTransaction.serializeMessage();

const activity = await client.signRawPayload({
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
const signer = new TurnkeySigner({
organizationId: turnkeyOrganizationId,
timestampMs: String(Date.now()),
parameters: {
signWith: turnkeyPrivateKeyId,
payload: messageToSign.toString("hex"),
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
// Note: unlike ECDSA, EdDSA's API does not support signing raw digests (see RFC 8032).
// Turnkey's signer requires an explicit value to be passed here to minimize ambiguity.
hashFunction: "HASH_FUNCTION_NOT_APPLICABLE",
},
client,
});

const signature = `${activity.activity.result.signRawPayloadResult?.r}${activity.activity.result.signRawPayloadResult?.s}`;
console.log(
`New signature: ${signature}\n(base58: ${base58.encode(
Buffer.from(signature, "hex")
)})`
);

transferTransaction.addSignature(fromKey, Buffer.from(signature, "hex"));
await signer.addSignature(transferTransaction, turnkeySolAddress);
return transferTransaction.serialize();
}
80 changes: 80 additions & 0 deletions examples/with-solana/src/createSolanaWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
TurnkeyActivityError,
TurnkeyClient,
createActivityPoller,
} from "@turnkey/http";
import * as crypto from "crypto";

export async function createNewSolanaWallet(
client: TurnkeyClient,
turnkeyOrganizationId: string
) {
console.log("creating a new Solana wallet in your Turnkey organization...\n");

const walletName = `Solana Wallet ${crypto.randomBytes(2).toString("hex")}`;

try {
const activityPoller = createActivityPoller({
client: client,
requestFn: client.createWallet,
});

const completedActivity = await activityPoller({
type: "ACTIVITY_TYPE_CREATE_WALLET",
organizationId: turnkeyOrganizationId,
parameters: {
walletName,
accounts: [
{
pathFormat: "PATH_FORMAT_BIP32",
// https://github.com/satoshilabs/slips/blob/master/slip-0044.md
path: "m/44'/501'/0'/0'",
curve: "CURVE_ED25519",
addressFormat: "ADDRESS_FORMAT_SOLANA",
},
],
},
timestampMs: String(Date.now()), // millisecond timestamp
});

const walletId = completedActivity.result.createWalletResult?.walletId;
if (!walletId) {
console.error(
"activity doesn't contain a valid wallet ID",
completedActivity
);
process.exit(1);
}

const address = completedActivity.result.createWalletResult?.addresses[0];
if (!address) {
console.error(
"activity result doesn't contain a valid address",
completedActivity
);
process.exit(1);
}

console.log(
[
`New Solana wallet created!`,
`- Name: ${walletName}`,
`- Wallet ID: ${walletId}`,
`- Solana address: ${address}`,
].join("\n")
);
return address;
} catch (error) {
// If needed, you can read from `TurnkeyActivityError` to find out why the activity didn't succeed
if (error instanceof TurnkeyActivityError) {
throw error;
}

throw new TurnkeyActivityError({
message: `Failed to create a new Solana wallet: ${
(error as Error).message
}`,
cause: error as Error,
});
}
}
20 changes: 8 additions & 12 deletions examples/with-solana/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ const TURNKEY_WAR_CHEST = "tkhqC9QX2gkqJtUFk2QKhBmQfFyyqZXSpr73VFRi35C";
// Load environment variables from `.env.local`
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });

import { createNewSolanaPrivateKey } from "./createSolanaKey";
import { deriveSolanaAddress } from "./solanaAddress";
import { createNewSolanaWallet } from "./createSolanaWallet";
import * as solanaNetwork from "./solanaNetwork";
import { createAndSignTransfer } from "./createSolanaTransfer";
import { input, confirm } from "@inquirer/prompts";
Expand All @@ -27,16 +26,13 @@ async function main() {
})
);

let privateKeyId = process.env.PRIVATE_KEY_ID;
if (!privateKeyId) {
privateKeyId = await createNewSolanaPrivateKey(
turnkeyClient,
organizationId
);
let solAddress = process.env.SOLANA_ADDRESS;
if (!solAddress) {
solAddress = await createNewSolanaWallet(turnkeyClient, organizationId);
console.log(`\nYour new Solana address: "${solAddress}"`);
} else {
console.log(`\nUsing existing Solana address from ENV: "${solAddress}"`);
}
const solAddress = await deriveSolanaAddress(turnkeyClient, privateKeyId);

console.log(`\nYour Solana address: "${solAddress}"`);

let balance = await solanaNetwork.balance(connection, solAddress);
while (balance === 0) {
Expand Down Expand Up @@ -92,7 +88,7 @@ async function main() {
toAddress: destination,
amount: Number(amount),
turnkeyOrganizationId: organizationId,
turnkeyPrivateKeyId: privateKeyId,
turnkeySolAddress: solAddress,
r-n-o marked this conversation as resolved.
Show resolved Hide resolved
});

// Broadcast the signed payload on devnet
Expand Down
21 changes: 0 additions & 21 deletions examples/with-solana/src/solanaAddress.ts

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"overrides": {
"@confio/[email protected]>protobufjs": ">=7.2.4",
"protobufjs@>=6.10.0 <7.2.4": ">=7.2.4",
"@babel/traverse": ">=7.23.2"
"@babel/traverse": ">=7.23.2",
"follow-redirects": ">=1.15.4"
}
}
}
Loading
Loading