@@ -38,6 +38,8 @@ public class SmartWallet
3838 private bool _deployed ;
3939 private bool _deploying ;
4040 private bool _initialized ;
41+ private bool _approved ;
42+ private bool _approving ;
4143
4244 public List < string > Accounts { get ; internal set ; }
4345 public string PersonalAddress { get ; internal set ; }
@@ -53,6 +55,8 @@ public SmartWallet(Web3 personalWeb3, ThirdwebSDK.SmartWalletConfig config)
5355 {
5456 factoryAddress = config . factoryAddress ,
5557 gasless = config . gasless ,
58+ erc20PaymasterAddress = config . erc20PaymasterAddress ,
59+ erc20TokenAddress = config . erc20TokenAddress ,
5660 bundlerUrl = string . IsNullOrEmpty ( config . bundlerUrl ) ? $ "https://{ ThirdwebManager . Instance . SDK . session . CurrentChainData . chainName } .bundler.thirdweb.com" : config . bundlerUrl ,
5761 paymasterUrl = string . IsNullOrEmpty ( config . paymasterUrl ) ? $ "https://{ ThirdwebManager . Instance . SDK . session . CurrentChainData . chainName } .bundler.thirdweb.com" : config . paymasterUrl ,
5862 entryPointAddress = string . IsNullOrEmpty ( config . entryPointAddress ) ? Constants . DEFAULT_ENTRYPOINT_ADDRESS : config . entryPointAddress ,
@@ -61,6 +65,8 @@ public SmartWallet(Web3 personalWeb3, ThirdwebSDK.SmartWalletConfig config)
6165 _deployed = false ;
6266 _initialized = false ;
6367 _deploying = false ;
68+ _approved = false ;
69+ _approving = false ;
6470 }
6571
6672 internal async Task < string > GetPersonalAddress ( )
@@ -197,8 +203,6 @@ private async Task<RpcResponseMessage> CreateUserOpAndSend(RpcRequestMessage req
197203 var transactionInput = JsonConvert . DeserializeObject < TransactionInput > ( JsonConvert . SerializeObject ( paramList [ 0 ] ) ) ;
198204 var dummySig = Constants . DUMMY_SIG ;
199205
200- var ( initCode , gas ) = await GetInitCode ( ) ;
201-
202206 var executeFn = new AccountContract . ExecuteFunction
203207 {
204208 Target = transactionInput . To ,
@@ -208,8 +212,37 @@ private async Task<RpcResponseMessage> CreateUserOpAndSend(RpcRequestMessage req
208212 } ;
209213 var executeInput = executeFn . CreateTransactionInput ( Accounts [ 0 ] ) ;
210214
215+ // Approve ERC20 tokens if any
216+
217+ if ( ! string . IsNullOrEmpty ( Config . erc20PaymasterAddress ) && ! _approved && ! _approving )
218+ {
219+ try
220+ {
221+ _approving = true ;
222+ var tokenContract = ThirdwebManager . Instance . SDK . GetContract ( Config . erc20TokenAddress ) ;
223+ var approvedAmount = await tokenContract . ERC20 . AllowanceOf ( Accounts [ 0 ] , Config . erc20PaymasterAddress ) ;
224+ if ( BigInteger . Parse ( approvedAmount . value ) == 0 )
225+ {
226+ ThirdwebDebug . Log ( $ "Approving tokens for ERC20Paymaster spending") ;
227+ _deploying = false ;
228+ await tokenContract . ERC20 . SetAllowance ( Config . erc20PaymasterAddress , ( BigInteger . Pow ( 2 , 96 ) - 1 ) . ToString ( ) . ToEth ( ) ) ;
229+ }
230+ _approved = true ;
231+ _approving = false ;
232+ await UpdateDeploymentStatus ( ) ;
233+ }
234+ catch ( Exception e )
235+ {
236+ _approving = false ;
237+ _approved = false ;
238+ throw new Exception ( $ "Approving tokens for ERC20Paymaster spending failed: { e . Message } ") ;
239+ }
240+ }
241+
211242 // Create the user operation and its safe (hexified) version
212243
244+ var ( initCode , gas ) = await GetInitCode ( ) ;
245+
213246 var gasPrices = await Utils . GetGasPriceAsync ( ThirdwebManager . Instance . SDK . session . ChainId ) ;
214247
215248 var partialUserOp = new EntryPointContract . UserOperation ( )
@@ -235,7 +268,9 @@ private async Task<RpcResponseMessage> CreateUserOpAndSend(RpcRequestMessage req
235268
236269 var gasEstimates = await BundlerClient . EthEstimateUserOperationGas ( Config . bundlerUrl , apiKey , requestMessage . Id , partialUserOp . EncodeUserOperation ( ) , Config . entryPointAddress ) ;
237270 partialUserOp . CallGasLimit = 50000 + new HexBigInteger ( gasEstimates . CallGasLimit ) . Value ;
238- partialUserOp . VerificationGasLimit = new HexBigInteger ( gasEstimates . VerificationGas ) . Value ;
271+ partialUserOp . VerificationGasLimit = string . IsNullOrEmpty ( Config . erc20PaymasterAddress )
272+ ? new HexBigInteger ( gasEstimates . VerificationGas ) . Value
273+ : new HexBigInteger ( gasEstimates . VerificationGas ) . Value * 3 ;
239274 partialUserOp . PreVerificationGas = new HexBigInteger ( gasEstimates . PreVerificationGas ) . Value ;
240275
241276 // Update paymaster data if any
@@ -258,8 +293,8 @@ private async Task<RpcResponseMessage> CreateUserOpAndSend(RpcRequestMessage req
258293 string txHash = null ;
259294 while ( txHash == null )
260295 {
261- var getUserOpResponse = await BundlerClient . EthGetUserOperationByHash ( Config . bundlerUrl , apiKey , requestMessage . Id , userOpHash ) ;
262- txHash = getUserOpResponse ? . transactionHash ;
296+ var userOpReceipt = await BundlerClient . EthGetUserOperationReceipt ( Config . bundlerUrl , apiKey , requestMessage . Id , userOpHash ) ;
297+ txHash = userOpReceipt ? . receipt ? . TransactionHash ;
263298 await new WaitForSecondsRealtime ( 1f ) ;
264299 }
265300 ThirdwebDebug . Log ( "Tx Hash: " + txHash ) ;
@@ -297,9 +332,19 @@ private async Task<BigInteger> GetNonce()
297332
298333 private async Task < byte [ ] > GetPaymasterAndData ( object requestId , UserOperationHexified userOp , string apiKey )
299334 {
300- return Config . gasless
301- ? ( await BundlerClient . PMSponsorUserOperation ( Config . paymasterUrl , apiKey , requestId , userOp , Config . entryPointAddress ) ) . paymasterAndData . HexStringToByteArray ( )
302- : new byte [ ] { } ;
335+ if ( ! string . IsNullOrEmpty ( Config . erc20PaymasterAddress ) && ! _approving )
336+ {
337+ return Config . erc20PaymasterAddress . HexToByteArray ( ) ;
338+ }
339+ else if ( Config . gasless )
340+ {
341+ var paymasterAndData = await BundlerClient . PMSponsorUserOperation ( Config . paymasterUrl , apiKey , requestId , userOp , Config . entryPointAddress ) ;
342+ return paymasterAndData . paymasterAndData . HexToByteArray ( ) ;
343+ }
344+ else
345+ {
346+ return new byte [ ] { } ;
347+ }
303348 }
304349 }
305350}
0 commit comments