@0xsequence/account 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/0xsequence-account.cjs.dev.js +86 -27
- package/dist/0xsequence-account.cjs.prod.js +86 -27
- package/dist/0xsequence-account.esm.js +86 -27
- package/dist/declarations/src/account.d.ts +6 -5
- package/package.json +10 -10
- package/src/account.ts +102 -38
- package/src/orchestrator/wrapper.ts +85 -0
- package/src/signer.ts +1 -1
|
@@ -140,8 +140,9 @@ class AccountSigner {
|
|
|
140
140
|
const finalTransactions = [...prepare.transactions, ...encodeGasRefundTransaction(feeOption)];
|
|
141
141
|
return this.account.sendTransaction(finalTransactions, this.chainId, prepare.feeQuote, undefined, undefined, (_this$options4 = this.options) != null && _this$options4.nonceSpace ? {
|
|
142
142
|
nonceSpace: this.options.nonceSpace
|
|
143
|
-
} : undefined);
|
|
143
|
+
} : undefined); // Will always have a transaction response
|
|
144
144
|
}
|
|
145
|
+
|
|
145
146
|
getBalance(blockTag) {
|
|
146
147
|
return this.provider.getBalance(this.account.address, blockTag);
|
|
147
148
|
}
|
|
@@ -410,6 +411,19 @@ class Account {
|
|
|
410
411
|
throw new Error(`Wallet ${this.address} is not fully migrated`);
|
|
411
412
|
}
|
|
412
413
|
}
|
|
414
|
+
async predecorateSignedTransactions(status, chainId) {
|
|
415
|
+
// Request signed predecorate transactions from child wallets
|
|
416
|
+
const bundles = await this.orchestrator.predecorateSignedTransactions({
|
|
417
|
+
chainId
|
|
418
|
+
});
|
|
419
|
+
// Get signed predecorate transaction
|
|
420
|
+
const predecorated = await this.predecorateTransactions([], status, chainId);
|
|
421
|
+
if (core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
|
|
422
|
+
// Sign it
|
|
423
|
+
bundles.push(await this.signTransactions(predecorated, chainId));
|
|
424
|
+
}
|
|
425
|
+
return bundles;
|
|
426
|
+
}
|
|
413
427
|
async predecorateTransactions(txs, status, chainId) {
|
|
414
428
|
// if onchain wallet config is not up to date
|
|
415
429
|
// then we should append an extra transaction that updates it
|
|
@@ -421,34 +435,49 @@ class Account {
|
|
|
421
435
|
}
|
|
422
436
|
return txs;
|
|
423
437
|
}
|
|
424
|
-
decorateTransactions(
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
|
|
438
|
+
async decorateTransactions(bundles, status, chainId) {
|
|
439
|
+
var _chainId;
|
|
440
|
+
if (!Array.isArray(bundles)) {
|
|
441
|
+
// Recurse with array
|
|
442
|
+
return this.decorateTransactions([bundles], status, chainId);
|
|
428
443
|
}
|
|
444
|
+
|
|
445
|
+
// Default to chainId of first bundle when not supplied
|
|
446
|
+
chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
|
|
447
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
448
|
+
const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
|
|
449
|
+
if (!hasBootstrapTxs && bundles.length === 1) {
|
|
450
|
+
return bundles[0];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Intent defaults to first bundle when no bootstrap transaction
|
|
454
|
+
const {
|
|
455
|
+
entrypoint
|
|
456
|
+
} = hasBootstrapTxs ? bootstrapBundle : bundles[0];
|
|
429
457
|
const decoratedBundle = {
|
|
430
|
-
entrypoint
|
|
431
|
-
chainId
|
|
432
|
-
|
|
433
|
-
|
|
458
|
+
entrypoint,
|
|
459
|
+
chainId,
|
|
460
|
+
// Intent of the first bundle is used
|
|
461
|
+
intent: bundles[0].intent,
|
|
462
|
+
transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
|
|
434
463
|
to: bundle.entrypoint,
|
|
435
464
|
data: core.commons.transaction.encodeBundleExecData(bundle),
|
|
436
465
|
gasLimit: 0,
|
|
437
466
|
delegateCall: false,
|
|
438
467
|
revertOnError: true,
|
|
439
468
|
value: 0
|
|
440
|
-
}]
|
|
469
|
+
}))]
|
|
441
470
|
};
|
|
442
471
|
|
|
443
472
|
// Re-compute the meta-transaction id to use the guest module subdigest
|
|
444
473
|
if (!status.onChain.deployed) {
|
|
445
|
-
decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule,
|
|
474
|
+
decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
|
|
446
475
|
}
|
|
447
476
|
return decoratedBundle;
|
|
448
477
|
}
|
|
449
|
-
decorateSignature(signature, status) {
|
|
478
|
+
async decorateSignature(signature, status) {
|
|
450
479
|
if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
|
|
451
|
-
return
|
|
480
|
+
return signature;
|
|
452
481
|
}
|
|
453
482
|
const coder = this.coders.signature;
|
|
454
483
|
const chain = status.presignedConfigurations.map(c => c.signature);
|
|
@@ -467,14 +496,13 @@ class Account {
|
|
|
467
496
|
signatures
|
|
468
497
|
});
|
|
469
498
|
}
|
|
470
|
-
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
|
|
499
|
+
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
|
|
471
500
|
// If we are signing a digest for chainId zero then we can never be fully migrated
|
|
472
501
|
// because Sequence v1 doesn't allow for signing a message on "all chains"
|
|
473
502
|
|
|
474
503
|
// So we ignore the state on "chain zero" and instead use one of the states of the networks
|
|
475
504
|
// wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
|
|
476
505
|
// the behaviour of being migrated on all chains
|
|
477
|
-
|
|
478
506
|
const chainRef = ethers.ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
|
|
479
507
|
const status = await this.status(chainRef);
|
|
480
508
|
this.mustBeFullyMigrated(status);
|
|
@@ -485,7 +513,7 @@ class Account {
|
|
|
485
513
|
throw new Error('Wallet cannot validate onchain');
|
|
486
514
|
}
|
|
487
515
|
const wallet = this.walletForStatus(chainId, status);
|
|
488
|
-
const signature = await wallet.signDigest(digest);
|
|
516
|
+
const signature = await wallet.signDigest(digest, metadata);
|
|
489
517
|
const decorated = decorate ? this.decorateSignature(signature, status) : signature;
|
|
490
518
|
|
|
491
519
|
// If the wallet can't validate onchain then we
|
|
@@ -504,8 +532,8 @@ class Account {
|
|
|
504
532
|
}
|
|
505
533
|
return decorated;
|
|
506
534
|
}
|
|
507
|
-
buildEIP6492Signature(signature, status, chainId) {
|
|
508
|
-
const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
|
|
535
|
+
async buildEIP6492Signature(signature, status, chainId) {
|
|
536
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
509
537
|
if (bootstrapBundle.transactions.length === 0) {
|
|
510
538
|
throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
|
|
511
539
|
}
|
|
@@ -561,8 +589,12 @@ class Account {
|
|
|
561
589
|
* by any of the migrations.
|
|
562
590
|
*
|
|
563
591
|
*/
|
|
564
|
-
buildBootstrapTransactions(status, chainId) {
|
|
565
|
-
|
|
592
|
+
async buildBootstrapTransactions(status, chainId) {
|
|
593
|
+
var _bundle$transactions;
|
|
594
|
+
const bundle = await this.orchestrator.buildDeployTransaction({
|
|
595
|
+
chainId
|
|
596
|
+
});
|
|
597
|
+
const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
|
|
566
598
|
|
|
567
599
|
// Add wallet deployment if needed
|
|
568
600
|
if (!status.onChain.deployed) {
|
|
@@ -571,6 +603,7 @@ class Account {
|
|
|
571
603
|
const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
|
|
572
604
|
transactions.push(...deployTransaction.transactions);
|
|
573
605
|
}
|
|
606
|
+
transactions.length;
|
|
574
607
|
|
|
575
608
|
// Get pending migrations
|
|
576
609
|
transactions.push(...status.signedMigrations.map(m => ({
|
|
@@ -620,9 +653,20 @@ class Account {
|
|
|
620
653
|
const status = pstatus || (await this.status(chainId));
|
|
621
654
|
this.mustBeFullyMigrated(status);
|
|
622
655
|
const wallet = this.walletForStatus(chainId, status);
|
|
656
|
+
const metadata = {
|
|
657
|
+
address: this.address,
|
|
658
|
+
digest: '',
|
|
659
|
+
// Set in wallet.signTransactions
|
|
660
|
+
chainId,
|
|
661
|
+
config: {
|
|
662
|
+
version: this.version
|
|
663
|
+
},
|
|
664
|
+
decorate: true,
|
|
665
|
+
cantValidateBehavior: 'ignore'
|
|
666
|
+
};
|
|
623
667
|
const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
|
|
624
668
|
space: options == null ? void 0 : options.nonceSpace
|
|
625
|
-
});
|
|
669
|
+
}, metadata);
|
|
626
670
|
return _extends({}, signed, {
|
|
627
671
|
signature: await this.decorateSignature(signed.signature, status)
|
|
628
672
|
});
|
|
@@ -701,9 +745,12 @@ class Account {
|
|
|
701
745
|
};
|
|
702
746
|
}
|
|
703
747
|
async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
|
|
704
|
-
|
|
748
|
+
if (!Array.isArray(signedBundle)) {
|
|
749
|
+
return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
|
|
750
|
+
}
|
|
751
|
+
const status = pstatus || (await this.status(signedBundle[0].chainId));
|
|
705
752
|
this.mustBeFullyMigrated(status);
|
|
706
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, status);
|
|
753
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, status);
|
|
707
754
|
callback == null ? void 0 : callback(decoratedBundle);
|
|
708
755
|
return this.relayer(chainId).relay(decoratedBundle, quote);
|
|
709
756
|
}
|
|
@@ -736,7 +783,7 @@ class Account {
|
|
|
736
783
|
nonce: 0 // The relayer also ignored the nonce
|
|
737
784
|
};
|
|
738
785
|
|
|
739
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
|
|
786
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
|
|
740
787
|
const data = core.commons.transaction.encodeBundleExecData(decoratedBundle);
|
|
741
788
|
const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
|
|
742
789
|
return _extends({}, res, {
|
|
@@ -760,9 +807,21 @@ class Account {
|
|
|
760
807
|
async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
|
|
761
808
|
const status = await this.status(chainId);
|
|
762
809
|
const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId);
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
810
|
+
const hasTxs = core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
|
|
811
|
+
const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
|
|
812
|
+
const childBundles = await this.orchestrator.predecorateSignedTransactions({
|
|
813
|
+
chainId
|
|
814
|
+
});
|
|
815
|
+
const bundles = [];
|
|
816
|
+
if (signed !== undefined && signed.transactions.length > 0) {
|
|
817
|
+
bundles.push(signed);
|
|
818
|
+
}
|
|
819
|
+
bundles.push(...childBundles.filter(b => b.transactions.length > 0));
|
|
820
|
+
if (bundles.length === 0) {
|
|
821
|
+
// Nothing to send?
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
|
|
766
825
|
}
|
|
767
826
|
async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
|
|
768
827
|
const digest = utils.encodeTypedDataDigest({
|
|
@@ -140,8 +140,9 @@ class AccountSigner {
|
|
|
140
140
|
const finalTransactions = [...prepare.transactions, ...encodeGasRefundTransaction(feeOption)];
|
|
141
141
|
return this.account.sendTransaction(finalTransactions, this.chainId, prepare.feeQuote, undefined, undefined, (_this$options4 = this.options) != null && _this$options4.nonceSpace ? {
|
|
142
142
|
nonceSpace: this.options.nonceSpace
|
|
143
|
-
} : undefined);
|
|
143
|
+
} : undefined); // Will always have a transaction response
|
|
144
144
|
}
|
|
145
|
+
|
|
145
146
|
getBalance(blockTag) {
|
|
146
147
|
return this.provider.getBalance(this.account.address, blockTag);
|
|
147
148
|
}
|
|
@@ -410,6 +411,19 @@ class Account {
|
|
|
410
411
|
throw new Error(`Wallet ${this.address} is not fully migrated`);
|
|
411
412
|
}
|
|
412
413
|
}
|
|
414
|
+
async predecorateSignedTransactions(status, chainId) {
|
|
415
|
+
// Request signed predecorate transactions from child wallets
|
|
416
|
+
const bundles = await this.orchestrator.predecorateSignedTransactions({
|
|
417
|
+
chainId
|
|
418
|
+
});
|
|
419
|
+
// Get signed predecorate transaction
|
|
420
|
+
const predecorated = await this.predecorateTransactions([], status, chainId);
|
|
421
|
+
if (core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
|
|
422
|
+
// Sign it
|
|
423
|
+
bundles.push(await this.signTransactions(predecorated, chainId));
|
|
424
|
+
}
|
|
425
|
+
return bundles;
|
|
426
|
+
}
|
|
413
427
|
async predecorateTransactions(txs, status, chainId) {
|
|
414
428
|
// if onchain wallet config is not up to date
|
|
415
429
|
// then we should append an extra transaction that updates it
|
|
@@ -421,34 +435,49 @@ class Account {
|
|
|
421
435
|
}
|
|
422
436
|
return txs;
|
|
423
437
|
}
|
|
424
|
-
decorateTransactions(
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
|
|
438
|
+
async decorateTransactions(bundles, status, chainId) {
|
|
439
|
+
var _chainId;
|
|
440
|
+
if (!Array.isArray(bundles)) {
|
|
441
|
+
// Recurse with array
|
|
442
|
+
return this.decorateTransactions([bundles], status, chainId);
|
|
428
443
|
}
|
|
444
|
+
|
|
445
|
+
// Default to chainId of first bundle when not supplied
|
|
446
|
+
chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
|
|
447
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
448
|
+
const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
|
|
449
|
+
if (!hasBootstrapTxs && bundles.length === 1) {
|
|
450
|
+
return bundles[0];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Intent defaults to first bundle when no bootstrap transaction
|
|
454
|
+
const {
|
|
455
|
+
entrypoint
|
|
456
|
+
} = hasBootstrapTxs ? bootstrapBundle : bundles[0];
|
|
429
457
|
const decoratedBundle = {
|
|
430
|
-
entrypoint
|
|
431
|
-
chainId
|
|
432
|
-
|
|
433
|
-
|
|
458
|
+
entrypoint,
|
|
459
|
+
chainId,
|
|
460
|
+
// Intent of the first bundle is used
|
|
461
|
+
intent: bundles[0].intent,
|
|
462
|
+
transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
|
|
434
463
|
to: bundle.entrypoint,
|
|
435
464
|
data: core.commons.transaction.encodeBundleExecData(bundle),
|
|
436
465
|
gasLimit: 0,
|
|
437
466
|
delegateCall: false,
|
|
438
467
|
revertOnError: true,
|
|
439
468
|
value: 0
|
|
440
|
-
}]
|
|
469
|
+
}))]
|
|
441
470
|
};
|
|
442
471
|
|
|
443
472
|
// Re-compute the meta-transaction id to use the guest module subdigest
|
|
444
473
|
if (!status.onChain.deployed) {
|
|
445
|
-
decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule,
|
|
474
|
+
decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
|
|
446
475
|
}
|
|
447
476
|
return decoratedBundle;
|
|
448
477
|
}
|
|
449
|
-
decorateSignature(signature, status) {
|
|
478
|
+
async decorateSignature(signature, status) {
|
|
450
479
|
if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
|
|
451
|
-
return
|
|
480
|
+
return signature;
|
|
452
481
|
}
|
|
453
482
|
const coder = this.coders.signature;
|
|
454
483
|
const chain = status.presignedConfigurations.map(c => c.signature);
|
|
@@ -467,14 +496,13 @@ class Account {
|
|
|
467
496
|
signatures
|
|
468
497
|
});
|
|
469
498
|
}
|
|
470
|
-
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
|
|
499
|
+
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
|
|
471
500
|
// If we are signing a digest for chainId zero then we can never be fully migrated
|
|
472
501
|
// because Sequence v1 doesn't allow for signing a message on "all chains"
|
|
473
502
|
|
|
474
503
|
// So we ignore the state on "chain zero" and instead use one of the states of the networks
|
|
475
504
|
// wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
|
|
476
505
|
// the behaviour of being migrated on all chains
|
|
477
|
-
|
|
478
506
|
const chainRef = ethers.ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
|
|
479
507
|
const status = await this.status(chainRef);
|
|
480
508
|
this.mustBeFullyMigrated(status);
|
|
@@ -485,7 +513,7 @@ class Account {
|
|
|
485
513
|
throw new Error('Wallet cannot validate onchain');
|
|
486
514
|
}
|
|
487
515
|
const wallet = this.walletForStatus(chainId, status);
|
|
488
|
-
const signature = await wallet.signDigest(digest);
|
|
516
|
+
const signature = await wallet.signDigest(digest, metadata);
|
|
489
517
|
const decorated = decorate ? this.decorateSignature(signature, status) : signature;
|
|
490
518
|
|
|
491
519
|
// If the wallet can't validate onchain then we
|
|
@@ -504,8 +532,8 @@ class Account {
|
|
|
504
532
|
}
|
|
505
533
|
return decorated;
|
|
506
534
|
}
|
|
507
|
-
buildEIP6492Signature(signature, status, chainId) {
|
|
508
|
-
const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
|
|
535
|
+
async buildEIP6492Signature(signature, status, chainId) {
|
|
536
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
509
537
|
if (bootstrapBundle.transactions.length === 0) {
|
|
510
538
|
throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
|
|
511
539
|
}
|
|
@@ -561,8 +589,12 @@ class Account {
|
|
|
561
589
|
* by any of the migrations.
|
|
562
590
|
*
|
|
563
591
|
*/
|
|
564
|
-
buildBootstrapTransactions(status, chainId) {
|
|
565
|
-
|
|
592
|
+
async buildBootstrapTransactions(status, chainId) {
|
|
593
|
+
var _bundle$transactions;
|
|
594
|
+
const bundle = await this.orchestrator.buildDeployTransaction({
|
|
595
|
+
chainId
|
|
596
|
+
});
|
|
597
|
+
const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
|
|
566
598
|
|
|
567
599
|
// Add wallet deployment if needed
|
|
568
600
|
if (!status.onChain.deployed) {
|
|
@@ -571,6 +603,7 @@ class Account {
|
|
|
571
603
|
const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
|
|
572
604
|
transactions.push(...deployTransaction.transactions);
|
|
573
605
|
}
|
|
606
|
+
transactions.length;
|
|
574
607
|
|
|
575
608
|
// Get pending migrations
|
|
576
609
|
transactions.push(...status.signedMigrations.map(m => ({
|
|
@@ -620,9 +653,20 @@ class Account {
|
|
|
620
653
|
const status = pstatus || (await this.status(chainId));
|
|
621
654
|
this.mustBeFullyMigrated(status);
|
|
622
655
|
const wallet = this.walletForStatus(chainId, status);
|
|
656
|
+
const metadata = {
|
|
657
|
+
address: this.address,
|
|
658
|
+
digest: '',
|
|
659
|
+
// Set in wallet.signTransactions
|
|
660
|
+
chainId,
|
|
661
|
+
config: {
|
|
662
|
+
version: this.version
|
|
663
|
+
},
|
|
664
|
+
decorate: true,
|
|
665
|
+
cantValidateBehavior: 'ignore'
|
|
666
|
+
};
|
|
623
667
|
const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
|
|
624
668
|
space: options == null ? void 0 : options.nonceSpace
|
|
625
|
-
});
|
|
669
|
+
}, metadata);
|
|
626
670
|
return _extends({}, signed, {
|
|
627
671
|
signature: await this.decorateSignature(signed.signature, status)
|
|
628
672
|
});
|
|
@@ -701,9 +745,12 @@ class Account {
|
|
|
701
745
|
};
|
|
702
746
|
}
|
|
703
747
|
async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
|
|
704
|
-
|
|
748
|
+
if (!Array.isArray(signedBundle)) {
|
|
749
|
+
return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
|
|
750
|
+
}
|
|
751
|
+
const status = pstatus || (await this.status(signedBundle[0].chainId));
|
|
705
752
|
this.mustBeFullyMigrated(status);
|
|
706
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, status);
|
|
753
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, status);
|
|
707
754
|
callback == null ? void 0 : callback(decoratedBundle);
|
|
708
755
|
return this.relayer(chainId).relay(decoratedBundle, quote);
|
|
709
756
|
}
|
|
@@ -736,7 +783,7 @@ class Account {
|
|
|
736
783
|
nonce: 0 // The relayer also ignored the nonce
|
|
737
784
|
};
|
|
738
785
|
|
|
739
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
|
|
786
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
|
|
740
787
|
const data = core.commons.transaction.encodeBundleExecData(decoratedBundle);
|
|
741
788
|
const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
|
|
742
789
|
return _extends({}, res, {
|
|
@@ -760,9 +807,21 @@ class Account {
|
|
|
760
807
|
async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
|
|
761
808
|
const status = await this.status(chainId);
|
|
762
809
|
const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId);
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
810
|
+
const hasTxs = core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
|
|
811
|
+
const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
|
|
812
|
+
const childBundles = await this.orchestrator.predecorateSignedTransactions({
|
|
813
|
+
chainId
|
|
814
|
+
});
|
|
815
|
+
const bundles = [];
|
|
816
|
+
if (signed !== undefined && signed.transactions.length > 0) {
|
|
817
|
+
bundles.push(signed);
|
|
818
|
+
}
|
|
819
|
+
bundles.push(...childBundles.filter(b => b.transactions.length > 0));
|
|
820
|
+
if (bundles.length === 0) {
|
|
821
|
+
// Nothing to send?
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
|
|
766
825
|
}
|
|
767
826
|
async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
|
|
768
827
|
const digest = utils.encodeTypedDataDigest({
|
|
@@ -136,8 +136,9 @@ class AccountSigner {
|
|
|
136
136
|
const finalTransactions = [...prepare.transactions, ...encodeGasRefundTransaction(feeOption)];
|
|
137
137
|
return this.account.sendTransaction(finalTransactions, this.chainId, prepare.feeQuote, undefined, undefined, (_this$options4 = this.options) != null && _this$options4.nonceSpace ? {
|
|
138
138
|
nonceSpace: this.options.nonceSpace
|
|
139
|
-
} : undefined);
|
|
139
|
+
} : undefined); // Will always have a transaction response
|
|
140
140
|
}
|
|
141
|
+
|
|
141
142
|
getBalance(blockTag) {
|
|
142
143
|
return this.provider.getBalance(this.account.address, blockTag);
|
|
143
144
|
}
|
|
@@ -406,6 +407,19 @@ class Account {
|
|
|
406
407
|
throw new Error(`Wallet ${this.address} is not fully migrated`);
|
|
407
408
|
}
|
|
408
409
|
}
|
|
410
|
+
async predecorateSignedTransactions(status, chainId) {
|
|
411
|
+
// Request signed predecorate transactions from child wallets
|
|
412
|
+
const bundles = await this.orchestrator.predecorateSignedTransactions({
|
|
413
|
+
chainId
|
|
414
|
+
});
|
|
415
|
+
// Get signed predecorate transaction
|
|
416
|
+
const predecorated = await this.predecorateTransactions([], status, chainId);
|
|
417
|
+
if (commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
|
|
418
|
+
// Sign it
|
|
419
|
+
bundles.push(await this.signTransactions(predecorated, chainId));
|
|
420
|
+
}
|
|
421
|
+
return bundles;
|
|
422
|
+
}
|
|
409
423
|
async predecorateTransactions(txs, status, chainId) {
|
|
410
424
|
// if onchain wallet config is not up to date
|
|
411
425
|
// then we should append an extra transaction that updates it
|
|
@@ -417,34 +431,49 @@ class Account {
|
|
|
417
431
|
}
|
|
418
432
|
return txs;
|
|
419
433
|
}
|
|
420
|
-
decorateTransactions(
|
|
421
|
-
|
|
422
|
-
if (
|
|
423
|
-
|
|
434
|
+
async decorateTransactions(bundles, status, chainId) {
|
|
435
|
+
var _chainId;
|
|
436
|
+
if (!Array.isArray(bundles)) {
|
|
437
|
+
// Recurse with array
|
|
438
|
+
return this.decorateTransactions([bundles], status, chainId);
|
|
424
439
|
}
|
|
440
|
+
|
|
441
|
+
// Default to chainId of first bundle when not supplied
|
|
442
|
+
chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
|
|
443
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
444
|
+
const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
|
|
445
|
+
if (!hasBootstrapTxs && bundles.length === 1) {
|
|
446
|
+
return bundles[0];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Intent defaults to first bundle when no bootstrap transaction
|
|
450
|
+
const {
|
|
451
|
+
entrypoint
|
|
452
|
+
} = hasBootstrapTxs ? bootstrapBundle : bundles[0];
|
|
425
453
|
const decoratedBundle = {
|
|
426
|
-
entrypoint
|
|
427
|
-
chainId
|
|
428
|
-
|
|
429
|
-
|
|
454
|
+
entrypoint,
|
|
455
|
+
chainId,
|
|
456
|
+
// Intent of the first bundle is used
|
|
457
|
+
intent: bundles[0].intent,
|
|
458
|
+
transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
|
|
430
459
|
to: bundle.entrypoint,
|
|
431
460
|
data: commons.transaction.encodeBundleExecData(bundle),
|
|
432
461
|
gasLimit: 0,
|
|
433
462
|
delegateCall: false,
|
|
434
463
|
revertOnError: true,
|
|
435
464
|
value: 0
|
|
436
|
-
}]
|
|
465
|
+
}))]
|
|
437
466
|
};
|
|
438
467
|
|
|
439
468
|
// Re-compute the meta-transaction id to use the guest module subdigest
|
|
440
469
|
if (!status.onChain.deployed) {
|
|
441
|
-
decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule,
|
|
470
|
+
decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
|
|
442
471
|
}
|
|
443
472
|
return decoratedBundle;
|
|
444
473
|
}
|
|
445
|
-
decorateSignature(signature, status) {
|
|
474
|
+
async decorateSignature(signature, status) {
|
|
446
475
|
if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
|
|
447
|
-
return
|
|
476
|
+
return signature;
|
|
448
477
|
}
|
|
449
478
|
const coder = this.coders.signature;
|
|
450
479
|
const chain = status.presignedConfigurations.map(c => c.signature);
|
|
@@ -463,14 +492,13 @@ class Account {
|
|
|
463
492
|
signatures
|
|
464
493
|
});
|
|
465
494
|
}
|
|
466
|
-
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
|
|
495
|
+
async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
|
|
467
496
|
// If we are signing a digest for chainId zero then we can never be fully migrated
|
|
468
497
|
// because Sequence v1 doesn't allow for signing a message on "all chains"
|
|
469
498
|
|
|
470
499
|
// So we ignore the state on "chain zero" and instead use one of the states of the networks
|
|
471
500
|
// wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
|
|
472
501
|
// the behaviour of being migrated on all chains
|
|
473
|
-
|
|
474
502
|
const chainRef = ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
|
|
475
503
|
const status = await this.status(chainRef);
|
|
476
504
|
this.mustBeFullyMigrated(status);
|
|
@@ -481,7 +509,7 @@ class Account {
|
|
|
481
509
|
throw new Error('Wallet cannot validate onchain');
|
|
482
510
|
}
|
|
483
511
|
const wallet = this.walletForStatus(chainId, status);
|
|
484
|
-
const signature = await wallet.signDigest(digest);
|
|
512
|
+
const signature = await wallet.signDigest(digest, metadata);
|
|
485
513
|
const decorated = decorate ? this.decorateSignature(signature, status) : signature;
|
|
486
514
|
|
|
487
515
|
// If the wallet can't validate onchain then we
|
|
@@ -500,8 +528,8 @@ class Account {
|
|
|
500
528
|
}
|
|
501
529
|
return decorated;
|
|
502
530
|
}
|
|
503
|
-
buildEIP6492Signature(signature, status, chainId) {
|
|
504
|
-
const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
|
|
531
|
+
async buildEIP6492Signature(signature, status, chainId) {
|
|
532
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
|
|
505
533
|
if (bootstrapBundle.transactions.length === 0) {
|
|
506
534
|
throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
|
|
507
535
|
}
|
|
@@ -557,8 +585,12 @@ class Account {
|
|
|
557
585
|
* by any of the migrations.
|
|
558
586
|
*
|
|
559
587
|
*/
|
|
560
|
-
buildBootstrapTransactions(status, chainId) {
|
|
561
|
-
|
|
588
|
+
async buildBootstrapTransactions(status, chainId) {
|
|
589
|
+
var _bundle$transactions;
|
|
590
|
+
const bundle = await this.orchestrator.buildDeployTransaction({
|
|
591
|
+
chainId
|
|
592
|
+
});
|
|
593
|
+
const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
|
|
562
594
|
|
|
563
595
|
// Add wallet deployment if needed
|
|
564
596
|
if (!status.onChain.deployed) {
|
|
@@ -567,6 +599,7 @@ class Account {
|
|
|
567
599
|
const deployTransaction = Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
|
|
568
600
|
transactions.push(...deployTransaction.transactions);
|
|
569
601
|
}
|
|
602
|
+
transactions.length;
|
|
570
603
|
|
|
571
604
|
// Get pending migrations
|
|
572
605
|
transactions.push(...status.signedMigrations.map(m => ({
|
|
@@ -616,9 +649,20 @@ class Account {
|
|
|
616
649
|
const status = pstatus || (await this.status(chainId));
|
|
617
650
|
this.mustBeFullyMigrated(status);
|
|
618
651
|
const wallet = this.walletForStatus(chainId, status);
|
|
652
|
+
const metadata = {
|
|
653
|
+
address: this.address,
|
|
654
|
+
digest: '',
|
|
655
|
+
// Set in wallet.signTransactions
|
|
656
|
+
chainId,
|
|
657
|
+
config: {
|
|
658
|
+
version: this.version
|
|
659
|
+
},
|
|
660
|
+
decorate: true,
|
|
661
|
+
cantValidateBehavior: 'ignore'
|
|
662
|
+
};
|
|
619
663
|
const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
|
|
620
664
|
space: options == null ? void 0 : options.nonceSpace
|
|
621
|
-
});
|
|
665
|
+
}, metadata);
|
|
622
666
|
return _extends({}, signed, {
|
|
623
667
|
signature: await this.decorateSignature(signed.signature, status)
|
|
624
668
|
});
|
|
@@ -697,9 +741,12 @@ class Account {
|
|
|
697
741
|
};
|
|
698
742
|
}
|
|
699
743
|
async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
|
|
700
|
-
|
|
744
|
+
if (!Array.isArray(signedBundle)) {
|
|
745
|
+
return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
|
|
746
|
+
}
|
|
747
|
+
const status = pstatus || (await this.status(signedBundle[0].chainId));
|
|
701
748
|
this.mustBeFullyMigrated(status);
|
|
702
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, status);
|
|
749
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, status);
|
|
703
750
|
callback == null ? void 0 : callback(decoratedBundle);
|
|
704
751
|
return this.relayer(chainId).relay(decoratedBundle, quote);
|
|
705
752
|
}
|
|
@@ -732,7 +779,7 @@ class Account {
|
|
|
732
779
|
nonce: 0 // The relayer also ignored the nonce
|
|
733
780
|
};
|
|
734
781
|
|
|
735
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
|
|
782
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
|
|
736
783
|
const data = commons.transaction.encodeBundleExecData(decoratedBundle);
|
|
737
784
|
const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
|
|
738
785
|
return _extends({}, res, {
|
|
@@ -756,9 +803,21 @@ class Account {
|
|
|
756
803
|
async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
|
|
757
804
|
const status = await this.status(chainId);
|
|
758
805
|
const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId);
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
806
|
+
const hasTxs = commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
|
|
807
|
+
const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
|
|
808
|
+
const childBundles = await this.orchestrator.predecorateSignedTransactions({
|
|
809
|
+
chainId
|
|
810
|
+
});
|
|
811
|
+
const bundles = [];
|
|
812
|
+
if (signed !== undefined && signed.transactions.length > 0) {
|
|
813
|
+
bundles.push(signed);
|
|
814
|
+
}
|
|
815
|
+
bundles.push(...childBundles.filter(b => b.transactions.length > 0));
|
|
816
|
+
if (bundles.length === 0) {
|
|
817
|
+
// Nothing to send?
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
|
|
762
821
|
}
|
|
763
822
|
async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
|
|
764
823
|
const digest = encodeTypedDataDigest({
|
|
@@ -76,11 +76,12 @@ export declare class Account {
|
|
|
76
76
|
walletFor(chainId: ethers.BigNumberish, context: commons.context.WalletContext, config: commons.config.Config, coders: typeof this.coders): Wallet;
|
|
77
77
|
status(chainId: ethers.BigNumberish, longestPath?: boolean): Promise<AccountStatus>;
|
|
78
78
|
private mustBeFullyMigrated;
|
|
79
|
+
predecorateSignedTransactions(status: AccountStatus, chainId: ethers.BigNumberish): Promise<commons.transaction.SignedTransactionBundle[]>;
|
|
79
80
|
predecorateTransactions(txs: commons.transaction.Transactionish, status: AccountStatus, chainId: ethers.BigNumberish): Promise<commons.transaction.Transactionish>;
|
|
80
|
-
decorateTransactions(
|
|
81
|
+
decorateTransactions(bundles: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[], status: AccountStatus, chainId?: ethers.BigNumberish): Promise<commons.transaction.IntendedTransactionBundle>;
|
|
81
82
|
decorateSignature<T extends ethers.BytesLike>(signature: T, status: Partial<Pick<AccountStatus, 'presignedConfigurations'>>): Promise<T | string>;
|
|
82
83
|
publishWitness(): Promise<void>;
|
|
83
|
-
signDigest(digest: ethers.BytesLike, chainId: ethers.BigNumberish, decorate?: boolean, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
|
|
84
|
+
signDigest(digest: ethers.BytesLike, chainId: ethers.BigNumberish, decorate?: boolean, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw', metadata?: object): Promise<string>;
|
|
84
85
|
private buildEIP6492Signature;
|
|
85
86
|
editConfig(changes: {
|
|
86
87
|
add?: commons.config.SimpleSigner[];
|
|
@@ -99,7 +100,7 @@ export declare class Account {
|
|
|
99
100
|
* by any of the migrations.
|
|
100
101
|
*
|
|
101
102
|
*/
|
|
102
|
-
buildBootstrapTransactions(status: AccountStatus, chainId: ethers.BigNumberish): commons.transaction.IntendedTransactionBundle
|
|
103
|
+
buildBootstrapTransactions(status: AccountStatus, chainId: ethers.BigNumberish): Promise<commons.transaction.IntendedTransactionBundle>;
|
|
103
104
|
bootstrapTransactions(chainId: ethers.BigNumberish, prestatus?: AccountStatus): Promise<Omit<commons.transaction.IntendedTransactionBundle, 'chainId'>>;
|
|
104
105
|
doBootstrap(chainId: ethers.BigNumberish, feeQuote?: FeeQuote, prestatus?: AccountStatus): Promise<commons.transaction.TransactionResponse<any>>;
|
|
105
106
|
signMessage(message: ethers.BytesLike, chainId: ethers.BigNumberish, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
|
|
@@ -115,7 +116,7 @@ export declare class Account {
|
|
|
115
116
|
migratedAllChains: boolean;
|
|
116
117
|
failedChains: number[];
|
|
117
118
|
}>;
|
|
118
|
-
sendSignedTransactions(signedBundle: commons.transaction.IntendedTransactionBundle, chainId: ethers.BigNumberish, quote?: FeeQuote, pstatus?: AccountStatus, callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void): Promise<ethers.providers.TransactionResponse>;
|
|
119
|
+
sendSignedTransactions(signedBundle: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[], chainId: ethers.BigNumberish, quote?: FeeQuote, pstatus?: AccountStatus, callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void): Promise<ethers.providers.TransactionResponse>;
|
|
119
120
|
fillGasLimits(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, status?: AccountStatus): Promise<commons.transaction.SimulatedTransaction[]>;
|
|
120
121
|
gasRefundQuotes(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, stubSignatureOverrides: Map<string, string>, status?: AccountStatus, options?: {
|
|
121
122
|
simulate?: boolean;
|
|
@@ -132,7 +133,7 @@ export declare class Account {
|
|
|
132
133
|
}): Promise<PreparedTransactions>;
|
|
133
134
|
sendTransaction(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, quote?: FeeQuote, skipPreDecorate?: boolean, callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void, options?: {
|
|
134
135
|
nonceSpace?: ethers.BigNumberish;
|
|
135
|
-
}): Promise<ethers.providers.TransactionResponse>;
|
|
136
|
+
}): Promise<ethers.providers.TransactionResponse | undefined>;
|
|
136
137
|
signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, message: Record<string, any>, chainId: ethers.BigNumberish, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
|
|
137
138
|
getAllSigners(): Promise<{
|
|
138
139
|
address: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xsequence/account",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "tools for migrating sequence wallets to new versions",
|
|
5
5
|
"repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/account",
|
|
6
6
|
"source": "src/index.ts",
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"ethers": "^5.5.2",
|
|
13
|
-
"@0xsequence/
|
|
14
|
-
"@0xsequence/
|
|
15
|
-
"@0xsequence/network": "1.
|
|
16
|
-
"@0xsequence/relayer": "1.
|
|
17
|
-
"@0xsequence/
|
|
18
|
-
"@0xsequence/
|
|
19
|
-
"@0xsequence/wallet": "1.
|
|
13
|
+
"@0xsequence/core": "1.3.0",
|
|
14
|
+
"@0xsequence/migration": "1.3.0",
|
|
15
|
+
"@0xsequence/network": "1.3.0",
|
|
16
|
+
"@0xsequence/relayer": "1.3.0",
|
|
17
|
+
"@0xsequence/utils": "1.3.0",
|
|
18
|
+
"@0xsequence/sessions": "1.3.0",
|
|
19
|
+
"@0xsequence/wallet": "1.3.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
23
23
|
"nyc": "^15.1.0",
|
|
24
|
-
"@0xsequence/signhub": "1.
|
|
25
|
-
"@0xsequence/tests": "1.
|
|
24
|
+
"@0xsequence/signhub": "1.3.0",
|
|
25
|
+
"@0xsequence/tests": "1.3.0"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"src",
|
package/src/account.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { commons, universal } from '@0xsequence/core'
|
|
2
|
+
import { WalletSignRequestMetadata } from '@0xsequence/core/src/commons'
|
|
2
3
|
import { migrator, defaults, version } from '@0xsequence/migration'
|
|
3
4
|
import { ChainId, NetworkConfig } from '@0xsequence/network'
|
|
4
5
|
import { FeeOption, FeeQuote, isRelayer, Relayer, RpcRelayer } from '@0xsequence/relayer'
|
|
@@ -352,6 +353,21 @@ export class Account {
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
356
|
+
async predecorateSignedTransactions(
|
|
357
|
+
status: AccountStatus,
|
|
358
|
+
chainId: ethers.BigNumberish
|
|
359
|
+
): Promise<commons.transaction.SignedTransactionBundle[]> {
|
|
360
|
+
// Request signed predecorate transactions from child wallets
|
|
361
|
+
const bundles = await this.orchestrator.predecorateSignedTransactions({ chainId })
|
|
362
|
+
// Get signed predecorate transaction
|
|
363
|
+
const predecorated = await this.predecorateTransactions([], status, chainId)
|
|
364
|
+
if (commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
|
|
365
|
+
// Sign it
|
|
366
|
+
bundles.push(await this.signTransactions(predecorated, chainId))
|
|
367
|
+
}
|
|
368
|
+
return bundles
|
|
369
|
+
}
|
|
370
|
+
|
|
355
371
|
async predecorateTransactions(
|
|
356
372
|
txs: commons.transaction.Transactionish,
|
|
357
373
|
status: AccountStatus,
|
|
@@ -369,29 +385,46 @@ export class Account {
|
|
|
369
385
|
return txs
|
|
370
386
|
}
|
|
371
387
|
|
|
372
|
-
decorateTransactions(
|
|
373
|
-
|
|
374
|
-
status: AccountStatus
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
|
|
388
|
+
async decorateTransactions(
|
|
389
|
+
bundles: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[],
|
|
390
|
+
status: AccountStatus,
|
|
391
|
+
chainId?: ethers.BigNumberish
|
|
392
|
+
): Promise<commons.transaction.IntendedTransactionBundle> {
|
|
393
|
+
if (!Array.isArray(bundles)) {
|
|
394
|
+
// Recurse with array
|
|
395
|
+
return this.decorateTransactions([bundles], status, chainId)
|
|
379
396
|
}
|
|
380
397
|
|
|
398
|
+
// Default to chainId of first bundle when not supplied
|
|
399
|
+
chainId = chainId ?? bundles[0].chainId
|
|
400
|
+
|
|
401
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId)
|
|
402
|
+
const hasBootstrapTxs = bootstrapBundle.transactions.length > 0
|
|
403
|
+
|
|
404
|
+
if (!hasBootstrapTxs && bundles.length === 1) {
|
|
405
|
+
return bundles[0]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Intent defaults to first bundle when no bootstrap transaction
|
|
409
|
+
const { entrypoint } = hasBootstrapTxs ? bootstrapBundle : bundles[0]
|
|
410
|
+
|
|
381
411
|
const decoratedBundle = {
|
|
382
|
-
entrypoint
|
|
383
|
-
chainId
|
|
384
|
-
|
|
412
|
+
entrypoint,
|
|
413
|
+
chainId,
|
|
414
|
+
// Intent of the first bundle is used
|
|
415
|
+
intent: bundles[0].intent,
|
|
385
416
|
transactions: [
|
|
386
417
|
...bootstrapBundle.transactions,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
418
|
+
...bundles.map(
|
|
419
|
+
(bundle): commons.transaction.Transaction => ({
|
|
420
|
+
to: bundle.entrypoint,
|
|
421
|
+
data: commons.transaction.encodeBundleExecData(bundle),
|
|
422
|
+
gasLimit: 0,
|
|
423
|
+
delegateCall: false,
|
|
424
|
+
revertOnError: true,
|
|
425
|
+
value: 0
|
|
426
|
+
})
|
|
427
|
+
)
|
|
395
428
|
]
|
|
396
429
|
}
|
|
397
430
|
|
|
@@ -399,7 +432,7 @@ export class Account {
|
|
|
399
432
|
if (!status.onChain.deployed) {
|
|
400
433
|
decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(
|
|
401
434
|
this.contexts[this.version].guestModule,
|
|
402
|
-
|
|
435
|
+
chainId,
|
|
403
436
|
decoratedBundle.transactions
|
|
404
437
|
)
|
|
405
438
|
}
|
|
@@ -407,12 +440,12 @@ export class Account {
|
|
|
407
440
|
return decoratedBundle
|
|
408
441
|
}
|
|
409
442
|
|
|
410
|
-
decorateSignature<T extends ethers.BytesLike>(
|
|
443
|
+
async decorateSignature<T extends ethers.BytesLike>(
|
|
411
444
|
signature: T,
|
|
412
445
|
status: Partial<Pick<AccountStatus, 'presignedConfigurations'>>
|
|
413
446
|
): Promise<T | string> {
|
|
414
447
|
if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
|
|
415
|
-
return
|
|
448
|
+
return signature
|
|
416
449
|
}
|
|
417
450
|
|
|
418
451
|
const coder = this.coders.signature
|
|
@@ -434,7 +467,8 @@ export class Account {
|
|
|
434
467
|
digest: ethers.BytesLike,
|
|
435
468
|
chainId: ethers.BigNumberish,
|
|
436
469
|
decorate: boolean = true,
|
|
437
|
-
cantValidateBehavior: 'ignore' | 'eip6492' | 'throw' = 'ignore'
|
|
470
|
+
cantValidateBehavior: 'ignore' | 'eip6492' | 'throw' = 'ignore',
|
|
471
|
+
metadata?: object
|
|
438
472
|
): Promise<string> {
|
|
439
473
|
// If we are signing a digest for chainId zero then we can never be fully migrated
|
|
440
474
|
// because Sequence v1 doesn't allow for signing a message on "all chains"
|
|
@@ -442,10 +476,8 @@ export class Account {
|
|
|
442
476
|
// So we ignore the state on "chain zero" and instead use one of the states of the networks
|
|
443
477
|
// wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
|
|
444
478
|
// the behaviour of being migrated on all chains
|
|
445
|
-
|
|
446
479
|
const chainRef = ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId
|
|
447
480
|
const status = await this.status(chainRef)
|
|
448
|
-
|
|
449
481
|
this.mustBeFullyMigrated(status)
|
|
450
482
|
|
|
451
483
|
// Check if we can validate onchain and what to do if we can't
|
|
@@ -455,7 +487,7 @@ export class Account {
|
|
|
455
487
|
}
|
|
456
488
|
|
|
457
489
|
const wallet = this.walletForStatus(chainId, status)
|
|
458
|
-
const signature = await wallet.signDigest(digest)
|
|
490
|
+
const signature = await wallet.signDigest(digest, metadata)
|
|
459
491
|
|
|
460
492
|
const decorated = decorate ? this.decorateSignature(signature, status) : signature
|
|
461
493
|
|
|
@@ -478,8 +510,8 @@ export class Account {
|
|
|
478
510
|
return decorated
|
|
479
511
|
}
|
|
480
512
|
|
|
481
|
-
private buildEIP6492Signature(signature: string, status: AccountStatus, chainId: ethers.BigNumberish): string {
|
|
482
|
-
const bootstrapBundle = this.buildBootstrapTransactions(status, chainId)
|
|
513
|
+
private async buildEIP6492Signature(signature: string, status: AccountStatus, chainId: ethers.BigNumberish): Promise<string> {
|
|
514
|
+
const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId)
|
|
483
515
|
if (bootstrapBundle.transactions.length === 0) {
|
|
484
516
|
throw new Error('Cannot build EIP-6492 signature without bootstrap transactions')
|
|
485
517
|
}
|
|
@@ -550,8 +582,12 @@ export class Account {
|
|
|
550
582
|
* by any of the migrations.
|
|
551
583
|
*
|
|
552
584
|
*/
|
|
553
|
-
buildBootstrapTransactions(
|
|
554
|
-
|
|
585
|
+
async buildBootstrapTransactions(
|
|
586
|
+
status: AccountStatus,
|
|
587
|
+
chainId: ethers.BigNumberish
|
|
588
|
+
): Promise<commons.transaction.IntendedTransactionBundle> {
|
|
589
|
+
const bundle = await this.orchestrator.buildDeployTransaction({ chainId })
|
|
590
|
+
const transactions: commons.transaction.Transaction[] = bundle?.transactions ?? []
|
|
555
591
|
|
|
556
592
|
// Add wallet deployment if needed
|
|
557
593
|
if (!status.onChain.deployed) {
|
|
@@ -561,6 +597,7 @@ export class Account {
|
|
|
561
597
|
|
|
562
598
|
transactions.push(...deployTransaction.transactions)
|
|
563
599
|
}
|
|
600
|
+
const len = transactions.length
|
|
564
601
|
|
|
565
602
|
// Get pending migrations
|
|
566
603
|
transactions.push(
|
|
@@ -622,7 +659,17 @@ export class Account {
|
|
|
622
659
|
this.mustBeFullyMigrated(status)
|
|
623
660
|
|
|
624
661
|
const wallet = this.walletForStatus(chainId, status)
|
|
625
|
-
|
|
662
|
+
|
|
663
|
+
const metadata: WalletSignRequestMetadata = {
|
|
664
|
+
address: this.address,
|
|
665
|
+
digest: '', // Set in wallet.signTransactions
|
|
666
|
+
chainId,
|
|
667
|
+
config: { version: this.version },
|
|
668
|
+
decorate: true,
|
|
669
|
+
cantValidateBehavior: 'ignore'
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const signed = await wallet.signTransactions(txs, options?.nonceSpace && { space: options?.nonceSpace }, metadata)
|
|
626
673
|
|
|
627
674
|
return {
|
|
628
675
|
...signed,
|
|
@@ -707,16 +754,19 @@ export class Account {
|
|
|
707
754
|
}
|
|
708
755
|
|
|
709
756
|
async sendSignedTransactions(
|
|
710
|
-
signedBundle: commons.transaction.IntendedTransactionBundle,
|
|
757
|
+
signedBundle: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[],
|
|
711
758
|
chainId: ethers.BigNumberish,
|
|
712
759
|
quote?: FeeQuote,
|
|
713
760
|
pstatus?: AccountStatus,
|
|
714
761
|
callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void
|
|
715
762
|
): Promise<ethers.providers.TransactionResponse> {
|
|
716
|
-
|
|
763
|
+
if (!Array.isArray(signedBundle)) {
|
|
764
|
+
return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback)
|
|
765
|
+
}
|
|
766
|
+
const status = pstatus || (await this.status(signedBundle[0].chainId))
|
|
717
767
|
this.mustBeFullyMigrated(status)
|
|
718
768
|
|
|
719
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, status)
|
|
769
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, status)
|
|
720
770
|
callback?.(decoratedBundle)
|
|
721
771
|
|
|
722
772
|
return this.relayer(chainId).relay(decoratedBundle, quote)
|
|
@@ -769,7 +819,7 @@ export class Account {
|
|
|
769
819
|
nonce: 0 // The relayer also ignored the nonce
|
|
770
820
|
}
|
|
771
821
|
|
|
772
|
-
const decoratedBundle = this.decorateTransactions(signedBundle, wstatus)
|
|
822
|
+
const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus)
|
|
773
823
|
const data = commons.transaction.encodeBundleExecData(decoratedBundle)
|
|
774
824
|
const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options)
|
|
775
825
|
return { ...res, decorated: decoratedBundle }
|
|
@@ -806,13 +856,27 @@ export class Account {
|
|
|
806
856
|
options?: {
|
|
807
857
|
nonceSpace?: ethers.BigNumberish
|
|
808
858
|
}
|
|
809
|
-
): Promise<ethers.providers.TransactionResponse> {
|
|
859
|
+
): Promise<ethers.providers.TransactionResponse | undefined> {
|
|
810
860
|
const status = await this.status(chainId)
|
|
811
861
|
|
|
812
862
|
const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId)
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
863
|
+
const hasTxs = commons.transaction.fromTransactionish(this.address, predecorated).length > 0
|
|
864
|
+
const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined
|
|
865
|
+
|
|
866
|
+
const childBundles = await this.orchestrator.predecorateSignedTransactions({ chainId })
|
|
867
|
+
|
|
868
|
+
const bundles: commons.transaction.SignedTransactionBundle[] = []
|
|
869
|
+
if (signed !== undefined && signed.transactions.length > 0) {
|
|
870
|
+
bundles.push(signed)
|
|
871
|
+
}
|
|
872
|
+
bundles.push(...childBundles.filter(b => b.transactions.length > 0))
|
|
873
|
+
|
|
874
|
+
if (bundles.length === 0) {
|
|
875
|
+
// Nothing to send?
|
|
876
|
+
return
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback)
|
|
816
880
|
}
|
|
817
881
|
|
|
818
882
|
async signTypedData(
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { commons } from '@0xsequence/core'
|
|
2
|
+
import { signers, Status } from '@0xsequence/signhub'
|
|
3
|
+
import { ethers } from 'ethers'
|
|
4
|
+
import { Account } from '../account'
|
|
5
|
+
|
|
6
|
+
export type MetadataWithChainId = {
|
|
7
|
+
chainId: ethers.BigNumberish
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Implements a wrapper for using Sequence accounts as nested signers in the signhub orchestrator.
|
|
11
|
+
export class AccountOrchestratorWrapper implements signers.SapientSigner {
|
|
12
|
+
constructor(public account: Account) {}
|
|
13
|
+
|
|
14
|
+
async getAddress(): Promise<string> {
|
|
15
|
+
return this.account.address
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getChainIdFromMetadata(metadata: object): ethers.BigNumber {
|
|
19
|
+
try {
|
|
20
|
+
const { chainId } = metadata as MetadataWithChainId
|
|
21
|
+
return ethers.BigNumber.from(chainId)
|
|
22
|
+
} catch (err) {
|
|
23
|
+
// Invalid metadata object
|
|
24
|
+
throw new Error('AccountOrchestratorWrapper only supports metadata with chain id')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async buildDeployTransaction(metadata: object): Promise<commons.transaction.TransactionBundle | undefined> {
|
|
29
|
+
const chainId = this.getChainIdFromMetadata(metadata)
|
|
30
|
+
const status = await this.account.status(chainId)
|
|
31
|
+
return this.account.buildBootstrapTransactions(status, chainId)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async predecorateSignedTransactions(metadata: object): Promise<commons.transaction.SignedTransactionBundle[]> {
|
|
35
|
+
const chainId = this.getChainIdFromMetadata(metadata)
|
|
36
|
+
const status = await this.account.status(chainId)
|
|
37
|
+
return this.account.predecorateSignedTransactions(status, chainId)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async decorateTransactions(
|
|
41
|
+
bundle: commons.transaction.IntendedTransactionBundle,
|
|
42
|
+
metadata: object
|
|
43
|
+
): Promise<commons.transaction.IntendedTransactionBundle> {
|
|
44
|
+
const chainId = this.getChainIdFromMetadata(metadata)
|
|
45
|
+
const status = await this.account.status(chainId)
|
|
46
|
+
return this.account.decorateTransactions(bundle, status)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async requestSignature(
|
|
50
|
+
_id: string,
|
|
51
|
+
message: ethers.utils.BytesLike,
|
|
52
|
+
metadata: object,
|
|
53
|
+
callbacks: {
|
|
54
|
+
onSignature: (signature: ethers.utils.BytesLike) => void
|
|
55
|
+
onRejection: (error: string) => void
|
|
56
|
+
onStatus: (situation: string) => void
|
|
57
|
+
}
|
|
58
|
+
): Promise<boolean> {
|
|
59
|
+
if (!commons.isWalletSignRequestMetadata(metadata)) {
|
|
60
|
+
throw new Error('AccountOrchestratorWrapper only supports wallet metadata requests')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { chainId, decorate } = metadata
|
|
64
|
+
// EIP-6492 not supported on nested signatures
|
|
65
|
+
// Default to throw instead of ignore. Ignoring should be explicit
|
|
66
|
+
const cantValidateBehavior = metadata.cantValidateBehavior ?? 'throw'
|
|
67
|
+
|
|
68
|
+
// For Sequence nested signatures we must use `signDigest` and not `signMessage`
|
|
69
|
+
// otherwise the account will hash the digest and the signature will be invalid.
|
|
70
|
+
try {
|
|
71
|
+
callbacks.onSignature(await this.account.signDigest(message, chainId, decorate, cantValidateBehavior, metadata))
|
|
72
|
+
} catch (err) {
|
|
73
|
+
callbacks.onRejection('Unable to sign account')
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
notifyStatusChange(_i: string, _s: Status, _m: object): void {}
|
|
81
|
+
|
|
82
|
+
suffix(): ethers.utils.BytesLike {
|
|
83
|
+
return [3]
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/signer.ts
CHANGED
|
@@ -157,7 +157,7 @@ export class AccountSigner implements ethers.Signer {
|
|
|
157
157
|
nonceSpace: this.options.nonceSpace
|
|
158
158
|
}
|
|
159
159
|
: undefined
|
|
160
|
-
)
|
|
160
|
+
) as Promise<ethers.providers.TransactionResponse> // Will always have a transaction response
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
getBalance(blockTag?: ethers.providers.BlockTag | undefined): Promise<ethers.BigNumber> {
|