Skip to content

Commit

Permalink
updated examples and added changeset
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewkmin committed Sep 12, 2024
1 parent 27bce6b commit c988ed0
Show file tree
Hide file tree
Showing 27 changed files with 889 additions and 102 deletions.
9 changes: 9 additions & 0 deletions .changeset/orange-beers-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@turnkey/sdk-browser": minor
"@turnkey/sdk-server": minor
"@turnkey/ethers": minor
"@turnkey/solana": minor
"@turnkey/viem": minor
---

Support awaiting consensus
2 changes: 2 additions & 0 deletions examples/with-ethers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"scripts": {
"start": "tsx src/index.ts",
"consensus": "tsx src/consensus.ts",
"retry": "tsx src/retry.ts",
"start-advanced": "tsx src/advanced.ts",
"start-sepolia-legacy": "tsx src/sepoliaLegacyTx.ts",
"clean": "rimraf ./dist ./.cache",
Expand Down
105 changes: 105 additions & 0 deletions examples/with-ethers/src/consensus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as path from "path";
import * as dotenv from "dotenv";

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

import { TurnkeySigner } from "@turnkey/ethers";
import { ethers } from "ethers";
import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server";
import { createNewWallet } from "./createNewWallet";
import { print, assertEqual } from "./util";

async function main() {
if (!process.env.SIGN_WITH) {
// If you don't specify a `SIGN_WITH`, we'll create a new wallet for you via calling the Turnkey API.
await createNewWallet();
return;
}

const turnkeyClient = new TurnkeyServerSDK({
apiBaseUrl: process.env.BASE_URL!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
apiPublicKey: process.env.API_PUBLIC_KEY!,
defaultOrganizationId: process.env.ORGANIZATION_ID!,
// 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.
//
// -----
//
activityPoller: {
intervalMs: 10_000,
numRetries: 5,
},
});

// Initialize a Turnkey Signer
const turnkeySigner = new TurnkeySigner({
client: turnkeyClient.apiClient(),
organizationId: process.env.ORGANIZATION_ID!,
signWith: process.env.SIGN_WITH!,
});

// Bring your own provider (such as Alchemy or Infura: https://docs.ethers.org/v6/api/providers/)
const network = "sepolia";
const provider = new ethers.JsonRpcProvider(
`https://${network}.infura.io/v3/${process.env.INFURA_KEY}`
);
const connectedSigner = turnkeySigner.connect(provider);

const chainId = (await connectedSigner.provider?.getNetwork())?.chainId ?? 0;
const address = await connectedSigner.getAddress();
const balance = (await connectedSigner.provider?.getBalance(address)) ?? 0;
const transactionCount = await connectedSigner.provider?.getTransactionCount(
address
);

print("Network:", `${network} (chain ID ${chainId})`);
print("Address:", address);
print("Balance:", `${ethers.formatEther(balance)} Ether`);
print("Transaction count:", `${transactionCount}`);

// 1. Sign a raw payload (`eth_sign` style)
const message = "Hello Turnkey";
const signature = await connectedSigner.signMessage(message);
const recoveredAddress = ethers.verifyMessage(message, signature);

print("Turnkey-powered signature:", `${signature}`);
print("Recovered address:", `${recoveredAddress}`);
assertEqual(recoveredAddress, address);

// Create a simple send transaction
const transactionAmount = "0.00001";
const destinationAddress = "0x2Ad9eA1E677949a536A270CEC812D6e868C88108";
const transactionRequest = {
to: destinationAddress,
value: ethers.parseEther(transactionAmount),
type: 2,
};

if (balance === 0) {
let warningMessage =
"The transaction won't be broadcasted because your account balance is zero.\n";
if (network === "sepolia") {
warningMessage +=
"Use https://sepoliafaucet.com/ to request funds on Sepolia, then run the script again.\n";
}

console.warn(warningMessage);
return;
}

// 1. Simple send tx
const sentTx = await connectedSigner.sendTransaction(transactionRequest);

print(
`Sent ${ethers.formatEther(sentTx.value)} Ether to ${sentTx.to}:`,
`https://${network}.etherscan.io/tx/${sentTx.hash}`
);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
10 changes: 0 additions & 10 deletions examples/with-ethers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ async function main() {
apiPrivateKey: process.env.API_PRIVATE_KEY!,
apiPublicKey: process.env.API_PUBLIC_KEY!,
defaultOrganizationId: process.env.ORGANIZATION_ID!,
// 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.
//
// -----
//
// activityPoller: {
// duration: 5_000,
// timeout: 6_0000,
// },
});

// Initialize a Turnkey Signer
Expand Down
133 changes: 133 additions & 0 deletions examples/with-ethers/src/retry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as path from "path";
import * as dotenv from "dotenv";

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

import { TurnkeySigner } from "@turnkey/ethers";
import { ethers } from "ethers";
import {
Turnkey as TurnkeyServerSDK,
TERMINAL_ACTIVITY_STATUSES,
} from "@turnkey/sdk-server";
import { createNewWallet } from "./createNewWallet";
import { print, sleep } from "./util";

async function main() {
if (!process.env.SIGN_WITH) {
// If you don't specify a `SIGN_WITH`, we'll create a new wallet for you via calling the Turnkey API.
await createNewWallet();
return;
}

const turnkeyClient = new TurnkeyServerSDK({
apiBaseUrl: process.env.BASE_URL!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
apiPublicKey: process.env.API_PUBLIC_KEY!,
defaultOrganizationId: process.env.ORGANIZATION_ID!,
// 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.
//
// -----
//
// activityPoller: {
// intervalMs: 10_000,
// numRetries: 5,
// },
});

// Initialize a Turnkey Signer
const turnkeySigner = new TurnkeySigner({
client: turnkeyClient.apiClient(),
organizationId: process.env.ORGANIZATION_ID!,
signWith: process.env.SIGN_WITH!,
});

// Bring your own provider (such as Alchemy or Infura: https://docs.ethers.org/v6/api/providers/)
const network = "sepolia";
const provider = new ethers.JsonRpcProvider(
`https://${network}.infura.io/v3/${process.env.INFURA_KEY}`
);
const connectedSigner = turnkeySigner.connect(provider);

const chainId = (await connectedSigner.provider?.getNetwork())?.chainId ?? 0;
const address = await connectedSigner.getAddress();
const balance = (await connectedSigner.provider?.getBalance(address)) ?? 0;
const transactionCount = await connectedSigner.provider?.getTransactionCount(
address
);

print("Network:", `${network} (chain ID ${chainId})`);
print("Address:", address);
print("Balance:", `${ethers.formatEther(balance)} Ether`);
print("Transaction count:", `${transactionCount}`);

// Create a simple send transaction
const transactionAmount = "0.00001";
const destinationAddress = "0x2Ad9eA1E677949a536A270CEC812D6e868C88108";
const transactionRequest = {
to: destinationAddress,
value: ethers.parseEther(transactionAmount),
type: 2,
};

if (balance === 0) {
let warningMessage =
"The transaction won't be broadcasted because your account balance is zero.\n";
if (network === "sepolia") {
warningMessage +=
"Use https://sepoliafaucet.com/ to request funds on Sepolia, then run the script again.\n";
}

console.warn(warningMessage);
return;
}

let sentTx;

// Simple send tx.
// If it does not succeed at first, wait for consensus, then attempt to broadcast the signed transaction
try {
sentTx = await connectedSigner.sendTransaction(transactionRequest);
} catch (error: any) {
const activityId = error["activityId"];
let activityStatus = error["activityStatus"];

while (!TERMINAL_ACTIVITY_STATUSES.includes(activityStatus)) {
console.log("Waiting for consensus...");

// Wait 5 seconds
await sleep(5_000);

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

console.log("Consensus reached! Moving onto broadcasting...");

// Break out of loop now that we have an activity that has reached terminal status.
// Get the signature
const signedTransaction =
await connectedSigner.getSignedTransactionFromActivity(activityId);

sentTx = await connectedSigner.provider?.broadcastTransaction(
signedTransaction
);
}

print(
`Sent ${ethers.formatEther(sentTx!.value)} Ether to ${sentTx!.to}:`,
`https://${network}.etherscan.io/tx/${sentTx!.hash}`
);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
4 changes: 4 additions & 0 deletions examples/with-ethers/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export function refineNonNull<T>(

return input;
}

export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
2 changes: 2 additions & 0 deletions examples/with-solana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"scripts": {
"start": "pnpm tsx src/index.ts",
"advanced": "pnpm tsx src/advanced.ts",
"consensus": "pnpm tsx src/consensus.ts",
"retry": "pnpm tsx src/retry.ts",
"token-transfer": "pnpm tsx src/tokenTransfer.ts",
"faucet": "pnpm tsx src/faucet.ts",
"jupiter-swap": "pnpm tsx src/jupiterSwap.ts",
Expand Down
Loading

0 comments on commit c988ed0

Please sign in to comment.