Skip to content

Commit 94da146

Browse files
committed
TokenPaymaster (BASE_USDC, CELO_USD, LISK_LSK)
1 parent b4eed8a commit 94da146

File tree

6 files changed

+175
-38
lines changed

6 files changed

+175
-38
lines changed

packages/thirdweb/src/wallets/smart/index.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import type { PreparedTransaction } from "../../transaction/prepare-transaction.
2222
import { readContract } from "../../transaction/read-contract.js";
2323
import { getAddress } from "../../utils/address.js";
2424
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
25-
import { concatHex } from "../../utils/encoding/helpers/concat-hex.js";
2625
import type { Hex } from "../../utils/encoding/hex.js";
2726
import { parseTypedData } from "../../utils/signatures/helpers/parseTypedData.js";
2827
import type {
@@ -45,7 +44,11 @@ import {
4544
prepareBatchExecute,
4645
prepareExecute,
4746
} from "./lib/calls.js";
48-
import { getDefaultAccountFactory } from "./lib/constants.js";
47+
import {
48+
ENTRYPOINT_ADDRESS_v0_6,
49+
getDefaultAccountFactory,
50+
getEntryPointVersion,
51+
} from "./lib/constants.js";
4952
import {
5053
clearAccountDeploying,
5154
createUnsignedUserOp,
@@ -58,6 +61,7 @@ import type {
5861
SmartAccountOptions,
5962
SmartWalletConnectionOptions,
6063
SmartWalletOptions,
64+
TokenPaymasterConfig,
6165
UserOperationV06,
6266
UserOperationV07,
6367
} from "./types.js";
@@ -196,12 +200,50 @@ export async function disconnectSmartWallet(
196200
async function createSmartAccount(
197201
options: SmartAccountOptions,
198202
): Promise<Account> {
203+
let erc20Paymaster = options.overrides?.tokenPaymaster;
204+
if (erc20Paymaster && typeof erc20Paymaster === "string") {
205+
if (
206+
getEntryPointVersion(
207+
options.overrides?.entrypointAddress || ENTRYPOINT_ADDRESS_v0_6,
208+
) !== "v0.7"
209+
) {
210+
throw new Error(
211+
"Token paymaster is only supported for entrypoint version v0.7",
212+
);
213+
}
214+
switch (erc20Paymaster) {
215+
case "BASE_USDC":
216+
erc20Paymaster = {
217+
chainId: 8453,
218+
paymasterAddress: "0x2222f2738BE6bB7aA0Bfe4AEeAf2908172CF5539",
219+
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
220+
balanceStorageSlot: 9,
221+
};
222+
break;
223+
case "CELO_CUSD":
224+
erc20Paymaster = {
225+
chainId: 42220,
226+
paymasterAddress: "0x3feA3c5744D715ff46e91C4e5C9a94426DfF2aF9",
227+
tokenAddress: "0x765DE816845861e75A25fCA122bb6898B8B1282a",
228+
balanceStorageSlot: 9,
229+
};
230+
break;
231+
case "LISK_LSK":
232+
erc20Paymaster = {
233+
chainId: 1135,
234+
paymasterAddress: "0x9eb8cf7fBa5ed9EeDCC97a0d52254cc0e9B1AC25",
235+
tokenAddress: "0xac485391EB2d7D88253a7F1eF18C37f4242D1A24",
236+
balanceStorageSlot: 9,
237+
};
238+
break;
239+
}
240+
}
241+
199242
const { accountContract } = options;
200243
const account: Account = {
201244
address: getAddress(accountContract.address),
202245
async sendTransaction(transaction: SendTransactionOption) {
203246
// if erc20 paymaster - check allowance and approve if needed
204-
const erc20Paymaster = options.overrides?.erc20Paymaster;
205247
let paymasterOverride:
206248
| undefined
207249
| ((
@@ -215,12 +257,7 @@ async function createSmartAccount(
215257
});
216258
const paymasterCallback = async (): Promise<PaymasterResult> => {
217259
return {
218-
paymasterAndData: concatHex([
219-
erc20Paymaster.address as Hex,
220-
erc20Paymaster?.token as Hex,
221-
]),
222-
// for 0.7 compatibility
223-
paymaster: erc20Paymaster.address as Hex,
260+
paymaster: erc20Paymaster.paymasterAddress as Hex,
224261
paymasterData: "0x",
225262
};
226263
};
@@ -436,13 +473,10 @@ async function createSmartAccount(
436473
async function approveERC20(args: {
437474
accountContract: ThirdwebContract;
438475
options: SmartAccountOptions;
439-
erc20Paymaster: {
440-
address: string;
441-
token: string;
442-
};
476+
erc20Paymaster: TokenPaymasterConfig;
443477
}) {
444478
const { accountContract, erc20Paymaster, options } = args;
445-
const tokenAddress = erc20Paymaster.token;
479+
const tokenAddress = erc20Paymaster.tokenAddress;
446480
const tokenContract = getContract({
447481
address: tokenAddress,
448482
chain: accountContract.chain,
@@ -451,7 +485,7 @@ async function approveERC20(args: {
451485
const accountAllowance = await allowance({
452486
contract: tokenContract,
453487
owner: accountContract.address,
454-
spender: erc20Paymaster.address,
488+
spender: erc20Paymaster.paymasterAddress,
455489
});
456490

457491
if (accountAllowance > 0n) {
@@ -460,7 +494,7 @@ async function approveERC20(args: {
460494

461495
const approveTx = approve({
462496
contract: tokenContract,
463-
spender: erc20Paymaster.address,
497+
spender: erc20Paymaster.paymasterAddress,
464498
amountWei: maxUint96 - 1n,
465499
});
466500
const transaction = await toSerializableTransaction({
@@ -478,7 +512,7 @@ async function approveERC20(args: {
478512
...options,
479513
overrides: {
480514
...options.overrides,
481-
erc20Paymaster: undefined,
515+
tokenPaymaster: undefined,
482516
},
483517
},
484518
});

packages/thirdweb/src/wallets/smart/lib/bundler.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,21 @@ export async function bundleUserOp(args: {
6767
* ```
6868
* @walletUtils
6969
*/
70-
export async function estimateUserOpGas(args: {
71-
userOp: UserOperationV06 | UserOperationV07;
72-
options: BundlerOptions;
73-
}): Promise<EstimationResult> {
70+
export async function estimateUserOpGas(
71+
args: {
72+
userOp: UserOperationV06 | UserOperationV07;
73+
options: BundlerOptions;
74+
},
75+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
76+
stateOverrides?: any,
77+
): Promise<EstimationResult> {
7478
const res = await sendBundlerRequest({
7579
...args,
7680
operation: "eth_estimateUserOperationGas",
7781
params: [
7882
hexlifyUserOp(args.userOp),
7983
args.options.entrypointAddress ?? ENTRYPOINT_ADDRESS_v0_6,
84+
stateOverrides,
8085
],
8186
});
8287

packages/thirdweb/src/wallets/smart/lib/userop.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { concat } from "viem";
1+
import { concat, encodePacked, keccak256, toBytes, toHex } from "viem";
22
import type { Chain } from "../../../chains/types.js";
33
import type { ThirdwebClient } from "../../../client/client.js";
44
import {
@@ -23,6 +23,7 @@ import type {
2323
BundlerOptions,
2424
PaymasterResult,
2525
SmartWalletOptions,
26+
TokenPaymasterConfig,
2627
UserOperationV06,
2728
UserOperationV07,
2829
} from "../types.js";
@@ -361,15 +362,41 @@ async function populateUserOp_v0_7(args: {
361362
paymasterResult.paymasterVerificationGasLimit;
362363
} else {
363364
// otherwise fallback to bundler for gas limits
364-
const estimates = await estimateUserOpGas({
365-
userOp: partialOp,
366-
options: bundlerOptions,
367-
});
365+
const stateOverrides = overrides?.tokenPaymaster
366+
? {
367+
[(overrides.tokenPaymaster as TokenPaymasterConfig).tokenAddress]: {
368+
stateDiff: {
369+
[keccak256(
370+
encodePacked(
371+
["address", "uint256"],
372+
[
373+
accountContract.address,
374+
BigInt(
375+
(overrides?.tokenPaymaster as TokenPaymasterConfig)
376+
.balanceStorageSlot,
377+
),
378+
],
379+
),
380+
)]: toBytes(toHex(BigInt(2) * BigInt(96) - BigInt(1)), {
381+
size: 32,
382+
}),
383+
},
384+
},
385+
}
386+
: {};
387+
const estimates = await estimateUserOpGas(
388+
{
389+
userOp: partialOp,
390+
options: bundlerOptions,
391+
},
392+
stateOverrides,
393+
);
368394
partialOp.callGasLimit = estimates.callGasLimit;
369395
partialOp.verificationGasLimit = estimates.verificationGasLimit;
370396
partialOp.preVerificationGas = estimates.preVerificationGas;
371-
partialOp.paymasterPostOpGasLimit =
372-
paymasterResult.paymasterPostOpGasLimit || 0n;
397+
partialOp.paymasterPostOpGasLimit = overrides?.tokenPaymaster
398+
? 500_000n
399+
: paymasterResult.paymasterPostOpGasLimit || 0n;
373400
partialOp.paymasterVerificationGasLimit =
374401
paymasterResult.paymasterVerificationGasLimit || 0n;
375402
// need paymaster to re-sign after estimates
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { base } from "src/chains/chain-definitions/base.js";
2+
import { sendTransaction } from "src/transaction/actions/send-transaction.js";
3+
import { prepareTransaction } from "src/transaction/prepare-transaction.js";
4+
import { beforeAll, describe, expect, it } from "vitest";
5+
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
6+
import { setThirdwebDomains } from "../../utils/domains.js";
7+
import type { Account } from "../interfaces/wallet.js";
8+
import { privateKeyToAccount } from "../private-key.js";
9+
import { smartWallet } from "./smart-wallet.js";
10+
11+
let personalAccount: Account;
12+
13+
const client = TEST_CLIENT;
14+
15+
describe.runIf(process.env.TW_SECRET_KEY).skip.sequential(
16+
"SmartWallet policy tests",
17+
{
18+
retry: 0,
19+
timeout: 240_000,
20+
},
21+
() => {
22+
beforeAll(async () => {
23+
setThirdwebDomains({
24+
rpc: "rpc.thirdweb-dev.com",
25+
storage: "storage.thirdweb-dev.com",
26+
bundler: "bundler.thirdweb-dev.com",
27+
});
28+
personalAccount = await privateKeyToAccount({
29+
client,
30+
privateKey:
31+
"edf401e8ddbb743f3353b055081cb220ce4c5c04e08da162d86e0dba7c6f0f01", // 0xa470E7c88611364f55B2d7912613e10AF2eA918D
32+
});
33+
});
34+
35+
it("can self transfer with BASE_USDC", async () => {
36+
const wallet = smartWallet({
37+
chain: base,
38+
gasless: true,
39+
overrides: {
40+
tokenPaymaster: "BASE_USDC",
41+
},
42+
});
43+
const smartAccount = await wallet.connect({
44+
client: TEST_CLIENT,
45+
personalAccount,
46+
});
47+
48+
console.log("smartAccount", smartAccount.address);
49+
50+
const tx = prepareTransaction({
51+
client,
52+
chain: base,
53+
to: smartAccount.address,
54+
value: 0n,
55+
});
56+
const receipt = await sendTransaction({
57+
transaction: tx,
58+
account: smartAccount,
59+
});
60+
expect(receipt.transactionHash).toBeDefined();
61+
});
62+
},
63+
);

packages/thirdweb/src/wallets/smart/smart-wallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ import { getDefaultAccountFactory } from "./lib/constants.js";
118118
* accountAddress: "0x...", // override account address
119119
* accountSalt: "0x...", // override account salt
120120
* entrypointAddress: "0x...", // override entrypoint address
121-
* erc20Paymaster: { ... }, // enable erc20 paymaster
121+
* erc20Paymaster: "BASE_USDC", // enable erc20 paymaster
122122
* bundlerUrl: "https://...", // override bundler url
123123
* paymaster: (userOp) => { ... }, // override paymaster
124124
* ...

packages/thirdweb/src/wallets/smart/types.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import type { Hex } from "../../utils/encoding/hex.js";
88
import type { Prettify } from "../../utils/type-utils.js";
99
import type { Account, SendTransactionOption } from "../interfaces/wallet.js";
1010

11+
export type TokenPaymasterConfig = {
12+
chainId: number;
13+
paymasterAddress: string;
14+
tokenAddress: string;
15+
balanceStorageSlot: number;
16+
};
17+
1118
export type SmartWalletOptions = Prettify<
1219
{
1320
chain: Chain; // TODO consider making default chain optional
@@ -17,24 +24,25 @@ export type SmartWalletOptions = Prettify<
1724
accountAddress?: string;
1825
accountSalt?: string;
1926
entrypointAddress?: string;
20-
erc20Paymaster?: {
21-
address: string;
22-
token: string;
23-
};
27+
tokenPaymaster?:
28+
| "BASE_USDC"
29+
| "CELO_CUSD"
30+
| "LISK_LSK"
31+
| TokenPaymasterConfig;
2432
paymaster?: (
25-
userOp: UserOperationV06 | UserOperationV07,
33+
userOp: UserOperationV06 | UserOperationV07
2634
) => Promise<PaymasterResult>;
2735
predictAddress?: (factoryContract: ThirdwebContract) => Promise<string>;
2836
createAccount?: (
29-
factoryContract: ThirdwebContract,
37+
factoryContract: ThirdwebContract
3038
) => PreparedTransaction;
3139
execute?: (
3240
accountContract: ThirdwebContract,
33-
transaction: SendTransactionOption,
41+
transaction: SendTransactionOption
3442
) => PreparedTransaction;
3543
executeBatch?: (
3644
accountContract: ThirdwebContract,
37-
transactions: SendTransactionOption[],
45+
transactions: SendTransactionOption[]
3846
) => PreparedTransaction;
3947
getAccountNonce?: (accountContract: ThirdwebContract) => Promise<bigint>;
4048
};
@@ -204,7 +212,7 @@ export type UserOperationReceipt = {
204212
};
205213

206214
export function formatUserOperationReceipt(
207-
userOpReceiptRaw: UserOperationReceipt,
215+
userOpReceiptRaw: UserOperationReceipt
208216
) {
209217
const { receipt: transactionReceipt } = userOpReceiptRaw;
210218

0 commit comments

Comments
 (0)