Skip to content

Commit b60a8bb

Browse files
committed
Addressed review comments
1 parent 3459d8f commit b60a8bb

File tree

11 files changed

+91
-118
lines changed

11 files changed

+91
-118
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "wallet_credentials" ALTER COLUMN "isDefault" DROP NOT NULL;

src/prisma/schema.prisma

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ model WalletDetails {
120120
}
121121

122122
model WalletCredentials {
123-
id String @id @default(uuid()) @map("id")
124-
type String @map("type")
125-
label String @map("label")
126-
data Json @map("data")
127-
isDefault Boolean @default(false) @map("isDefault")
128-
129-
createdAt DateTime @default(now()) @map("createdAt")
130-
updatedAt DateTime @updatedAt @map("updatedAt")
131-
deletedAt DateTime? @map("deletedAt")
123+
id String @id @default(uuid())
124+
type String
125+
label String
126+
data Json
127+
isDefault Boolean? @default(false)
128+
129+
createdAt DateTime @default(now())
130+
updatedAt DateTime @updatedAt
131+
deletedAt DateTime?
132132
133133
wallets WalletDetails[]
134134

src/server/routes/backend-wallet/create.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
CircleWalletError,
3434
createCircleWalletDetails,
3535
} from "../../utils/wallets/circle";
36+
import assert from "node:assert";
3637

