Skip to content

Commit 1a6038c

Browse files
authored
Managed Token Paymaster (#57)
1 parent de4f3fa commit 1a6038c

File tree

2 files changed

+85
-46
lines changed

2 files changed

+85
-46
lines changed

Thirdweb.Console/Program.cs

+7-8
Original file line numberDiff line numberDiff line change
@@ -121,26 +121,25 @@
121121

122122
#region ERC20 Smart Wallet - Base USDC
123123

124-
// var erc20SmartWalletSepolia = await SmartWallet.Create(
124+
// var erc20SmartWallet = await SmartWallet.Create(
125125
// personalWallet: privateKeyWallet,
126126
// chainId: 8453, // base mainnet
127127
// gasless: true,
128128
// factoryAddress: "0xEc87d96E3F324Dcc828750b52994C6DC69C8162b",
129129
// entryPoint: Constants.ENTRYPOINT_ADDRESS_V07,
130-
// erc20PaymasterAddress: "0xb867732eD7f59c77F0D9afB94cE28aEb2B43fada", // TokenPaymaster
131-
// erc20PaymasterToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC
130+
// tokenPaymaster: TokenPaymaster.BASE_USDC
132131
// );
133-
// var erc20SmartWalletSepoliaAddress = await erc20SmartWalletSepolia.GetAddress();
134-
// Console.WriteLine($"ERC20 Smart Wallet address: {erc20SmartWalletSepoliaAddress}");
132+
// var erc20SmartWalletAddress = await erc20SmartWallet.GetAddress();
133+
// Console.WriteLine($"ERC20 Smart Wallet address: {erc20SmartWalletAddress}");
135134

136135
// var selfTransfer = await ThirdwebTransaction.Create(
137-
// wallet: erc20SmartWalletSepolia,
138-
// txInput: new ThirdwebTransactionInput() { To = erc20SmartWalletSepoliaAddress, },
136+
// wallet: erc20SmartWallet,
137+
// txInput: new ThirdwebTransactionInput() { To = erc20SmartWalletAddress, },
139138
// chainId: 8453
140139
// );
141140

142141
// var estimateGas = await ThirdwebTransaction.EstimateGasCosts(selfTransfer);
143-
// Console.WriteLine($"Self transfer gas estimate: {estimateGas.ether}");
142+
// Console.WriteLine($"Self transfer gas estimate: {estimateGas.Ether}");
144143
// Console.WriteLine("Make sure you have enough USDC!");
145144
// Console.ReadLine();
146145

Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs

+78-38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111

1212
namespace Thirdweb;
1313

14+
public enum TokenPaymaster
15+
{
16+
NONE,
17+
BASE_USDC,
18+
}
19+
1420
public class SmartWallet : IThirdwebWallet
1521
{
1622
public ThirdwebClient Client
@@ -35,9 +41,42 @@ public bool IsDeploying
3541
private readonly string _paymasterUrl;
3642
private readonly string _erc20PaymasterAddress;
3743
private readonly string _erc20PaymasterToken;
44+
private readonly BigInteger _erc20PaymasterStorageSlot;
3845
private bool _isApproving;
3946
private bool _isApproved;
4047

48+
private struct TokenPaymasterConfig()
49+
{
50+
public BigInteger ChainId;
51+
public string PaymasterAddress;
52+
public string TokenAddress;
53+
public BigInteger BalanceStorageSlot;
54+
}
55+
56+
private static readonly Dictionary<TokenPaymaster, TokenPaymasterConfig> _tokenPaymasterConfig = new()
57+
{
58+
{
59+
TokenPaymaster.NONE,
60+
new TokenPaymasterConfig()
61+
{
62+
ChainId = 0,
63+
PaymasterAddress = null,
64+
TokenAddress = null,
65+
BalanceStorageSlot = 0
66+
}
67+
},
68+
{
69+
TokenPaymaster.BASE_USDC,
70+
new TokenPaymasterConfig()
71+
{
72+
ChainId = 8453,
73+
PaymasterAddress = "0x0c6199eE133EB4ff8a6bbD03370336C5A5d9D536",
74+
TokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
75+
BalanceStorageSlot =9
76+
}
77+
}
78+
};
79+
4180
private bool UseERC20Paymaster => !string.IsNullOrEmpty(this._erc20PaymasterAddress) && !string.IsNullOrEmpty(this._erc20PaymasterToken);
4281

4382
protected SmartWallet(
@@ -50,7 +89,8 @@ protected SmartWallet(
5089
ThirdwebContract factoryContract,
5190
ThirdwebContract accountContract,
5291
string erc20PaymasterAddress,
53-
string erc20PaymasterToken
92+
string erc20PaymasterToken,
93+
BigInteger erc20PaymasterStorageSlot
5494
)
5595
{
5696
this.Client = personalAccount.Client;
@@ -65,6 +105,7 @@ string erc20PaymasterToken
65105
this._accountContract = accountContract;
66106
this._erc20PaymasterAddress = erc20PaymasterAddress;
67107
this._erc20PaymasterToken = erc20PaymasterToken;
108+
this._erc20PaymasterStorageSlot = erc20PaymasterStorageSlot;
68109
}
69110

70111
public static async Task<SmartWallet> Create(
@@ -76,8 +117,7 @@ public static async Task<SmartWallet> Create(
76117
string entryPoint = null,
77118
string bundlerUrl = null,
78119
string paymasterUrl = null,
79-
string erc20PaymasterAddress = null,
80-
string erc20PaymasterToken = null
120+
TokenPaymaster tokenPaymaster = TokenPaymaster.NONE
81121
)
82122
{
83123
if (!await personalWallet.IsConnected())
@@ -112,7 +152,21 @@ public static async Task<SmartWallet> Create(
112152
accountContract = await ThirdwebContract.Create(personalWallet.Client, accountAddress, chainId, accountAbi);
113153
}
114154

115-
return new SmartWallet(personalWallet, gasless, chainId, bundlerUrl, paymasterUrl, entryPointContract, factoryContract, accountContract, erc20PaymasterAddress, erc20PaymasterToken);
155+
var erc20PmInfo = _tokenPaymasterConfig[tokenPaymaster];
156+
157+
if (tokenPaymaster != TokenPaymaster.NONE)
158+
{
159+
if (entryPointVersion != 7)
160+
{
161+
throw new InvalidOperationException("Token paymasters are only supported in entry point version 7.");
162+
}
163+
if (erc20PmInfo.ChainId != chainId)
164+
{
165+
throw new InvalidOperationException("Token paymaster chain ID does not match the smart account chain ID.");
166+
}
167+
}
168+
169+
return new SmartWallet(personalWallet, gasless, chainId, bundlerUrl, paymasterUrl, entryPointContract, factoryContract, accountContract, erc20PmInfo.PaymasterAddress, erc20PmInfo.TokenAddress, erc20PmInfo.BalanceStorageSlot);
116170
}
117171

118172
public async Task<bool> IsDeployed()
@@ -330,56 +384,42 @@ private async Task<object> SignUserOp(ThirdwebTransactionInput transactionInput,
330384
Signature = Constants.DUMMY_SIG.HexToBytes(),
331385
};
332386

333-
// Update paymaster data if any
334-
335-
var res = await this.GetPaymasterAndData(requestId, EncodeUserOperation(partialUserOp), true);
336-
partialUserOp.Paymaster = res.Paymaster;
337-
partialUserOp.PaymasterData = res.PaymasterData?.HexToBytes() ?? Array.Empty<byte>();
338-
partialUserOp.PreVerificationGas = new HexBigInteger(res.PreVerificationGas ?? "0").Value;
339-
partialUserOp.VerificationGasLimit = new HexBigInteger(res.VerificationGasLimit ?? "0").Value;
340-
partialUserOp.CallGasLimit = new HexBigInteger(res.CallGasLimit ?? "0").Value;
341-
partialUserOp.PaymasterVerificationGasLimit = new HexBigInteger(res.PaymasterVerificationGasLimit ?? "0").Value;
342-
partialUserOp.PaymasterPostOpGasLimit = new HexBigInteger(res.PaymasterPostOpGasLimit ?? "0").Value;
343-
344-
// Estimate gas
387+
// Update Paymaster Data / Estimate gas
345388

346389
if (
347-
(this.UseERC20Paymaster && !this._isApproving)
348-
|| partialUserOp.PreVerificationGas.IsZero
349-
|| partialUserOp.VerificationGasLimit.IsZero
350-
|| partialUserOp.CallGasLimit.IsZero
351-
|| partialUserOp.PaymasterVerificationGasLimit.IsZero
352-
|| partialUserOp.PaymasterPostOpGasLimit.IsZero
390+
this.UseERC20Paymaster && !this._isApproving
353391
)
354392
{
355-
Dictionary<string, object> stateDict = null;
356-
if (this.UseERC20Paymaster && !this._isApproving)
357-
{
358-
var abiEncoder = new ABIEncode();
359-
var slotBytes = abiEncoder.GetABIEncoded(new ABIValue("address", this._accountContract.Address), new ABIValue("uint256", new BigInteger(9)));
360-
var desiredBalance = BigInteger.Pow(2, 96) - 1;
361-
var storageDict = new Dictionary<string, string>
393+
var abiEncoder = new ABIEncode();
394+
var slotBytes = abiEncoder.GetABIEncoded(new ABIValue("address", this._accountContract.Address), new ABIValue("uint256", this._erc20PaymasterStorageSlot));
395+
var desiredBalance = BigInteger.Pow(2, 96) - 1;
396+
var storageDict = new Dictionary<string, string>
362397
{
363398
{ new Sha3Keccack().CalculateHash(slotBytes).BytesToHex().ToString(), desiredBalance.ToHexBigInteger().HexValue.HexToBytes32().BytesToHex() }
364399
};
365-
stateDict = new Dictionary<string, object> { { this._erc20PaymasterToken, new { stateDiff = storageDict } } };
366-
res = await this.GetPaymasterAndData(requestId, EncodeUserOperation(partialUserOp), simulation);
367-
partialUserOp.Paymaster = res.Paymaster;
368-
partialUserOp.PaymasterData = res.PaymasterData.HexToBytes();
369-
}
400+
var stateDict = new Dictionary<string, object> { { this._erc20PaymasterToken, new { stateDiff = storageDict } } };
401+
var res = await this.GetPaymasterAndData(requestId, EncodeUserOperation(partialUserOp), simulation);
402+
partialUserOp.Paymaster = res.Paymaster;
403+
partialUserOp.PaymasterData = res.PaymasterData.HexToBytes();
370404

371405
var gasEstimates = await BundlerClient.EthEstimateUserOperationGas(this.Client, this._bundlerUrl, requestId, EncodeUserOperation(partialUserOp), this._entryPointContract.Address, stateDict);
372406
partialUserOp.CallGasLimit = 21000 + new HexBigInteger(gasEstimates.CallGasLimit).Value;
373407
partialUserOp.VerificationGasLimit = new HexBigInteger(gasEstimates.VerificationGasLimit).Value;
374408
partialUserOp.PreVerificationGas = new HexBigInteger(gasEstimates.PreVerificationGas).Value;
375409
partialUserOp.PaymasterVerificationGasLimit = new HexBigInteger(gasEstimates.PaymasterVerificationGasLimit).Value;
376410
partialUserOp.PaymasterPostOpGasLimit = new HexBigInteger(gasEstimates.PaymasterPostOpGasLimit).Value;
411+
}
412+
else
413+
{
377414

378-
// Update paymaster data if any
379-
380-
res = await this.GetPaymasterAndData(requestId, EncodeUserOperation(partialUserOp), simulation);
415+
var res = await this.GetPaymasterAndData(requestId, EncodeUserOperation(partialUserOp), true);
381416
partialUserOp.Paymaster = res.Paymaster;
382-
partialUserOp.PaymasterData = res.PaymasterData.HexToBytes();
417+
partialUserOp.PaymasterData = res.PaymasterData?.HexToBytes() ?? Array.Empty<byte>();
418+
partialUserOp.PreVerificationGas = new HexBigInteger(res.PreVerificationGas ?? "0").Value;
419+
partialUserOp.VerificationGasLimit = new HexBigInteger(res.VerificationGasLimit ?? "0").Value;
420+
partialUserOp.CallGasLimit = new HexBigInteger(res.CallGasLimit ?? "0").Value;
421+
partialUserOp.PaymasterVerificationGasLimit = new HexBigInteger(res.PaymasterVerificationGasLimit ?? "0").Value;
422+
partialUserOp.PaymasterPostOpGasLimit = new HexBigInteger(res.PaymasterPostOpGasLimit ?? "0").Value;
383423
}
384424

385425
// Hash, sign and encode the user operation

0 commit comments

Comments
 (0)