Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions packages/aws-kms-wallet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# @thirdweb-dev/aws-kms-wallet

This package provides AWS KMS wallet functionality for thirdweb SDK.

## Installation

```bash
npm install @thirdweb-dev/aws-kms-wallet
```

## Usage

```typescript
import { getAwsKmsAccount } from "@thirdweb-dev/aws-kms-wallet";
import { ThirdwebClient } from "thirdweb";

const client = new ThirdwebClient({
// your client config
});

const account = await getAwsKmsAccount({
keyId: "your-kms-key-id",
config: {
// your AWS KMS config
region: "us-east-1",
},
client,
});

// Use the account for transactions, signing messages, etc.
const tx = await account.sendTransaction({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the standalone function sendTransaction({ transaction, account }) instead

const transaction = prepareContractCall(...);
const result = await sendTransaction({ transaction, account });

// transaction details
});
```

## Requirements

- Node.js 18+
- AWS KMS key with ECC_SECG_P256K1 key spec
- AWS credentials configured in your environment

## License

Apache-2.0
53 changes: 53 additions & 0 deletions packages/aws-kms-wallet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@thirdweb-dev/aws-kms-wallet",
"version": "0.1.0",
"type": "module",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/types/index.d.ts",
"typings": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.js",
"default": "./dist/cjs/index.js"
},
"./package.json": "./package.json"
},
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/aws-kms-wallet",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/thirdweb-dev/js/issues"
},
"author": "thirdweb eng <[email protected]>",
"files": ["dist/"],
"sideEffects": false,
"engines": {
"node": ">=18"
},
"dependencies": {
"@aws-sdk/client-kms": "^3.592.0",
"aws-kms-signer": "0.5.3",
"viem": "2.22.17"
},
"peerDependencies": {
"thirdweb": "^5.88.1"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/node": "^22.13.0",
"typescript": "5.7.3",
"vitest": "3.0.5"
},
"scripts": {
"format": "biome format ./src --write",
"lint": "biome check ./src && tsc --project ./tsconfig.build.json --module esnext --noEmit",
"fix": "biome check ./src --fix",
"clean": "rm -rf dist/",
"build": "pnpm clean && pnpm build:types && pnpm build:cjs && pnpm build:esm",
"build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
"build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
"test": "vitest run"
}
}
121 changes: 121 additions & 0 deletions packages/aws-kms-wallet/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Buffer } from "node:buffer";
import type { KMSClientConfig } from "@aws-sdk/client-kms";
import { KmsSigner } from "aws-kms-signer";
import type { Hex, ThirdwebClient, toSerializableTransaction } from "thirdweb";
import {
type Address,
eth_sendRawTransaction,
getRpcClient,
keccak256,
} from "thirdweb";
import { serializeTransaction } from "thirdweb/transaction";
import { hashMessage } from "thirdweb/utils";
import type { Account } from "thirdweb/wallets";
import type { SignableMessage, TypedData, TypedDataDefinition } from "viem";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Ox types: https://oxlib.sh/

import { hashTypedData } from "viem";
import { getChain } from "./utils/chain";

type SendTransactionResult = {
transactionHash: Hex;
};

type SerializableTransaction = Awaited<
ReturnType<typeof toSerializableTransaction>
>;

type SendTransactionOption = SerializableTransaction & {
chainId: number;
};

type AwsKmsAccountOptions = {
keyId: string;
config?: KMSClientConfig;
client: ThirdwebClient;
};

type AwsKmsAccount = Account;

export async function getAwsKmsAccount(
options: AwsKmsAccountOptions,
): Promise<AwsKmsAccount> {
const { keyId, config, client } = options;
const signer = new KmsSigner(keyId, config);

// Populate address immediately
const addressUnprefixed = await signer.getAddress();
const address = `0x${addressUnprefixed}` as Address;

async function signTransaction(tx: SerializableTransaction): Promise<Hex> {
const serializedTx = serializeTransaction({ transaction: tx });
const txHash = keccak256(serializedTx);
const signature = await signer.sign(Buffer.from(txHash.slice(2), "hex"));

const r = `0x${signature.r.toString("hex")}` as Hex;
const s = `0x${signature.s.toString("hex")}` as Hex;
const v = BigInt(signature.v);

const yParity: 0 | 1 = signature.v % 2 === 0 ? 1 : 0;

const signedTx = serializeTransaction({
transaction: tx,
signature: {
r,
s,
v,
yParity,
},
});

return signedTx;
}

/**
* Sign a message with the account's private key.
* If the message is a string, it will be prefixed with the Ethereum message prefix.
* If the message is an object with a `raw` property, it will be signed as-is.
*/
async function signMessage({
message,
}: {
message: SignableMessage;
}): Promise<Hex> {
const messageHash = hashMessage(message);
const signature = await signer.sign(
Buffer.from(messageHash.slice(2), "hex"),
);
return `0x${signature.toString()}`;
}

async function signTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(_typedData: TypedDataDefinition<typedData, primaryType>): Promise<Hex> {
const typedDataHash = hashTypedData(_typedData);
const signature = await signer.sign(
Buffer.from(typedDataHash.slice(2), "hex"),
);
return `0x${signature.toString()}`;
}

async function sendTransaction(
tx: SendTransactionOption,
): Promise<SendTransactionResult> {
const rpcRequest = getRpcClient({
client: client,
chain: await getChain(tx.chainId),
});

const signedTx = await signTransaction(tx);

const transactionHash = await eth_sendRawTransaction(rpcRequest, signedTx);
return { transactionHash };
}

return {
address,
sendTransaction,
signMessage,
signTypedData,
signTransaction,
} as AwsKmsAccount satisfies Account;
}
7 changes: 7 additions & 0 deletions packages/aws-kms-wallet/src/utils/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Chain } from "thirdweb";

export async function getChain(chainId: number): Promise<Chain> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wut

return {
id: chainId,
} as Chain;
}
10 changes: 10 additions & 0 deletions packages/aws-kms-wallet/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.build.json",
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.bench.ts"],
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"emitDeclarationOnly": true
}
}
Loading