3738
const requestBodySchema = Type.Union([
3839
// Base schema for non-circle wallet types
@@ -87,7 +88,7 @@ export const createBackendWallet = async (fastify: FastifyInstance) => {
8788
handler: async (req, reply) => {
8889
const { label } = req.body;
8990

90-
let walletAddress: string | undefined = undefined;
91+
let walletAddress: string;
9192
const config = await getConfig();
9293

9394
const walletType =
@@ -129,9 +130,7 @@ export const createBackendWallet = async (fastify: FastifyInstance) => {
129130
case CircleWalletType.circle:
130131
{
131132
// we need this if here for typescript to statically type the credentialId and walletSetId
132-
if (req.body.type !== "circle")
133-
throw new Error("Invalid Circle wallet type"); // invariant
134-
133+
assert(req.body.type === "circle", "Expected circle wallet type");
135134
const { credentialId, walletSetId } = req.body;
136135

137136
try {
@@ -159,9 +158,7 @@ export const createBackendWallet = async (fastify: FastifyInstance) => {
159158
case CircleWalletType.smartCircle:
160159
{
161160
// we need this if here for typescript to statically type the credentialId and walletSetId
162-
if (req.body.type !== "smart:circle")
163-
throw new Error("Invalid Circle wallet type"); // invariant
164-
161+
assert(req.body.type === "circle", "Expected circle wallet type");
165162
const { credentialId, walletSetId } = req.body;
166163

167164
try {
@@ -235,10 +232,12 @@ export const createBackendWallet = async (fastify: FastifyInstance) => {
235232
walletAddress = getAddress(smartLocalWallet.address);
236233
}
237234
break;
238-
}
239-
240-
if (!walletAddress) {
241-
throw new Error("Invalid state"); // invariant, typescript cannot exhaustive check because enums
235+
default:
236+
throw createCustomError(
237+
"Unkown wallet type",
238+
StatusCodes.BAD_REQUEST,
239+
"CREATE_WALLET_ERROR",
240+
);
242241
}
243242

244243
reply.status(StatusCodes.OK).send({

src/server/routes/configuration/wallets/update.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ const requestBodySchema = Type.Union([
2121
gcpApplicationCredentialEmail: Type.String(),
2222
gcpApplicationCredentialPrivateKey: Type.String(),
2323
}),
24-
Type.Object({
25-
awsAccessKeyId: Type.String(),
26-
awsSecretAccessKey: Type.String(),
27-
awsRegion: Type.String(),
28-
}),
2924
Type.Object({
3025
circleApiKey: Type.String(),
3126
}),

src/server/routes/wallet-credentials/create.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const responseSchema = Type.Object({
3030
id: Type.String(),
3131
type: Type.String(),
3232
label: Type.String(),
33-
isDefault: Type.Boolean(),
33+
isDefault: Type.Union([Type.Boolean(), Type.Null()]),
3434
createdAt: Type.String(),
3535
updatedAt: Type.String(),
3636
}),
@@ -47,9 +47,7 @@ responseSchema.example = {
4747
},
4848
};
4949

50-
export const createWalletCredentialRoute = async (
51-
fastify: FastifyInstance,
52-
) => {
50+
export const createWalletCredentialRoute = async (fastify: FastifyInstance) => {
5351
fastify.withTypeProvider().route<{
5452
Body: Static<typeof requestBodySchema>;
5553
Reply: Static<typeof responseSchema>;

src/server/routes/wallet-credentials/get-all.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,19 @@ import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { getAllWalletCredentials } from "../../../shared/db/wallet-credentials/get-all-wallet-credentials";
55
import { standardResponseSchema } from "../../schemas/shared-api-schemas";
6+
import { PaginationSchema } from "../../schemas/pagination";
67

7-
const QuerySchema = Type.Object({
8-
page: Type.Integer({
9-
description: "The page of credentials to get.",
10-
examples: ["1"],
11-
default: "1",
12-
minimum: 1,
13-
}),
14-
limit: Type.Integer({
15-
description: "The number of credentials to get per page.",
16-
examples: ["10"],
17-
default: "10",
18-
minimum: 1,
19-
}),
20-
});
8+
const QuerySchema = PaginationSchema;
219

2210
const responseSchema = Type.Object({
2311
result: Type.Array(
2412
Type.Object({
2513
id: Type.String(),
2614
type: Type.String(),
2715
label: Type.Union([Type.String(), Type.Null()]),
28-
isDefault: Type.Boolean(),
16+
isDefault: Type.Union([Type.Boolean(), Type.Null()]),
2917
createdAt: Type.String(),
3018
updatedAt: Type.String(),
31-
deletedAt: Type.Union([Type.String(), Type.Null()]),
3219
}),
3320
),
3421
});
@@ -76,9 +63,8 @@ export async function getAllWalletCredentialsRoute(fastify: FastifyInstance) {
7663
...cred,
7764
createdAt: cred.createdAt.toISOString(),
7865
updatedAt: cred.updatedAt.toISOString(),
79-
deletedAt: cred.deletedAt?.toISOString() || null,
8066
})),
8167
});
8268
},
8369
});
84-
}
70+
}

src/server/utils/wallets/circle/index.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
DEFAULT_ACCOUNT_FACTORY_V0_7,
2727
ENTRYPOINT_ADDRESS_v0_7,
2828
} from "thirdweb/wallets/smart";
29+
import { stringify } from "thirdweb/utils";
2930

3031
export class CircleWalletError extends Error {
3132
constructor(message: string) {
@@ -64,13 +65,12 @@ export async function provisionCircleWallet({
6465
});
6566

6667
walletSetId = walletSet.data?.walletSet.id;
68+
if (!walletSetId)
69+
throw new CircleWalletError(
70+
"Did not receive walletSetId, and failed to create one automatically",
71+
);
6772
}
6873

69-
if (!walletSetId)
70-
throw new CircleWalletError(
71-
"Did not receive walletSetId, and failed to create one automatically",
72-
);
73-
7474
const provisionWalletResponse = await circleDeveloperSdk
7575
.createWallets({
7676
accountType: "EOA",
@@ -153,15 +153,6 @@ export async function getCircleAccount({
153153
const wallet = walletResponse.data?.wallet;
154154
const address = wallet?.address as Address;
155155

156-
function stringify(data: unknown) {
157-
return JSON.stringify(data, (_, value) => {
158-
if (typeof value === "bigint") {
159-
return value.toString();
160-
}
161-
return value;
162-
});
163-
}
164-
165156
async function signTransaction(tx: SerializableTransaction) {
166157
const signature = await circleDeveloperSdk
167158
.signTransaction({
Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import { z } from "zod";
21
import { encrypt } from "../../utils/crypto";
32
import { registerEntitySecretCiphertext } from "@circle-fin/developer-controlled-wallets";
43
import { prisma } from "../client";
54
import { getConfig } from "../../utils/cache/get-config";
65
import { WalletCredentialsError } from "./get-wallet-credential";
76
import { randomBytes } from "node:crypto";
8-
9-
export const entitySecretSchema = z.string().regex(/^[0-9a-fA-F]{64}$/, {
10-
message: "entitySecret must be a 32-byte hex string",
11-
});
7+
import { cirlceEntitySecretZodSchema } from "../../schemas/wallet";
128

139
// will be expanded to be a discriminated union of all supported wallet types
1410
export type CreateWalletCredentialsParams = {
@@ -24,55 +20,60 @@ export const createWalletCredential = async ({
2420
entitySecret,
2521
isDefault,
2622
}: CreateWalletCredentialsParams) => {
27-
// not handling other wallet types because we only support circle for now
2823
const { walletConfiguration } = await getConfig();
29-
const circleApiKey = walletConfiguration.circle?.apiKey;
3024

31-
if (!circleApiKey) {
32-
throw new WalletCredentialsError("No Circle API Key Configured");
33-
}
25+
switch (type) {
26+
case "circle": {
27+
const circleApiKey = walletConfiguration.circle?.apiKey;
3428

35-
if (entitySecret) {
36-
const { error } = entitySecretSchema.safeParse(entitySecret);
37-
if (error) {
38-
throw new WalletCredentialsError(
39-
"Invalid provided entity secret for Circle",
40-
);
41-
}
42-
}
29+
if (!circleApiKey) {
30+
throw new WalletCredentialsError("No Circle API Key Configured");
31+
}
4332

44-
// If entitySecret is not provided, generate a random one
45-
const finalEntitySecret = entitySecret ?? randomBytes(32).toString("hex");
46-
// Create the wallet credentials
47-
const walletCredentials = await prisma.walletCredentials.create({
48-
data: {
49-
type,
50-
label,
51-
isDefault: isDefault ?? false,
52-
data: {
53-
entitySecret: encrypt(finalEntitySecret),
54-
},
55-
},
56-
});
33+
if (entitySecret) {
34+
const { error } = cirlceEntitySecretZodSchema.safeParse(entitySecret);
35+
if (error) {
36+
throw new WalletCredentialsError(
37+
"Invalid provided entity secret for Circle",
38+
);
39+
}
40+
}
5741

58-
// try registering the entity secret. See: https://developers.circle.com/w3s/developer-controlled-create-your-first-wallet
59-
try {
60-
await registerEntitySecretCiphertext({
61-
apiKey: circleApiKey,
62-
entitySecret: finalEntitySecret,
63-
});
64-
} catch (e: unknown) {
65-
// If failed to registeer, permanently delete erroneously created credential
66-
await prisma.walletCredentials.delete({
67-
where: {
68-
id: walletCredentials.id,
69-
},
70-
});
42+
// If entitySecret is not provided, generate a random one
43+
const finalEntitySecret = entitySecret ?? randomBytes(32).toString("hex");
44+
// Create the wallet credentials
45+
const walletCredentials = await prisma.walletCredentials.create({
46+
data: {
47+
type,
48+
label,
49+
isDefault: isDefault ?? false,
50+
data: {
51+
entitySecret: encrypt(finalEntitySecret),
52+
},
53+
},
54+
});
7155

72-
throw new WalletCredentialsError(
73-
`Could not register Entity Secret with Circle\n${JSON.stringify(e)}`,
74-
);
75-
}
56+
// try registering the entity secret. See: https://developers.circle.com/w3s/developer-controlled-create-your-first-wallet
57+
try {
58+
await registerEntitySecretCiphertext({
59+
apiKey: circleApiKey,
60+
entitySecret: finalEntitySecret,
61+
recoveryFileDownloadPath: "/dev/null",
62+
});
63+
} catch (e: unknown) {
64+
// If failed to registeer, permanently delete erroneously created credential
65+
await prisma.walletCredentials.delete({
66+
where: {
67+
id: walletCredentials.id,
68+
},
69+
});
70+
71+
throw new WalletCredentialsError(
72+
`Could not register Entity Secret with Circle\n${JSON.stringify(e)}`,
73+
);
74+
}
7675

77-
return walletCredentials;
76+
return walletCredentials;
77+
}
78+
}
7879
};

src/shared/db/wallet-credentials/get-all-wallet-credentials.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ interface GetAllWalletCredentialsParams {
88
}
99

1010
export const getAllWalletCredentials = async ({
11-
pgtx,
1211
page = 1,
1312
limit = 10,
1413
}: GetAllWalletCredentialsParams) => {
@@ -27,7 +26,6 @@ export const getAllWalletCredentials = async ({
2726
isDefault: true,
2827
createdAt: true,
2928
updatedAt: true,
30-
deletedAt: true,
3129
},
3230
orderBy: {
3331
createdAt: "desc",

src/shared/db/wallets/get-wallet-details.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ const smartLocalWalletSchema = localWalletSchema
5858
type: z.literal("smart:local"),
5959
})
6060
.merge(smartWalletPartialSchema)
61-
.merge(baseWalletPartialSchema);
6261

6362
const awsKmsWalletSchema = z
6463
.object({
@@ -74,7 +73,6 @@ const smartAwsKmsWalletSchema = awsKmsWalletSchema
7473
type: z.literal("smart:aws-kms"),
7574
})
7675
.merge(smartWalletPartialSchema)
77-
.merge(baseWalletPartialSchema);
7876

7977
const gcpKmsWalletSchema = z
8078
.object({
@@ -90,7 +88,6 @@ const smartGcpKmsWalletSchema = gcpKmsWalletSchema
9088
type: z.literal("smart:gcp-kms"),
9189
})
9290
.merge(smartWalletPartialSchema)
93-
.merge(baseWalletPartialSchema);
9491

9592
const circleWalletSchema = z
9693
.object({
@@ -108,7 +105,6 @@ const smartCircleWalletSchema = circleWalletSchema
108105
type: z.literal("smart:circle"),
109106
})
110107
.merge(smartWalletPartialSchema)
111-
.merge(baseWalletPartialSchema);
112108

113109
const walletDetailsSchema = z.discriminatedUnion("type", [
114110
localWalletSchema,
@@ -124,7 +120,8 @@ const walletDetailsSchema = z.discriminatedUnion("type", [
124120
export type SmartBackendWalletDetails =
125121
| z.infer<typeof smartLocalWalletSchema>
126122
| z.infer<typeof smartAwsKmsWalletSchema>
127-
| z.infer<typeof smartGcpKmsWalletSchema>;
123+
| z.infer<typeof smartGcpKmsWalletSchema>
124+
| z.infer<typeof smartCircleWalletSchema>;
128125

129126
export function isSmartBackendWallet(
130127
wallet: ParsedWalletDetails,

0 commit comments

Comments
 (0)