@@ -16,6 +16,7 @@ import { ERC4337EthersSigner } from "./lib/erc4337-signer";
16
16
import { BigNumber , ethers , providers , utils } from "ethers" ;
17
17
import {
18
18
getChainProvider ,
19
+ getGasPrice ,
19
20
SignerPermissionsInput ,
20
21
SignerWithPermissions ,
21
22
SmartContract ,
@@ -25,6 +26,8 @@ import {
25
26
} from "@thirdweb-dev/sdk" ;
26
27
import { AccountAPI } from "./lib/account" ;
27
28
import { AddressZero } from "@account-abstraction/utils" ;
29
+ import { TransactionDetailsForUserOp } from "./lib/transaction-details" ;
30
+ import { BatchData } from "./lib/base-api" ;
28
31
29
32
export class SmartWalletConnector extends Connector < SmartWalletConnectionArgs > {
30
33
protected config : SmartWalletConfig ;
@@ -165,6 +168,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
165
168
return restrictions . approvedCallTargets . includes ( transaction . getTarget ( ) ) ;
166
169
}
167
170
171
+ /// PREPARED TRANSACTIONS
172
+
168
173
/**
169
174
* Send a single transaction without waiting for confirmations
170
175
* @param transactions
@@ -199,21 +204,14 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
199
204
throw new Error ( "Personal wallet not connected" ) ;
200
205
}
201
206
const signer = await this . getSigner ( ) ;
202
- const targets = transactions . map ( ( tx ) => tx . getTarget ( ) ) ;
203
- const data = transactions . map ( ( tx ) => tx . encode ( ) ) ;
204
- const values = await Promise . all ( transactions . map ( ( tx ) => tx . getValue ( ) ) ) ;
205
- const callData = await this . accountApi . encodeExecuteBatch (
206
- targets ,
207
- values ,
208
- data ,
209
- ) ;
207
+ const { tx, batchData } = await this . prepareBatchTx ( transactions ) ;
210
208
return await signer . sendTransaction (
211
209
{
212
210
to : await signer . getAddress ( ) ,
213
- data : callData ,
211
+ data : tx . encode ( ) ,
214
212
value : 0 ,
215
213
} ,
216
- true , // batched tx flag
214
+ batchData ,
217
215
) ;
218
216
}
219
217
@@ -232,6 +230,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
232
230
} ;
233
231
}
234
232
233
+ /// RAW TRANSACTIONS
234
+
235
235
async sendRaw (
236
236
transaction : utils . Deferrable < providers . TransactionRequest > ,
237
237
) : Promise < providers . TransactionResponse > {
@@ -259,26 +259,14 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
259
259
throw new Error ( "Personal wallet not connected" ) ;
260
260
}
261
261
const signer = await this . getSigner ( ) ;
262
- const resolvedTxs = await Promise . all (
263
- transactions . map ( ( transaction ) =>
264
- ethers . utils . resolveProperties ( transaction ) ,
265
- ) ,
266
- ) ;
267
- const targets = resolvedTxs . map ( ( tx ) => tx . to || AddressZero ) ;
268
- const data = resolvedTxs . map ( ( tx ) => tx . data || "0x" ) ;
269
- const values = resolvedTxs . map ( ( tx ) => tx . value || BigNumber . from ( 0 ) ) ;
270
- const callData = await this . accountApi . encodeExecuteBatch (
271
- targets ,
272
- values ,
273
- data ,
274
- ) ;
262
+ const batch = await this . prepareBatchRaw ( transactions ) ;
275
263
return signer . sendTransaction (
276
264
{
277
265
to : await signer . getAddress ( ) ,
278
- data : callData ,
266
+ data : batch . tx . encode ( ) ,
279
267
value : 0 ,
280
268
} ,
281
- true , // batched tx flag
269
+ batch . batchData , // batched tx flag
282
270
) ;
283
271
}
284
272
@@ -292,6 +280,67 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
292
280
} ;
293
281
}
294
282
283
+ /// ESTIMATION
284
+
285
+ async estimate ( transaction : Transaction ) {
286
+ if ( ! this . accountApi ) {
287
+ throw new Error ( "Personal wallet not connected" ) ;
288
+ }
289
+ return this . estimateTx ( {
290
+ target : transaction . getTarget ( ) ,
291
+ data : transaction . encode ( ) ,
292
+ value : await transaction . getValue ( ) ,
293
+ } ) ;
294
+ }
295
+
296
+ async estimateRaw (
297
+ transaction : utils . Deferrable < providers . TransactionRequest > ,
298
+ ) {
299
+ if ( ! this . accountApi ) {
300
+ throw new Error ( "Personal wallet not connected" ) ;
301
+ }
302
+ const tx = await ethers . utils . resolveProperties ( transaction ) ;
303
+ return this . estimateTx ( {
304
+ target : tx . to || AddressZero ,
305
+ data : tx . data ?. toString ( ) || "" ,
306
+ value : tx . value || BigNumber . from ( 0 ) ,
307
+ } ) ;
308
+ }
309
+
310
+ async estimateBatch ( transactions : Transaction < any > [ ] ) {
311
+ if ( ! this . accountApi ) {
312
+ throw new Error ( "Personal wallet not connected" ) ;
313
+ }
314
+ const { tx, batchData } = await this . prepareBatchTx ( transactions ) ;
315
+ return this . estimateTx (
316
+ {
317
+ target : tx . getTarget ( ) ,
318
+ data : tx . encode ( ) ,
319
+ value : await tx . getValue ( ) ,
320
+ } ,
321
+ batchData ,
322
+ ) ;
323
+ }
324
+
325
+ async estimateBatchRaw (
326
+ transactions : utils . Deferrable < providers . TransactionRequest > [ ] ,
327
+ ) {
328
+ if ( ! this . accountApi ) {
329
+ throw new Error ( "Personal wallet not connected" ) ;
330
+ }
331
+ const { tx, batchData } = await this . prepareBatchRaw ( transactions ) ;
332
+ return this . estimateTx (
333
+ {
334
+ target : tx . getTarget ( ) ,
335
+ data : tx . encode ( ) ,
336
+ value : await tx . getValue ( ) ,
337
+ } ,
338
+ batchData ,
339
+ ) ;
340
+ }
341
+
342
+ //// DEPLOYMENT
343
+
295
344
/**
296
345
* Manually deploy the smart wallet contract. If already deployed this will throw an error.
297
346
* Note that this is not necessary as the smart wallet will be deployed automatically on the first transaction the user makes.
@@ -301,16 +350,16 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
301
350
if ( ! this . accountApi ) {
302
351
throw new Error ( "Personal wallet not connected" ) ;
303
352
}
304
- if ( await this . accountApi . isAcountDeployed ( ) ) {
305
- throw new Error ( "Smart wallet already deployed" ) ;
306
- }
307
353
const signer = await this . getSigner ( ) ;
308
354
const tx = await signer . sendTransaction (
309
355
{
310
356
to : await signer . getAddress ( ) ,
311
357
data : "0x" ,
312
358
} ,
313
- true , // batched tx flag to avoid hitting the Router fallback method
359
+ {
360
+ targets : [ ] ,
361
+ data : [ ] ,
362
+ } , // batched tx flag to avoid hitting the Router fallback method
314
363
) ;
315
364
const receipt = await tx . wait ( ) ;
316
365
return { receipt } ;
@@ -334,6 +383,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
334
383
}
335
384
}
336
385
386
+ //// PERMISSIONS
387
+
337
388
async grantPermissions (
338
389
target : string ,
339
390
permissions : SignerPermissionsInput ,
@@ -472,4 +523,96 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
472
523
} ,
473
524
} ;
474
525
}
526
+
527
+ /// PRIVATE METHODS
528
+
529
+ private async estimateTx (
530
+ tx : TransactionDetailsForUserOp ,
531
+ batchData ?: BatchData ,
532
+ ) {
533
+ if ( ! this . accountApi ) {
534
+ throw new Error ( "Personal wallet not connected" ) ;
535
+ }
536
+ let deployGasLimit = BigNumber . from ( 0 ) ;
537
+ const [ provider , isDeployed ] = await Promise . all ( [
538
+ this . getProvider ( ) ,
539
+ this . isDeployed ( ) ,
540
+ ] ) ;
541
+ if ( ! isDeployed ) {
542
+ deployGasLimit = await this . estimateDeploymentGasLimit ( ) ;
543
+ }
544
+ const [ { callGasLimit : transactionGasLimit } , gasPrice ] = await Promise . all (
545
+ [
546
+ this . accountApi . encodeUserOpCallDataAndGasLimit ( tx , batchData ) ,
547
+ getGasPrice ( provider ) ,
548
+ ] ,
549
+ ) ;
550
+ const transactionCost = transactionGasLimit . mul ( gasPrice ) ;
551
+ const deployCost = deployGasLimit . mul ( gasPrice ) ;
552
+ const totalCost = deployCost . add ( transactionCost ) ;
553
+
554
+ return {
555
+ ether : utils . formatEther ( totalCost ) ,
556
+ wei : totalCost ,
557
+ details : {
558
+ deployGasLimit,
559
+ transactionGasLimit,
560
+ gasPrice,
561
+ transactionCost,
562
+ deployCost,
563
+ totalCost,
564
+ } ,
565
+ } ;
566
+ }
567
+
568
+ private async estimateDeploymentGasLimit ( ) {
569
+ if ( ! this . accountApi ) {
570
+ throw new Error ( "Personal wallet not connected" ) ;
571
+ }
572
+ const initCode = await this . accountApi . getInitCode ( ) ;
573
+ const [ initGas , verificationGasLimit ] = await Promise . all ( [
574
+ this . accountApi . estimateCreationGas ( initCode ) ,
575
+ this . accountApi . getVerificationGasLimit ( ) ,
576
+ ] ) ;
577
+ return BigNumber . from ( verificationGasLimit ) . add ( initGas ) ;
578
+ }
579
+
580
+ private async prepareBatchRaw (
581
+ transactions : ethers . utils . Deferrable < ethers . providers . TransactionRequest > [ ] ,
582
+ ) {
583
+ if ( ! this . accountApi ) {
584
+ throw new Error ( "Personal wallet not connected" ) ;
585
+ }
586
+ const resolvedTxs = await Promise . all (
587
+ transactions . map ( ( transaction ) =>
588
+ ethers . utils . resolveProperties ( transaction ) ,
589
+ ) ,
590
+ ) ;
591
+ const targets = resolvedTxs . map ( ( tx ) => tx . to || AddressZero ) ;
592
+ const data = resolvedTxs . map ( ( tx ) => tx . data || "0x" ) ;
593
+ const values = resolvedTxs . map ( ( tx ) => tx . value || BigNumber . from ( 0 ) ) ;
594
+ return {
595
+ tx : await this . accountApi . prepareExecuteBatch ( targets , values , data ) ,
596
+ batchData : {
597
+ targets,
598
+ data,
599
+ } ,
600
+ } ;
601
+ }
602
+
603
+ private async prepareBatchTx ( transactions : Transaction < any > [ ] ) {
604
+ if ( ! this . accountApi ) {
605
+ throw new Error ( "Personal wallet not connected" ) ;
606
+ }
607
+ const targets = transactions . map ( ( tx ) => tx . getTarget ( ) ) ;
608
+ const data = transactions . map ( ( tx ) => tx . encode ( ) ) ;
609
+ const values = await Promise . all ( transactions . map ( ( tx ) => tx . getValue ( ) ) ) ;
610
+ return {
611
+ tx : await this . accountApi . prepareExecuteBatch ( targets , values , data ) ,
612
+ batchData : {
613
+ targets,
614
+ data,
615
+ } ,
616
+ } ;
617
+ }
475
618
}
0 commit comments