Skip to content

Commit d9818fe

Browse files
authored
add x-account-factory-address header to contract-write and send-transaction (#645)
* add `x-account-factory-address` header to contract-write and send-transaction * fix: custom error when invalid accountFactoryAddress * chore: Update x-account-factory-address description in wallet schema
1 parent d1ed9e9 commit d9818fe

File tree

7 files changed

+76
-8
lines changed

7 files changed

+76
-8
lines changed

src/db/transactions/queueTx.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { maybeBigInt, normalizeAddress } from "../../utils/primitiveTypes";
66
import { insertTransaction } from "../../utils/transaction/insertTransaction";
77

88
interface QueueTxParams {
9+
// we should move away from Transaction type (v4 SDK)
910
tx: Transaction<any> | DeployTransaction;
1011
chainId: number;
1112
extension: ContractExtension;
@@ -14,6 +15,7 @@ interface QueueTxParams {
1415
deployedContractType?: string;
1516
simulateTx?: boolean;
1617
idempotencyKey?: string;
18+
accountFactoryAddress?: Address;
1719
txOverrides?: {
1820
gas?: string;
1921
maxFeePerGas?: string;
@@ -31,6 +33,7 @@ export const queueTx = async ({
3133
simulateTx,
3234
idempotencyKey,
3335
txOverrides,
36+
accountFactoryAddress,
3437
}: QueueTxParams) => {
3538
// Transaction Details
3639
const functionName = tx.getMethod();
@@ -68,6 +71,7 @@ export const queueTx = async ({
6871
signerAddress,
6972
accountAddress: normalizeAddress(await tx.getSignerAddress()),
7073
target: normalizeAddress(tx.getTarget()),
74+
accountFactoryAddress,
7175
},
7276
idempotencyKey,
7377
shouldSimulate: simulateTx,

src/server/middleware/error.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ export const createCustomDateTimestampError = (key: string): CustomError => {
3030
);
3131
};
3232

33+
export const createBadAddressError = (key: string): CustomError => {
34+
return createCustomError(
35+
`Invalid ${key} Value. Needs to be a valid EVM address`,
36+
422,
37+
"INVALID_ADDRESS",
38+
);
39+
};
40+
3341
const flipObject = (data: any) =>
3442
Object.fromEntries(Object.entries(data).map(([key, value]) => [value, key]));
3543

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "../../schemas/sharedApiSchemas";
1212
import { txOverridesSchema } from "../../schemas/txOverrides";
1313
import {
14+
maybeAddress,
1415
walletChainParamSchema,
1516
walletWithAAHeaderSchema,
1617
} from "../../schemas/wallet";
@@ -73,6 +74,7 @@ export async function sendTransaction(fastify: FastifyInstance) {
7374
"x-backend-wallet-address": fromAddress,
7475
"x-idempotency-key": idempotencyKey,
7576
"x-account-address": accountAddress,
77+
"x-account-factory-address": accountFactoryAddress,
7678
} = request.headers as Static<typeof walletWithAAHeaderSchema>;
7779

7880
const chainId = await getChainIdFromChain(chain);
@@ -90,7 +92,10 @@ export async function sendTransaction(fastify: FastifyInstance) {
9092
accountAddress: accountAddress as Address,
9193
signerAddress: fromAddress as Address,
9294
target: toAddress as Address | undefined,
93-
95+
accountFactoryAddress: maybeAddress(
96+
accountFactoryAddress,
97+
"x-account-factory-address",
98+
),
9499
gas: maybeBigInt(txOverrides?.gas),
95100
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
96101
maxPriorityFeePerGas: maybeBigInt(

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
transactionWritesResponseSchema,
1212
} from "../../../schemas/sharedApiSchemas";
1313
import { txOverridesWithValueSchema } from "../../../schemas/txOverrides";
14-
import { walletWithAAHeaderSchema } from "../../../schemas/wallet";
14+
import {
15+
maybeAddress,
16+
walletWithAAHeaderSchema,
17+
} from "../../../schemas/wallet";
1518
import { getChainIdFromChain } from "../../../utils/chain";
1619

1720
// INPUT
@@ -66,6 +69,7 @@ export async function writeToContract(fastify: FastifyInstance) {
6669
"x-backend-wallet-address": walletAddress,
6770
"x-account-address": accountAddress,
6871
"x-idempotency-key": idempotencyKey,
72+
"x-account-factory-address": accountFactoryAddress,
6973
} = request.headers as Static<typeof walletWithAAHeaderSchema>;
7074

7175
const chainId = await getChainIdFromChain(chain);
@@ -76,6 +80,7 @@ export async function writeToContract(fastify: FastifyInstance) {
7680
accountAddress,
7781
abi,
7882
});
83+
7984
const tx = contract.prepare(functionName, args, {
8085
value: txOverrides?.value,
8186
gasLimit: txOverrides?.gas,
@@ -90,6 +95,10 @@ export async function writeToContract(fastify: FastifyInstance) {
9095
extension: "none",
9196
idempotencyKey,
9297
txOverrides,
98+
accountFactoryAddress: maybeAddress(
99+
accountFactoryAddress,
100+
"x-account-factory-address",
101+
),
93102
});
94103

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

src/server/schemas/wallet/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Type } from "@sinclair/typebox";
2+
import { Address, getAddress } from "thirdweb";
23
import { env } from "../../../utils/env";
4+
import { createBadAddressError } from "../../middleware/error";
35
import { AddressSchema } from "../address";
46

57
export const walletHeaderSchema = Type.Object({
@@ -20,8 +22,31 @@ export const walletWithAAHeaderSchema = Type.Object({
2022
...AddressSchema,
2123
description: "Smart account address",
2224
}),
25+
"x-account-factory-address": Type.Optional({
26+
...AddressSchema,
27+
description:
28+
"Smart account factory address. If omitted, engine will try to resolve it from the chain.",
29+
}),
2330
});
2431

32+
/**
33+
* Helper function to parse an address string.
34+
* Returns undefined if the address is undefined.
35+
*
36+
* Throws a custom 422 INVALID_ADDRESS error with variableName if the address is invalid (other than undefined).
37+
*/
38+
export function maybeAddress(
39+
address: string | undefined,
40+
variableName: string,
41+
): Address | undefined {
42+
if (!address) return undefined;
43+
try {
44+
return getAddress(address);
45+
} catch {
46+
throw createBadAddressError(variableName);
47+
}
48+
}
49+
2550
export const walletChainParamSchema = Type.Object({
2651
chain: Type.String({
2752
examples: ["80002"],

src/utils/transaction/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type InsertedTransaction = {
3333
// User Operation
3434
signerAddress?: Address;
3535
accountAddress?: Address;
36+
accountFactoryAddress?: Address;
3637
target?: Address;
3738
sender?: Address;
3839
};

src/utils/transaction/userOperation.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Address,
33
Hex,
4+
getAddress,
45
getContract,
56
prepareContractCall,
67
readContract,
@@ -25,6 +26,7 @@ export const generateSignedUserOperation = async (
2526
gas,
2627
signerAddress,
2728
accountAddress,
29+
accountFactoryAddress: userProvidedAccountFactoryAddress,
2830
target,
2931
from,
3032
data,
@@ -43,12 +45,26 @@ export const generateSignedUserOperation = async (
4345
address: accountAddress as Address,
4446
});
4547

46-
// Resolve Factory Contract Address from Smart-Account Contract
47-
const accountFactoryAddress = await readContract({
48-
contract: smartAccountContract,
49-
method: "function factory() view returns (address)",
50-
params: [],
51-
});
48+
// use the user provided factory address if available
49+
let accountFactoryAddress = userProvidedAccountFactoryAddress;
50+
51+
if (!accountFactoryAddress) {
52+
// Resolve Factory Contract Address from Smart-Account Contract
53+
try {
54+
const onchainAccountFactoryAddress = await readContract({
55+
contract: smartAccountContract,
56+
method: "function factory() view returns (address)",
57+
params: [],
58+
});
59+
60+
accountFactoryAddress = getAddress(onchainAccountFactoryAddress);
61+
} catch (e) {
62+
// if no factory address is found, throw an error
63+
throw new Error(
64+
`Failed to find factory address for account '${accountAddress}' on chain '${chainId}'`,
65+
);
66+
}
67+
}
5268

5369
// Resolve Factory Contract
5470
const accountFactoryContract = getContract({

0 commit comments

Comments
 (0)