Skip to content

Commit 94afe24

Browse files
authored
Smart Backend Wallets (#709)
* smart backend wallet creation + initial flow * refactor wallet detail decryption - add sbw flow for v4 sdk getWallet * change names * Refactor import statement for TransactionReceipt in sdk.ts * Refactor biome.json to ignore the "sdk" directory * Fix smart backend wallet functionality for both v4 and v5 SDK - Refactor walletDetailsToAccount function and add caching for admin accounts * use v6 factory default * consistent naming * remove redundant comment * entrypoint updates * chainId in sign + thirdweb client in e2e tests * worker refactors * more tests * consistent naming * conditionally require chainId for signing smart account messsages * naming consistencies * reject transaction if unsupported chain * fix userop tests * fix: only transform QueuedTransaction if sbw * Refactor SWRCache to log errors during cache revalidation
1 parent 07b29de commit 94afe24

39 files changed

+1970
-5981
lines changed

biome.json

+3
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@
2626
"noStaticOnlyClass": "off"
2727
}
2828
}
29+
},
30+
"files": {
31+
"ignore": ["sdk"]
2932
}
3033
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@bull-board/fastify": "^5.21.1",
3232
"@cloud-cryptographic-wallet/cloud-kms-signer": "^0.1.2",
3333
"@cloud-cryptographic-wallet/signer": "^0.0.5",
34+
"@ethersproject/json-wallets": "^5.7.0",
3435
"@fastify/basic-auth": "^5.1.1",
3536
"@fastify/cookie": "^8.3.0",
3637
"@fastify/express": "^2.3.0",

