@@ -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,7 @@ 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" ;
28
30
29
31
export class SmartWalletConnector extends Connector < SmartWalletConnectionArgs > {
30
32
protected config : SmartWalletConfig ;
@@ -165,6 +167,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
165
167
return restrictions . approvedCallTargets . includes ( transaction . getTarget ( ) ) ;
166
168
}
167
169
170
+ /// PREPARED TRANSACTIONS
171
+
168
172
/**
169
173
* Send a single transaction without waiting for confirmations
170
174
* @param transactions
@@ -199,18 +203,11 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
199
203
throw new Error ( "Personal wallet not connected" ) ;
200
204
}
201
205
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
- ) ;
206
+ const batchData = await this . prepareBatchTx ( transactions ) ;
210
207
return await signer . sendTransaction (
211
208
{
212
209
to : await signer . getAddress ( ) ,
213
- data : callData ,
210
+ data : batchData . encode ( ) ,
214
211
value : 0 ,
215
212
} ,
216
213
true , // batched tx flag
@@ -232,6 +229,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
232
229
} ;
233
230
}
234
231
232
+ /// RAW TRANSACTIONS
233
+
235
234
async sendRaw (
236
235
transaction : utils . Deferrable < providers . TransactionRequest > ,
237
236
) : Promise < providers . TransactionResponse > {
@@ -259,23 +258,11 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
259
258
throw new Error ( "Personal wallet not connected" ) ;
260
259
}
261
260
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
- ) ;
261
+ const batchData = await this . prepareBatchRaw ( transactions ) ;
275
262
return signer . sendTransaction (
276
263
{
277
264
to : await signer . getAddress ( ) ,
278
- data : callData ,
265
+ data : batchData . encode ( ) ,
279
266
value : 0 ,
280
267
} ,
281
268
true , // batched tx flag
@@ -292,6 +279,75 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
292
279
} ;
293
280
}
294
281
282
+ /// ESTIMATION
283
+
284
+ async estimate ( transaction : Transaction ) {
285
+ if ( ! this . accountApi ) {
286
+ throw new Error ( "Personal wallet not connected" ) ;
287
+ }
288
+ console . log ( "single" , transaction . getTarget ( ) , transaction . encode ( ) . length ) ;
289
+ return this . estimateTx (
290
+ {
291
+ target : transaction . getTarget ( ) ,
292
+ data : transaction . encode ( ) ,
293
+ value : await transaction . getValue ( ) ,
294
+ } ,
295
+ false ,
296
+ ) ;
297
+ }
298
+
299
+ async estimateRaw (
300
+ transaction : utils . Deferrable < providers . TransactionRequest > ,
301
+ ) {
302
+ if ( ! this . accountApi ) {
303
+ throw new Error ( "Personal wallet not connected" ) ;
304
+ }
305
+ const tx = await ethers . utils . resolveProperties ( transaction ) ;
306
+ return this . estimateTx (
307
+ {
308
+ target : tx . to || AddressZero ,
309
+ data : tx . data ?. toString ( ) || "" ,
310
+ value : tx . value || BigNumber . from ( 0 ) ,
311
+ } ,
312
+ false ,
313
+ ) ;
314
+ }
315
+
316
+ async estimateBatch ( transactions : Transaction < any > [ ] ) {
317
+ if ( ! this . accountApi ) {
318
+ throw new Error ( "Personal wallet not connected" ) ;
319
+ }
320
+ const batch = await this . prepareBatchTx ( transactions ) ;
321
+ console . log ( "batch" , batch . getTarget ( ) , batch . encode ( ) . length ) ;
322
+ return this . estimateTx (
323
+ {
324
+ target : batch . getTarget ( ) ,
325
+ data : batch . encode ( ) ,
326
+ value : await batch . getValue ( ) ,
327
+ } ,
328
+ true ,
329
+ ) ;
330
+ }
331
+
332
+ async estimateBatchRaw (
333
+ transactions : utils . Deferrable < providers . TransactionRequest > [ ] ,
334
+ ) {
335
+ if ( ! this . accountApi ) {
336
+ throw new Error ( "Personal wallet not connected" ) ;
337
+ }
338
+ const batch = await this . prepareBatchRaw ( transactions ) ;
339
+ return this . estimateTx (
340
+ {
341
+ target : batch . getTarget ( ) ,
342
+ data : batch . encode ( ) ,
343
+ value : await batch . getValue ( ) ,
344
+ } ,
345
+ true ,
346
+ ) ;
347
+ }
348
+
349
+ //// DEPLOYMENT
350
+
295
351
/**
296
352
* Manually deploy the smart wallet contract. If already deployed this will throw an error.
297
353
* Note that this is not necessary as the smart wallet will be deployed automatically on the first transaction the user makes.
@@ -334,6 +390,8 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
334
390
}
335
391
}
336
392
393
+ //// PERMISSIONS
394
+
337
395
async grantPermissions (
338
396
target : string ,
339
397
permissions : SignerPermissionsInput ,
@@ -472,4 +530,81 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
472
530
} ,
473
531
} ;
474
532
}
533
+
534
+ /// PRIVATE METHODS
535
+
536
+ private async estimateTx ( tx : TransactionDetailsForUserOp , batched : boolean ) {
537
+ if ( ! this . accountApi ) {
538
+ throw new Error ( "Personal wallet not connected" ) ;
539
+ }
540
+ let deployGasLimit = BigNumber . from ( 0 ) ;
541
+ const [ provider , isDeployed ] = await Promise . all ( [
542
+ this . getProvider ( ) ,
543
+ this . isDeployed ( ) ,
544
+ ] ) ;
545
+ if ( ! isDeployed ) {
546
+ deployGasLimit = await this . estimateDeploymentGasLimit ( ) ;
547
+ }
548
+ const [ { callGasLimit : transactionGasLimit } , gasPrice ] = await Promise . all (
549
+ [
550
+ await this . accountApi . encodeUserOpCallDataAndGasLimit ( tx , batched ) ,
551
+ getGasPrice ( provider ) ,
552
+ ] ,
553
+ ) ;
554
+ const transactionCost = transactionGasLimit . mul ( gasPrice ) ;
555
+ const deployCost = deployGasLimit . mul ( gasPrice ) ;
556
+ const totalCost = deployCost . add ( transactionCost ) ;
557
+
558
+ return {
559
+ ether : utils . formatEther ( totalCost ) ,
560
+ wei : totalCost ,
561
+ details : {
562
+ deployGasLimit,
563
+ transactionGasLimit,
564
+ gasPrice,
565
+ transactionCost,
566
+ deployCost,
567
+ totalCost,
568
+ } ,
569
+ } ;
570
+ }
571
+
572
+ private async estimateDeploymentGasLimit ( ) {
573
+ if ( ! this . accountApi ) {
574
+ throw new Error ( "Personal wallet not connected" ) ;
575
+ }
576
+ const initCode = await this . accountApi . getInitCode ( ) ;
577
+ const [ initGas , verificationGasLimit ] = await Promise . all ( [
578
+ this . accountApi . estimateCreationGas ( initCode ) ,
579
+ this . accountApi . getVerificationGasLimit ( ) ,
580
+ ] ) ;
581
+ return BigNumber . from ( verificationGasLimit ) . add ( initGas ) ;
582
+ }
583
+
584
+ private async prepareBatchRaw (
585
+ transactions : ethers . utils . Deferrable < ethers . providers . TransactionRequest > [ ] ,
586
+ ) {
587
+ if ( ! this . accountApi ) {
588
+ throw new Error ( "Personal wallet not connected" ) ;
589
+ }
590
+ const resolvedTxs = await Promise . all (
591
+ transactions . map ( ( transaction ) =>
592
+ ethers . utils . resolveProperties ( transaction ) ,
593
+ ) ,
594
+ ) ;
595
+ const targets = resolvedTxs . map ( ( tx ) => tx . to || AddressZero ) ;
596
+ const data = resolvedTxs . map ( ( tx ) => tx . data || "0x" ) ;
597
+ const values = resolvedTxs . map ( ( tx ) => tx . value || BigNumber . from ( 0 ) ) ;
598
+ return this . accountApi . prepareExecuteBatch ( targets , values , data ) ;
599
+ }
600
+
601
+ private async prepareBatchTx ( transactions : Transaction < any > [ ] ) {
602
+ if ( ! this . accountApi ) {
603
+ throw new Error ( "Personal wallet not connected" ) ;
604
+ }
605
+ const targets = transactions . map ( ( tx ) => tx . getTarget ( ) ) ;
606
+ const data = transactions . map ( ( tx ) => tx . encode ( ) ) ;
607
+ const values = await Promise . all ( transactions . map ( ( tx ) => tx . getValue ( ) ) ) ;
608
+ return this . accountApi . prepareExecuteBatch ( targets , values , data ) ;
609
+ }
475
610
}
0 commit comments