-
Notifications
You must be signed in to change notification settings - Fork 105
Smart Backend Wallets #709
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
Changes from 1 commit
68c21b6
05491fe
082b54a
1b87760
fc12095
aaa5691
dbc567f
941e55d
51bd785
17245a1
7bb1b80
a6ece2b
8984844
f4ec6f9
550dc14
3256832
38b46c6
3a47e77
a3957ba
ef64f4c
b5503b9
b64dff4
c1986ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import type { Address } from "thirdweb"; | ||
| import type { PrismaTransaction } from "../../schema/prisma"; | ||
| import { encrypt } from "../../utils/crypto"; | ||
| import { getPrismaWithPostgresTx } from "../client"; | ||
|
|
@@ -8,13 +9,17 @@ type CreateWalletDetailsParams = { | |
| address: string; | ||
| label?: string; | ||
| } & ( | ||
| | { | ||
| type: "local"; | ||
| encryptedJson: string; // ENCRYPTION IS NOT HANDLED HERE, process privatekey with legacyLocalCrytpo before passing to this function | ||
| } | ||
| | { | ||
| type: "aws-kms"; | ||
| awsKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change | ||
| awsKmsArn: string; | ||
|
|
||
| awsKmsSecretAccessKey?: string; // will be encrypted and stored, pass plaintext to this function | ||
| awsKmsAccessKeyId?: string; | ||
| awsKmsSecretAccessKey: string; // will be encrypted and stored, pass plaintext to this function | ||
| awsKmsAccessKeyId: string; | ||
| } | ||
| | { | ||
| type: "gcp-kms"; | ||
|
|
@@ -24,11 +29,39 @@ type CreateWalletDetailsParams = { | |
| gcpKmsKeyVersionId?: string; // depcrecated and unused, todo: remove with next breaking change | ||
| gcpKmsLocationId?: string; // depcrecated and unused, todo: remove with next breaking change | ||
|
|
||
| gcpApplicationCredentialPrivateKey?: string; // encrypted | ||
| gcpApplicationCredentialEmail?: string; | ||
| gcpApplicationCredentialPrivateKey: string; // will be encrypted and stored, pass plaintext to this function | ||
| gcpApplicationCredentialEmail: string; | ||
| } | ||
| | { | ||
| type: "smart:aws-kms"; | ||
| awsKmsArn: string; | ||
| awsKmsSecretAccessKey: string; // will be encrypted and stored, pass plaintext to this function | ||
| awsKmsAccessKeyId: string; | ||
| accountSignerAddress: Address; | ||
|
|
||
| accountFactoryAddress?: Address; | ||
| } | ||
| | { | ||
| type: "smart:gcp-kms"; | ||
| gcpKmsResourcePath: string; | ||
| gcpApplicationCredentialPrivateKey: string; // will be encrypted and stored, pass plaintext to this function | ||
| gcpApplicationCredentialEmail: string; | ||
| accountSignerAddress: Address; | ||
|
|
||
| accountFactoryAddress?: Address; | ||
| } | ||
arcoraven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| | { | ||
| type: "smart:local"; | ||
| encryptedJson: string; // ENCRYPTION IS NOT HANDLED HERE, process privatekey with legacyLocalCrytpo before passing to this function | ||
| accountSignerAddress: Address; | ||
|
|
||
| accountFactoryAddress?: Address; | ||
| } | ||
| ); | ||
|
|
||
| /** | ||
| * Create a new WalletDetails row in DB | ||
| */ | ||
| export const createWalletDetails = async ({ | ||
| pgtx, | ||
| ...walletDetails | ||
|
|
@@ -47,15 +80,23 @@ export const createWalletDetails = async ({ | |
| ); | ||
| } | ||
|
|
||
| if (walletDetails.type === "local") { | ||
| return prisma.walletDetails.create({ | ||
| data: { | ||
| ...walletDetails, | ||
| address: walletDetails.address.toLowerCase(), | ||
| encryptedJson: walletDetails.encryptedJson, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (walletDetails.type === "aws-kms") { | ||
| return prisma.walletDetails.create({ | ||
| data: { | ||
| ...walletDetails, | ||
| address: walletDetails.address.toLowerCase(), | ||
|
|
||
| awsKmsSecretAccessKey: walletDetails.awsKmsSecretAccessKey | ||
| ? encrypt(walletDetails.awsKmsSecretAccessKey) | ||
| : undefined, | ||
| awsKmsSecretAccessKey: encrypt(walletDetails.awsKmsSecretAccessKey), | ||
| }, | ||
| }); | ||
| } | ||
|
|
@@ -66,11 +107,60 @@ export const createWalletDetails = async ({ | |
| ...walletDetails, | ||
| address: walletDetails.address.toLowerCase(), | ||
|
|
||
| gcpApplicationCredentialPrivateKey: | ||
| walletDetails.gcpApplicationCredentialPrivateKey | ||
| ? encrypt(walletDetails.gcpApplicationCredentialPrivateKey) | ||
| : undefined, | ||
| gcpApplicationCredentialPrivateKey: encrypt( | ||
| walletDetails.gcpApplicationCredentialPrivateKey, | ||
| ), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (walletDetails.type === "smart:aws-kms") { | ||
| return prisma.walletDetails.create({ | ||
| data: { | ||
| ...walletDetails, | ||
|
|
||
| address: walletDetails.address.toLowerCase(), | ||
| awsKmsSecretAccessKey: encrypt(walletDetails.awsKmsSecretAccessKey), | ||
| accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(), | ||
|
|
||
| accountFactoryAddress: | ||
| walletDetails.accountFactoryAddress?.toLowerCase(), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (walletDetails.type === "smart:gcp-kms") { | ||
| return prisma.walletDetails.create({ | ||
| data: { | ||
| ...walletDetails, | ||
|
|
||
| address: walletDetails.address.toLowerCase(), | ||
| accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(), | ||
|
|
||
| gcpApplicationCredentialPrivateKey: encrypt( | ||
| walletDetails.gcpApplicationCredentialPrivateKey, | ||
| ), | ||
|
|
||
| accountFactoryAddress: | ||
| walletDetails.accountFactoryAddress?.toLowerCase(), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (walletDetails.type === "smart:local") { | ||
| return prisma.walletDetails.create({ | ||
| data: { | ||
| ...walletDetails, | ||
| address: walletDetails.address.toLowerCase(), | ||
| accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(), | ||
|
|
||
| accountFactoryAddress: | ||
| walletDetails.accountFactoryAddress?.toLowerCase(), | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| // we will never reach here | ||
| // this helps typescript understand that this function will always return | ||
|
||
| throw new Error("Unsupported wallet type"); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "wallet_details" ADD COLUMN "accountFactoryAddress" TEXT, | ||
| ADD COLUMN "accountSignerAddress" TEXT; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,9 @@ model WalletDetails { | |
| gcpKmsResourcePath String? @map("gcpKmsResourcePath") @db.Text | ||
| gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") /// if not available, default to: Configuration.gcpApplicationCredentialEmail | ||
| gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") /// if not available, default to: Configuration.gcpApplicationCredentialPrivateKey | ||
| // Smart Backend Wallet | ||
| accountSignerAddress String? @map("accountSignerAddress") /// this, and either local, aws or gcp encryptedJson, are required for smart wallet | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: This naming confused me because
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the Between |
||
| accountFactoryAddress String? @map("accountFactoryAddress") /// optional even for smart wallet, if not available default factory will be used | ||
|
|
||
| @@map("wallet_details") | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ import { | |
| } from "./fetchAwsKmsWalletParams"; | ||
| import { importAwsKmsWallet } from "./importAwsKmsWallet"; | ||
|
|
||
| type CreateAwsKmsWalletParams = { | ||
| export type CreateAwsKmsWalletParams = { | ||
| label?: string; | ||
| } & Partial<AwsKmsWalletParams>; | ||
|
|
||
|
|
@@ -16,15 +16,35 @@ export class CreateAwsKmsWalletError extends Error {} | |
| * Create an AWS KMS wallet, and store it into the database | ||
| * All optional parameters are overrides for the configuration in the database | ||
| * If any required parameter cannot be resolved from either the configuration or the overrides, an error is thrown. | ||
| * If credentials (awsAccessKeyId and awsSecretAccessKey) are explicitly provided, they will be stored separately from the global configuration | ||
| * Credentials (awsAccessKeyId and awsSecretAccessKey) are explicitly stored separately from the global configuration | ||
| */ | ||
| export const createAwsKmsWallet = async ({ | ||
| export const createAndStoreAwsKmsWallet = async ({ | ||
| label, | ||
| ...overrides | ||
| }: CreateAwsKmsWalletParams): Promise<string> => { | ||
| const { awsKmsArn, params } = await createAwsKmsWallet(overrides); | ||
|
|
||
| return importAwsKmsWallet({ | ||
|
||
| awsKmsArn, | ||
| label, | ||
| crendentials: { | ||
| accessKeyId: params.awsAccessKeyId, | ||
| secretAccessKey: params.awsSecretAccessKey, | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Creates an AWS KMS wallet and returns the AWS KMS ARN | ||
| * All optional parameters are overrides for the configuration in the database | ||
| * If any required parameter cannot be resolved from either the configuration or the overrides, an error is thrown. | ||
| */ | ||
| export const createAwsKmsWallet = async ( | ||
| params: Partial<AwsKmsWalletParams>, | ||
| ) => { | ||
| let kmsWalletParams: AwsKmsWalletParams; | ||
| try { | ||
| kmsWalletParams = await fetchAwsKmsWalletParams(overrides); | ||
| kmsWalletParams = await fetchAwsKmsWalletParams(params); | ||
| } catch (e) { | ||
| if (e instanceof FetchAwsKmsWalletParamsError) { | ||
| throw new CreateAwsKmsWalletError(e.message); | ||
|
|
@@ -54,12 +74,9 @@ export const createAwsKmsWallet = async ({ | |
| } | ||
|
|
||
| const awsKmsArn = res.KeyMetadata.Arn; | ||
| return importAwsKmsWallet({ | ||
|
|
||
| return { | ||
| awsKmsArn, | ||
| label, | ||
| crendentials: { | ||
| accessKeyId: kmsWalletParams.awsAccessKeyId, | ||
| secretAccessKey: kmsWalletParams.awsSecretAccessKey, | ||
| }, | ||
| }); | ||
| params: kmsWalletParams, | ||
| }; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't making these fields non-optional be a breaking change? We could ofc update the dashboard, but it'll impact anyone in the slim chance they're automating KMS backend wallet creation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided to always denormalize and store credentials in the DB row, regardless of it being an override or coming from config. Users don't have to pass it in, the route fills in this from the config.