sdk/src/Engine.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class EngineLogic {
6666

6767
constructor(config?: Partial<OpenAPIConfig>, HttpRequest: HttpRequestConstructor = FetchHttpRequest) {
6868
this.request = new HttpRequest({
69-
BASE: config?.BASE ?? 'https://YOUR_ENGINE_URL',
69+
BASE: config?.BASE ?? '',
7070
VERSION: config?.VERSION ?? '1.0.0',
7171
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
7272
CREDENTIALS: config?.CREDENTIALS ?? 'include',

sdk/src/core/OpenAPI.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type OpenAPIConfig = {
2020
};
2121

2222
export const OpenAPI: OpenAPIConfig = {
23-
BASE: 'https://YOUR_ENGINE_URL',
23+
BASE: '',
2424
VERSION: '1.0.0',
2525
WITH_CREDENTIALS: false,
2626
CREDENTIALS: 'include',

sdk/src/services/BackendWalletService.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ export class BackendWalletService {
2020
requestBody?: {
2121
label?: string;
2222
/**
23-
* Optional wallet type. If not provided, the default wallet type will be used.
23+
* Type of new wallet to create. It is recommended to always provide this value. If not provided, the default wallet type will be used.
2424
*/
25-
type?: ('local' | 'aws-kms' | 'gcp-kms');
25+
type?: ('local' | 'aws-kms' | 'gcp-kms' | 'smart:aws-kms' | 'smart:gcp-kms' | 'smart:local');
2626
},
2727
): CancelablePromise<{
2828
result: {
@@ -31,6 +31,7 @@ export class BackendWalletService {
3131
*/
3232
walletAddress: string;
3333
status: string;
34+
type: ('local' | 'aws-kms' | 'gcp-kms' | 'smart:aws-kms' | 'smart:gcp-kms' | 'smart:local');
3435
};
3536
}> {
3637
return this.httpRequest.request({
@@ -664,6 +665,7 @@ export class BackendWalletService {
664665
requestBody: {
665666
message: string;
666667
isBytes?: boolean;
668+
chainId?: number;
667669
},
668670
xIdempotencyKey?: string,
669671
): CancelablePromise<{

sdk/src/services/ConfigurationService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class ConfigurationService {
1717
*/
1818
public getWalletsConfiguration(): CancelablePromise<{
1919
result: {
20-
type: ('local' | 'aws-kms' | 'gcp-kms');
20+
type: ('local' | 'aws-kms' | 'gcp-kms' | 'smart:aws-kms' | 'smart:gcp-kms' | 'smart:local');
2121
awsAccessKeyId: (string | null);
2222
awsRegion: (string | null);
2323
gcpApplicationProjectId: (string | null);
@@ -58,7 +58,7 @@ export class ConfigurationService {
5858
}),
5959
): CancelablePromise<{
6060
result: {
61-
type: ('local' | 'aws-kms' | 'gcp-kms');
61+
type: ('local' | 'aws-kms' | 'gcp-kms' | 'smart:aws-kms' | 'smart:gcp-kms' | 'smart:local');
6262
awsAccessKeyId: (string | null);
6363
awsRegion: (string | null);
6464
gcpApplicationProjectId: (string | null);

src/db/wallets/createWalletDetails.ts

+105-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Address } from "thirdweb";
12
import type { PrismaTransaction } from "../../schema/prisma";
23
import { encrypt } from "../../utils/crypto";
34
import { getPrismaWithPostgresTx } from "../client";
@@ -8,13 +9,17 @@ type CreateWalletDetailsParams = {
89
address: string;
910
label?: string;
1011
} & (
12+
| {
13+
type: "local";
14+
encryptedJson: string; // ENCRYPTION IS NOT HANDLED HERE, process privatekey with legacyLocalCrytpo before passing to this function
15+
}
1116
| {
1217
type: "aws-kms";
1318
awsKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
1419
awsKmsArn: string;
1520

16-
awsKmsSecretAccessKey?: string; // will be encrypted and stored, pass plaintext to this function
17-
awsKmsAccessKeyId?: string;
21+
awsKmsSecretAccessKey: string; // will be encrypted and stored, pass plaintext to this function
22+
awsKmsAccessKeyId: string;
1823
}
1924
| {
2025
type: "gcp-kms";
@@ -24,11 +29,42 @@ type CreateWalletDetailsParams = {
2429
gcpKmsKeyVersionId?: string; // depcrecated and unused, todo: remove with next breaking change
2530
gcpKmsLocationId?: string; // depcrecated and unused, todo: remove with next breaking change
2631

27-
gcpApplicationCredentialPrivateKey?: string; // encrypted
28-
gcpApplicationCredentialEmail?: string;
32+
gcpApplicationCredentialPrivateKey: string; // will be encrypted and stored, pass plaintext to this function
33+
gcpApplicationCredentialEmail: string;
34+
}
35+
| {
36+
type: "smart:aws-kms";
37+
awsKmsArn: string;
38+
awsKmsSecretAccessKey: string; // will be encrypted and stored, pass plaintext to this function
39+
awsKmsAccessKeyId: string;
40+
accountSignerAddress: Address;
41+
42+
accountFactoryAddress: Address | undefined;
43+
entrypointAddress: Address | undefined;
44+
}
45+
| {
46+
type: "smart:gcp-kms";
47+
gcpKmsResourcePath: string;
48+
gcpApplicationCredentialPrivateKey: string; // will be encrypted and stored, pass plaintext to this function
49+
gcpApplicationCredentialEmail: string;
50+
accountSignerAddress: Address;
51+
52+
accountFactoryAddress: Address | undefined;
53+
entrypointAddress: Address | undefined;
54+
}
55+
| {
56+
type: "smart:local";
57+
encryptedJson: string; // ENCRYPTION IS NOT HANDLED HERE, process privatekey with legacyLocalCrytpo before passing to this function
58+
accountSignerAddress: Address;
59+
60+
accountFactoryAddress: Address | undefined;
61+
entrypointAddress: Address | undefined;
2962
}
3063
);
3164

65+
/**
66+
* Create a new WalletDetails row in DB
67+
*/
3268
export const createWalletDetails = async ({
3369
pgtx,
3470
...walletDetails
@@ -47,15 +83,23 @@ export const createWalletDetails = async ({
4783
);
4884
}
4985

86+
if (walletDetails.type === "local") {
87+
return prisma.walletDetails.create({
88+
data: {
89+
...walletDetails,
90+
address: walletDetails.address.toLowerCase(),
91+
encryptedJson: walletDetails.encryptedJson,
92+
},
93+
});
94+
}
95+
5096
if (walletDetails.type === "aws-kms") {
5197
return prisma.walletDetails.create({
5298
data: {
5399
...walletDetails,
54100
address: walletDetails.address.toLowerCase(),
55101

56-
awsKmsSecretAccessKey: walletDetails.awsKmsSecretAccessKey
57-
? encrypt(walletDetails.awsKmsSecretAccessKey)
58-
: undefined,
102+
awsKmsSecretAccessKey: encrypt(walletDetails.awsKmsSecretAccessKey),
59103
},
60104
});
61105
}
@@ -66,11 +110,61 @@ export const createWalletDetails = async ({
66110
...walletDetails,
67111
address: walletDetails.address.toLowerCase(),
68112

69-
gcpApplicationCredentialPrivateKey:
70-
walletDetails.gcpApplicationCredentialPrivateKey
71-
? encrypt(walletDetails.gcpApplicationCredentialPrivateKey)
72-
: undefined,
113+
gcpApplicationCredentialPrivateKey: encrypt(
114+
walletDetails.gcpApplicationCredentialPrivateKey,
115+
),
116+
},
117+
});
118+
}
119+
120+
if (walletDetails.type === "smart:aws-kms") {
121+
return prisma.walletDetails.create({
122+
data: {
123+
...walletDetails,
124+
125+
address: walletDetails.address.toLowerCase(),
126+
awsKmsSecretAccessKey: encrypt(walletDetails.awsKmsSecretAccessKey),
127+
accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(),
128+
129+
accountFactoryAddress:
130+
walletDetails.accountFactoryAddress?.toLowerCase(),
131+
entrypointAddress: walletDetails.entrypointAddress?.toLowerCase(),
132+
},
133+
});
134+
}
135+
136+
if (walletDetails.type === "smart:gcp-kms") {
137+
return prisma.walletDetails.create({
138+
data: {
139+
...walletDetails,
140+
141+
address: walletDetails.address.toLowerCase(),
142+
accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(),
143+
144+
gcpApplicationCredentialPrivateKey: encrypt(
145+
walletDetails.gcpApplicationCredentialPrivateKey,
146+
),
147+
148+
accountFactoryAddress:
149+
walletDetails.accountFactoryAddress?.toLowerCase(),
150+
entrypointAddress: walletDetails.entrypointAddress?.toLowerCase(),
73151
},
74152
});
75153
}
154+
155+
if (walletDetails.type === "smart:local") {
156+
return prisma.walletDetails.create({
157+
data: {
158+
...walletDetails,
159+
address: walletDetails.address.toLowerCase(),
160+
accountSignerAddress: walletDetails.accountSignerAddress.toLowerCase(),
161+
162+
accountFactoryAddress:
163+
walletDetails.accountFactoryAddress?.toLowerCase(),
164+
entrypointAddress: walletDetails.entrypointAddress?.toLowerCase(),
165+
},
166+
});
167+
}
168+
169+
throw new Error("Unsupported wallet type");
76170
};

0 commit comments

Comments
 (0)