Skip to content

Commit

Permalink
Merge pull-request #117
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewkmin committed Sep 18, 2023
2 parents 89d9999 + cd62031 commit 397b6b5
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 23 deletions.
1 change: 1 addition & 0 deletions examples/with-viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"start": "pnpm -w run build-all && tsx src/index.ts",
"start-advanced": "pnpm -w run build-all && tsx src/advanced.ts",
"clean": "rimraf ./dist ./.cache",
"typecheck": "tsc --noEmit"
},
Expand Down
128 changes: 128 additions & 0 deletions examples/with-viem/src/advanced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as path from "path";
import * as dotenv from "dotenv";

import { createAccount } from "@turnkey/viem";
import { TurnkeyClient } from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
import {
createWalletClient,
http,
recoverMessageAddress,
recoverTypedDataAddress,
stringToHex,
hexToBytes,
type Account,
} from "viem";
import { sepolia } from "viem/chains";
import { print, assertEqual } from "./util";

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

async function main() {
const turnkeyClient = new TurnkeyClient(
{
baseUrl: process.env.BASE_URL!,
},
new ApiKeyStamper({
apiPublicKey: process.env.API_PUBLIC_KEY!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
})
);

const turnkeyAccount = await createAccount({
client: turnkeyClient,
organizationId: process.env.ORGANIZATION_ID!,
privateKeyId: process.env.PRIVATE_KEY_ID!,
});

const client = createWalletClient({
account: turnkeyAccount as Account,
chain: sepolia,
transport: http(
`https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY!}`
),
});

const address = client.account.address;
const baseMessage = "Hello Turnkey";

// 1. Sign a raw hex message
const hexMessage = { raw: stringToHex(baseMessage) };
let signature = await client.signMessage({
message: hexMessage,
});
let recoveredAddress = await recoverMessageAddress({
message: hexMessage,
signature,
});

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

// 2. Sign a raw bytes message
const bytesMessage = { raw: hexToBytes(stringToHex(baseMessage)) };
signature = await client.signMessage({
message: bytesMessage,
});
recoveredAddress = await recoverMessageAddress({
message: bytesMessage,
signature,
});

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

// 3. Sign typed data (EIP-712)
const domain = {
name: "Ether Mail",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
} as const;

// The named list of all type definitions
const types = {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person" },
{ name: "contents", type: "string" },
],
} as const;

const typedData = {
account: turnkeyAccount,
domain,
types,
primaryType: "Mail",
message: {
from: {
name: "Cow",
wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
},
to: {
name: "Bob",
wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
},
contents: "Hello, Bob!",
},
} as const;

signature = await client.signTypedData(typedData);
recoveredAddress = await recoverTypedDataAddress({
...typedData,
signature,
});

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

main().catch((error) => {
console.error(error);
process.exit(1);
});
23 changes: 18 additions & 5 deletions examples/with-viem/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import * as dotenv from "dotenv";
import { createAccount } from "@turnkey/viem";
import { TurnkeyClient } from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
import { createWalletClient, http } from "viem";
import { createWalletClient, http, recoverMessageAddress } from "viem";
import { sepolia } from "viem/chains";
import { print, assertEqual } from "./util";

