@0xsequence/account 1.2.9 → 1.4.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
  }
@@ -209,6 +210,7 @@ class Account {
209
210
  this.tracker = options.tracker;
210
211
  this.networks = options.networks;
211
212
  this.orchestrator = options.orchestrator;
213
+ this.jwt = options.jwt;
212
214
  this.migrations = options.migrations || migration.defaults.DefaultMigrations;
213
215
  this.migrator = new migration.migrator.Migrator(options.tracker, this.migrations, this.contexts);
214
216
  }
@@ -259,7 +261,7 @@ class Account {
259
261
  providerFor(chainId) {
260
262
  const found = this.network(chainId);
261
263
  if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`);
262
- return found.provider || new ethers.ethers.providers.StaticJsonRpcProvider(utils.getDefaultConnectionInfo(found.rpcUrl), {
264
+ return found.provider || new ethers.ethers.providers.StaticJsonRpcProvider(utils.getEthersConnectionInfo(found.rpcUrl, undefined, this.jwt), {
263
265
  name: '',
264
266
  chainId: ethers.ethers.BigNumber.from(chainId).toNumber()
265
267
  });
@@ -275,11 +277,16 @@ class Account {
275
277
  const found = this.network(chainId);
276
278
  if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`);
277
279
  if (relayer.isRelayer(found.relayer)) return found.relayer;
278
- return new relayer.RpcRelayer(found.relayer);
280
+ return new relayer.RpcRelayer(_extends({}, found.relayer, {
281
+ jwtAuth: this.jwt
282
+ }));
279
283
  }
280
284
  setOrchestrator(orchestrator) {
281
285
  this.orchestrator = orchestrator;
282
286
  }
287
+ setJwt(jwt) {
288
+ this.jwt = jwt;
289
+ }
283
290
  contextFor(version) {
284
291
  const ctx = this.contexts[version];
285
292
  if (!ctx) throw new Error(`Context not found for version ${version}`);
@@ -410,6 +417,19 @@ class Account {
410
417
  throw new Error(`Wallet ${this.address} is not fully migrated`);
411
418
  }
412
419
  }
420
+ async predecorateSignedTransactions(status, chainId) {
421
+ // Request signed predecorate transactions from child wallets
422
+ const bundles = await this.orchestrator.predecorateSignedTransactions({
423
+ chainId
424
+ });
425
+ // Get signed predecorate transaction
426
+ const predecorated = await this.predecorateTransactions([], status, chainId);
427
+ if (core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
428
+ // Sign it
429
+ bundles.push(await this.signTransactions(predecorated, chainId));
430
+ }
431
+ return bundles;
432
+ }
413
433
  async predecorateTransactions(txs, status, chainId) {
414
434
  // if onchain wallet config is not up to date
415
435
  // then we should append an extra transaction that updates it
@@ -421,34 +441,49 @@ class Account {
421
441
  }
422
442
  return txs;
423
443
  }
424
- decorateTransactions(bundle, status) {
425
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
426
- if (bootstrapBundle.transactions.length === 0) {
427
- return bundle;
444
+ async decorateTransactions(bundles, status, chainId) {
445
+ var _chainId;
446
+ if (!Array.isArray(bundles)) {
447
+ // Recurse with array
448
+ return this.decorateTransactions([bundles], status, chainId);
449
+ }
450
+
451
+ // Default to chainId of first bundle when not supplied
452
+ chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
453
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
454
+ const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
455
+ if (!hasBootstrapTxs && bundles.length === 1) {
456
+ return bundles[0];
428
457
  }
458
+
459
+ // Intent defaults to first bundle when no bootstrap transaction
460
+ const {
461
+ entrypoint
462
+ } = hasBootstrapTxs ? bootstrapBundle : bundles[0];
429
463
  const decoratedBundle = {
430
- entrypoint: bootstrapBundle.entrypoint,
431
- chainId: bundle.chainId,
432
- intent: bundle.intent,
433
- transactions: [...bootstrapBundle.transactions, {
464
+ entrypoint,
465
+ chainId,
466
+ // Intent of the first bundle is used
467
+ intent: bundles[0].intent,
468
+ transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
434
469
  to: bundle.entrypoint,
435
470
  data: core.commons.transaction.encodeBundleExecData(bundle),
436
471
  gasLimit: 0,
437
472
  delegateCall: false,
438
473
  revertOnError: true,
439
474
  value: 0
440
- }]
475
+ }))]
441
476
  };
442
477
 
443
478
  // Re-compute the meta-transaction id to use the guest module subdigest
444
479
  if (!status.onChain.deployed) {
445
- decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, decoratedBundle.chainId, decoratedBundle.transactions);
480
+ decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
446
481
  }
447
482
  return decoratedBundle;
448
483
  }
449
- decorateSignature(signature, status) {
484
+ async decorateSignature(signature, status) {
450
485
  if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
451
- return new Promise(resolve => resolve(signature));
486
+ return signature;
452
487
  }
453
488
  const coder = this.coders.signature;
454
489
  const chain = status.presignedConfigurations.map(c => c.signature);
@@ -467,14 +502,13 @@ class Account {
467
502
  signatures
468
503
  });
469
504
  }
470
- async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
505
+ async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
471
506
  // If we are signing a digest for chainId zero then we can never be fully migrated
472
507
  // because Sequence v1 doesn't allow for signing a message on "all chains"
473
508
 
474
509
  // So we ignore the state on "chain zero" and instead use one of the states of the networks
475
510
  // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
476
511
  // the behaviour of being migrated on all chains
477
-
478
512
  const chainRef = ethers.ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
479
513
  const status = await this.status(chainRef);
480
514
  this.mustBeFullyMigrated(status);
@@ -485,7 +519,7 @@ class Account {
485
519
  throw new Error('Wallet cannot validate onchain');
486
520
  }
487
521
  const wallet = this.walletForStatus(chainId, status);
488
- const signature = await wallet.signDigest(digest);
522
+ const signature = await wallet.signDigest(digest, metadata);
489
523
  const decorated = decorate ? this.decorateSignature(signature, status) : signature;
490
524
 
491
525
  // If the wallet can't validate onchain then we
@@ -504,8 +538,8 @@ class Account {
504
538
  }
