Skip to content

Commit

Permalink
feedback: update solana example
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewkmin committed Sep 12, 2024
1 parent 989e946 commit 1975153
Showing 1 changed file with 103 additions and 36 deletions.
139 changes: 103 additions & 36 deletions examples/with-solana/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import * as path from "path";
import nacl from "tweetnacl";
import bs58 from "bs58";
import { input, confirm } from "@inquirer/prompts";
import type { Transaction } from "@solana/web3.js";

import { Turnkey } from "@turnkey/sdk-server";
import { TurnkeySigner } from "@turnkey/solana";

const TURNKEY_WAR_CHEST = "tkhqC9QX2gkqJtUFk2QKhBmQfFyyqZXSpr73VFRi35C";
import { PublicKey, type Transaction } from "@solana/web3.js";

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

import {
TurnkeyActivityConsensusNeededError,
TERMINAL_ACTIVITY_STATUSES,
} from "@turnkey/http";
import { Turnkey } from "@turnkey/sdk-server";
import { TurnkeySigner } from "@turnkey/solana";
import {
createNewSolanaWallet,
solanaNetwork,
Expand All @@ -21,6 +22,8 @@ import {
} from "./utils";
import { createTransfer } from "./utils/createSolanaTransfer";

const TURNKEY_WAR_CHEST = "tkhqC9QX2gkqJtUFk2QKhBmQfFyyqZXSpr73VFRi35C";

async function main() {
const organizationId = process.env.ORGANIZATION_ID!;

Expand All @@ -33,14 +36,14 @@ async function main() {
defaultOrganizationId: organizationId,
// The following config is useful in contexts where an activity requires consensus.
// By default, if the activity is not initially successful, it will poll a maximum
// of 3 times with an interval of 1000 milliseconds.
// of 3 times with an interval of 10000 milliseconds.
//
// -----
//
activityPoller: {
intervalMs: 10_000,
numRetries: 5,
},
// activityPoller: {
// intervalMs: 10_000,
// numRetries: 5,
// },
});

const turnkeySigner = new TurnkeySigner({
Expand Down Expand Up @@ -73,6 +76,46 @@ async function main() {
balance = await solanaNetwork.balance(connection, solAddress);
}

print("SOL balance:", `${balance} Lamports`);

// 1. Sign and verify a message
const message = await input({
message: "Message to sign",
default: "Hello Turnkey",
});
const messageAsUint8Array = Buffer.from(message);

let signature;
try {
signature = await signMessage({
signer: turnkeySigner,
fromAddress: solAddress,
message,
});
} catch (error: any) {
signature = await handleActivityError(error).then(
async (activityId: string) => {
const rawSignature = await turnkeySigner.getSignatureFromActivity(
activityId
);
return Buffer.from(rawSignature, "hex");
}
);
}

const isValidSignature = nacl.sign.detached.verify(
messageAsUint8Array,
signature,
bs58.decode(solAddress)
);

if (!isValidSignature) {
throw new Error("unable to verify signed message");
}

print("Turnkey-powered signature:", `${bs58.encode(signature)}`);

// 2. Create, sign, and verify a transfer transaction
const destination = await input({
message: `Destination address:`,
default: TURNKEY_WAR_CHEST,
Expand All @@ -82,7 +125,7 @@ async function main() {
// Any other amount is possible.
const amount = await input({
message: `Amount (in Lamports) to send to ${TURNKEY_WAR_CHEST}:`,
default: `${balance - 5000}`,
default: "100",
validate: function (str) {
var n = Math.floor(Number(str));
if (n !== Infinity && String(n) === str && n > 0) {
Expand All @@ -99,37 +142,26 @@ async function main() {
},
});

// 1. Sign and verify a message
const message = "Hello world!";
const messageAsUint8Array = Buffer.from(message);

const signature = await signMessage({
signer: turnkeySigner,
fromAddress: solAddress,
message,
});

const isValidSignature = nacl.sign.detached.verify(
messageAsUint8Array,
signature,
bs58.decode(solAddress)
);

if (!isValidSignature) {
throw new Error("unable to verify signed message");
}

print("\nTurnkey-powered signature:", `${bs58.encode(signature)}`);

// 2. Create, sign, and verify a transfer transaction
const transaction = await createTransfer({
fromAddress: solAddress,
toAddress: destination,
amount: Number(amount),
version: "legacy",
});

await turnkeySigner.addSignature(transaction, solAddress);
try {
await turnkeySigner.addSignature(transaction, solAddress);
} catch (error: any) {
await handleActivityError(error).then(async (activityId: string) => {
const rawSignature = await turnkeySigner.getSignatureFromActivity(
activityId
);
transaction.addSignature(
new PublicKey(solAddress),
Buffer.from(rawSignature, "hex")
);
});
}

const verified = (transaction as Transaction).verifySignatures();

Expand All @@ -141,6 +173,41 @@ async function main() {
await solanaNetwork.broadcast(connection, transaction);

process.exit(0);

async function handleActivityError(error: any) {
if (error instanceof TurnkeyActivityConsensusNeededError) {
const activityId = error["activityId"]!;
let activityStatus = error["activityStatus"]!;

while (!TERMINAL_ACTIVITY_STATUSES.includes(activityStatus)) {
console.log("\nWaiting for consensus...\n");

const retry = await input({
message: "Consensus reached? y/n",
default: "y",
});

if (retry === "n") {
continue;
}

// Refresh activity status
activityStatus = (
await turnkeyClient.apiClient().getActivity({
activityId,
organizationId: process.env.ORGANIZATION_ID!,
})
).activity.status;
}

console.log("\nConsensus reached! Moving on...\n");

return activityId;
}

// Rethrow error
throw error;
}
}

main().catch((error) => {
Expand Down

0 comments on commit 1975153

Please sign in to comment.