// Load environment variables from `.env.local`
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
Expand Down Expand Up @@ -38,6 +39,7 @@ async function main() {
// This demo sends ETH back to our faucet (we keep a bunch of Sepolia ETH at this address)
const turnkeyFaucet = "0x08d2b0a37F869FF76BACB5Bab3278E26ab7067B7";

// 1. Simple send tx
const transactionRequest = {
to: turnkeyFaucet as `0x${string}`,
value: 1000000000000000n,
Expand All @@ -47,13 +49,24 @@ async function main() {

print("Source address", client.account.address);
print("Transaction", `https://sepolia.etherscan.io/tx/${txHash}`);

// 2. Sign a simple message
let address = client.account.address;
let message = "Hello Turnkey";
let signature = await client.signMessage({
message,
});
let recoveredAddress = await recoverMessageAddress({
message,
signature,
});

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

main().catch((error) => {
console.error(error);
process.exit(1);
});

function print(header: string, body: string): void {
console.log(`${header}\n\t${body}\n`);
}
9 changes: 9 additions & 0 deletions examples/with-viem/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function print(header: string, body: string): void {
console.log(`${header}\n\t${body}\n`);
}

export function assertEqual<T>(left: T, right: T) {
if (left !== right) {
throw new Error(`${JSON.stringify(left)} !== ${JSON.stringify(right)}`);
}
}
6 changes: 6 additions & 0 deletions packages/viem/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @turnkey/viem

## 0.2.4

### Patch Changes

- 0ec2d94: Addresses a bug when signing raw messages (see https://github.com/tkhq/sdk/issues/116)

## 0.2.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/viem/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@turnkey/viem",
"version": "0.2.3",
"version": "0.2.4",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "Apache-2.0",
Expand Down
35 changes: 18 additions & 17 deletions packages/viem/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { hashTypedData, serializeTransaction, signatureToHex } from "viem";
import { toAccount } from "viem/accounts";
import { keccak256 } from "viem";
import { hashMessage } from "viem";
import type {
Hex,
HashTypedDataParameters,
LocalAccount,
SerializeTransactionFn,
Expand Down Expand Up @@ -44,12 +45,12 @@ export async function createAccount(input: {
}

return toAccount({
address: ethereumAddress as `0x${string}`,
address: ethereumAddress as Hex,
signMessage: function ({
message,
}: {
message: SignableMessage;
}): Promise<`0x${string}`> {
}): Promise<Hex> {
return signMessage(client, message, organizationId, privateKeyId);
},
signTransaction: function <
Expand All @@ -59,7 +60,7 @@ export async function createAccount(input: {
args?:
| { serializer?: SerializeTransactionFn<TTransactionSerializable> }
| undefined
): Promise<`0x${string}`> {
): Promise<Hex> {
const serializer = !args?.serializer
? serializeTransaction
: args.serializer;
Expand All @@ -74,7 +75,7 @@ export async function createAccount(input: {
},
signTypedData: function (
typedData: TypedData | { [key: string]: unknown }
): Promise<`0x${string}`> {
): Promise<Hex> {
return signTypedData(client, typedData, organizationId, privateKeyId);
},
});
Expand Down Expand Up @@ -145,12 +146,12 @@ export async function createApiKeyAccount(
}

return toAccount({
address: ethereumAddress as `0x${string}`,
address: ethereumAddress as Hex,
signMessage: function ({
message,
}: {
message: SignableMessage;
}): Promise<`0x${string}`> {
}): Promise<Hex> {
return signMessage(client, message, organizationId, privateKeyId);
},
signTransaction: function <
Expand All @@ -160,7 +161,7 @@ export async function createApiKeyAccount(
args?:
| { serializer?: SerializeTransactionFn<TTransactionSerializable> }
| undefined
): Promise<`0x${string}`> {
): Promise<Hex> {
const serializer = !args?.serializer
? serializeTransaction
: args.serializer;
Expand All @@ -175,7 +176,7 @@ export async function createApiKeyAccount(
},
signTypedData: function (
typedData: TypedData | { [key: string]: unknown }
): Promise<`0x${string}`> {
): Promise<Hex> {
return signTypedData(client, typedData, organizationId, privateKeyId);
},
});
Expand All @@ -186,15 +187,15 @@ async function signMessage(
message: SignableMessage,
organizationId: string,
privateKeyId: string
): Promise<`0x${string}`> {
const hashedMessage = keccak256(message as `0x${string}`);
): Promise<Hex> {
const hashedMessage = hashMessage(message);
const signedMessage = await signMessageWithErrorWrapping(
client,
hashedMessage,
organizationId,
privateKeyId
);
return `${signedMessage}` as `0x${string}`;
return `${signedMessage}` as Hex;
}

async function signTransaction<
Expand All @@ -205,7 +206,7 @@ async function signTransaction<
serializer: SerializeTransactionFn<TTransactionSerializable>,
organizationId: string,
privateKeyId: string
): Promise<`0x${string}`> {
): Promise<Hex> {
const serializedTx = serializer(transaction);
const nonHexPrefixedSerializedTx = serializedTx.replace(/^0x/, "");
return await signTransactionWithErrorWrapping(
Expand All @@ -221,7 +222,7 @@ async function signTypedData(
data: TypedData | { [key: string]: unknown },
organizationId: string,
privateKeyId: string
): Promise<`0x${string}`> {
): Promise<Hex> {
const hashToSign = hashTypedData(data as HashTypedDataParameters);

return await signMessageWithErrorWrapping(
Expand All @@ -237,7 +238,7 @@ async function signTransactionWithErrorWrapping(
unsignedTransaction: string,
organizationId: string,
privateKeyId: string
): Promise<`0x${string}`> {
): Promise<Hex> {
let signedTx: string;
try {
signedTx = await signTransactionImpl(
Expand Down Expand Up @@ -298,7 +299,7 @@ async function signMessageWithErrorWrapping(
message: string,
organizationId: string,
privateKeyId: string
): Promise<`0x${string}`> {
): Promise<Hex> {
let signedMessage: string;
try {
signedMessage = await signMessageImpl(
Expand All @@ -318,7 +319,7 @@ async function signMessageWithErrorWrapping(
});
}

return signedMessage as `0x${string}`;
return signedMessage as Hex;
}

async function signMessageImpl(
Expand Down

0 comments on commit 397b6b5

Please sign in to comment.