505
539
  return decorated;
506
540
  }
507
- buildEIP6492Signature(signature, status, chainId) {
508
- const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
541
+ async buildEIP6492Signature(signature, status, chainId) {
542
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
509
543
  if (bootstrapBundle.transactions.length === 0) {
510
544
  throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
511
545
  }
@@ -561,8 +595,12 @@ class Account {
561
595
  * by any of the migrations.
562
596
  *
563
597
  */
564
- buildBootstrapTransactions(status, chainId) {
565
- const transactions = [];
598
+ async buildBootstrapTransactions(status, chainId) {
599
+ var _bundle$transactions;
600
+ const bundle = await this.orchestrator.buildDeployTransaction({
601
+ chainId
602
+ });
603
+ const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
566
604
 
567
605
  // Add wallet deployment if needed
568
606
  if (!status.onChain.deployed) {
@@ -571,6 +609,7 @@ class Account {
571
609
  const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
572
610
  transactions.push(...deployTransaction.transactions);
573
611
  }
612
+ transactions.length;
574
613
 
575
614
  // Get pending migrations
576
615
  transactions.push(...status.signedMigrations.map(m => ({
@@ -620,9 +659,20 @@ class Account {
620
659
  const status = pstatus || (await this.status(chainId));
621
660
  this.mustBeFullyMigrated(status);
622
661
  const wallet = this.walletForStatus(chainId, status);
662
+ const metadata = {
663
+ address: this.address,
664
+ digest: '',
665
+ // Set in wallet.signTransactions
666
+ chainId,
667
+ config: {
668
+ version: this.version
669
+ },
670
+ decorate: true,
671
+ cantValidateBehavior: 'ignore'
672
+ };
623
673
  const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
624
674
  space: options == null ? void 0 : options.nonceSpace
625
- });
675
+ }, metadata);
626
676
  return _extends({}, signed, {
627
677
  signature: await this.decorateSignature(signed.signature, status)
628
678
  });
@@ -701,9 +751,12 @@ class Account {
701
751
  };
702
752
  }
703
753
  async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
704
- const status = pstatus || (await this.status(signedBundle.chainId));
754
+ if (!Array.isArray(signedBundle)) {
755
+ return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
756
+ }
757
+ const status = pstatus || (await this.status(signedBundle[0].chainId));
705
758
  this.mustBeFullyMigrated(status);
706
- const decoratedBundle = this.decorateTransactions(signedBundle, status);
759
+ const decoratedBundle = await this.decorateTransactions(signedBundle, status);
707
760
  callback == null ? void 0 : callback(decoratedBundle);
708
761
  return this.relayer(chainId).relay(decoratedBundle, quote);
709
762
  }
@@ -736,7 +789,7 @@ class Account {
736
789
  nonce: 0 // The relayer also ignored the nonce
737
790
  };
738
791
 
739
- const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
792
+ const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
740
793
  const data = core.commons.transaction.encodeBundleExecData(decoratedBundle);
741
794
  const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
742
795
  return _extends({}, res, {
@@ -760,9 +813,21 @@ class Account {
760
813
  async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
761
814
  const status = await this.status(chainId);
762
815
  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);
816
+ const hasTxs = core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
817
+ const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
818
+ const childBundles = await this.orchestrator.predecorateSignedTransactions({
819
+ chainId
820
+ });
821
+ const bundles = [];
822
+ if (signed !== undefined && signed.transactions.length > 0) {
823
+ bundles.push(signed);
824
+ }
825
+ bundles.push(...childBundles.filter(b => b.transactions.length > 0));
826
+ if (bundles.length === 0) {
827
+ // Nothing to send?
828
+ return;
829
+ }
830
+ return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
766
831
  }
767
832
  async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
768
833
  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
  }
@@ -209,6 +210,7 @@ class Account {
209
210
  this.tracker = options.tracker;
210
211
  this.networks = options.networks;
211
212
  this.orchestrator = options.orchestrator;
213
+ this.jwt = options.jwt;
212
214
  this.migrations = options.migrations || migration.defaults.DefaultMigrations;
213
215
  this.migrator = new migration.migrator.Migrator(options.tracker, this.migrations, this.contexts);
214
216
  }
@@ -259,7 +261,7 @@ class Account {
259
261
  providerFor(chainId) {
260
262
  const found = this.network(chainId);
261
263
  if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`);
262
- return found.provider || new ethers.ethers.providers.StaticJsonRpcProvider(utils.getDefaultConnectionInfo(found.rpcUrl), {
264
+ return found.provider || new ethers.ethers.providers.StaticJsonRpcProvider(utils.getEthersConnectionInfo(found.rpcUrl, undefined, this.jwt), {
263
265
  name: '',
264
266
  chainId: ethers.ethers.BigNumber.from(chainId).toNumber()
265
267
  });
@@ -275,11 +277,16 @@ class Account {
275
277
  const found = this.network(chainId);
276
278
  if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`);
277
279
  if (relayer.isRelayer(found.relayer)) return found.relayer;
278
- return new relayer.RpcRelayer(found.relayer);
280
+ return new relayer.RpcRelayer(_extends({}, found.relayer, {
281
+ jwtAuth: this.jwt
282
+ }));
279
283
  }
280
284
  setOrchestrator(orchestrator) {
281
285
  this.orchestrator = orchestrator;
282
286
  }
287
+ setJwt(jwt) {
288
+ this.jwt = jwt;
289
+ }
283
290
  contextFor(version) {
284
291
  const ctx = this.contexts[version];
285
292
  if (!ctx) throw new Error(`Context not found for version ${version}`);
@@ -410,6 +417,19 @@ class Account {
410
417
  throw new Error(`Wallet ${this.address} is not fully migrated`);
411
418
  }
412
419
  }
420
+ async predecorateSignedTransactions(status, chainId) {
421
+ // Request signed predecorate transactions from child wallets
422
+ const bundles = await this.orchestrator.predecorateSignedTransactions({
423
+ chainId
424
+ });
425
+ // Get signed predecorate transaction
426
+ const predecorated = await this.predecorateTransactions([], status, chainId);
427
+ if (core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
428
+ // Sign it
429
+ bundles.push(await this.signTransactions(predecorated, chainId));
430
+ }
431
+ return bundles;
432
+ }
413
433
  async predecorateTransactions(txs, status, chainId) {
414
434
  // if onchain wallet config is not up to date
415
435
  // then we should append an extra transaction that updates it
@@ -421,34 +441,49 @@ class Account {
421
441
  }
422
442
  return txs;
423
443
  }
424
- decorateTransactions(bundle, status) {
425
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
426
- if (bootstrapBundle.transactions.length === 0) {
427
- return bundle;
444
+ async decorateTransactions(bundles, status, chainId) {
445
+ var _chainId;
446
+ if (!Array.isArray(bundles)) {
447
+ // Recurse with array
448
+ return this.decorateTransactions([bundles], status, chainId);
449
+ }
450
+
451
+ // Default to chainId of first bundle when not supplied
452
+ chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
453
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
454
+ const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
455
+ if (!hasBootstrapTxs && bundles.length === 1) {
456
+ return bundles[0];
428
457
  }
458
+
459
+ // Intent defaults to first bundle when no bootstrap transaction
460
+ const {
461
+ entrypoint
462
+ } = hasBootstrapTxs ? bootstrapBundle : bundles[0];
429
463
  const decoratedBundle = {
430
- entrypoint: bootstrapBundle.entrypoint,
431
- chainId: bundle.chainId,
432
- intent: bundle.intent,
433
- transactions: [...bootstrapBundle.transactions, {
464
+ entrypoint,
465
+ chainId,
466
+ // Intent of the first bundle is used
467
+ intent: bundles[0].intent,
468
+ transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
434
469
  to: bundle.entrypoint,
435
470
  data: core.commons.transaction.encodeBundleExecData(bundle),
436
471
  gasLimit: 0,
437
472
  delegateCall: false,
438
473
  revertOnError: true,
439
474
  value: 0
440
- }]
475
+ }))]
441
476
  };
442
477
 
443
478
  // Re-compute the meta-transaction id to use the guest module subdigest
444
479
  if (!status.onChain.deployed) {
445
- decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, decoratedBundle.chainId, decoratedBundle.transactions);
480
+ decoratedBundle.intent.id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
446
481
  }
447
482
  return decoratedBundle;
448
483
  }
449
- decorateSignature(signature, status) {
484
+ async decorateSignature(signature, status) {
450
485
  if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
451
- return new Promise(resolve => resolve(signature));
486
+ return signature;
452
487
  }
453
488
  const coder = this.coders.signature;
454
489
  const chain = status.presignedConfigurations.map(c => c.signature);
@@ -467,14 +502,13 @@ class Account {
467
502
  signatures
468
503
  });
469
504
  }
470
- async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
505
+ async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
471
506
  // If we are signing a digest for chainId zero then we can never be fully migrated
472
507
  // because Sequence v1 doesn't allow for signing a message on "all chains"
473
508
 
474
509
  // So we ignore the state on "chain zero" and instead use one of the states of the networks
475
510
  // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
476
511
  // the behaviour of being migrated on all chains
477
-
478
512
  const chainRef = ethers.ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
479
513
  const status = await this.status(chainRef);
480
514
  this.mustBeFullyMigrated(status);
@@ -485,7 +519,7 @@ class Account {
485
519
  throw new Error('Wallet cannot validate onchain');
486
520
  }
487
521
  const wallet = this.walletForStatus(chainId, status);
488
- const signature = await wallet.signDigest(digest);
522
+ const signature = await wallet.signDigest(digest, metadata);
489
523
  const decorated = decorate ? this.decorateSignature(signature, status) : signature;
490
524
 
491
525
  // If the wallet can't validate onchain then we
@@ -504,8 +538,8 @@ class Account {
504
538
  }
505
539
  return decorated;
506
540
  }
