Skip to content

Commit cfccfc7

Browse files
add-enclave-functionality
1 parent 3496ef4 commit cfccfc7

File tree

11 files changed

+228
-49
lines changed

11 files changed

+228
-49
lines changed

src/server/routes/contract/write/write.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Type, type Static } from "@sinclair/typebox";
1+
import { type Static, Type } from "@sinclair/typebox";
22
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { prepareContractCall, resolveMethod } from "thirdweb";
5-
import { parseAbiParams, type AbiFunction } from "thirdweb/utils";
5+
import { type AbiFunction, parseAbiParams } from "thirdweb/utils";
66
import { getContractV5 } from "../../../../shared/utils/cache/get-contractv5";
77
import { prettifyError } from "../../../../shared/utils/error";
88
import { queueTransaction } from "../../../../shared/utils/transaction/queue-transation";
@@ -16,11 +16,12 @@ import {
1616
import { txOverridesWithValueSchema } from "../../../schemas/tx-overrides";
1717
import {
1818
maybeAddress,
19-
requiredAddress,
20-
walletWithAAHeaderSchema,
19+
type walletWithAAHeaderSchema,
20+
walletWithAAOrEnclaveHeaderSchema,
2121
} from "../../../schemas/wallet";
2222
import { sanitizeAbi, sanitizeFunctionName } from "../../../utils/abi";
2323
import { getChainIdFromChain } from "../../../utils/chain";
24+
import { parseEnclaveHeaders } from "../../../utils/convertor";
2425
import { parseTransactionOverrides } from "../../../utils/transaction-overrides";
2526

2627
// INPUT
@@ -60,7 +61,7 @@ export async function writeToContract(fastify: FastifyInstance) {
6061
tags: ["Contract"],
6162
operationId: "write",
6263
params: contractParamSchema,
63-
headers: walletWithAAHeaderSchema,
64+
headers: walletWithAAOrEnclaveHeaderSchema,
6465
querystring: requestQuerystringSchema,
6566
body: writeRequestBodySchema,
6667
response: {
@@ -72,12 +73,13 @@ export async function writeToContract(fastify: FastifyInstance) {
7273
const { simulateTx } = request.query;
7374
const { functionName, args, txOverrides, abi } = request.body;
7475
const {
75-
"x-backend-wallet-address": fromAddress,
76+
"x-backend-wallet-address": backendWalletAddress,
7677
"x-account-address": accountAddress,
7778
"x-idempotency-key": idempotencyKey,
7879
"x-account-factory-address": accountFactoryAddress,
7980
"x-account-salt": accountSalt,
8081
} = request.headers as Static<typeof walletWithAAHeaderSchema>;
82+
const enclave = await parseEnclaveHeaders(request.headers, chain);
8183

8284
const chainId = await getChainIdFromChain(chain);
8385
const contract = await getContractV5({
@@ -118,14 +120,18 @@ export async function writeToContract(fastify: FastifyInstance) {
118120
const queueId = await queueTransaction({
119121
functionName,
120122
transaction,
121-
fromAddress: requiredAddress(fromAddress, "x-backend-wallet-address"),
123+
fromAddress: maybeAddress(
124+
backendWalletAddress,
125+
"x-backend-wallet-address",
126+
),
122127
toAddress: maybeAddress(contractAddress, "to"),
123128
accountAddress: maybeAddress(accountAddress, "x-account-address"),
124129
accountFactoryAddress: maybeAddress(
125130
accountFactoryAddress,
126131
"x-account-factory-address",
127132
),
128133
accountSalt,
134+
enclave,
129135
txOverrides,
130136
idempotencyKey,
131137
shouldSimulate: simulateTx,

src/server/schemas/wallet/index.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,52 @@
11
import { Type } from "@sinclair/typebox";
2-
import { getAddress, type Address } from "thirdweb";
2+
import { type Address, getAddress } from "thirdweb";
33
import { env } from "../../../shared/utils/env";
44
import { badAddressError } from "../../middleware/error";
55
import { AddressSchema } from "../address";
66
import { chainIdOrSlugSchema } from "../chain";
77

8-
export const walletHeaderSchema = Type.Object({
9-
"x-backend-wallet-address": {
10-
...AddressSchema,
11-
description: "Backend wallet address",
12-
},
8+
export const idempotentHeaderSchema = Type.Object({
139
"x-idempotency-key": Type.Optional(
1410
Type.String({
1511
maxLength: 200,
1612
description: `Transactions submitted with the same idempotency key will be de-duplicated. Only the last ${env.TRANSACTION_HISTORY_COUNT} transactions are compared.`,
1713
}),
1814
),
1915
});
16+
export const enclaveWalletHeaderSchema = Type.Object({
17+
"x-enclave-wallet-auth-token": Type.Optional(
18+
Type.String({
19+
description:
20+
"Auth token of an enclave wallet. mutually exclusive with other wallet headers",
21+
}),
22+
),
23+
"x-client-id": Type.Optional(
24+
Type.String({
25+
description:
26+
"Client id of an enclave wallet. mutually exclusive with other wallet headers",
27+
}),
28+
),
29+
"x-ecosystem-id": Type.Optional(
30+
Type.RegExp(/^ecosystem\.[a-zA-Z0-9_-]+$/, {
31+
description:
32+
"Ecosystem id of an enclave wallet. mutually exclusive with other wallet headers",
33+
}),
34+
),
35+
"x-ecosystem-partner-id": Type.Optional(
36+
Type.String({
37+
description:
38+
"Ecosystem partner id of an enclave wallet. mutually exclusive with other wallet headers",
39+
}),
40+
),
41+
});
42+
43+
export const walletHeaderSchema = Type.Object({
44+
"x-backend-wallet-address": {
45+
...AddressSchema,
46+
description: "Backend wallet address",
47+
},
48+
...idempotentHeaderSchema.properties,
49+
});
2050

2151
export const walletWithAAHeaderSchema = Type.Object({
2252
...walletHeaderSchema.properties,
@@ -39,6 +69,11 @@ export const walletWithAAHeaderSchema = Type.Object({
3969
),
4070
});
4171

72+
export const walletWithAAOrEnclaveHeaderSchema = Type.Object({
73+
...walletWithAAHeaderSchema.properties,
74+
...enclaveWalletHeaderSchema.properties,
75+
});
76+
4277
/**
4378
* Helper function to parse an address string.
4479
* Returns undefined if the address is undefined.

src/server/utils/convertor.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import type { Static } from "@sinclair/typebox";
12
import { BigNumber } from "ethers";
3+
import type { FastifyRequest } from "fastify";
4+
import type { EcosystemWalletId } from "thirdweb/dist/types/wallets/wallet-types";
5+
import type { EnclaveWalletParams } from "../../shared/utils/cache/get-enclave-wallet";
6+
import type { enclaveWalletHeaderSchema } from "../schemas/wallet";
27

38
const isHexBigNumber = (value: unknown) => {
49
const isNonNullObject = typeof value === "object" && value !== null;
510
const hasType = isNonNullObject && "type" in value;
6-
return hasType && value.type === "BigNumber" && "hex" in value
7-
}
11+
return hasType && value.type === "BigNumber" && "hex" in value;
12+
};
813
export const bigNumberReplacer = (value: unknown): unknown => {
914
// if we find a BigNumber then make it into a string (since that is safe)
1015
if (BigNumber.isBigNumber(value) || isHexBigNumber(value)) {
@@ -17,3 +22,26 @@ export const bigNumberReplacer = (value: unknown): unknown => {
1722

1823
return value;
1924
};
25+
26+
export const parseEnclaveHeaders = async (
27+
headers: FastifyRequest["headers"],
28+
chain: string,
29+
): Promise<EnclaveWalletParams> => {
30+
const {
31+
"x-enclave-wallet-auth-token": authToken = "",
32+
"x-client-id": clientId = "",
33+
"x-ecosystem-id": id,
34+
"x-ecosystem-partner-id": partnerId,
35+
} = headers as Static<typeof enclaveWalletHeaderSchema>;
36+
let ecosystem: EnclaveWalletParams["ecosystem"];
37+
if (id) {
38+
ecosystem = { id: id as EcosystemWalletId, partnerId };
39+
}
40+
41+
return {
42+
authToken,
43+
clientId,
44+
ecosystem,
45+
chain,
46+
};
47+
};

src/shared/utils/account.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,23 @@ import {
1818
import { getSmartWalletV5 } from "./cache/get-smart-wallet-v5";
1919
import { getChain } from "./chain";
2020
import { thirdwebClient } from "./sdk";
21+
import { type EnclaveWalletParams, getEnclaveWalletAccount } from "./cache/get-enclave-wallet";
2122

2223
export const _accountsCache = new LRUMap<string, Account>(2048);
2324

24-
export const getAccount = async (args: {
25+
export type GetAccountArgs = {
2526
chainId: number;
26-
from: Address;
27+
from: Address | undefined;
2728
accountAddress?: Address;
28-
}): Promise<Account> => {
29-
const { chainId, from, accountAddress } = args;
29+
enclave?: EnclaveWalletParams
30+
}
31+
32+
export const getAccount = async (args: GetAccountArgs): Promise<Account> => {
33+
const { chainId, from, accountAddress, enclave } = args;
3034
const chain = await getChain(chainId);
3135

36+
if (enclave) return getEnclaveWalletAccount(enclave);
37+
if (!from) throw new Error("from is required");
3238
if (accountAddress) return getSmartWalletV5({ chain, accountAddress, from });
3339

3440
// Get from cache.
@@ -48,9 +54,9 @@ export const getAccount = async (args: {
4854
};
4955

5056
export const walletDetailsToAccount = async ({
51-
walletDetails,
52-
chain,
53-
}: {
57+
walletDetails,
58+
chain,
59+
}: {
5460
walletDetails: ParsedWalletDetails;
5561
chain: Chain;
5662
}) => {
@@ -164,9 +170,9 @@ export const _adminAccountsCache = new LRUMap<string, Account>(2048);
164170
* Will throw if the wallet is not a smart backend wallet
165171
*/
166172
export const getSmartBackendWalletAdminAccount = async ({
167-
chainId,
168-
accountAddress,
169-
}: {
173+
chainId,
174+
accountAddress,
175+
}: {
170176
chainId: number;
171177
accountAddress: Address;
172178
}) => {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Address } from "thirdweb";
2+
import { ecosystemWallet, inAppWallet } from "thirdweb/wallets";
3+
import { getChainIdFromChain } from "../../../server/utils/chain";
4+
import { getChain } from "../chain";
5+
import { thirdwebClient as client } from "../sdk";
6+
7+
export interface EnclaveWalletParams {
8+
chain: string;
9+
authToken: string;
10+
clientId: string;
11+
ecosystem?: {
12+
id: `ecosystem.${string}`;
13+
partnerId?: string;
14+
};
15+
}
16+
17+
export const getEnclaveWalletAccount = async ({
18+
chain: chainSlug,
19+
authToken,
20+
clientId,
21+
ecosystem,
22+
}: EnclaveWalletParams) => {
23+
const wallet = ecosystem
24+
? ecosystemWallet(ecosystem.id, { partnerId: ecosystem.partnerId })
25+
: inAppWallet();
26+
const chainId = await getChainIdFromChain(chainSlug);
27+
const chain = await getChain(chainId);
28+
return wallet.autoConnect({
29+
client,
30+
authResult: {
31+
storedToken: {
32+
jwtToken: "",
33+
authProvider: "Cognito",
34+
authDetails: {
35+
userWalletId: "",
36+
recoveryShareManagement: "ENCLAVE",
37+
},
38+
developerClientId: clientId,
39+
cookieString: authToken,
40+
shouldStoreCookieString: false,
41+
isNewUser: false,
42+
},
43+
},
44+
chain,
45+
});
46+
};
47+
48+
export const getEnclaveWalletAddress = async (params: EnclaveWalletParams) => {
49+
const account = await getEnclaveWalletAccount(params);
50+
return account.address as Address;
51+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { AsyncStorage } from "@thirdweb-dev/wallets";
2+
3+
export class MemoryStorage implements AsyncStorage {
4+
data: Map<string, string> = new Map();
5+
6+
async getItem(key: string) {
7+
return this.data.get(key) || null;
8+
}
9+
10+
async setItem(key: string, value: string) {
11+
this.data.set(key, value);
12+
}
13+
14+
async removeItem(key: string) {
15+
this.data.delete(key);
16+
}
17+
}

src/shared/utils/transaction/insert-transaction.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { StatusCodes } from "http-status-codes";
22
import { randomUUID } from "node:crypto";
3-
import { TransactionDB } from "../../../shared/db/transactions/db";
3+
import { TransactionDB } from "../../db/transactions/db";
44
import {
55
getWalletDetails,
66
isSmartBackendWallet,
77
type ParsedWalletDetails,
8-
} from "../../../shared/db/wallets/get-wallet-details";
8+
} from "../../db/wallets/get-wallet-details";
99
import { doesChainSupportService } from "../../lib/chain/chain-capabilities";
1010
import { createCustomError } from "../../../server/middleware/error";
1111
import { SendTransactionQueue } from "../../../worker/queues/send-transaction-queue";
@@ -14,11 +14,13 @@ import { recordMetrics } from "../prometheus";
1414
import { reportUsage } from "../usage";
1515
import { doSimulateTransaction } from "./simulate-queued-transaction";
1616
import type { InsertedTransaction, QueuedTransaction } from "./types";
17+
import type { EnclaveWalletParams } from "../cache/get-enclave-wallet";
1718

1819
interface InsertTransactionData {
1920
insertedTransaction: InsertedTransaction;
2021
idempotencyKey?: string;
2122
shouldSimulate?: boolean;
23+
enclave?: EnclaveWalletParams;
2224
}
2325

2426
/**
@@ -30,13 +32,17 @@ interface InsertTransactionData {
3032
export const insertTransaction = async (
3133
args: InsertTransactionData,
3234
): Promise<string> => {
33-
const { insertedTransaction, idempotencyKey, shouldSimulate = false } = args;
35+
const {
36+
insertedTransaction,
37+
idempotencyKey,
38+
shouldSimulate = false,
39+
enclave
40+
} = args;
3441

3542
// The queueId uniquely represents an enqueued transaction.
3643
// It's also used as the idempotency key (default = no idempotence).
37-
let queueId: string = randomUUID();
38-
if (idempotencyKey) {
39-
queueId = idempotencyKey;
44+
const queueId: string = idempotencyKey ?? randomUUID();
45+
if (queueId === idempotencyKey) {
4046
if (await TransactionDB.exists(queueId)) {
4147
// No-op. Return the existing queueId.
4248
return queueId;
@@ -151,7 +157,7 @@ export const insertTransaction = async (
151157

152158
// Simulate the transaction.
153159
if (shouldSimulate) {
154-
const error = await doSimulateTransaction(queuedTransaction);
160+
const error = await doSimulateTransaction(queuedTransaction, enclave);
155161
if (error) {
156162
throw createCustomError(
157163
`Simulation failed: ${error.replace(/[\r\n]+/g, " --- ")}`,
@@ -165,6 +171,7 @@ export const insertTransaction = async (
165171
await SendTransactionQueue.add({
166172
queueId: queuedTransaction.queueId,
167173
resendCount: 0,
174+
enclave,
168175
});
169176
reportUsage([{ action: "queue_tx", input: queuedTransaction }]);
170177

0 commit comments

Comments
 (0)