@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.
@@ -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(bundle, status) {
425
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
426
- if (bootstrapBundle.transactions.length === 0) {
427
- return bundle;
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: bootstrapBundle.entrypoint,
431
- chainId: bundle.chainId,
432
- intent: bundle.intent,
433
- transactions: [...bootstrapBundle.transactions, {
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, decoratedBundle.chainId, decoratedBundle.transactions);
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 new Promise(resolve => resolve(signature));
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
- const transactions = [];
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
- const status = pstatus || (await this.status(signedBundle.chainId));
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 signed = await this.signTransactions(predecorated, chainId, undefined, options);
764
- // TODO: is it safe to pass status again here?
765
- return this.sendSignedTransactions(signed, chainId, quote, undefined, callback);
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(bundle, status) {
425
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
426
- if (bootstrapBundle.transactions.length === 0) {
427
- return bundle;
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: bootstrapBundle.entrypoint,
431
- chainId: bundle.chainId,
432
- intent: bundle.intent,
433
- transactions: [...bootstrapBundle.transactions, {
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, decoratedBundle.chainId, decoratedBundle.transactions);
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 new Promise(resolve => resolve(signature));
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
- const transactions = [];
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
- const status = pstatus || (await this.status(signedBundle.chainId));
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 signed = await this.signTransactions(predecorated, chainId, undefined, options);
764
- // TODO: is it safe to pass status again here?
765
- return this.sendSignedTransactions(signed, chainId, quote, undefined, callback);
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(bundle, status) {
421
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
422
- if (bootstrapBundle.transactions.length === 0) {
423
- return bundle;
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: bootstrapBundle.entrypoint,
427
- chainId: bundle.chainId,
428
- intent: bundle.intent,
429
- transactions: [...bootstrapBundle.transactions, {
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, decoratedBundle.chainId, decoratedBundle.transactions);
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 new Promise(resolve => resolve(signature));
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
- const transactions = [];
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
- const status = pstatus || (await this.status(signedBundle.chainId));
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 signed = await this.signTransactions(predecorated, chainId, undefined, options);
760
- // TODO: is it safe to pass status again here?
761
- return this.sendSignedTransactions(signed, chainId, quote, undefined, callback);
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(bundle: commons.transaction.IntendedTransactionBundle, status: AccountStatus): commons.transaction.IntendedTransactionBundle;
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.2.9",
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/migration": "1.2.9",
14
- "@0xsequence/core": "1.2.9",
15
- "@0xsequence/network": "1.2.9",
16
- "@0xsequence/relayer": "1.2.9",
17
- "@0xsequence/sessions": "1.2.9",
18
- "@0xsequence/utils": "1.2.9",
19
- "@0xsequence/wallet": "1.2.9"
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.2.9",
25
- "@0xsequence/tests": "1.2.9"
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
- bundle: commons.transaction.IntendedTransactionBundle,
374
- status: AccountStatus
375
- ): commons.transaction.IntendedTransactionBundle {
376
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId)
377
- if (bootstrapBundle.transactions.length === 0) {
378
- return bundle
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: bootstrapBundle.entrypoint,
383
- chainId: bundle.chainId,
384
- intent: bundle.intent,
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
- to: bundle.entrypoint,
389
- data: commons.transaction.encodeBundleExecData(bundle),
390
- gasLimit: 0,
391
- delegateCall: false,
392
- revertOnError: true,
393
- value: 0
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
- decoratedBundle.chainId,
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 new Promise(resolve => resolve(signature))
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(status: AccountStatus, chainId: ethers.BigNumberish): commons.transaction.IntendedTransactionBundle {
554
- const transactions: commons.transaction.Transaction[] = []
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
- const signed = await wallet.signTransactions(txs, options?.nonceSpace && { space: options?.nonceSpace })
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
- const status = pstatus || (await this.status(signedBundle.chainId))
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 signed = await this.signTransactions(predecorated, chainId, undefined, options)
814
- // TODO: is it safe to pass status again here?
815
- return this.sendSignedTransactions(signed, chainId, quote, undefined, callback)
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> {