507
- buildEIP6492Signature(signature, status, chainId) {
508
- const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
541
+ async buildEIP6492Signature(signature, status, chainId) {
542
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
509
543
  if (bootstrapBundle.transactions.length === 0) {
510
544
  throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
511
545
  }
@@ -561,8 +595,12 @@ class Account {
561
595
  * by any of the migrations.
562
596
  *
563
597
  */
564
- buildBootstrapTransactions(status, chainId) {
565
- const transactions = [];
598
+ async buildBootstrapTransactions(status, chainId) {
599
+ var _bundle$transactions;
600
+ const bundle = await this.orchestrator.buildDeployTransaction({
601
+ chainId
602
+ });
603
+ const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
566
604
 
567
605
  // Add wallet deployment if needed
568
606
  if (!status.onChain.deployed) {
@@ -571,6 +609,7 @@ class Account {
571
609
  const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
572
610
  transactions.push(...deployTransaction.transactions);
573
611
  }
612
+ transactions.length;
574
613
 
575
614
  // Get pending migrations
576
615
  transactions.push(...status.signedMigrations.map(m => ({
@@ -620,9 +659,20 @@ class Account {
620
659
  const status = pstatus || (await this.status(chainId));
621
660
  this.mustBeFullyMigrated(status);
622
661
  const wallet = this.walletForStatus(chainId, status);
662
+ const metadata = {
663
+ address: this.address,
664
+ digest: '',
665
+ // Set in wallet.signTransactions
666
+ chainId,
667
+ config: {
668
+ version: this.version
669
+ },
670
+ decorate: true,
671
+ cantValidateBehavior: 'ignore'
672
+ };
623
673
  const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
624
674
  space: options == null ? void 0 : options.nonceSpace
625
- });
675
+ }, metadata);
626
676
  return _extends({}, signed, {
627
677
  signature: await this.decorateSignature(signed.signature, status)
628
678
  });
@@ -701,9 +751,12 @@ class Account {
701
751
  };
702
752
  }
703
753
  async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
704
- const status = pstatus || (await this.status(signedBundle.chainId));
754
+ if (!Array.isArray(signedBundle)) {
755
+ return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
756
+ }
757
+ const status = pstatus || (await this.status(signedBundle[0].chainId));
705
758
  this.mustBeFullyMigrated(status);
706
- const decoratedBundle = this.decorateTransactions(signedBundle, status);
759
+ const decoratedBundle = await this.decorateTransactions(signedBundle, status);
707
760
  callback == null ? void 0 : callback(decoratedBundle);
708
761
  return this.relayer(chainId).relay(decoratedBundle, quote);
709
762
  }
@@ -736,7 +789,7 @@ class Account {
736
789
  nonce: 0 // The relayer also ignored the nonce
737
790
  };
738
791
 
739
- const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
792
+ const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
740
793
  const data = core.commons.transaction.encodeBundleExecData(decoratedBundle);
741
794
  const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
742
795
  return _extends({}, res, {
@@ -760,9 +813,21 @@ class Account {
760
813
  async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
761
814
  const status = await this.status(chainId);
762
815
  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);
816
+ const hasTxs = core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
817
+ const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
818
+ const childBundles = await this.orchestrator.predecorateSignedTransactions({
819
+ chainId
820
+ });
821
+ const bundles = [];
822
+ if (signed !== undefined && signed.transactions.length > 0) {
823
+ bundles.push(signed);
824
+ }
825
+ bundles.push(...childBundles.filter(b => b.transactions.length > 0));
826
+ if (bundles.length === 0) {
827
+ // Nothing to send?
828
+ return;
829
+ }
830
+ return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
766
831
  }
767
832
  async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
768
833
  const digest = utils.encodeTypedDataDigest({
@@ -1,7 +1,7 @@
1
1
  import { commons, universal } from '@0xsequence/core';
2
2
  import { defaults, migrator, version } from '@0xsequence/migration';
3
3
  import { proto, isRelayer, RpcRelayer } from '@0xsequence/relayer';
4
- import { getDefaultConnectionInfo, encodeTypedDataDigest } from '@0xsequence/utils';
4
+ import { getEthersConnectionInfo, encodeTypedDataDigest } from '@0xsequence/utils';
5
5
  import { Wallet } from '@0xsequence/wallet';
6
6
  import { ethers } from 'ethers';
7
7
 
@@ -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
  }
@@ -205,6 +206,7 @@ class Account {
205
206
  this.tracker = options.tracker;
206
207
  this.networks = options.networks;
207
208
  this.orchestrator = options.orchestrator;
209
+ this.jwt = options.jwt;
208
210
  this.migrations = options.migrations || defaults.DefaultMigrations;
209
211
  this.migrator = new migrator.Migrator(options.tracker, this.migrations, this.contexts);
210
212
  }
@@ -255,7 +257,7 @@ class Account {
255
257
  providerFor(chainId) {
256
258
  const found = this.network(chainId);
257
259
  if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`);
258
- return found.provider || new ethers.providers.StaticJsonRpcProvider(getDefaultConnectionInfo(found.rpcUrl), {
260
+ return found.provider || new ethers.providers.StaticJsonRpcProvider(getEthersConnectionInfo(found.rpcUrl, undefined, this.jwt), {
259
261
  name: '',
260
262
  chainId: ethers.BigNumber.from(chainId).toNumber()
261
263
  });
@@ -271,11 +273,16 @@ class Account {
271
273
  const found = this.network(chainId);
272
274
  if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`);
273
275
  if (isRelayer(found.relayer)) return found.relayer;
274
- return new RpcRelayer(found.relayer);
276
+ return new RpcRelayer(_extends({}, found.relayer, {
277
+ jwtAuth: this.jwt
278
+ }));
275
279
  }
276
280
  setOrchestrator(orchestrator) {
277
281
  this.orchestrator = orchestrator;
278
282
  }
283
+ setJwt(jwt) {
284
+ this.jwt = jwt;
285
+ }
279
286
  contextFor(version) {
280
287
  const ctx = this.contexts[version];
281
288
  if (!ctx) throw new Error(`Context not found for version ${version}`);
@@ -406,6 +413,19 @@ class Account {
406
413
  throw new Error(`Wallet ${this.address} is not fully migrated`);
407
414
  }
408
415
  }
416
+ async predecorateSignedTransactions(status, chainId) {
417
+ // Request signed predecorate transactions from child wallets
418
+ const bundles = await this.orchestrator.predecorateSignedTransactions({
419
+ chainId
420
+ });
421
+ // Get signed predecorate transaction
422
+ const predecorated = await this.predecorateTransactions([], status, chainId);
423
+ if (commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
424
+ // Sign it
425
+ bundles.push(await this.signTransactions(predecorated, chainId));
426
+ }
427
+ return bundles;
428
+ }
409
429
  async predecorateTransactions(txs, status, chainId) {
410
430
  // if onchain wallet config is not up to date
411
431
  // then we should append an extra transaction that updates it
@@ -417,34 +437,49 @@ class Account {
417
437
  }
418
438
  return txs;
419
439
  }
420
- decorateTransactions(bundle, status) {
421
- const bootstrapBundle = this.buildBootstrapTransactions(status, bundle.chainId);
422
- if (bootstrapBundle.transactions.length === 0) {
423
- return bundle;
440
+ async decorateTransactions(bundles, status, chainId) {
441
+ var _chainId;
442
+ if (!Array.isArray(bundles)) {
443
+ // Recurse with array
444
+ return this.decorateTransactions([bundles], status, chainId);
445
+ }
446
+
447
+ // Default to chainId of first bundle when not supplied
448
+ chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
449
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
450
+ const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
451
+ if (!hasBootstrapTxs && bundles.length === 1) {
452
+ return bundles[0];
424
453
  }
454
+
455
+ // Intent defaults to first bundle when no bootstrap transaction
456
+ const {
457
+ entrypoint
458
+ } = hasBootstrapTxs ? bootstrapBundle : bundles[0];
425
459
  const decoratedBundle = {
426
- entrypoint: bootstrapBundle.entrypoint,
427
- chainId: bundle.chainId,
428
- intent: bundle.intent,
429
- transactions: [...bootstrapBundle.transactions, {
460
+ entrypoint,
461
+ chainId,
462
+ // Intent of the first bundle is used
463
+ intent: bundles[0].intent,
464
+ transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
430
465
  to: bundle.entrypoint,
431
466
  data: commons.transaction.encodeBundleExecData(bundle),
432
467
  gasLimit: 0,
433
468
  delegateCall: false,
434
469
  revertOnError: true,
435
470
  value: 0
436
- }]
471
+ }))]
437
472
  };
438
473
 
439
474
  // Re-compute the meta-transaction id to use the guest module subdigest
440
475
  if (!status.onChain.deployed) {
441
- decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, decoratedBundle.chainId, decoratedBundle.transactions);
476
+ decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
442
477
  }
443
478
  return decoratedBundle;
444
479
  }
445
- decorateSignature(signature, status) {
480
+ async decorateSignature(signature, status) {
446
481
  if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
447
- return new Promise(resolve => resolve(signature));
482
+ return signature;
448
483
  }
449
484
  const coder = this.coders.signature;
450
485
  const chain = status.presignedConfigurations.map(c => c.signature);
@@ -463,14 +498,13 @@ class Account {
463
498
  signatures
464
499
  });
465
500
  }
466
- async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore') {
501
+ async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
467
502
  // If we are signing a digest for chainId zero then we can never be fully migrated
468
503
  // because Sequence v1 doesn't allow for signing a message on "all chains"
469
504
 
470
505
  // So we ignore the state on "chain zero" and instead use one of the states of the networks
471
506
  // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
472
507
  // the behaviour of being migrated on all chains
473
-
474
508
  const chainRef = ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
475
509
  const status = await this.status(chainRef);
476
510
  this.mustBeFullyMigrated(status);
@@ -481,7 +515,7 @@ class Account {
481
515
  throw new Error('Wallet cannot validate onchain');
482
516
  }
483
517
  const wallet = this.walletForStatus(chainId, status);
484
- const signature = await wallet.signDigest(digest);
518
+ const signature = await wallet.signDigest(digest, metadata);
485
519
  const decorated = decorate ? this.decorateSignature(signature, status) : signature;
486
520
 
487
521
  // If the wallet can't validate onchain then we
@@ -500,8 +534,8 @@ class Account {
500
534
  }
501
535
  return decorated;
502
536
  }
503
- buildEIP6492Signature(signature, status, chainId) {
504
- const bootstrapBundle = this.buildBootstrapTransactions(status, chainId);
537
+ async buildEIP6492Signature(signature, status, chainId) {
538
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
505
539
  if (bootstrapBundle.transactions.length === 0) {
506
540
  throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
507
541
  }
@@ -557,8 +591,12 @@ class Account {
557
591
  * by any of the migrations.
558
592
  *
559
593
  */
560
- buildBootstrapTransactions(status, chainId) {
561
- const transactions = [];
594
+ async buildBootstrapTransactions(status, chainId) {
595
+ var _bundle$transactions;
596
+ const bundle = await this.orchestrator.buildDeployTransaction({
597
+ chainId
598
+ });
599
+ const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
562
600
 
563
601
  // Add wallet deployment if needed
564
602
  if (!status.onChain.deployed) {
@@ -567,6 +605,7 @@ class Account {
567
605
  const deployTransaction = Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
568
606
  transactions.push(...deployTransaction.transactions);
569
607
  }
608
+ transactions.length;
570
609
 
571
610
  // Get pending migrations
572
611
  transactions.push(...status.signedMigrations.map(m => ({
@@ -616,9 +655,20 @@ class Account {
616
655
  const status = pstatus || (await this.status(chainId));
617
656
  this.mustBeFullyMigrated(status);
618
657
  const wallet = this.walletForStatus(chainId, status);
658
+ const metadata = {
659
+ address: this.address,
660
+ digest: '',
661
+ // Set in wallet.signTransactions
662
+ chainId,
663
+ config: {
664
+ version: this.version
665
+ },
666
+ decorate: true,
667
+ cantValidateBehavior: 'ignore'
668
+ };
619
669
  const signed = await wallet.signTransactions(txs, (options == null ? void 0 : options.nonceSpace) && {
620
670
  space: options == null ? void 0 : options.nonceSpace
621
- });
671
+ }, metadata);
622
672
  return _extends({}, signed, {
623
673
  signature: await this.decorateSignature(signed.signature, status)
624
674
  });
@@ -697,9 +747,12 @@ class Account {
697
747
  };
698
748
  }
699
749
  async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback) {
700
- const status = pstatus || (await this.status(signedBundle.chainId));
750
+ if (!Array.isArray(signedBundle)) {
751
+ return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback);
752
+ }
753
+ const status = pstatus || (await this.status(signedBundle[0].chainId));
701
754
  this.mustBeFullyMigrated(status);
702
- const decoratedBundle = this.decorateTransactions(signedBundle, status);
755
+ const decoratedBundle = await this.decorateTransactions(signedBundle, status);
703
756
  callback == null ? void 0 : callback(decoratedBundle);
704
757
  return this.relayer(chainId).relay(decoratedBundle, quote);
705
758
  }
@@ -732,7 +785,7 @@ class Account {
732
785
  nonce: 0 // The relayer also ignored the nonce
733
786
  };
734
787
 
735
- const decoratedBundle = this.decorateTransactions(signedBundle, wstatus);
788
+ const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
736
789
  const data = commons.transaction.encodeBundleExecData(decoratedBundle);
737
790
  const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
738
791
  return _extends({}, res, {
@@ -756,9 +809,21 @@ class Account {
756
809
  async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
757
810
  const status = await this.status(chainId);
758
811
  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);
812
+ const hasTxs = commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
813
+ const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
814
+ const childBundles = await this.orchestrator.predecorateSignedTransactions({
815
+ chainId
816
+ });
817
+ const bundles = [];
818
+ if (signed !== undefined && signed.transactions.length > 0) {
819
+ bundles.push(signed);
820
+ }
821
+ bundles.push(...childBundles.filter(b => b.transactions.length > 0));
822
+ if (bundles.length === 0) {
823
+ // Nothing to send?
824
+ return;
825
+ }
826
+ return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback);
762
827
  }
763
828
  async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
764
829
  const digest = encodeTypedDataDigest({
@@ -35,6 +35,7 @@ export type AccountOptions = {
35
35
  migrations?: migrator.Migrations;
36
36
  orchestrator: Orchestrator;
37
37
  networks: NetworkConfig[];
38
+ jwt?: string;
38
39
  };
39
40
  export interface PreparedTransactions {
40
41
  transactions: commons.transaction.SimulatedTransaction[];
@@ -50,6 +51,7 @@ export declare class Account {
50
51
  readonly migrator: migrator.Migrator;
51
52
  readonly migrations: migrator.Migrations;
52
53
  private orchestrator;
54
+ private jwt?;
53
55
  constructor(options: AccountOptions);
54
56
  getSigner(chainId: ChainId, options?: AccountSignerOptions): AccountSigner;
55
57
  static new(options: {
@@ -71,16 +73,18 @@ export declare class Account {
71
73
  reader(chainId: ethers.BigNumberish): commons.reader.Reader;
72
74
  relayer(chainId: ethers.BigNumberish): Relayer;
73
75
  setOrchestrator(orchestrator: Orchestrator): void;
76
+ setJwt(jwt: string): void;
74
77
  contextFor(version: number): commons.context.WalletContext;
75
78
  walletForStatus(chainId: ethers.BigNumberish, status: Pick<AccountStatus, 'version'> & Pick<AccountStatus, 'config'>): Wallet;
76
79
  walletFor(chainId: ethers.BigNumberish, context: commons.context.WalletContext, config: commons.config.Config, coders: typeof this.coders): Wallet;
77
80
  status(chainId: ethers.BigNumberish, longestPath?: boolean): Promise<AccountStatus>;
78
81
  private mustBeFullyMigrated;
82
+ predecorateSignedTransactions(status: AccountStatus, chainId: ethers.BigNumberish): Promise<commons.transaction.SignedTransactionBundle[]>;
79
83
  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;
84
+ decorateTransactions(bundles: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[], status: AccountStatus, chainId?: ethers.BigNumberish): Promise<commons.transaction.IntendedTransactionBundle>;
81
85
  decorateSignature<T extends ethers.BytesLike>(signature: T, status: Partial<Pick<AccountStatus, 'presignedConfigurations'>>): Promise<T | string>;
82
86
  publishWitness(): Promise<void>;
83
- signDigest(digest: ethers.BytesLike, chainId: ethers.BigNumberish, decorate?: boolean, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
87
+ signDigest(digest: ethers.BytesLike, chainId: ethers.BigNumberish, decorate?: boolean, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw', metadata?: object): Promise<string>;
84
88
  private buildEIP6492Signature;
85
89
  editConfig(changes: {
86
90
  add?: commons.config.SimpleSigner[];
@@ -99,7 +103,7 @@ export declare class Account {
99
103
  * by any of the migrations.
100
104
  *
101
105
  */
102
- buildBootstrapTransactions(status: AccountStatus, chainId: ethers.BigNumberish): commons.transaction.IntendedTransactionBundle;
106
+ buildBootstrapTransactions(status: AccountStatus, chainId: ethers.BigNumberish): Promise<commons.transaction.IntendedTransactionBundle>;
103
107
  bootstrapTransactions(chainId: ethers.BigNumberish, prestatus?: AccountStatus): Promise<Omit<commons.transaction.IntendedTransactionBundle, 'chainId'>>;
104
108
  doBootstrap(chainId: ethers.BigNumberish, feeQuote?: FeeQuote, prestatus?: AccountStatus): Promise<commons.transaction.TransactionResponse<any>>;
105
109
  signMessage(message: ethers.BytesLike, chainId: ethers.BigNumberish, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
@@ -115,7 +119,7 @@ export declare class Account {
115
119
  migratedAllChains: boolean;
116
120
  failedChains: number[];
117
121
  }>;
118
- sendSignedTransactions(signedBundle: commons.transaction.IntendedTransactionBundle, chainId: ethers.BigNumberish, quote?: FeeQuote, pstatus?: AccountStatus, callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void): Promise<ethers.providers.TransactionResponse>;
122
+ 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
123
  fillGasLimits(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, status?: AccountStatus): Promise<commons.transaction.SimulatedTransaction[]>;
120
124
  gasRefundQuotes(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, stubSignatureOverrides: Map<string, string>, status?: AccountStatus, options?: {
121
125
  simulate?: boolean;
@@ -132,7 +136,7 @@ export declare class Account {
132
136
  }): Promise<PreparedTransactions>;
133
137
  sendTransaction(txs: commons.transaction.Transactionish, chainId: ethers.BigNumberish, quote?: FeeQuote, skipPreDecorate?: boolean, callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void, options?: {
134
138
  nonceSpace?: ethers.BigNumberish;
135
- }): Promise<ethers.providers.TransactionResponse>;
139
+ }): Promise<ethers.providers.TransactionResponse | undefined>;
136
140
  signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, message: Record<string, any>, chainId: ethers.BigNumberish, cantValidateBehavior?: 'ignore' | 'eip6492' | 'throw'): Promise<string>;
137
141
  getAllSigners(): Promise<{
138
142
  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.4.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.4.0",
14
+ "@0xsequence/migration": "1.4.0",
15
+ "@0xsequence/network": "1.4.0",
16
+ "@0xsequence/relayer": "1.4.0",
17
+ "@0xsequence/sessions": "1.4.0",
18
+ "@0xsequence/utils": "1.4.0",
19
+ "@0xsequence/wallet": "1.4.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.4.0",
25
+ "@0xsequence/tests": "1.4.0"
26
26
  },
27
27
  "files": [
28
28
  "src",
package/src/account.ts CHANGED
@@ -1,10 +1,11 @@
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'
5
6
  import { tracker } from '@0xsequence/sessions'
6
7
  import { Orchestrator } from '@0xsequence/signhub'
7
- import { encodeTypedDataDigest, getDefaultConnectionInfo } from '@0xsequence/utils'
8
+ import { encodeTypedDataDigest, getEthersConnectionInfo } from '@0xsequence/utils'
8
9
  import { Wallet } from '@0xsequence/wallet'
9
10
  import { ethers, TypedDataDomain, TypedDataField } from 'ethers'
10
11
  import { AccountSigner, AccountSignerOptions } from './signer'
@@ -52,6 +53,9 @@ export type AccountOptions = {
52
53
 
53
54
  // Networks information and providers
54
55
  networks: NetworkConfig[]
56
+
57
+ // Jwt
58
+ jwt?: string
55
59
  }
56
60
 
57
61
  export interface PreparedTransactions {
@@ -95,6 +99,8 @@ export class Account {
95
99
 
96
100
  private orchestrator: Orchestrator
97
101
 
102
+ private jwt?: string
103
+
98
104
  constructor(options: AccountOptions) {
99
105
  this.address = ethers.utils.getAddress(options.address)
100
106
 
@@ -102,6 +108,7 @@ export class Account {
102
108
  this.tracker = options.tracker
103
109
  this.networks = options.networks
104
110
  this.orchestrator = options.orchestrator
111
+ this.jwt = options.jwt
105
112
 
106
113
  this.migrations = options.migrations || defaults.DefaultMigrations
107
114
  this.migrator = new migrator.Migrator(options.tracker, this.migrations, this.contexts)
@@ -173,7 +180,7 @@ export class Account {
173
180
  if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`)
174
181
  return (
175
182
  found.provider ||
176
- new ethers.providers.StaticJsonRpcProvider(getDefaultConnectionInfo(found.rpcUrl), {
183
+ new ethers.providers.StaticJsonRpcProvider(getEthersConnectionInfo(found.rpcUrl, undefined, this.jwt), {
177
184
  name: '',
178
185
  chainId: ethers.BigNumber.from(chainId).toNumber()
179
186
  })
@@ -192,13 +199,17 @@ export class Account {
192
199
  const found = this.network(chainId)
193
200
  if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`)
194
201
  if (isRelayer(found.relayer)) return found.relayer
195
- return new RpcRelayer(found.relayer)
202
+ return new RpcRelayer({ ...found.relayer, jwtAuth: this.jwt })
196
203
  }
197
204
 
198
205
  setOrchestrator(orchestrator: Orchestrator) {
199
206
  this.orchestrator = orchestrator
200
207
  }
201
208
 
209
+ setJwt(jwt: string) {
210
+ this.jwt = jwt
211
+ }
212
+
202
213
  contextFor(version: number): commons.context.WalletContext {
203
214
  const ctx = this.contexts[version]
204
215
  if (!ctx) throw new Error(`Context not found for version ${version}`)
@@ -352,6 +363,21 @@ export class Account {
352
363
  }
353
364
  }
354
365
 
366
+ async predecorateSignedTransactions(
367
+ status: AccountStatus,
368
+ chainId: ethers.BigNumberish
369
+ ): Promise<commons.transaction.SignedTransactionBundle[]> {
370
+ // Request signed predecorate transactions from child wallets
371
+ const bundles = await this.orchestrator.predecorateSignedTransactions({ chainId })
372
+ // Get signed predecorate transaction
373
+ const predecorated = await this.predecorateTransactions([], status, chainId)
374
+ if (commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
375
+ // Sign it
376
+ bundles.push(await this.signTransactions(predecorated, chainId))
377
+ }
378
+ return bundles
379
+ }
380
+
355
381
  async predecorateTransactions(
356
382
  txs: commons.transaction.Transactionish,
357
383
  status: AccountStatus,
@@ -369,29 +395,46 @@ export class Account {
369
395
  return txs
370
396
  }
371
397
 
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
398
+ async decorateTransactions(
399
+ bundles: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[],
400
+ status: AccountStatus,
401
+ chainId?: ethers.BigNumberish
402
+ ): Promise<commons.transaction.IntendedTransactionBundle> {
403
+ if (!Array.isArray(bundles)) {
404
+ // Recurse with array
405
+ return this.decorateTransactions([bundles], status, chainId)
406
+ }
407
+
408
+ // Default to chainId of first bundle when not supplied
409
+ chainId = chainId ?? bundles[0].chainId
410
+
411
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId)
412
+ const hasBootstrapTxs = bootstrapBundle.transactions.length > 0
413
+
414
+ if (!hasBootstrapTxs && bundles.length === 1) {
415
+ return bundles[0]
379
416
  }
380
417
 
418
+ // Intent defaults to first bundle when no bootstrap transaction
419
+ const { entrypoint } = hasBootstrapTxs ? bootstrapBundle : bundles[0]
420
+
381
421
  const decoratedBundle = {
382
- entrypoint: bootstrapBundle.entrypoint,
383
- chainId: bundle.chainId,
384
- intent: bundle.intent,
422
+ entrypoint,
423
+ chainId,
424
+ // Intent of the first bundle is used
425
+ intent: bundles[0].intent,
385
426
  transactions: [
386
427
  ...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
- }
428
+ ...bundles.map(
429
+ (bundle): commons.transaction.Transaction => ({
430
+ to: bundle.entrypoint,
431
+ data: commons.transaction.encodeBundleExecData(bundle),
432
+ gasLimit: 0,
433
+ delegateCall: false,
434
+ revertOnError: true,
435
+ value: 0
436
+ })
437
+ )
395
438
  ]
396
439
  }
397
440
 
@@ -399,7 +442,7 @@ export class Account {
399
442
  if (!status.onChain.deployed) {
400
443
  decoratedBundle.intent.id = commons.transaction.subdigestOfGuestModuleTransactions(
401
444
  this.contexts[this.version].guestModule,
402
- decoratedBundle.chainId,
445
+ chainId,
403
446
  decoratedBundle.transactions
404
447
  )
405
448
  }
@@ -407,12 +450,12 @@ export class Account {
407
450
  return decoratedBundle
408
451
  }
409
452
 
410
- decorateSignature<T extends ethers.BytesLike>(
453
+ async decorateSignature<T extends ethers.BytesLike>(
411
454
  signature: T,
412
455
  status: Partial<Pick<AccountStatus, 'presignedConfigurations'>>
413
456
  ): Promise<T | string> {
414
457
  if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
415
- return new Promise(resolve => resolve(signature))
458
+ return signature
416
459
  }
417
460
 
418
461
  const coder = this.coders.signature
@@ -434,7 +477,8 @@ export class Account {
434
477
  digest: ethers.BytesLike,
435
478
  chainId: ethers.BigNumberish,
436
479
  decorate: boolean = true,
437
- cantValidateBehavior: 'ignore' | 'eip6492' | 'throw' = 'ignore'
480
+ cantValidateBehavior: 'ignore' | 'eip6492' | 'throw' = 'ignore',
481
+ metadata?: object
438
482
  ): Promise<string> {
439
483
  // If we are signing a digest for chainId zero then we can never be fully migrated
440
484
  // because Sequence v1 doesn't allow for signing a message on "all chains"
@@ -442,10 +486,8 @@ export class Account {
442
486
  // So we ignore the state on "chain zero" and instead use one of the states of the networks
443
487
  // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
444
488
  // the behaviour of being migrated on all chains
445
-
446
489
  const chainRef = ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId
447
490
  const status = await this.status(chainRef)
448
-
449
491
  this.mustBeFullyMigrated(status)
450
492
 
451
493
  // Check if we can validate onchain and what to do if we can't
@@ -455,7 +497,7 @@ export class Account {
455
497
  }
456
498
 
457
499
  const wallet = this.walletForStatus(chainId, status)
458
- const signature = await wallet.signDigest(digest)
500
+ const signature = await wallet.signDigest(digest, metadata)
459
501
 
460
502
  const decorated = decorate ? this.decorateSignature(signature, status) : signature
461
503
 
@@ -478,8 +520,8 @@ export class Account {
478
520
  return decorated
479
521
  }
480
522
 
481
- private buildEIP6492Signature(signature: string, status: AccountStatus, chainId: ethers.BigNumberish): string {
482
- const bootstrapBundle = this.buildBootstrapTransactions(status, chainId)
523
+ private async buildEIP6492Signature(signature: string, status: AccountStatus, chainId: ethers.BigNumberish): Promise<string> {
524
+ const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId)
483
525
  if (bootstrapBundle.transactions.length === 0) {
484
526
  throw new Error('Cannot build EIP-6492 signature without bootstrap transactions')
485
527
  }
@@ -550,8 +592,12 @@ export class Account {
550
592
  * by any of the migrations.
551
593
  *
552
594
  */
553
- buildBootstrapTransactions(status: AccountStatus, chainId: ethers.BigNumberish): commons.transaction.IntendedTransactionBundle {
554
- const transactions: commons.transaction.Transaction[] = []
595
+ async buildBootstrapTransactions(
596
+ status: AccountStatus,
597
+ chainId: ethers.BigNumberish
598
+ ): Promise<commons.transaction.IntendedTransactionBundle> {
599
+ const bundle = await this.orchestrator.buildDeployTransaction({ chainId })
600
+ const transactions: commons.transaction.Transaction[] = bundle?.transactions ?? []
555
601
 
556
602
  // Add wallet deployment if needed
557
603
  if (!status.onChain.deployed) {
@@ -561,6 +607,7 @@ export class Account {
561
607
 
562
608
  transactions.push(...deployTransaction.transactions)
563
609
  }
610
+ const len = transactions.length
564
611
 
565
612
  // Get pending migrations
566
613
  transactions.push(
@@ -622,7 +669,17 @@ export class Account {
622
669
  this.mustBeFullyMigrated(status)
623
670
 
624
671
  const wallet = this.walletForStatus(chainId, status)
625
- const signed = await wallet.signTransactions(txs, options?.nonceSpace && { space: options?.nonceSpace })
672
+
673
+ const metadata: WalletSignRequestMetadata = {
674
+ address: this.address,
675
+ digest: '', // Set in wallet.signTransactions
676
+ chainId,
677
+ config: { version: this.version },
678
+ decorate: true,
679
+ cantValidateBehavior: 'ignore'
680
+ }
681
+
682
+ const signed = await wallet.signTransactions(txs, options?.nonceSpace && { space: options?.nonceSpace }, metadata)
626
683
 
627
684
  return {
628
685
  ...signed,
@@ -707,16 +764,19 @@ export class Account {
707
764
  }
708
765
 
709
766
  async sendSignedTransactions(
710
- signedBundle: commons.transaction.IntendedTransactionBundle,
767
+ signedBundle: commons.transaction.IntendedTransactionBundle | commons.transaction.IntendedTransactionBundle[],
711
768
  chainId: ethers.BigNumberish,
712
769
  quote?: FeeQuote,
713
770
  pstatus?: AccountStatus,
714
771
  callback?: (bundle: commons.transaction.IntendedTransactionBundle) => void
715
772
  ): Promise<ethers.providers.TransactionResponse> {
716
- const status = pstatus || (await this.status(signedBundle.chainId))
773
+ if (!Array.isArray(signedBundle)) {
774
+ return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback)
775
+ }
776
+ const status = pstatus || (await this.status(signedBundle[0].chainId))
717
777
  this.mustBeFullyMigrated(status)
718
778
 
719
- const decoratedBundle = this.decorateTransactions(signedBundle, status)
779
+ const decoratedBundle = await this.decorateTransactions(signedBundle, status)
720
780
  callback?.(decoratedBundle)
721
781
 
722
782
  return this.relayer(chainId).relay(decoratedBundle, quote)
@@ -769,7 +829,7 @@ export class Account {
769
829
  nonce: 0 // The relayer also ignored the nonce
770
830
  }
771
831
 
772
- const decoratedBundle = this.decorateTransactions(signedBundle, wstatus)
832
+ const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus)
773
833
  const data = commons.transaction.encodeBundleExecData(decoratedBundle)
774
834
  const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options)
775
835
  return { ...res, decorated: decoratedBundle }
@@ -806,13 +866,27 @@ export class Account {
806
866
  options?: {
807
867
  nonceSpace?: ethers.BigNumberish
808
868
  }
809
- ): Promise<ethers.providers.TransactionResponse> {
869
+ ): Promise<ethers.providers.TransactionResponse | undefined> {
810
870
  const status = await this.status(chainId)
811
871
 
812
872
  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)
873
+ const hasTxs = commons.transaction.fromTransactionish(this.address, predecorated).length > 0
874
+ const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined
875
+
876
+ const childBundles = await this.orchestrator.predecorateSignedTransactions({ chainId })
877
+
878
+ const bundles: commons.transaction.SignedTransactionBundle[] = []
879
+ if (signed !== undefined && signed.transactions.length > 0) {
880
+ bundles.push(signed)
881
+ }
882
+ bundles.push(...childBundles.filter(b => b.transactions.length > 0))
883
+
884
+ if (bundles.length === 0) {
885
+ // Nothing to send?
886
+ return
887
+ }
888
+
889
+ return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback)
816
890
  }
817
891
 
818
892
  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> {