@0xsequence/provider 0.0.0-20220614152529 → 0.0.0-20220630211555

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.
@@ -5,12 +5,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var eventemitter2 = require('eventemitter2');
6
6
  var utils = require('@0xsequence/utils');
7
7
  var ethers = require('ethers');
8
- var config = require('@0xsequence/config');
9
- var providers = require('@ethersproject/providers');
10
8
  var network = require('@0xsequence/network');
9
+ var config = require('@0xsequence/config');
11
10
  var wallet = require('@0xsequence/wallet');
12
11
  var transactions = require('@0xsequence/transactions');
13
12
  var auth = require('@0xsequence/auth');
13
+ var providers = require('@ethersproject/providers');
14
14
 
15
15
  function _extends() {
16
16
  _extends = Object.assign || function (target) {
@@ -478,629 +478,569 @@ class BaseProviderTransport {
478
478
 
479
479
  }
480
480
 
481
- const SIGNER_READY_TIMEOUT = 10000;
482
- class WalletRequestHandler {
483
- // signer interface of the wallet. A null value means there is no signer (ie. user not signed in). An undefined
484
- // value means the signer state is unknown, usually meaning the wallet app is booting up and initializing. Of course
485
- // a Signer value is the actually interface to a signed-in account
486
- constructor(_signer, prompter, mainnetNetworks, testnetNetworks = []) {
487
- var _this = this;
481
+ const recoverWalletConfig = async (address, digest, signature, chainId, provider, configTracker, walletContext) => {
482
+ const subDigest = utils.packMessageData(address, chainId, digest);
483
+ const config$1 = await wallet.recoverConfig(subDigest, signature, provider, walletContext || network.sequenceContext, ethers.ethers.BigNumber.from(chainId).toNumber(), true, configTracker);
488
484
 
489
- this.signer = void 0;
490
- this.signerReadyCallbacks = [];
491
- this.prompter = void 0;
492
- this.mainnetNetworks = void 0;
493
- this.testnetNetworks = void 0;
494
- this._openIntent = void 0;
495
- this._connectOptions = void 0;
496
- this._defaultNetworkId = void 0;
497
- this._chainId = void 0;
498
- this.events = new eventemitter2.EventEmitter2();
485
+ if (walletContext) {
486
+ const recoveredWalletAddress = config.addressOf(config$1, walletContext);
499
487
 
500
- this.promptConnect = async function (options) {
501
- if (!options && !_this._connectOptions) {
502
- // this is an unexpected state and should not happen
503
- throw new Error('prompter connect options are empty');
504
- }
488
+ if (config$1.address && config$1.address !== recoveredWalletAddress) {
489
+ throw new Error('recovered address does not match the WalletConfig address, check the WalletContext');
490
+ } else {
491
+ config$1.address = recoveredWalletAddress;
492
+ }
493
+ }
505
494
 
506
- if (!_this.prompter) {
507
- // if prompter is null, we'll auto connect
508
- return _this.connect(options);
509
- }
495
+ return config$1;
496
+ };
497
+ const isBrowserExtension = () => window.location.protocol === 'chrome-extension:' || window.location.protocol === 'moz-extension:';
498
+ class LocalStorage {
499
+ constructor() {}
510
500
 
511
- const promptConnectDetails = await _this.prompter.promptConnect(options || _this._connectOptions).catch(_ => {
512
- return {
513
- connected: false
514
- };
515
- });
516
- const connectDetails = promptConnectDetails;
501
+ static getInstance() {
502
+ if (!LocalStorage._instance) {
503
+ LocalStorage._instance = {
504
+ getItem: key => Promise.resolve(window.localStorage.getItem(key)),
505
+ setItem: (key, value) => Promise.resolve(window.localStorage.setItem(key, value)),
506
+ removeItem: key => Promise.resolve(window.localStorage.removeItem(key))
507
+ };
508
+ }
517
509
 
518
- if (connectDetails.connected && !connectDetails.session) {
519
- connectDetails.session = await _this.walletSession();
520
- }
510
+ return this._instance;
511
+ }
521
512
 
522
- return promptConnectDetails;
523
- };
513
+ static use(instance) {
514
+ LocalStorage._instance = instance;
515
+ }
524
516
 
525
- this.sendAsync = async function (request, callback, chainId) {
526
- const response = {
527
- jsonrpc: '2.0',
528
- id: request.id,
529
- result: null
530
- };
531
- await _this.getSigner();
517
+ } // window.localstorage helper
532
518
 
533
- try {
534
- // only allow public json rpc method to the provider when user is not logged in, aka signer is not set
535
- if ((!_this.signer || _this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
536
- // throw new Error(`not logged in. ${request.method} is unavailable`)
537
- throw ErrSignedInRequired;
538
- } // wallet signer
519
+ LocalStorage._instance = void 0;
520
+ class LocalStore {
521
+ constructor(key, def) {
522
+ this.def = def;
523
+ this.key = void 0;
524
+ this.key = key;
525
+ }
539
526
 
527
+ async get() {
528
+ const val = await LocalStorage.getInstance().getItem(this.key);
540
529
 
541
- const signer = _this.signer;
542
- if (!signer) throw new Error('WalletRequestHandler: wallet signer is not configured'); // fetch the provider for the specific chain, or undefined will select defaultChain
530
+ if (val === null) {
531
+ return this.def;
532
+ }
543
533
 
544
- const provider = await signer.getProvider(chainId);
545
- if (!provider) throw new Error(`WalletRequestHandler: wallet provider is not configured for chainId ${chainId}`);
534
+ try {
535
+ return JSON.parse(val);
536
+ } catch (err) {
537
+ console.error(err);
538
+ }
546
539
 
547
- switch (request.method) {
548
- case 'net_version':
549
- {
550
- const result = await provider.send('net_version', []);
551
- response.result = result;
552
- break;
553
- }
540
+ return;
541
+ }
554
542
 
555
- case 'eth_chainId':
556
- {
557
- const result = await provider.send('eth_chainId', []);
558
- response.result = result;
559
- break;
560
- }
543
+ set(val) {
544
+ val ? LocalStorage.getInstance().setItem(this.key, JSON.stringify(val)) : LocalStorage.getInstance().removeItem(this.key);
545
+ }
561
546
 
562
- case 'eth_accounts':
563
- {
564
- const walletAddress = await signer.getAddress();
565
- response.result = [walletAddress];
566
- break;
567
- }
547
+ del() {
548
+ LocalStorage.getInstance().removeItem(this.key);
549
+ }
568
550
 
569
- case 'eth_getBalance':
570
- {
571
- const [accountAddress, blockTag] = request.params;
572
- const walletBalance = await provider.getBalance(accountAddress, blockTag);
573
- response.result = walletBalance.toHexString();
574
- break;
575
- }
551
+ }
576
552
 
577
- case 'personal_sign':
578
- case 'eth_sign':
579
- {
580
- // note: message from json-rpc input is in hex format
581
- let message; // there is a difference in the order of the params:
582
- // personal_sign: [data, address]
583
- // eth_sign: [address, data]
553
+ const TRANSPORT_SESSION_LS_KEY = '@sequence.transportSession';
554
+ class BaseWalletTransport {
555
+ // appOrigin identifies the dapp's origin which opened the app. A transport
556
+ // will auto-detect and set this value if it can. This is determined
557
+ // as the parent app/window which opened the wallet.
558
+ constructor(walletRequestHandler) {
559
+ var _this = this;
584
560
 
585
- if (request.method === 'personal_sign') {
586
- const [data, address] = request.params;
587
- message = data;
588
- } else {
589
- const [address, data] = request.params;
590
- message = data;
591
- }
561
+ this.walletRequestHandler = void 0;
562
+ this._sessionId = void 0;
563
+ this._registered = void 0;
564
+ this._init = void 0;
565
+ this._initNonce = void 0;
566
+ this._initCallback = void 0;
567
+ this.appOrigin = void 0;
592
568
 
593
- let sig = ''; // Message must be prefixed with "\x19Ethereum Signed Message:\n"
594
- // as defined by EIP-191
569
+ this.sendAsync = async function (request, callback, chainId) {
570
+ throw new Error('abstract method');
571
+ };
595
572
 
596
- message = prefixEIP191Message(message); // TODO:
597
- // if (process.env.TEST_MODE === 'true' && this.prompter === null) {
573
+ this.handleMessage = async function (message) {
574
+ const request = message; // ensure initial handshake is complete before accepting
575
+ // other kinds of messages.
598
576
 
599
- if (_this.prompter === null) {
600
- // prompter is null, so we'll sign from here
601
- sig = await signer.signMessage(ethers.ethers.utils.arrayify(message), chainId);
602
- } else {
603
- const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
577
+ if (_this._init !== exports.InitState.OK) {
578
+ if (request.type === exports.EventType.INIT) {
579
+ if (_this.isValidInitAck(message)) {
580
+ // successful init
581
+ if (_this._initCallback) _this._initCallback();
582
+ } else {
583
+ // failed init
584
+ if (_this._initCallback) _this._initCallback('invalid init');
585
+ return;
586
+ }
587
+ }
604
588
 
605
- if (promptResultForDeployment) {
606
- sig = await _this.prompter.promptSignMessage({
607
- chainId: chainId,
608
- message
609
- }, _this.connectOptions);
610
- }
611
- }
589
+ return;
590
+ } // ensure signer is ready to handle requests
591
+ // if (this.walletRequestHandler.getSigner() === undefined) {
592
+ // await this.walletRequestHandler.signerReady()
593
+ // }
594
+ // handle request
612
595
 
613
- if (sig && sig.length > 0) {
614
- response.result = sig;
615
- } else {
616
- // The user has declined the request when value is null
617
- throw new Error('declined by user');
618
- }
619
596
 
620
- break;
621
- }
597
+ switch (request.type) {
598
+ case exports.EventType.OPEN:
599
+ {
600
+ if (_this._init !== exports.InitState.OK) return;
601
+ const session = {
602
+ sessionId: request.data.sessionId,
603
+ intent: request.data.intent,
604
+ networkId: request.data.networkId
605
+ };
606
+ await _this.open(session);
607
+ return;
608
+ }
622
609
 
623
- case 'eth_signTypedData':
624
- case 'eth_signTypedData_v4':
625
- {
626
- // note: signingAddress from json-rpc input is in hex format, and typedDataObject
627
- // should be an object, but in some instances may be double string encoded
628
- const [signingAddress, typedDataObject] = request.params;
629
- let typedData = undefined;
610
+ case exports.EventType.CLOSE:
611
+ {
612
+ if (_this._init !== exports.InitState.OK) return; // noop. just here to capture the message so event emitters may be notified
630
613
 
631
- if (typeof typedDataObject === 'string') {
632
- try {
633
- typedData = JSON.parse(typedDataObject);
634
- } catch (e) {}
635
- } else {
636
- typedData = typedDataObject;
637
- }
614
+ return;
615
+ }
638
616
 
639
- if (!typedData || !typedData.domain || !typedData.types || !typedData.message) {
640
- throw new Error('invalid typedData object');
641
- }
617
+ case exports.EventType.MESSAGE:
618
+ {
619
+ const response = await _this.walletRequestHandler.sendMessageRequest(request);
642
620
 
643
- let sig = '';
621
+ _this.sendMessage(response);
644
622
 
645
- if (_this.prompter === null) {
646
- // prompter is null, so we'll sign from here
647
- sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainId);
648
- } else {
649
- const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
623
+ if (response.data.error) ;
650
624
 
651
- if (promptResultForDeployment) {
652
- sig = await _this.prompter.promptSignMessage({
653
- chainId: chainId,
654
- typedData: typedData
655
- }, _this.connectOptions);
656
- }
657
- }
625
+ return;
626
+ }
658
627
 
659
- if (sig && sig.length > 0) {
660
- response.result = sig;
661
- } else {
662
- // The user has declined the request when value is null
663
- throw new Error('declined by user');
664
- }
665
-
666
- break;
667
- }
628
+ default:
629
+ {
630
+ utils.logger.error(`unexpected payload type ${request.type}`);
631
+ }
632
+ }
633
+ };
668
634
 
669
- case 'eth_sendTransaction':
670
- {
671
- // https://eth.wiki/json-rpc/API#eth_sendtransaction
672
- const [transactionParams] = request.params;
673
- let txnHash = '';
635
+ this.sendMessageRequest = async function (message) {
636
+ return _this.walletRequestHandler.sendMessageRequest(message);
637
+ };
674
638
 
675
- if (_this.prompter === null) {
676
- // prompter is null, so we'll send from here
677
- const txnResponse = await signer.sendTransaction(transactionParams, chainId);
678
- txnHash = txnResponse.hash;
679
- } else {
680
- // prompt user to provide the response
681
- txnHash = await _this.prompter.promptSendTransaction(transactionParams, chainId, _this.connectOptions);
682
- }
639
+ this.open = async function ({
640
+ sessionId,
641
+ intent,
642
+ networkId
643
+ }) {
644
+ if (sessionId) {
645
+ _this._sessionId = utils.sanitizeNumberString(sessionId); // persist transport session in localstorage for restoring after redirect/reload
683
646
 
684
- if (txnHash) {
685
- response.result = txnHash;
686
- } else {
687
- // The user has declined the request when value is null
688
- throw new Error('declined by user');
689
- }
647
+ _this.saveTransportSession({
648
+ sessionId,
649
+ intent,
650
+ networkId
651
+ });
652
+ }
690
653
 
691
- break;
692
- }
654
+ _this.walletRequestHandler.setOpenIntent(intent); // init handshake for certain transports, before we can open the communication.
655
+ //
656
+ // for example, with the window-transport, we have to exchange messages to determine the
657
+ // origin host of the dapp.
693
658
 
694
- case 'eth_signTransaction':
695
- {
696
- // https://eth.wiki/json-rpc/API#eth_signTransaction
697
- const [transaction] = request.params;
698
- const sender = ethers.ethers.utils.getAddress(transaction.from);
699
659
 
700
- if (sender !== (await signer.getAddress())) {
701
- throw new Error('sender address does not match wallet');
702
- }
660
+ await _this.init(); // Prepare connect options from intent
703
661
 
704
- if (_this.prompter === null) {
705
- // The eth_signTransaction method expects a `string` return value we instead return a `SignedTransactions` object,
706
- // this can only be broadcasted using an RPC provider with support for signed Sequence transactions, like this one.
707
- //
708
- // TODO: verify serializing / transporting the SignedTransaction object works as expected, most likely however
709
- // we will want to resolveProperties the bignumber values to hex strings
710
- response.result = await signer.signTransactions(transaction, chainId);
711
- } else {
712
- response.result = await _this.prompter.promptSignTransaction(transaction, chainId, _this.connectOptions);
713
- }
662
+ if (intent && intent.type === 'connect' && intent.options) {
663
+ const connectOptions = intent.options;
664
+ const authorizeOptions = connectOptions; // overlapping types
665
+ // Sanity/integrity check the intent payload, and set authorization origin
666
+ // if its been determined as part of the init handshake from earlier.
714
667
 
715
- break;
668
+ if (_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
669
+ if (!isBrowserExtension()) {
670
+ if (authorizeOptions.origin !== _this.appOrigin) {
671
+ throw new Error('origin is invalid');
716
672
  }
673
+ }
674
+ } else if (!_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
675
+ // ie. when we can't determine the origin in our transport, but dapp provides it to us.
676
+ // we just sanitize the origin host.
677
+ connectOptions.origin = utils.sanitizeHost(authorizeOptions.origin);
678
+ } else if (_this.appOrigin) {
679
+ // ie. when we auto-determine the origin such as in window-transport
680
+ connectOptions.origin = _this.appOrigin;
681
+ }
717
682
 
718
- case 'eth_sendRawTransaction':
719
- {
720
- // NOTE: we're not using a prompter here as the transaction is already signed
721
- // and would have prompted the user upon signing.
722
- // https://eth.wiki/json-rpc/API#eth_sendRawTransaction
723
- if (transactions.isSignedTransactions(request.params[0])) {
724
- const txChainId = ethers.BigNumber.from(request.params[0].chainId).toNumber();
725
- const tx = await (await signer.getRelayer(txChainId)).relay(request.params[0]);
726
- response.result = (await tx).hash;
727
- } else {
728
- const tx = await provider.sendTransaction(request.params[0]);
729
- response.result = tx.hash;
730
- }
731
-
732
- break;
733
- }
683
+ if (connectOptions.app) {
684
+ connectOptions.app = utils.sanitizeAlphanumeric(connectOptions.app);
685
+ } // Set connect options on the walletRequestHandler as our primary
686
+ // wallet controller
734
687
 
735
- case 'eth_getTransactionCount':
736
- {
737
- const address = ethers.ethers.utils.getAddress(request.params[0]);
738
- const tag = request.params[1];
739
- const walletAddress = ethers.ethers.utils.getAddress(await signer.getAddress());
740
688
 
741
- if (address === walletAddress) {
742
- const count = await signer.getTransactionCount(tag);
743
- response.result = ethers.ethers.BigNumber.from(count).toHexString();
744
- } else {
745
- const count = await provider.getTransactionCount(address, tag);
746
- response.result = ethers.ethers.BigNumber.from(count).toHexString();
747
- }
689
+ _this.walletRequestHandler.setConnectOptions(connectOptions);
748
690
 
749
- break;
750
- }
691
+ if (connectOptions.networkId) {
692
+ networkId = connectOptions.networkId;
693
+ }
694
+ } else {
695
+ _this.walletRequestHandler.setConnectOptions(undefined);
696
+ } // ensure signer is ready
751
697
 
752
- case 'eth_blockNumber':
753
- {
754
- response.result = await provider.getBlockNumber();
755
- break;
756
- }
757
698
 
758
- case 'eth_getBlockByNumber':
759
- {
760
- response.result = await provider.getBlock(request.params[0]
761
- /* , jsonRpcRequest.params[1] */
762
- );
763
- break;
764
- }
699
+ await _this.walletRequestHandler.getSigner(); // Notify open and proceed to prompt for connection if intended
765
700
 
766
- case 'eth_getBlockByHash':
767
- {
768
- response.result = await provider.getBlock(request.params[0]
769
- /* , jsonRpcRequest.params[1] */
770
- );
771
- break;
772
- }
701
+ if (!(await _this.walletRequestHandler.isSignedIn())) {
702
+ // open wallet without a specific connected chainId, as the user is not signed in
703
+ _this.notifyOpen({
704
+ sessionId: _this._sessionId
705
+ });
773
706
 
774
- case 'eth_getTransactionByHash':
775
- {
776
- response.result = await provider.getTransaction(request.params[0]);
777
- break;
778
- }
707
+ return true;
708
+ } else {
709
+ // Set default network, in case of error chainId will be undefined or 0
710
+ let chainId = undefined;
779
711
 
780
- case 'eth_call':
781
- {
782
- const [transactionObject, blockTag] = request.params;
783
- response.result = await provider.call(transactionObject, blockTag);
784
- break;
785
- }
712
+ try {
713
+ if (networkId) {
714
+ chainId = await _this.walletRequestHandler.setDefaultNetwork(networkId, false);
715
+ } else {
716
+ chainId = await _this.walletRequestHandler.getChainId();
717
+ }
718
+ } catch (err) {
719
+ console.error(err);
720
+ } // Failed to set default network on open -- quit + close
786
721
 
787
- case 'eth_getCode':
788
- {
789
- const [contractAddress, blockTag] = request.params;
790
- response.result = await provider.getCode(contractAddress, blockTag);
791
- break;
792
- }
793
722
 
794
- case 'eth_estimateGas':
795
- {
796
- const [transactionObject] = request.params;
797
- response.result = await provider.estimateGas(transactionObject);
798
- break;
799
- }
723
+ if (!chainId || chainId <= 0) {
724
+ _this.notifyOpen({
725
+ sessionId: _this._sessionId,
726
+ error: `failed to open wallet on network`
727
+ });
800
728
 
801
- case 'eth_gasPrice':
802
- {
803
- const gasPrice = await provider.getGasPrice();
804
- response.result = gasPrice.toHexString();
805
- break;
806
- }
729
+ return false;
730
+ } // prompt user with a connect request. the options will be used as previously set above.
731
+ // upon success, the walletRequestHandler will notify the dapp with the ConnectDetails.
732
+ // upon cancellation by user, the walletRequestHandler will throw an error
807
733
 
808
- case 'wallet_switchEthereumChain':
809
- {
810
- const [switchParams] = request.params;
811
734
 
812
- if (!switchParams.chainId || switchParams.chainId.length === 0) {
813
- throw new Error('invalid chainId');
814
- }
735
+ if (intent && intent.type === 'connect') {
736
+ // notify wallet is opened, without session details
737
+ _this.notifyOpen({
738
+ sessionId: _this._sessionId
739
+ });
815
740
 
816
- const _chainId = ethers.ethers.BigNumber.from(switchParams.chainId);
741
+ try {
742
+ const connectDetails = await _this.walletRequestHandler.promptConnect(intent.options);
817
743
 
818
- const ok = await _this.setDefaultNetwork(_chainId.toString(), true);
744
+ if (connectDetails.connected) {
745
+ _this.walletRequestHandler.notifyConnect(connectDetails);
746
+ }
747
+ } catch (err) {
748
+ utils.logger.warn('promptConnect not connected:', err);
749
+ } finally {
750
+ // auto-close by default, unless intent is to keep open
751
+ if (!intent.options || intent.options.keepWalletOpened !== true) {
752
+ _this.notifyClose();
753
+ }
754
+ }
755
+ } else {
756
+ // user is already connected, notify session details.
757
+ // TODO: in future, keep list if 'connected' dapps / sessions in the session
758
+ // controller, and only sync with allowed apps
759
+ _this.notifyOpen({
760
+ sessionId: _this._sessionId,
761
+ chainId: `${chainId}`,
762
+ session: await _this.walletRequestHandler.walletSession()
763
+ });
764
+ }
765
+ }
819
766
 
820
- if (!ok) {
821
- throw new Error(`unable to set chainId ${_chainId}`);
822
- }
767
+ return true;
768
+ };
823
769
 
824
- response.result = null; // success
770
+ this.saveTransportSession = session => {
771
+ LocalStorage.getInstance().setItem(TRANSPORT_SESSION_LS_KEY, JSON.stringify(session));
772
+ };
825
773
 
826
- break;
827
- }
828
- // smart wallet method
774
+ this.getCachedTransportSession = async function () {
775
+ const session = await LocalStorage.getInstance().getItem(TRANSPORT_SESSION_LS_KEY);
829
776
 
830
- case 'sequence_getWalletContext':
831
- {
832
- response.result = await signer.getWalletContext();
833
- break;
834
- }
835
- // smart wallet method
777
+ try {
778
+ return session ? JSON.parse(session) : null;
779
+ } catch (err) {
780
+ console.error(`unable to parse transport session: ${session}`);
781
+ return null;
782
+ }
783
+ };
836
784
 
837
- case 'sequence_getWalletConfig':
838
- {
839
- const [_chainId2] = request.params;
840
- response.result = await signer.getWalletConfig(_chainId2);
841
- break;
842
- }
843
- // smart wallet method
785
+ this.walletRequestHandler = walletRequestHandler;
786
+ this._init = exports.InitState.NIL;
787
+ this.walletRequestHandler.on('connect', connectDetails => {
788
+ if (!this.registered) return; // means user has logged in and wallet is connected to the app
844
789
 
845
- case 'sequence_getWalletState':
846
- {
847
- const [_chainId3] = request.params;
848
- response.result = await signer.getWalletState(_chainId3);
849
- break;
850
- }
851
- // smart wallet method
790
+ this.notifyConnect(connectDetails);
791
+ });
792
+ this.walletRequestHandler.on('disconnect', error => {
793
+ if (!this.registered) return; // means user has logged out the app / disconnected wallet from the app
852
794
 
853
- case 'sequence_getNetworks':
854
- {
855
- // NOTE: must ensure that the response result below returns clean serialized data, which is to omit
856
- // the provider and relayer objects and only return the urls so can be reinstantiated on dapp side.
857
- // This is handled by this.getNetworks() but noted here for future readers.
858
- response.result = await _this.getNetworks(true);
859
- break;
860
- }
861
- // smart wallet method
795
+ this.notifyDisconnect(error);
796
+ });
797
+ this.walletRequestHandler.on('accountsChanged', (accounts, origin) => {
798
+ if (!this.registered) return;
799
+ this.notifyAccountsChanged(accounts, origin);
800
+ });
801
+ this.walletRequestHandler.on('networks', networks => {
802
+ if (!this.registered) return;
803
+ this.notifyNetworks(networks);
862
804
 
863
- case 'sequence_updateConfig':
864
- {
865
- throw new Error('sequence_updateConfig method is not allowed from a dapp'); // NOTE: method is disabled as we don't need a dapp to request to update a config.
866
- // However, if we ever want this, we can enable it but must also use the prompter
867
- // for confirmation.
868
- //
869
- // const [newConfig] = request.params
870
- // response.result = await signer.updateConfig(newConfig)
805
+ if (!networks || networks.length === 0) {
806
+ this.notifyChainChanged('0x0');
807
+ } else {
808
+ this.notifyChainChanged(ethers.ethers.utils.hexlify(networks.find(network => network.isDefaultChain).chainId));
809
+ }
810
+ });
811
+ this.walletRequestHandler.on('walletContext', walletContext => {
812
+ if (!this.registered || !walletContext) return;
813
+ this.notifyWalletContext(walletContext);
814
+ });
815
+ this.walletRequestHandler.on('close', error => {
816
+ if (!this.registered) return;
817
+ this.notifyClose(error);
818
+ });
819
+ }
871
820
 
872
- break;
873
- }
874
- // smart wallet method
821
+ get registered() {
822
+ return this._registered;
823
+ }
875
824
 
876
- case 'sequence_publishConfig':
877
- {
878
- throw new Error('sequence_publishConfig method is not allowed from a dapp');
879
- break;
880
- }
881
- // relayer method
825
+ register() {
826
+ throw new Error('abstract method');
827
+ }
882
828
 
883
- case 'sequence_estimateGasLimits':
884
- {
885
- // TODO
886
- break;
887
- }
888
- // relayer method
829
+ unregister() {
830
+ throw new Error('abstract method');
831
+ }
889
832
 
890
- case 'sequence_gasRefundOptions':
891
- {
892
- // TODO
893
- break;
894
- }
895
- // relayer method
833
+ sendMessage(message) {
834
+ throw new Error('abstract method');
835
+ }
896
836
 
897
- case 'sequence_getNonce':
898
- {
899
- // TODO
900
- break;
901
- }
902
- // relayer method
837
+ notifyOpen(openInfo) {
838
+ const {
839
+ chainId,
840
+ sessionId,
841
+ session,
842
+ error
843
+ } = openInfo;
844
+ this.sendMessage({
845
+ idx: -1,
846
+ type: exports.EventType.OPEN,
847
+ data: {
848
+ chainId,
849
+ sessionId,
850
+ session,
851
+ error
852
+ }
853
+ });
854
+ }
903
855
 
904
- case 'sequence_relay':
905
- {
906
- // TODO
907
- break;
908
- }
909
- // set default network of wallet
856
+ notifyClose(error) {
857
+ this.sendMessage({
858
+ idx: -1,
859
+ type: exports.EventType.CLOSE,
860
+ data: error ? {
861
+ error
862
+ } : null
863
+ });
864
+ }
910
865
 
911
- case 'sequence_setDefaultNetwork':
912
- {
913
- const [defaultNetworkId] = request.params;
866
+ notifyConnect(connectDetails) {
867
+ this.sendMessage({
868
+ idx: -1,
869
+ type: exports.EventType.CONNECT,
870
+ data: connectDetails
871
+ });
872
+ }
914
873
 
915
- if (!defaultNetworkId) {
916
- throw new Error('invalid request, method argument defaultNetworkId cannot be empty');
917
- }
874
+ notifyDisconnect(error) {
875
+ this.sendMessage({
876
+ idx: -1,
877
+ type: exports.EventType.DISCONNECT,
878
+ data: error ? {
879
+ error
880
+ } : null
881
+ });
882
+ }
918
883
 
919
- const ok = await _this.setDefaultNetwork(defaultNetworkId);
884
+ notifyAccountsChanged(accounts, origin) {
885
+ this.sendMessage({
886
+ idx: -1,
887
+ type: exports.EventType.ACCOUNTS_CHANGED,
888
+ data: accounts,
889
+ origin: origin
890
+ });
891
+ }
920
892
 
921
- if (!ok) {
922
- throw new Error(`unable to set default network ${defaultNetworkId}`);
923
- }
893
+ notifyChainChanged(chainIdHex) {
894
+ this.sendMessage({
895
+ idx: -1,
896
+ type: exports.EventType.CHAIN_CHANGED,
897
+ data: chainIdHex
898
+ });
899
+ }
924
900
 
925
- response.result = await _this.getNetworks(true);
926
- break;
927
- }
901
+ notifyNetworks(networks) {
902
+ this.sendMessage({
903
+ idx: -1,
904
+ type: exports.EventType.NETWORKS,
905
+ data: networks
906
+ });
907
+ }
928
908
 
929
- default:
930
- {
931
- // NOTE: provider here will be chain-bound if chainId is provided
932
- const providerResponse = await provider.send(request.method, request.params);
933
- response.result = providerResponse;
934
- }
935
- }
936
- } catch (err) {
937
- utils.logger.error(err); // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#rpc-errors
909
+ notifyWalletContext(walletContext) {
910
+ this.sendMessage({
911
+ idx: -1,
912
+ type: exports.EventType.WALLET_CONTEXT,
913
+ data: walletContext
914
+ });
915
+ }
938
916
 
939
- response.result = null;
940
- response.error = _extends({}, new Error(err), {
941
- code: 4001
942
- });
943
- }
917
+ isValidInitAck(message) {
918
+ if (this._init === exports.InitState.OK) {
919
+ // we're already in init state, we shouldn't handle this message
920
+ utils.logger.warn("isValidInitAck, already in init'd state, so inquiry is invalid.");
921
+ return false;
922
+ }
944
923
 
945
- callback(undefined, response);
946
- };
924
+ if (message.type !== exports.EventType.INIT) {
925
+ utils.logger.warn('isValidInitAck, invalid message type, expecting init');
926
+ return false;
927
+ }
947
928
 
948
- this.isSignedIn = async function () {
949
- await _this.signerReady();
950
- return !!_this.signer;
951
- };
929
+ const {
930
+ sessionId,
931
+ nonce
932
+ } = message.data;
952
933
 
953
- this.getSigner = async function () {
954
- await _this.signerReady();
934
+ if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
935
+ utils.logger.error('invalid init ack');
936
+ return false;
937
+ }
955
938
 
956
- if (_this.signer === undefined) {
957
- throw new Error('signerReady failed resolve');
958
- }
939
+ if (sessionId !== this._sessionId || nonce !== this._initNonce) {
940
+ utils.logger.error('invalid init ack match');
941
+ return false;
942
+ } // all checks pass, its true
959
943
 
960
- return _this.signer;
961
- };
962
944
 
963
- this.signer = _signer;
964
- this.prompter = prompter;
965
- this.mainnetNetworks = mainnetNetworks;
966
- this.testnetNetworks = testnetNetworks;
945
+ return true;
967
946
  }
968
947
 
969
- async signIn(signer, options = {}) {
970
- this.setSigner(signer);
971
- const {
972
- connect,
973
- mainnetNetworks,
974
- testnetNetworks,
975
- defaultNetworkId
976
- } = options;
948
+ init() {
949
+ return new Promise((resolve, reject) => {
950
+ // avoid re-init`ing, or if there is a transport which doesn't require
951
+ // it, then it may set this._init to OK in its constructor.
952
+ if (this._init === exports.InitState.OK) {
953
+ resolve();
954
+ return;
955
+ }
977
956
 
978
- if (mainnetNetworks && mainnetNetworks.length > 0) {
979
- this.mainnetNetworks = mainnetNetworks;
980
- }
957
+ if (this._init !== exports.InitState.NIL || this._initCallback) {
958
+ reject('transport init is in progress');
959
+ return;
960
+ } // start init timeout, if we don't receive confirmation
961
+ // from provider within this amount of time, then we timeout
981
962
 
982
- if (testnetNetworks && testnetNetworks.length > 0) {
983
- this.testnetNetworks = testnetNetworks;
984
- }
985
963
 
986
- if ((!this.mainnetNetworks || this.mainnetNetworks.length === 0) && (!this.testnetNetworks || this.testnetNetworks.length === 0)) {
987
- throw new Error('signIn failed as network configuration is empty');
988
- }
964
+ const initTimeout = setTimeout(() => {
965
+ utils.logger.warn('transport init timed out');
989
966
 
990
- const networkId = defaultNetworkId || this._defaultNetworkId;
967
+ if (this._initCallback) {
968
+ this._initCallback('transport init timed out');
969
+ }
970
+ }, PROVIDER_OPEN_TIMEOUT / 2); // setup callback as we receive the init message async in the handleMessage function
991
971
 
992
- if (networkId) {
993
- if (!(await this.setDefaultNetwork(networkId, false))) {
994
- throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${networkId}`);
995
- }
996
- } // Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
997
- // necessary auth request, and then notify the dapp of the 'connect' details.
998
- //
999
- // NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
1000
- // we don't trigger the promptConnect flow, as we consider the user just authenticated
1001
- // for this dapp, so its safe to authorize in the connect() method without the prompt.
1002
- //
1003
- // NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
972
+ this._initCallback = error => {
973
+ this._initCallback = undefined; // reset
1004
974
 
975
+ clearTimeout(initTimeout);
1005
976
 
1006
- if (connect) {
1007
- const connectOptions = this._connectOptions;
1008
- const connectDetails = await this.connect(connectOptions);
1009
- this.notifyConnect(connectDetails);
977
+ if (error) {
978
+ reject(error);
979
+ } else {
980
+ this._init = exports.InitState.OK;
981
+ resolve();
982
+ }
983
+ }; // send init request with random nonce to the provider, where we expect
984
+ // for the provider to echo it back to us as complete handshake
1010
985
 
1011
- if (!connectOptions || connectOptions.keepWalletOpened !== true) {
1012
- this.notifyClose();
1013
- }
1014
- }
1015
- }
1016
986
 
1017
- signOut() {
1018
- // signed out state
1019
- this.setSigner(null);
987
+ this._initNonce = `${performance.now()}`;
988
+ this.sendMessage({
989
+ idx: -1,
990
+ type: exports.EventType.INIT,
991
+ data: {
992
+ nonce: this._initNonce
993
+ }
994
+ });
995
+ this._init = exports.InitState.SENT_NONCE; // NOTE: the promise will resolve in the _initCallback method
996
+ // which will be called from either handleMessage or the initTimeout
997
+ });
1020
998
  }
1021
999
 
1022
- signerReset() {
1023
- // resetting signer puts the wallet in an uninitialized state, which requires the app to
1024
- // re-initiatize and set the signer either as "null" (ie. no signer) or "Signer" (ie. signed in).
1025
- this.signer = undefined;
1026
- }
1000
+ }
1027
1001
 
1028
- signerReady(timeout = SIGNER_READY_TIMEOUT) {
1029
- return new Promise((resolve, reject) => {
1030
- if (this.signer !== undefined) {
1031
- resolve();
1032
- } else {
1033
- setTimeout(() => {
1034
- if (this.signer === undefined) {
1035
- this.signerReadyCallbacks = [];
1036
- reject(`signerReady timed out`);
1037
- }
1038
- }, timeout);
1039
- this.signerReadyCallbacks.push(resolve);
1040
- }
1041
- });
1002
+ class ProxyMessageChannel {
1003
+ constructor() {
1004
+ this.app = void 0;
1005
+ this.wallet = void 0;
1006
+ const port1 = new ProxyMessageChannelPort();
1007
+ const port2 = new ProxyMessageChannelPort();
1008
+ port1.conn = port2;
1009
+ port2.conn = port1;
1010
+ this.app = port1;
1011
+ this.wallet = port2;
1042
1012
  }
1043
1013
 
1044
- async connect(options) {
1045
- if (!this.signer) {
1046
- return {
1047
- connected: false,
1048
- chainId: '0x0',
1049
- error: 'unable to connect without signed in account'
1050
- };
1051
- }
1014
+ }
1015
+ class ProxyMessageChannelPort {
1016
+ constructor() {
1017
+ this.conn = void 0;
1018
+ this.events = new eventemitter2.EventEmitter2();
1052
1019
 
1053
- const connectDetails = {
1054
- connected: true,
1055
- chainId: ethers.ethers.utils.hexlify(await this.getChainId())
1020
+ this.handleMessage = message => {
1021
+ throw new Error('ProxyMessageChannelPort is not registered');
1056
1022
  };
1057
1023
 
1058
- if (options && options.authorize) {
1059
- // Perform ethauth eip712 request and construct the ConnectDetails response
1060
- // including the auth proof
1061
- const authOptions = {
1062
- app: options.app,
1063
- origin: options.origin,
1064
- expiry: options.expiry
1065
- }; // if (typeof(options.authorize) === 'object') {
1066
- // authOptions = { ...authOptions, ...options.authorize }
1067
- // }
1068
-
1069
- try {
1070
- connectDetails.proof = await auth.signAuthorization(this.signer, authOptions);
1071
- } catch (err) {
1072
- utils.logger.warn(`connect, signAuthorization failed for options: ${JSON.stringify(options)}, due to: ${err.message}`);
1073
- return {
1074
- connected: false,
1075
- chainId: '0x0',
1076
- error: `signAuthorization failed: ${err.message}`
1077
- };
1078
- }
1079
- } // Build session response for connect details
1080
-
1081
-
1082
- connectDetails.session = await this.walletSession();
1083
- return connectDetails;
1084
- }
1024
+ this.sendMessage = message => {
1025
+ this.conn.handleMessage(message); // trigger events
1085
1026
 
1086
- // sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler
1087
- // (aka, the signer in this instance) and then responds with a wrapped response of
1088
- // ProviderMessageResponse to be sent over the transport
1089
- sendMessageRequest(message) {
1090
- return new Promise(resolve => {
1091
- this.sendAsync(message.data, (error, response) => {
1092
- // TODO: if response includes data.error, why do we need a separate error argument here?
1093
- const responseMessage = _extends({}, message, {
1094
- data: response
1095
- }); // NOTE: we always resolve here, are the sendAsync call will wrap any exceptions
1096
- // in the error field of the response to ensure we send back to the user
1027
+ if (message.type === 'open') {
1028
+ this.events.emit('open', message);
1029
+ }
1097
1030
 
1031
+ if (message.type === 'close') {
1032
+ this.events.emit('close', message);
1033
+ }
1098
1034
 
1099
- resolve(responseMessage);
1100
- }, message.chainId);
1101
- });
1102
- } // sendAsync implements the JsonRpcHandler interface for sending JsonRpcRequests to the wallet
1035
+ if (message.type === 'connect') {
1036
+ this.events.emit('connect', message);
1037
+ }
1103
1038
 
1039
+ if (message.type === 'disconnect') {
1040
+ this.events.emit('disconnect', message);
1041
+ }
1042
+ };
1043
+ }
1104
1044
 
1105
1045
  on(event, fn) {
1106
1046
  this.events.on(event, fn);
@@ -1110,2063 +1050,2157 @@ class WalletRequestHandler {
1110
1050
  this.events.once(event, fn);
1111
1051
  }
1112
1052
 
1113
- async getAddress() {
1114
- if (!this.signer) {
1115
- return '';
1116
- } else {
1117
- return this.signer.getAddress();
1118
- }
1119
- }
1053
+ }
1120
1054
 
1121
- async getChainId() {
1122
- if (!this.signer) {
1123
- return 0;
1124
- } else {
1125
- if (this._chainId) return this._chainId; // memoized
1055
+ class ProxyMessageProvider extends BaseProviderTransport {
1056
+ constructor(port) {
1057
+ super();
1058
+ this.port = void 0;
1126
1059
 
1127
- this._chainId = await this.signer.getChainId();
1128
- return this._chainId;
1129
- }
1130
- }
1060
+ this.register = () => {
1061
+ this.port.handleMessage = message => {
1062
+ this.handleMessage(message);
1063
+ };
1131
1064
 
1132
- get openIntent() {
1133
- return this._openIntent;
1134
- }
1065
+ this.on('open', (...args) => {
1066
+ this.port.events.emit('open', ...args);
1067
+ });
1068
+ this.on('close', (...args) => {
1069
+ this.port.events.emit('close', ...args);
1070
+ });
1071
+ this.on('connect', (...args) => {
1072
+ this.port.events.emit('connect', ...args);
1073
+ });
1074
+ this.on('disconnect', (...args) => {
1075
+ this.port.events.emit('disconnect', ...args);
1076
+ });
1077
+ this._registered = true;
1078
+ };
1135
1079
 
1136
- setOpenIntent(intent) {
1137
- this._openIntent = intent;
1138
- }
1080
+ this.unregister = () => {
1081
+ this._registered = false;
1082
+ this.closeWallet();
1083
+ this.events.removeAllListeners(); // @ts-ignore
1139
1084
 
1140
- get connectOptions() {
1141
- return this._connectOptions;
1142
- }
1085
+ this.port.handleMessage = undefined;
1086
+ };
1143
1087
 
1144
- setConnectOptions(options) {
1145
- this._connectOptions = options;
1146
- }
1088
+ this.openWallet = (path, intent, networkId) => {
1089
+ if (this.state === exports.OpenState.CLOSED) {
1090
+ this.state = exports.OpenState.OPENING;
1091
+ const sessionId = `${performance.now()}`;
1092
+ this._sessionId = sessionId;
1093
+ this.sendMessage({
1094
+ idx: -1,
1095
+ type: exports.EventType.OPEN,
1096
+ data: {
1097
+ path,
1098
+ intent,
1099
+ networkId,
1100
+ sessionId
1101
+ }
1102
+ });
1103
+ }
1104
+ };
1147
1105
 
1148
- get defaultNetworkId() {
1149
- return this._defaultNetworkId;
1150
- }
1106
+ this.state = exports.OpenState.CLOSED;
1107
+ this.port = port;
1151
1108
 
1152
- async setDefaultNetwork(chainId, notifyNetworks = true) {
1153
- if (!chainId) return undefined;
1154
- this._defaultNetworkId = chainId;
1155
- this._chainId = undefined;
1109
+ if (!port) {
1110
+ throw new Error('port argument cannot be empty');
1111
+ } // disable init handshake for proxy-transport, we set it to OK, to
1112
+ // consider it in completed state.
1156
1113
 
1157
- if (this.signer && this.signer.setNetworks) {
1158
- const defaultChainId = this.signer.setNetworks(this.mainnetNetworks, this.testnetNetworks, chainId);
1159
1114
 
1160
- if (defaultChainId && notifyNetworks) {
1161
- await this.notifyNetworks();
1162
- }
1115
+ this._init = exports.InitState.OK;
1116
+ }
1163
1117
 
1164
- return defaultChainId;
1165
- } else {
1166
- return undefined;
1167
- }
1118
+ closeWallet() {
1119
+ this.sendMessage({
1120
+ idx: -1,
1121
+ type: exports.EventType.CLOSE,
1122
+ data: null
1123
+ });
1124
+ this.close();
1168
1125
  }
1169
1126
 
1170
- async getNetworks(jsonRpcResponse) {
1171
- if (!this.signer) {
1172
- utils.logger.warn('signer not set: getNetworks is returning an empty list');
1173
- return [];
1127
+ sendMessage(message) {
1128
+ if (!message.idx) {
1129
+ throw new Error('message idx is empty');
1174
1130
  }
1175
1131
 
1176
- const networks = await this.signer.getNetworks();
1132
+ this.port.sendMessage(message);
1133
+ }
1177
1134
 
1178
- if (jsonRpcResponse) {
1179
- // omit provider and relayer objects as they are not serializable
1180
- return networks.map(n => {
1181
- const network = _extends({}, n);
1135
+ }
1182
1136
 
1183
- network.provider = undefined;
1184
- network.relayer = undefined;
1185
- return network;
1186
- });
1187
- } else {
1188
- return networks;
1189
- }
1137
+ class ProxyMessageHandler extends BaseWalletTransport {
1138
+ constructor(walletRequestHandler, port) {
1139
+ super(walletRequestHandler);
1140
+ this.port = void 0;
1141
+ this.port = port;
1142
+ this._init = exports.InitState.OK;
1190
1143
  }
1191
1144
 
1192
- async walletSession() {
1193
- return !this.signer ? undefined : {
1194
- walletContext: await this.signer.getWalletContext(),
1195
- accountAddress: await this.signer.getAddress(),
1196
- networks: await this.getNetworks(true)
1145
+ register() {
1146
+ this.port.handleMessage = message => {
1147
+ this.handleMessage(message);
1197
1148
  };
1198
- }
1199
1149
 
1200
- notifyConnect(connectDetails, origin) {
1201
- var _connectDetails$sessi;
1150
+ this._registered = true;
1151
+ } // note: we can't decide whether to restore the session within register(), because session info is
1152
+ // received asyncronously via EventType.OPEN after register() is executed.
1153
+ // And in the case of a redirect/reload, EventType.OPEN is not sent at all,
1154
+ // because the wallet is already open.
1155
+ //
1156
+ // call this method from wallet redirect hander when a session restore is needed
1202
1157
 
1203
- this.events.emit('connect', connectDetails);
1204
1158
 
1205
- if ((_connectDetails$sessi = connectDetails.session) != null && _connectDetails$sessi.accountAddress) {
1206
- var _connectDetails$sessi2;
1159
+ async restoreSession() {
1160
+ const cachedSession = await this.getCachedTransportSession();
1207
1161
 
1208
- this.events.emit('accountsChanged', [(_connectDetails$sessi2 = connectDetails.session) == null ? void 0 : _connectDetails$sessi2.accountAddress], origin);
1162
+ if (cachedSession) {
1163
+ this.open(cachedSession);
1209
1164
  }
1210
1165
  }
1211
1166
 
1212
- notifyDisconnect(origin) {
1213
- this.events.emit('accountsChanged', [], origin);
1214
- this.events.emit('disconnect');
1167
+ unregister() {
1168
+ // @ts-ignore
1169
+ this.port.handleMessage = undefined;
1170
+ this._registered = false;
1215
1171
  }
1216
1172
 
1217
- async notifyNetworks(networks) {
1218
- const n = networks || (await this.getNetworks(true));
1219
- this.events.emit('networks', n);
1220
-
1221
- if (n.length > 0) {
1222
- const defaultNetwork = n.find(network => network.isDefaultChain);
1223
-
1224
- if (defaultNetwork) {
1225
- this.events.emit('chainChanged', ethers.ethers.utils.hexlify(defaultNetwork.chainId));
1226
- }
1227
- } else {
1228
- this.events.emit('chainChanged', '0x0');
1229
- }
1173
+ sendMessage(message) {
1174
+ this.port.sendMessage(message);
1230
1175
  }
1231
1176
 
1232
- async notifyWalletContext() {
1233
- if (!this.signer) {
1234
- utils.logger.warn('signer not set: skipping to notify wallet context');
1235
- return;
1236
- }
1177
+ }
1237
1178
 
1238
- const walletContext = await this.signer.getWalletContext();
1239
- this.events.emit('walletContext', walletContext);
1240
- }
1179
+ class MuxMessageProvider {
1180
+ constructor(...messageProviders) {
1181
+ var _this = this;
1241
1182
 
1242
- notifyClose(error) {
1243
- this.events.emit('close', error);
1244
- }
1183
+ this.messageProviders = void 0;
1184
+ this.provider = void 0;
1245
1185
 
1246
- setSigner(signer) {
1247
- this.signer = signer;
1186
+ this.register = () => {
1187
+ if (this.messageProviders.length === 1) {
1188
+ this.provider = this.messageProviders[0];
1189
+ this.provider.register();
1190
+ return;
1191
+ } // REVIEW/NOTE: ........ this method does not work for the chrome-extension. The issue becomes
1192
+ // when the browser quits or restarts, the "open" event is never triggered. Perhaps the code here is fine,
1193
+ // or maybe its not. What should happen is when a dapp makes a request, it will call openWallet
1194
+ // below, in which case one of the events will register. So perhaps this is fine.
1248
1195
 
1249
- if (signer !== undefined) {
1250
- for (let i = 0; i < this.signerReadyCallbacks.length; i++) {
1251
- this.signerReadyCallbacks[i]();
1252
- }
1253
1196
 
1254
- this.signerReadyCallbacks = [];
1255
- }
1256
- }
1197
+ this.messageProviders.forEach(m => {
1198
+ m.register();
1199
+ m.once('open', () => {
1200
+ // the first one to open is the winner, and others will be unregistered
1201
+ if (!this.provider) {
1202
+ this.provider = m; // unregister other providers
1257
1203
 
1258
- async handleConfirmWalletDeployPrompt(prompter, signer, chainId) {
1259
- // check if wallet is deployed and up to date, if not, prompt user to deploy
1260
- // if no chainId is provided, we'll assume the wallet is auth chain wallet and is up to date
1261
- if (!chainId) {
1262
- return true;
1263
- }
1204
+ this.messageProviders.forEach(m => {
1205
+ if (this.provider !== m) {
1206
+ m.unregister();
1207
+ }
1208
+ });
1209
+ }
1210
+ });
1211
+ });
1212
+ };
1213
+
1214
+ this.unregister = () => {
1215
+ this.messageProviders.forEach(m => m.unregister());
1216
+ this.provider = undefined;
1217
+ };
1264
1218
 
1265
- const isUpToDate = await isWalletUpToDate(signer, chainId);
1219
+ this.openWallet = (path, intent, networkId) => {
1220
+ if (this.provider) {
1221
+ this.provider.openWallet(path, intent, networkId);
1222
+ return;
1223
+ }
1266
1224
 
1267
- if (isUpToDate) {
1268
- return true;
1269
- }
1225
+ this.messageProviders.forEach(m => m.openWallet(path, intent, networkId));
1226
+ };
1270
1227
 
1271
- const promptResult = await prompter.promptConfirmWalletDeploy(chainId, this.connectOptions); // if client returned true, check again to make sure wallet is deployed and up to date
1228
+ this.sendAsync = async function (request, callback, chainId) {
1229
+ if (_this.provider) {
1230
+ _this.provider.sendAsync(request, callback, chainId);
1272
1231
 
1273
- if (promptResult) {
1274
- const isPromptResultCorrect = await isWalletUpToDate(signer, chainId);
1232
+ return;
1233
+ }
1275
1234
 
1276
- if (!isPromptResultCorrect) {
1277
- utils.logger.error('WalletRequestHandler: result for promptConfirmWalletDeploy is not correct');
1278
- return false;
1279
- } else {
1280
- return true;
1235
+ throw new Error('impossible state, wallet must be opened first');
1236
+ };
1237
+
1238
+ this.sendMessageRequest = async function (message) {
1239
+ if (_this.provider) {
1240
+ return _this.provider.sendMessageRequest(message);
1281
1241
  }
1282
- }
1283
1242
 
1284
- return false;
1285
- }
1243
+ throw new Error('impossible state, wallet must be opened first');
1244
+ };
1286
1245
 
1287
- }
1288
- const permittedJsonRpcMethods = ['net_version', 'eth_chainId', 'eth_getBalance', 'eth_getTransactionCount', 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getBlockByHash', 'eth_getTransactionByHash', 'eth_getCode', 'eth_estimateGas', 'eth_gasPrice', 'sequence_getWalletContext', 'sequence_getNetworks', 'sequence_setDefaultNetwork'];
1246
+ this.waitUntilOpened = async function () {
1247
+ if (_this.provider) {
1248
+ return _this.provider.waitUntilOpened();
1249
+ }
1289
1250
 
1290
- class Web3Provider extends providers.Web3Provider {
1291
- static isSequenceProvider(cand) {
1292
- return isSequenceProvider(cand);
1293
- }
1251
+ return Promise.race(_this.messageProviders.map(p => p.waitUntilOpened()));
1252
+ };
1294
1253
 
1295
- constructor(provider, defaultChainId) {
1296
- const sender = new network.JsonRpcSender(provider, network.maybeChainId(defaultChainId));
1297
- provider = sender;
1298
- super(provider, 'any');
1299
- this._sender = void 0;
1300
- this._isSequenceProvider = void 0;
1301
- this._defaultChainId = void 0;
1302
- this._sender = sender;
1303
- this._isSequenceProvider = true;
1304
- this._defaultChainId = network.maybeChainId(defaultChainId);
1305
- }
1254
+ this.waitUntilConnected = async function () {
1255
+ if (_this.provider) {
1256
+ return _this.provider.waitUntilConnected();
1257
+ }
1306
1258
 
1307
- sendAsync(request, callback, chainId) {
1308
- this._sender.sendAsync(request, callback, chainId);
1309
- }
1259
+ throw new Error('impossible state, wallet must be opened first');
1260
+ };
1310
1261
 
1311
- send(method, params, chainId) {
1312
- return this._sender.send(method, params, chainId);
1262
+ this.messageProviders = messageProviders;
1263
+ this.provider = undefined;
1313
1264
  }
1314
1265
 
1315
- request(request) {
1316
- return this.send(request.method, request.params || [], request.chainId);
1266
+ add(...messageProviders) {
1267
+ this.messageProviders.push(...messageProviders);
1317
1268
  }
1318
1269
 
1319
- getSigner() {
1320
- return new Web3Signer(this, this._defaultChainId);
1270
+ closeWallet() {
1271
+ if (this.provider) {
1272
+ this.provider.closeWallet();
1273
+ }
1321
1274
  }
1322
1275
 
1323
- async getChainId() {
1324
- // TODO: is it safe to memoize this?
1325
- const result = await this.send('eth_chainId', []);
1326
- const chainId = ethers.ethers.BigNumber.from(result).toNumber();
1327
-
1328
- if (this._defaultChainId && this._defaultChainId !== chainId) {
1329
- throw new Error(`provider chainId (${chainId}) does not match provider-bound chainId ${this._defaultChainId}`);
1276
+ isOpened() {
1277
+ if (this.provider) {
1278
+ return this.provider.isOpened();
1330
1279
  }
1331
1280
 
1332
- return chainId;
1333
- }
1334
-
1335
- }
1336
- function isSequenceProvider(provider) {
1337
- const cand = provider;
1338
- return cand && cand.send !== undefined && cand._isSequenceProvider === true;
1339
- }
1340
- class LocalWeb3Provider extends Web3Provider {
1341
- constructor(signer, networks) {
1342
- const walletRequestHandler = new WalletRequestHandler(signer, null, networks || []);
1343
- super(walletRequestHandler);
1281
+ return false;
1344
1282
  }
1345
1283
 
1346
- }
1347
- class Web3Signer extends wallet.Signer {
1348
- constructor(provider, defaultChainId) {
1349
- super();
1350
- this.provider = void 0;
1351
- this.defaultChainId = void 0;
1352
- this._address = void 0;
1353
- this._index = void 0;
1354
- this._context = void 0;
1355
- this._networks = void 0;
1356
- this._providers = {};
1357
- this.provider = provider;
1358
- this.defaultChainId = defaultChainId;
1359
- } // memoized
1360
-
1284
+ isConnected() {
1285
+ if (this.provider) {
1286
+ return this.provider.isConnected();
1287
+ }
1361
1288
 
1362
- //
1363
- // ethers AbstractSigner methods
1364
- //
1365
- async getAddress() {
1366
- if (this._address) return this._address;
1367
- const accounts = await this.provider.send('eth_accounts', []);
1368
- this._address = accounts[0];
1369
- this._index = 0;
1370
- return ethers.ethers.utils.getAddress(this._address);
1289
+ return false;
1371
1290
  }
1372
1291
 
1373
- signTransaction(transaction) {
1374
- // TODO .. since ethers isn't using this method, perhaps we will?
1375
- throw new Error('signTransaction is unsupported, use signTransactions instead');
1292
+ on(event, fn) {
1293
+ if (this.provider) {
1294
+ this.provider.on(event, fn);
1295
+ return;
1296
+ }
1297
+
1298
+ this.messageProviders.forEach(m => {
1299
+ m.on(event, fn);
1300
+ });
1376
1301
  }
1377
1302
 
1378
- connect(provider) {
1379
- throw new Error('unsupported: cannot alter JSON-RPC Signer connection');
1380
- } //
1381
- // Sequence Signer methods
1382
- //
1383
- // getProvider returns a Web3Provider instance for the current chain. Note that this method
1384
- // and signer is bound to a particular chain to prevent misuse. If you'd like a provider
1385
- // for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
1303
+ once(event, fn) {
1304
+ if (this.provider) {
1305
+ this.provider.once(event, fn);
1306
+ return;
1307
+ }
1386
1308
 
1309
+ this.messageProviders.forEach(m => {
1310
+ m.once(event, fn);
1311
+ });
1312
+ }
1387
1313
 
1388
- async getProvider(chainId) {
1389
- if (chainId) {
1390
- const currentChainId = await this.getChainId();
1314
+ emit(event, ...args) {
1315
+ if (this.provider) {
1316
+ return this.provider.emit(event, ...args);
1317
+ }
1391
1318
 
1392
- if (currentChainId !== chainId) {
1393
- throw new Error(`signer is attempting to access chain ${chainId}, but is already bound to chain ${currentChainId}`);
1394
- }
1319
+ for (let i = 0; i < this.messageProviders.length; i++) {
1320
+ this.messageProviders[i].emit(event, ...args);
1395
1321
  }
1396
1322
 
1397
- return this.provider;
1398
- } // getSender returns a Web3Provider instance via the signer transport. Note: for our case
1399
- // the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
1400
- // requests are sent to the window transport. Therefore, for anything non-signing related
1401
- // you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
1402
- // as we do in the WalletProvider.
1403
- //
1404
- // This method is primarily utilized internally when routing requests to a particular chainId.
1323
+ return true;
1324
+ }
1405
1325
 
1326
+ sendMessage(message) {
1327
+ if (!message.idx || message.idx <= 0) {
1328
+ throw new Error('message idx is empty');
1329
+ }
1406
1330
 
1407
- async getSender(chainId) {
1408
- if (!chainId || chainId && chainId === this.defaultChainId) {
1409
- return this.provider;
1331
+ if (this.provider) {
1332
+ this.provider.sendMessage(message);
1333
+ } else {
1334
+ throw new Error('impossible state, wallet must be opened first');
1410
1335
  }
1336
+ }
1411
1337
 
1412
- if (!this._providers[chainId]) {
1413
- this._providers[chainId] = new Web3Provider(new network.JsonRpcSender(this.provider, chainId), chainId);
1338
+ handleMessage(message) {
1339
+ if (this.provider) {
1340
+ this.provider.handleMessage(message);
1341
+ return;
1414
1342
  }
1415
1343
 
1416
- return this._providers[chainId];
1344
+ throw new Error('impossible state, wallet must be opened first');
1417
1345
  }
1418
1346
 
1419
- getRelayer(chainId) {
1420
- // TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
1421
- // sequence_estimateGasLimits
1422
- // sequence_gasRefundOptions
1423
- // sequence_getNonce
1424
- // sequence_relay
1425
- throw new Error('TODO');
1426
- }
1347
+ }
1427
1348
 
1428
- async getWalletContext() {
1429
- if (!this._context) {
1430
- this._context = await this.provider.send('sequence_getWalletContext', []);
1431
- }
1349
+ let registeredWindowMessageProvider;
1350
+ class WindowMessageProvider extends BaseProviderTransport {
1351
+ constructor(walletAppURL) {
1352
+ super();
1353
+ this.walletURL = void 0;
1354
+ this.walletWindow = void 0;
1432
1355
 
1433
- return this._context;
1434
- }
1356
+ this.register = () => {
1357
+ /* eslint-disable */
1358
+ if (registeredWindowMessageProvider) {
1359
+ // overriding the registered message provider
1360
+ registeredWindowMessageProvider.unregister();
1361
+ registeredWindowMessageProvider = this;
1362
+ } // listen for incoming messages from wallet
1435
1363
 
1436
- async getWalletConfig(chainId) {
1437
- return await this.provider.send('sequence_getWalletConfig', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
1438
- }
1439
1364
 
1440
- async getWalletState(chainId) {
1441
- return await this.provider.send('sequence_getWalletState', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
1442
- }
1365
+ window.addEventListener('message', this.onWindowEvent);
1366
+ registeredWindowMessageProvider = this;
1367
+ /* eslint-enable */
1368
+ // open heartbeat
1443
1369
 
1444
- async getNetworks() {
1445
- if (!this._networks) {
1446
- this._networks = await this.provider.send('sequence_getNetworks', []);
1447
- }
1370
+ this.on('open', () => {
1371
+ // Heartbeat to track if window closed
1372
+ const popup = this.walletWindow;
1373
+ const interval = setInterval(() => {
1374
+ if (popup && popup.closed) {
1375
+ clearInterval(interval);
1376
+ this.close();
1377
+ }
1378
+ }, 500);
1379
+ }); // close clean up
1448
1380
 
1449
- return this._networks;
1450
- }
1381
+ this.on('close', () => {
1382
+ if (this.walletWindow) {
1383
+ this.walletWindow.close();
1384
+ this.walletWindow = null;
1385
+ }
1386
+ });
1387
+ this._registered = true;
1388
+ };
1451
1389
 
1452
- async getSigners() {
1453
- const networks = await this.getNetworks();
1454
- const authChainId = networks.find(n => n.isAuthChain);
1390
+ this.unregister = () => {
1391
+ this._registered = false;
1392
+ this.closeWallet(); // disable message listener
1455
1393
 
1456
- if (!authChainId) {
1457
- throw new Error('authChainId could not be determined from network list');
1458
- }
1394
+ if (registeredWindowMessageProvider === this) {
1395
+ registeredWindowMessageProvider = undefined;
1396
+ }
1459
1397
 
1460
- const walletConfig = await this.getWalletConfig(authChainId);
1398
+ window.removeEventListener('message', this.onWindowEvent); // clear event listeners
1461
1399
 
1462
- if (!walletConfig || walletConfig.length === 0) {
1463
- throw new Error(`walletConfig returned zero results for authChainId {authChainId}`);
1464
- }
1400
+ this.events.removeAllListeners();
1401
+ };
1465
1402
 
1466
- return walletConfig[0].signers.map(s => s.address);
1467
- } // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
1468
- // multi-chain support.
1403
+ this.openWallet = (path, intent, networkId) => {
1404
+ if (this.walletWindow && this.isOpened()) {
1405
+ // TODO: update the location of window to path
1406
+ this.walletWindow.focus();
1407
+ return;
1408
+ } // Instantiate new walletURL for this call
1469
1409
 
1470
1410
 
1471
- async signMessage(message, chainId, allSigners) {
1472
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
1473
- const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
1474
- const address = await this.getAddress(); // NOTE: as of ethers v5.5, it switched to using personal_sign, see
1475
- // https://github.com/ethers-io/ethers.js/pull/1542 and see
1476
- // https://github.com/WalletConnect/walletconnect-docs/issues/32 for additional info.
1411
+ const walletURL = new URL(this.walletURL.href);
1412
+ const windowSessionParams = new WindowSessionParams();
1477
1413
 
1478
- return await provider.send('personal_sign', [ethers.ethers.utils.hexlify(data), address]);
1479
- } // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
1480
- // multi-chain support.
1414
+ if (path && path !== '') {
1415
+ walletURL.pathname = path.toLowerCase();
1416
+ } // Set session, intent and network id on walletURL
1481
1417
 
1482
1418
 
1483
- async signTypedData(domain, types, message, chainId, allSigners) {
1484
- // Populate any ENS names (in-place)
1485
- // const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
1486
- // return this.provider.resolveName(name)
1487
- // })
1488
- return await this.provider.send('eth_signTypedData_v4', [await this.getAddress(), ethers.ethers.utils._TypedDataEncoder.getPayload(domain, types, message)], network.maybeChainId(chainId) || this.defaultChainId);
1489
- } // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
1490
- // multi-chain support.
1419
+ this._init = exports.InitState.NIL;
1420
+ this._sessionId = `${performance.now()}`;
1421
+ windowSessionParams.set('sid', this._sessionId);
1491
1422
 
1423
+ if (intent) {
1424
+ // for the window-transport, we eagerly/optimistically set the origin host
1425
+ // when connecting to the wallet, however, this will be verified and enforced
1426
+ // on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
1427
+ if (intent.type === 'connect') {
1428
+ if (!intent.options) intent.options = {}; // skip setting origin host if we're in an browser extension execution context
1429
+ // allow origin that is passed in
1492
1430
 
1493
- async sendTransaction(transaction, chainId, allSigners) {
1494
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
1495
- const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
1496
- return ethers.ethers.utils.poll(() => {
1497
- return provider.getTransaction(hash).then(tx => {
1498
- if (tx === null) {
1499
- return undefined;
1431
+ if (!isBrowserExtension()) {
1432
+ intent.options.origin = window.location.origin;
1500
1433
  }
1434
+ } // encode intent as base64 url-encoded param
1501
1435
 
1502
- return provider._wrapTransaction(tx, hash);
1503
- });
1504
- }, {
1505
- onceBlock: this.provider
1506
- }).catch(error => {
1507
- error.transactionHash = hash;
1508
- throw error;
1509
- });
1510
- }); // @ts-ignore
1511
-
1512
- return tx;
1513
- } // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
1514
- // send multiple transaction as a single payload and just one on-chain transaction.
1515
1436
 
1437
+ windowSessionParams.set('intent', utils.base64EncodeObject(intent));
1438
+ }
1516
1439
 
1517
- async sendTransactionBatch(transactions, chainId, allSigners) {
1518
- const batch = await wallet.resolveArrayProperties(transactions);
1440
+ if (networkId) {
1441
+ windowSessionParams.set('net', `${networkId}`);
1442
+ } // Open popup window on center of the app window
1519
1443
 
1520
- if (!batch || batch.length === 0) {
1521
- throw new Error('cannot send empty batch');
1522
- } // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
1523
1444
 
1445
+ let windowSize;
1446
+ let windowPos;
1524
1447
 
1525
- if (batch.find(v => v.wait !== undefined && v.wait !== null)) {
1526
- throw new Error('transaction request expected for sendTransactionBatch, transaction response found');
1527
- }
1448
+ if (isBrowserExtension()) {
1449
+ windowSize = [450, 750];
1450
+ windowPos = [Math.abs(window.screen.width / 2 - windowSize[0] / 2), Math.abs(window.screen.height / 2 - windowSize[1] / 2)];
1451
+ } else {
1452
+ windowSize = [450, 750];
1453
+ windowPos = [Math.abs(window.screenX + window.innerWidth / 2 - windowSize[0] / 2), Math.abs(window.screenY + window.innerHeight / 2 - windowSize[1] / 2)];
1454
+ }
1528
1455
 
1529
- const tx = _extends({}, batch[0]);
1456
+ const windowFeatures = `toolbar=0,location=0,menubar=0,scrollbars=yes,status=yes` + `,width=${windowSize[0]},height=${windowSize[1]}` + `,left=${windowPos[0]},top=${windowPos[1]}`; // serialize params
1530
1457
 
1531
- if (batch.length > 1) {
1532
- tx.auxiliary = batch.splice(1);
1533
- }
1458
+ walletURL.search = windowSessionParams.toString();
1459
+ this.walletWindow = window.open(walletURL.href, 'sequence.app', windowFeatures); // TODO: move this somewhere else
1460
+ // TODO: perhaps we trigger a .on('openTimeout') event..? maybe.. could help.
1461
+ // Popup blocking detection and notice
1462
+ // let warned = false
1463
+ // const warnPopupBlocked = () => {
1464
+ // if (warned) return
1465
+ // warned = true
1466
+ // // alert('popup is blocked! hey yo') // NOTE: for debug purposes only
1467
+ // throw new Error('popup is blocked')
1468
+ // }
1469
+ // const popupCheck = setTimeout(() => {
1470
+ // if (!popup || popup.closed || typeof popup.closed === 'undefined') {
1471
+ // // popup is definitely blocked if we reach here.
1472
+ // warnPopupBlocked()
1473
+ // }
1474
+ // }, 1000)
1475
+ // const popupBlocked = popup === null || popup === undefined
1476
+ // if (popupBlocked) {
1477
+ // warnPopupBlocked()
1478
+ // return
1479
+ // }
1480
+ };
1534
1481
 
1535
- return this.sendTransaction(tx, chainId, allSigners);
1536
- }
1482
+ this.onWindowEvent = event => {
1483
+ // Security check, ensure message is coming from wallet origin url
1484
+ if (event.origin !== this.walletURL.origin) {
1485
+ // Safetly can skip events not from the wallet
1486
+ return;
1487
+ }
1537
1488
 
1538
- signTransactions(transaction, chainId, allSigners) {
1539
- transaction = utils.shallowCopy(transaction); // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
1540
- // the wire.. see sendUncheckedTransaction and resolveProperties
1489
+ let message;
1541
1490
 
1542
- return this.provider.send('eth_signTransaction', [transaction], network.maybeChainId(chainId) || this.defaultChainId);
1543
- }
1491
+ try {
1492
+ message = JSON.parse(event.data);
1493
+ } catch (err) {
1494
+ // event is not a ProviderMessage JSON object, skip
1495
+ return;
1496
+ }
1544
1497
 
1545
- sendSignedTransactions(signedTxs, chainId) {
1546
- // sequence_relay
1547
- throw new Error('TODO');
1548
- } // updateConfig..
1549
- // NOTE: this is not supported by the remote wallet by default.
1498
+ if (!message) {
1499
+ throw new Error('ProviderMessage object is empty');
1500
+ } // handle message with base message provider
1550
1501
 
1551
1502
 
1552
- async updateConfig(newConfig) {
1553
- // sequence_updateConfig
1554
- const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId);
1503
+ this.handleMessage(message);
1504
+ };
1555
1505
 
1556
- if (tx === null) {
1557
- return [config, undefined];
1558
- }
1506
+ this.walletURL = new URL(walletAppURL);
1507
+ }
1559
1508
 
1560
- const provider = await this.getSender(this.defaultChainId);
1561
- return [config, provider._wrapTransaction(tx, tx.hash)];
1562
- } // publishConfig..
1563
- // NOTE: this is not supported by the remote wallet by default.
1509
+ closeWallet() {
1510
+ var _this$walletWindow;
1564
1511
 
1512
+ this.close();
1513
+ (_this$walletWindow = this.walletWindow) == null ? void 0 : _this$walletWindow.close();
1514
+ } // onmessage, receives ProviderMessageResponse from the wallet post-message transport
1565
1515
 
1566
- async publishConfig() {
1567
- const provider = await this.getSender(this.defaultChainId);
1568
- const tx = await provider.send('sequence_publishConfig', []);
1569
1516
 
1570
- if (tx === null) {
1571
- return undefined;
1517
+ sendMessage(message) {
1518
+ if (!this.walletWindow) {
1519
+ utils.logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable');
1520
+ return;
1572
1521
  }
1573
1522
 
1574
- return provider._wrapTransaction(tx, tx.hash);
1523
+ const postedMessage = typeof message !== 'string' ? JSON.stringify(message) : message;
1524
+ this.walletWindow.postMessage(postedMessage, this.walletURL.origin);
1575
1525
  }
1576
1526
 
1577
- async isDeployed(chainId) {
1578
- const provider = await this.getSender(network.maybeChainId(chainId));
1579
- const walletCode = await provider.getCode(await this.getAddress());
1580
- return !!walletCode && walletCode !== '0x';
1581
- } //
1582
- // ethers JsonRpcSigner methods
1583
- //
1584
-
1585
-
1586
- async _legacySignMessage(message, chainId, allSigners) {
1587
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
1588
- const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
1589
- const address = await this.getAddress(); // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
1590
- // NOTE: ethers since 5.5 has switched to using personal_sign, we should review, etc.
1591
-
1592
- return await provider.send('eth_sign', [address, ethers.ethers.utils.hexlify(data)]);
1593
- }
1527
+ }
1594
1528
 
1595
- async _signTypedData(domain, types, message, chainId, allSigners) {
1596
- return this.signTypedData(domain, types, message, chainId, allSigners);
1597
- }
1529
+ class WindowMessageHandler extends BaseWalletTransport {
1530
+ constructor(walletRequestHandler) {
1531
+ var _this;
1598
1532
 
1599
- async sendUncheckedTransaction(transaction, chainId) {
1600
- transaction = utils.shallowCopy(transaction);
1601
- const fromAddress = this.getAddress(); // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
1602
- //
1603
- // TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
1604
- // and send it to our relayer url instead for estimation..
1605
- //
1606
- // if (!transaction.gasLimit) {
1607
- // const estimate = shallowCopy(transaction)
1608
- // estimate.from = fromAddress
1609
- // transaction.gasLimit = this.provider.estimateGas(estimate)
1610
- // }
1533
+ super(walletRequestHandler);
1534
+ _this = this;
1535
+ this.parentWindow = void 0;
1536
+ this._isPopup = false;
1611
1537
 
1612
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
1613
- return utils.resolveProperties({
1614
- tx: utils.resolveProperties(transaction),
1615
- sender: await fromAddress
1616
- }).then(({
1617
- tx,
1618
- sender
1619
- }) => {
1620
- if (tx.from != null) {
1621
- if (ethers.ethers.utils.getAddress(tx.from) !== sender) {
1622
- // logger.throwArgumentError("from address mismatch", "transaction", transaction)
1623
- throw new Error(`from address mismatch for transaction ${transaction}`);
1624
- }
1625
- } else {
1626
- tx.from = sender;
1538
+ this.onWindowEvent = async function (event) {
1539
+ if (!event.origin || event.origin === '') {
1540
+ // skip same-origin or when event.origin is empty/undefined
1541
+ return;
1627
1542
  }
1628
1543
 
1629
- const hexTx = hexlifyTransaction(tx);
1630
- return provider.send('eth_sendTransaction', [hexTx]).then(hash => {
1631
- return hash;
1632
- }, error => {
1633
- // return checkError("sendTransaction", error, hexTx)
1634
- throw new Error(`sendTransaction ${error}`);
1635
- });
1636
- });
1637
- }
1638
-
1639
- connectUnchecked() {
1640
- throw new Error('connectUnchecked is unsupported');
1641
- }
1642
-
1643
- async unlock(password) {
1644
- const address = await this.getAddress();
1645
- return this.provider.send('personal_unlockAccount', [address, password, null]);
1646
- }
1647
-
1648
- } // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
1649
- // Convert an ethers.js transaction into a JSON-RPC transaction
1544
+ if (_this.appOrigin && event.origin !== _this.appOrigin) {
1545
+ // skip message as not from expected app origin
1546
+ return;
1547
+ } // Wallet always expects json-rpc request messages from a dapp
1650
1548
 
1651
- const allowedTransactionKeys = {
1652
- chainId: true,
1653
- data: true,
1654
- gasLimit: true,
1655
- gasPrice: true,
1656
- nonce: true,
1657
- to: true,
1658
- value: true,
1659
- from: true,
1660
- auxiliary: true,
1661
- expiration: true,
1662
- afterNonce: true,
1663
- delegateCall: true,
1664
- revertOnError: true
1665
- };
1666
1549
 
1667
- const hexlifyTransaction = (transaction, allowExtra) => {
1668
- // Check only allowed properties are given
1669
- const allowed = utils.shallowCopy(allowedTransactionKeys);
1550
+ let request;
1670
1551
 
1671
- if (allowExtra) {
1672
- for (const key in allowExtra) {
1673
- if (allowExtra[key]) {
1674
- allowed[key] = true;
1552
+ try {
1553
+ request = JSON.parse(event.data);
1554
+ } catch (err) {
1555
+ // event is not a ProviderMessage JSON object, skip
1556
+ return;
1675
1557
  }
1676
- }
1677
- }
1678
-
1679
- ethers.ethers.utils.checkProperties(transaction, allowed);
1680
- const result = {} // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
1681
- ;
1682
- ['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
1683
- if (!transaction[key]) {
1684
- return;
1685
- }
1686
1558
 
1687
- const value = ethers.ethers.utils.hexValue(transaction[key]);
1559
+ utils.logger.debug('RECEIVED MESSAGE', request); // Record event origin for valid init ack
1688
1560
 
1689
- if (key === 'gasLimit') {
1690
- key = 'gas';
1691
- }
1561
+ if (_this._init !== exports.InitState.OK && _this.isValidInitAck(request)) {
1562
+ _this.appOrigin = event.origin;
1563
+ }
1692
1564
 
1693
- result[key] = value;
1694
- });
1695
- ['from', 'to', 'data'].forEach(key => {
1696
- if (!transaction[key]) {
1697
- return;
1698
- }
1565
+ if (_this._init === exports.InitState.OK && (!_this.appOrigin || _this.appOrigin.length < 8)) {
1566
+ // impossible state
1567
+ utils.logger.error('impossible state, init.OK and appOrigin required');
1568
+ return;
1569
+ } // Handle message via the base transport
1699
1570
 
1700
- result[key] = ethers.ethers.utils.hexlify(transaction[key]);
1701
- });
1702
- const auxiliary = transaction['auxiliary'];
1703
1571
 
1704
- if (auxiliary && auxiliary.length > 0) {
1705
- result['auxiliary'] = [];
1706
- auxiliary.forEach(a => {
1707
- result['auxiliary'].push(hexlifyTransaction(a));
1708
- });
1709
- }
1572
+ _this.handleMessage(request);
1573
+ };
1710
1574
 
1711
- return result;
1712
- };
1575
+ this.getWindowTransportSession = windowParams => {
1576
+ const params = new WindowSessionParams(windowParams);
1577
+ return {
1578
+ sessionId: params.get('sid'),
1579
+ networkId: params.get('net'),
1580
+ intent: utils.base64DecodeObject(params.get('intent'))
1581
+ };
1582
+ };
1713
1583
 
1714
- const eip191prefix = ethers.ethers.utils.toUtf8Bytes('\x19Ethereum Signed Message:\n');
1715
- const messageToBytes = message => {
1716
- if (ethers.ethers.utils.isBytes(message) || ethers.ethers.utils.isHexString(message)) {
1717
- return ethers.ethers.utils.arrayify(message);
1584
+ this._init = exports.InitState.NIL;
1718
1585
  }
1719
1586
 
1720
- return ethers.ethers.utils.toUtf8Bytes(message);
1721
- };
1722
- const prefixEIP191Message = message => {
1723
- const messageBytes = messageToBytes(message);
1724
- return ethers.ethers.utils.concat([eip191prefix, ethers.ethers.utils.toUtf8Bytes(String(messageBytes.length)), messageBytes]);
1725
- };
1726
- const isValidSignature = async (address, digest, sig, provider, chainId, walletContext) => {
1727
- if (!chainId) {
1728
- var _await$provider$getNe;
1587
+ async register(windowHref) {
1588
+ const isPopup = parent.window.opener !== null;
1589
+ this._isPopup = isPopup;
1729
1590
 
1730
- chainId = (_await$provider$getNe = await provider.getNetwork()) == null ? void 0 : _await$provider$getNe.chainId;
1731
- }
1591
+ if (isPopup !== true) {
1592
+ return;
1593
+ } // record open details (sessionId + default network) from the window url
1732
1594
 
1733
- if (!walletContext && Web3Provider.isSequenceProvider(provider)) {
1734
- walletContext = await provider.getSigner().getWalletContext();
1735
- }
1736
1595
 
1737
- return wallet.isValidSignature(address, digest, sig, provider, walletContext, chainId);
1738
- };
1739
- const isValidMessageSignature = async (address, message, signature, provider, chainId, walletContext) => {
1740
- const prefixed = prefixEIP191Message(message);
1741
- const digest = utils.encodeMessageDigest(prefixed);
1742
- return isValidSignature(address, digest, signature, provider, chainId, walletContext);
1743
- };
1744
- const isValidTypedDataSignature = (address, typedData, signature, provider, chainId, walletContext) => {
1745
- return isValidSignature(address, utils.encodeTypedDataDigest(typedData), signature, provider, chainId, walletContext);
1746
- };
1747
- const recoverWalletConfig = async (address, digest, signature, chainId, walletContext) => {
1748
- const subDigest = utils.packMessageData(address, chainId, digest);
1749
- const config$1 = await wallet.recoverConfig(subDigest, signature);
1596
+ const {
1597
+ pathname,
1598
+ search: rawParams
1599
+ } = new URL(windowHref || window.location.href);
1600
+ let session = this.getWindowTransportSession(rawParams); // provider should always include sid when opening a new window
1750
1601
 
1751
- if (walletContext) {
1752
- const recoveredWalletAddress = config.addressOf(config$1, walletContext);
1602
+ const isNewWindowSession = !!session.sessionId; // attempt to restore previous session in the case of a redirect or window reload
1753
1603
 
1754
- if (config$1.address && config$1.address !== recoveredWalletAddress) {
1755
- throw new Error('recovered address does not match the WalletConfig address, check the WalletContext');
1756
- } else {
1757
- config$1.address = recoveredWalletAddress;
1604
+ if (!isNewWindowSession) {
1605
+ session = await this.getCachedTransportSession();
1758
1606
  }
1759
- }
1760
-
1761
- return config$1;
1762
- };
1763
- const isBrowserExtension = () => window.location.protocol === 'chrome-extension:' || window.location.protocol === 'moz-extension:';
1764
- /**
1765
- * Returns the status of a signer's wallet on given chain by checking wallet deployment and config status
1766
- *
1767
- * @param {Signer} signer
1768
- * @param {number} chainId
1769
- * @return {Promise<boolean>} Promise that returns true if the wallet is up to date, false otherwise
1770
- */
1771
1607
 
1772
- const isWalletUpToDate = async (signer, chainId) => {
1773
- var _walletState$find;
1608
+ if (!session) {
1609
+ utils.logger.error('window session is undefined');
1610
+ return;
1611
+ } // record parent window instance for communication
1774
1612
 
1775
- const walletState = await signer.getWalletState();
1776
- const networks = await signer.getNetworks();
1777
- const walletStateForRequiredChain = walletState.find(state => state.chainId === chainId);
1778
1613
 
1779
- if (!walletStateForRequiredChain) {
1780
- throw new Error(`WalletRequestHandler: could not find wallet state for chainId ${chainId}`);
1781
- }
1614
+ this.parentWindow = parent.window.opener; // listen for window-transport requests
1782
1615
 
1783
- const isDeployed = walletStateForRequiredChain.deployed;
1616
+ window.addEventListener('message', this.onWindowEvent, false);
1617
+ this._registered = true; // send open event to the app which opened us
1784
1618
 
1785
- if (!networks) {
1786
- throw new Error(`isWalletUpToDate util: could not get networks from signer`);
1787
- }
1619
+ this.open(session).then(opened => {
1620
+ if (!opened) {
1621
+ var _session;
1788
1622
 
1789
- const authChain = networks.find(network => network.isAuthChain);
1623
+ const err = `failed to open to network ${(_session = session) == null ? void 0 : _session.networkId}`;
1624
+ utils.logger.error(err);
1625
+ this.notifyClose({
1626
+ message: err
1627
+ });
1628
+ window.close();
1629
+ }
1630
+ }).catch(e => {
1631
+ var _session2;
1790
1632
 
1791
- if (!authChain) {
1792
- throw new Error(`isWalletUpToDate util: could not get auth chain network information`);
1633
+ const err = `failed to open to network ${(_session2 = session) == null ? void 0 : _session2.networkId}, due to: ${e}`;
1634
+ utils.logger.error(err);
1635
+ this.notifyClose({
1636
+ message: err
1637
+ });
1638
+ window.close();
1639
+ });
1793
1640
  }
1794
1641
 
1795
- const authChainId = authChain.chainId;
1796
- const authChainConfig = (_walletState$find = walletState.find(state => state.chainId === authChainId)) == null ? void 0 : _walletState$find.config;
1642
+ unregister() {
1643
+ window.removeEventListener('message', this.onWindowEvent);
1644
+ this._registered = false;
1645
+ } // onmessage is called when (the wallet) receives request messages from the dapp
1646
+ // over the window post-messaging transport
1797
1647
 
1798
- if (!authChainConfig) {
1799
- throw new Error(`isWalletUpToDate util: could not get auth chain config`);
1800
- }
1801
1648
 
1802
- const requiredChainConfig = walletStateForRequiredChain.config;
1649
+ // postMessage sends message to the dapp window
1650
+ sendMessage(message) {
1651
+ // prepare payload
1652
+ const payload = JSON.stringify(message); // post-message to app.
1653
+ // only for init requests, we send to '*' origin
1803
1654
 
1804
- if (!requiredChainConfig) {
1805
- throw new Error(`isWalletUpToDate util: could not get config for chainId ${chainId}`);
1655
+ if (message.type === exports.EventType.INIT) {
1656
+ this.postMessage(payload, true);
1657
+ } else {
1658
+ this.postMessage(payload);
1659
+ }
1806
1660
  }
1807
1661
 
1808
- const isUpToDate = config.isConfigEqual(authChainConfig, requiredChainConfig);
1809
- return isDeployed && isUpToDate;
1810
- };
1811
- class LocalStorage {
1812
- constructor() {}
1662
+ get isPopup() {
1663
+ return this._isPopup;
1664
+ }
1813
1665
 
1814
- static getInstance() {
1815
- if (!LocalStorage._instance) {
1816
- LocalStorage._instance = {
1817
- getItem: key => Promise.resolve(window.localStorage.getItem(key)),
1818
- setItem: (key, value) => Promise.resolve(window.localStorage.setItem(key, value)),
1819
- removeItem: key => Promise.resolve(window.localStorage.removeItem(key))
1820
- };
1666
+ postMessage(message, init = false) {
1667
+ if (init !== true && this._init !== exports.InitState.OK) {
1668
+ utils.logger.error('impossible state, should not be calling postMessage until inited');
1669
+ return;
1821
1670
  }
1822
1671
 
1823
- return this._instance;
1672
+ if (init) {
1673
+ // init message transmission to global target -- for 'init' payloads only
1674
+ this.parentWindow.postMessage(message, '*');
1675
+ } else {
1676
+ // open message transmission
1677
+ if (this.appOrigin && this.appOrigin.length > 4) {
1678
+ // just above '.com'
1679
+ this.parentWindow.postMessage(message, this.appOrigin);
1680
+ } else {
1681
+ utils.logger.error('unable to postMessage as parentOrigin is invalid');
1682
+ }
1683
+ }
1824
1684
  }
1825
1685
 
1826
- static use(instance) {
1827
- LocalStorage._instance = instance;
1828
- }
1686
+ }
1829
1687
 
1830
- } // window.localstorage helper
1688
+ const SIGNER_READY_TIMEOUT = 10000;
1689
+ class WalletRequestHandler {
1690
+ // signer interface of the wallet. A null value means there is no signer (ie. user not signed in). An undefined
1691
+ // value means the signer state is unknown, usually meaning the wallet app is booting up and initializing. Of course
1692
+ // a Signer value is the actually interface to a signed-in account
1693
+ constructor(_signer, prompter, mainnetNetworks, testnetNetworks = []) {
1694
+ var _this = this;
1831
1695
 
1832
- LocalStorage._instance = void 0;
1833
- class LocalStore {
1834
- constructor(key, def) {
1835
- this.def = def;
1836
- this.key = void 0;
1837
- this.key = key;
1838
- }
1696
+ this.signer = void 0;
1697
+ this.signerReadyCallbacks = [];
1698
+ this.prompter = void 0;
1699
+ this.mainnetNetworks = void 0;
1700
+ this.testnetNetworks = void 0;
1701
+ this._openIntent = void 0;
1702
+ this._connectOptions = void 0;
1703
+ this._defaultNetworkId = void 0;
1704
+ this._chainId = void 0;
1705
+ this.events = new eventemitter2.EventEmitter2();
1839
1706
 
1840
- async get() {
1841
- const val = await LocalStorage.getInstance().getItem(this.key);
1707
+ this.promptConnect = async function (options) {
1708
+ if (!options && !_this._connectOptions) {
1709
+ // this is an unexpected state and should not happen
1710
+ throw new Error('prompter connect options are empty');
1711
+ }
1842
1712
 
1843
- if (val === null) {
1844
- return this.def;
1845
- }
1713
+ if (!_this.prompter) {
1714
+ // if prompter is null, we'll auto connect
1715
+ return _this.connect(options);
1716
+ }
1846
1717
 
1847
- try {
1848
- return JSON.parse(val);
1849
- } catch (err) {
1850
- console.error(err);
1851
- }
1718
+ const promptConnectDetails = await _this.prompter.promptConnect(options || _this._connectOptions).catch(_ => {
1719
+ return {
1720
+ connected: false
1721
+ };
1722
+ });
1723
+ const connectDetails = promptConnectDetails;
1852
1724
 
1853
- return;
1854
- }
1725
+ if (connectDetails.connected && !connectDetails.session) {
1726
+ connectDetails.session = await _this.walletSession();
1727
+ }
1728
+
1729
+ return promptConnectDetails;
1730
+ };
1731
+
1732
+ this.sendAsync = async function (request, callback, chainId) {
1733
+ var _request$params, _request$params2, _request$params3, _request$params4, _request$params6;
1855
1734
 
1856
- set(val) {
1857
- val ? LocalStorage.getInstance().setItem(this.key, JSON.stringify(val)) : LocalStorage.getInstance().removeItem(this.key);
1858
- }
1735
+ const response = {
1736
+ jsonrpc: '2.0',
1737
+ id: request.id,
1738
+ result: null
1739
+ };
1740
+ await _this.getSigner();
1859
1741
 
1860
- del() {
1861
- LocalStorage.getInstance().removeItem(this.key);
1862
- }
1742
+ try {
1743
+ // only allow public json rpc method to the provider when user is not logged in, aka signer is not set
1744
+ if ((!_this.signer || _this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
1745
+ // throw new Error(`not logged in. ${request.method} is unavailable`)
1746
+ throw ErrSignedInRequired;
1747
+ } // wallet signer
1863
1748
 
1864
- }
1865
1749
 
1866
- const TRANSPORT_SESSION_LS_KEY = '@sequence.transportSession';
1867
- class BaseWalletTransport {
1868
- // appOrigin identifies the dapp's origin which opened the app. A transport
1869
- // will auto-detect and set this value if it can. This is determined
1870
- // as the parent app/window which opened the wallet.
1871
- constructor(walletRequestHandler) {
1872
- var _this = this;
1750
+ const signer = _this.signer;
1751
+ if (!signer) throw new Error('WalletRequestHandler: wallet signer is not configured'); // fetch the provider for the specific chain, or undefined will select defaultChain
1873
1752
 
1874
- this.walletRequestHandler = void 0;
1875
- this._sessionId = void 0;
1876
- this._registered = void 0;
1877
- this._init = void 0;
1878
- this._initNonce = void 0;
1879
- this._initCallback = void 0;
1880
- this.appOrigin = void 0;
1753
+ const provider = await signer.getProvider(chainId);
1754
+ if (!provider) throw new Error(`WalletRequestHandler: wallet provider is not configured for chainId ${chainId}`);
1881
1755
 
1882
- this.sendAsync = async function (request, callback, chainId) {
1883
- throw new Error('abstract method');
1884
- };
1756
+ switch (request.method) {
1757
+ case 'net_version':
1758
+ {
1759
+ const result = await provider.send('net_version', []);
1760
+ response.result = result;
1761
+ break;
1762
+ }
1885
1763
 
1886
- this.handleMessage = async function (message) {
1887
- const request = message; // ensure initial handshake is complete before accepting
1888
- // other kinds of messages.
1764
+ case 'eth_chainId':
1765
+ {
1766
+ const result = await provider.send('eth_chainId', []);
1767
+ response.result = result;
1768
+ break;
1769
+ }
1889
1770
 
1890
- if (_this._init !== exports.InitState.OK) {
1891
- if (request.type === exports.EventType.INIT) {
1892
- if (_this.isValidInitAck(message)) {
1893
- // successful init
1894
- if (_this._initCallback) _this._initCallback();
1895
- } else {
1896
- // failed init
1897
- if (_this._initCallback) _this._initCallback('invalid init');
1898
- return;
1899
- }
1900
- }
1771
+ case 'eth_accounts':
1772
+ {
1773
+ const walletAddress = await signer.getAddress();
1774
+ response.result = [walletAddress];
1775
+ break;
1776
+ }
1901
1777
 
1902
- return;
1903
- } // ensure signer is ready to handle requests
1904
- // if (this.walletRequestHandler.getSigner() === undefined) {
1905
- // await this.walletRequestHandler.signerReady()
1906
- // }
1907
- // handle request
1778
+ case 'eth_getBalance':
1779
+ {
1780
+ const [accountAddress, blockTag] = request.params;
1781
+ const walletBalance = await provider.getBalance(accountAddress, blockTag);
1782
+ response.result = walletBalance.toHexString();
1783
+ break;
1784
+ }
1908
1785
 
1786
+ case 'eth_sign':
1787
+ case 'personal_sign':
1788
+ case 'sequence_sign':
1789
+ {
1790
+ // note: message from json-rpc input is in hex format
1791
+ let message;
1792
+ let options; // there is a difference in the order of the params:
1793
+ // eth_sign: [address, data]
1794
+ // personal_sign: [data, address]
1795
+ // sequence_sign: [data, address, options]
1909
1796
 
1910
- switch (request.type) {
1911
- case exports.EventType.OPEN:
1912
- {
1913
- if (_this._init !== exports.InitState.OK) return;
1914
- const session = {
1915
- sessionId: request.data.sessionId,
1916
- intent: request.data.intent,
1917
- networkId: request.data.networkId
1918
- };
1919
- await _this.open(session);
1920
- return;
1921
- }
1797
+ switch (request.method) {
1798
+ case 'eth_sign':
1799
+ message = (_request$params = request.params) == null ? void 0 : _request$params[1];
1800
+ break;
1922
1801
 
1923
- case exports.EventType.CLOSE:
1924
- {
1925
- if (_this._init !== exports.InitState.OK) return; // noop. just here to capture the message so event emitters may be notified
1802
+ case 'personal_sign':
1803
+ message = (_request$params2 = request.params) == null ? void 0 : _request$params2[0];
1804
+ break;
1926
1805
 
1927
- return;
1928
- }
1806
+ case 'sequence_sign':
1807
+ message = (_request$params3 = request.params) == null ? void 0 : _request$params3[0];
1808
+ options = (_request$params4 = request.params) == null ? void 0 : _request$params4[2];
1809
+ break;
1810
+ }
1929
1811
 
1930
- case exports.EventType.MESSAGE:
1931
- {
1932
- const response = await _this.walletRequestHandler.sendMessageRequest(request);
1812
+ let sig = ''; // Message must be prefixed with "\x19Ethereum Signed Message:\n"
1813
+ // as defined by EIP-191
1933
1814
 
1934
- _this.sendMessage(response);
1815
+ message = wallet.prefixEIP191Message(message); // TODO:
1816
+ // if (process.env.TEST_MODE === 'true' && this.prompter === null) {
1935
1817
 
1936
- if (response.data.error) ;
1818
+ if (_this.prompter === null) {
1819
+ // prompter is null, so we'll sign from here
1820
+ sig = await signer.signMessage(ethers.ethers.utils.arrayify(message), chainId);
1821
+ } else {
1822
+ if (typeof options === 'object' && options.counterfactual === true) {
1823
+ sig = await _this.prompter.promptSignMessage({
1824
+ chainId,
1825
+ message
1826
+ }, _this.connectOptions);
1827
+ } else {
1828
+ const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
1829
+
1830
+ if (promptResultForDeployment) {
1831
+ sig = await _this.prompter.promptSignMessage({
1832
+ chainId,
1833
+ message
1834
+ }, _this.connectOptions);
1835
+ }
1836
+ }
1837
+ }
1937
1838
 
1938
- return;
1939
- }
1839
+ if (sig && sig.length > 0) {
1840
+ response.result = sig;
1841
+ } else {
1842
+ // The user has declined the request when value is null
1843
+ throw new Error('declined by user');
1844
+ }
1940
1845
 
1941
- default:
1942
- {
1943
- utils.logger.error(`unexpected payload type ${request.type}`);
1944
- }
1945
- }
1946
- };
1846
+ break;
1847
+ }
1947
1848
 
1948
- this.sendMessageRequest = async function (message) {
1949
- return _this.walletRequestHandler.sendMessageRequest(message);
1950
- };
1849
+ case 'eth_signTypedData':
1850
+ case 'eth_signTypedData_v4':
1851
+ case 'sequence_signTypedData_v4':
1852
+ {
1853
+ var _request$params5;
1951
1854
 
1952
- this.open = async function ({
1953
- sessionId,
1954
- intent,
1955
- networkId
1956
- }) {
1957
- if (sessionId) {
1958
- _this._sessionId = utils.sanitizeNumberString(sessionId); // persist transport session in localstorage for restoring after redirect/reload
1855
+ // note: signingAddress from json-rpc input is in hex format, and typedDataObject
1856
+ // should be an object, but in some instances may be double string encoded
1857
+ const typedDataObject = (_request$params5 = request.params) == null ? void 0 : _request$params5[1];
1858
+ let options;
1959
1859
 
1960
- _this.saveTransportSession({
1961
- sessionId,
1962
- intent,
1963
- networkId
1964
- });
1965
- }
1860
+ switch (request.method) {
1861
+ case 'sequence_signTypedData_v4':
1862
+ options = (_request$params6 = request.params) == null ? void 0 : _request$params6[2];
1863
+ break;
1864
+ }
1966
1865
 
1967
- _this.walletRequestHandler.setOpenIntent(intent); // init handshake for certain transports, before we can open the communication.
1968
- //
1969
- // for example, with the window-transport, we have to exchange messages to determine the
1970
- // origin host of the dapp.
1866
+ let typedData = undefined;
1971
1867
 
1868
+ if (typeof typedDataObject === 'string') {
1869
+ try {
1870
+ typedData = JSON.parse(typedDataObject);
1871
+ } catch (e) {}
1872
+ } else {
1873
+ typedData = typedDataObject;
1874
+ }
1972
1875
 
1973
- await _this.init(); // Prepare connect options from intent
1876
+ if (!typedData || !typedData.domain || !typedData.types || !typedData.message) {
1877
+ throw new Error('invalid typedData object');
1878
+ }
1974
1879
 
1975
- if (intent && intent.type === 'connect' && intent.options) {
1976
- const connectOptions = intent.options;
1977
- const authorizeOptions = connectOptions; // overlapping types
1978
- // Sanity/integrity check the intent payload, and set authorization origin
1979
- // if its been determined as part of the init handshake from earlier.
1880
+ let sig = '';
1980
1881
 
1981
- if (_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
1982
- if (!isBrowserExtension()) {
1983
- if (authorizeOptions.origin !== _this.appOrigin) {
1984
- throw new Error('origin is invalid');
1985
- }
1986
- }
1987
- } else if (!_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
1988
- // ie. when we can't determine the origin in our transport, but dapp provides it to us.
1989
- // we just sanitize the origin host.
1990
- connectOptions.origin = utils.sanitizeHost(authorizeOptions.origin);
1991
- } else if (_this.appOrigin) {
1992
- // ie. when we auto-determine the origin such as in window-transport
1993
- connectOptions.origin = _this.appOrigin;
1994
- }
1882
+ if (_this.prompter === null) {
1883
+ // prompter is null, so we'll sign from here
1884
+ sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainId);
1885
+ } else {
1886
+ if (typeof options === 'object' && options.counterfactual === true) {
1887
+ sig = await _this.prompter.promptSignMessage({
1888
+ chainId,
1889
+ typedData
1890
+ }, _this.connectOptions);
1891
+ } else {
1892
+ const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
1893
+
1894
+ if (promptResultForDeployment) {
1895
+ sig = await _this.prompter.promptSignMessage({
1896
+ chainId,
1897
+ typedData
1898
+ }, _this.connectOptions);
1899
+ }
1900
+ }
1901
+ }
1995
1902
 
1996
- if (connectOptions.app) {
1997
- connectOptions.app = utils.sanitizeAlphanumeric(connectOptions.app);
1998
- } // Set connect options on the walletRequestHandler as our primary
1999
- // wallet controller
1903
+ if (sig && sig.length > 0) {
1904
+ response.result = sig;
1905
+ } else {
1906
+ // The user has declined the request when value is null
1907
+ throw new Error('declined by user');
1908
+ }
2000
1909
 
1910
+ break;
1911
+ }
2001
1912
 
2002
- _this.walletRequestHandler.setConnectOptions(connectOptions);
1913
+ case 'eth_sendTransaction':
1914
+ {
1915
+ // https://eth.wiki/json-rpc/API#eth_sendtransaction
1916
+ const [transactionParams] = request.params;
1917
+ let txnHash = '';
2003
1918
 
2004
- if (connectOptions.networkId) {
2005
- networkId = connectOptions.networkId;
2006
- }
2007
- } else {
2008
- _this.walletRequestHandler.setConnectOptions(undefined);
2009
- } // ensure signer is ready
1919
+ if (_this.prompter === null) {
1920
+ // prompter is null, so we'll send from here
1921
+ const txnResponse = await signer.sendTransaction(transactionParams, chainId);
1922
+ txnHash = txnResponse.hash;
1923
+ } else {
1924
+ // prompt user to provide the response
1925
+ txnHash = await _this.prompter.promptSendTransaction(transactionParams, chainId, _this.connectOptions);
1926
+ }
2010
1927
 
1928
+ if (txnHash) {
1929
+ response.result = txnHash;
1930
+ } else {
1931
+ // The user has declined the request when value is null
1932
+ throw new Error('declined by user');
1933
+ }
2011
1934
 
2012
- await _this.walletRequestHandler.getSigner(); // Notify open and proceed to prompt for connection if intended
1935
+ break;
1936
+ }
2013
1937
 
2014
- if (!(await _this.walletRequestHandler.isSignedIn())) {
2015
- // open wallet without a specific connected chainId, as the user is not signed in
2016
- _this.notifyOpen({
2017
- sessionId: _this._sessionId
2018
- });
1938
+ case 'eth_signTransaction':
1939
+ {
1940
+ // https://eth.wiki/json-rpc/API#eth_signTransaction
1941
+ const [transaction] = request.params;
1942
+ const sender = ethers.ethers.utils.getAddress(transaction.from);
2019
1943
 
2020
- return true;
2021
- } else {
2022
- // Set default network, in case of error chainId will be undefined or 0
2023
- let chainId = undefined;
1944
+ if (sender !== (await signer.getAddress())) {
1945
+ throw new Error('sender address does not match wallet');
1946
+ }
2024
1947
 
2025
- try {
2026
- if (networkId) {
2027
- chainId = await _this.walletRequestHandler.setDefaultNetwork(networkId, false);
2028
- } else {
2029
- chainId = await _this.walletRequestHandler.getChainId();
2030
- }
2031
- } catch (err) {
2032
- console.error(err);
2033
- } // Failed to set default network on open -- quit + close
1948
+ if (_this.prompter === null) {
1949
+ // The eth_signTransaction method expects a `string` return value we instead return a `SignedTransactions` object,
1950
+ // this can only be broadcasted using an RPC provider with support for signed Sequence transactions, like this one.
1951
+ //
1952
+ // TODO: verify serializing / transporting the SignedTransaction object works as expected, most likely however
1953
+ // we will want to resolveProperties the bignumber values to hex strings
1954
+ response.result = await signer.signTransactions(transaction, chainId);
1955
+ } else {
1956
+ response.result = await _this.prompter.promptSignTransaction(transaction, chainId, _this.connectOptions);
1957
+ }
2034
1958
 
1959
+ break;
1960
+ }
2035
1961
 
2036
- if (!chainId || chainId <= 0) {
2037
- _this.notifyOpen({
2038
- sessionId: _this._sessionId,
2039
- error: `failed to open wallet on network ${networkId}`
2040
- });
1962
+ case 'eth_sendRawTransaction':
1963
+ {
1964
+ // NOTE: we're not using a prompter here as the transaction is already signed
1965
+ // and would have prompted the user upon signing.
1966
+ // https://eth.wiki/json-rpc/API#eth_sendRawTransaction
1967
+ if (transactions.isSignedTransactionBundle(request.params[0])) {
1968
+ const result = await signer.sendSignedTransactions(request.params[0], chainId);
1969
+ response.result = result.hash;
1970
+ } else {
1971
+ const tx = await provider.sendTransaction(request.params[0]);
1972
+ response.result = tx.hash;
1973
+ }
2041
1974
 
2042
- return false;
2043
- } // prompt user with a connect request. the options will be used as previously set above.
2044
- // upon success, the walletRequestHandler will notify the dapp with the ConnectDetails.
2045
- // upon cancellation by user, the walletRequestHandler will throw an error
1975
+ break;
1976
+ }
2046
1977
 
1978
+ case 'eth_getTransactionCount':
1979
+ {
1980
+ const address = ethers.ethers.utils.getAddress(request.params[0]);
1981
+ const tag = request.params[1];
1982
+ const walletAddress = ethers.ethers.utils.getAddress(await signer.getAddress());
2047
1983
 
2048
- if (intent && intent.type === 'connect') {
2049
- // notify wallet is opened, without session details
2050
- _this.notifyOpen({
2051
- sessionId: _this._sessionId
2052
- });
1984
+ if (address === walletAddress) {
1985
+ const count = await signer.getTransactionCount(tag);
1986
+ response.result = ethers.ethers.BigNumber.from(count).toHexString();
1987
+ } else {
1988
+ const count = await provider.getTransactionCount(address, tag);
1989
+ response.result = ethers.ethers.BigNumber.from(count).toHexString();
1990
+ }
2053
1991
 
2054
- try {
2055
- const connectDetails = await _this.walletRequestHandler.promptConnect(intent.options);
1992
+ break;
1993
+ }
2056
1994
 
2057
- if (connectDetails.connected) {
2058
- _this.walletRequestHandler.notifyConnect(connectDetails);
1995
+ case 'eth_blockNumber':
1996
+ {
1997
+ response.result = await provider.getBlockNumber();
1998
+ break;
2059
1999
  }
2060
- } catch (err) {
2061
- utils.logger.warn('promptConnect not connected:', err);
2062
- } finally {
2063
- // auto-close by default, unless intent is to keep open
2064
- if (!intent.options || intent.options.keepWalletOpened !== true) {
2065
- _this.notifyClose();
2000
+
2001
+ case 'eth_getBlockByNumber':
2002
+ {
2003
+ response.result = await provider.getBlock(request.params[0]
2004
+ /* , jsonRpcRequest.params[1] */
2005
+ );
2006
+ break;
2066
2007
  }
2067
- }
2068
- } else {
2069
- // user is already connected, notify session details.
2070
- // TODO: in future, keep list if 'connected' dapps / sessions in the session
2071
- // controller, and only sync with allowed apps
2072
- _this.notifyOpen({
2073
- sessionId: _this._sessionId,
2074
- chainId: `${chainId}`,
2075
- session: await _this.walletRequestHandler.walletSession()
2076
- });
2077
- }
2078
- }
2079
2008
 
2080
- return true;
2081
- };
2009
+ case 'eth_getBlockByHash':
2010
+ {
2011
+ response.result = await provider.getBlock(request.params[0]
2012
+ /* , jsonRpcRequest.params[1] */
2013
+ );
2014
+ break;
2015
+ }
2082
2016
 
2083
- this.saveTransportSession = session => {
2084
- LocalStorage.getInstance().setItem(TRANSPORT_SESSION_LS_KEY, JSON.stringify(session));
2085
- };
2017
+ case 'eth_getTransactionByHash':
2018
+ {
2019
+ response.result = await provider.getTransaction(request.params[0]);
2020
+ break;
2021
+ }
2086
2022
 
2087
- this.getCachedTransportSession = async function () {
2088
- const session = await LocalStorage.getInstance().getItem(TRANSPORT_SESSION_LS_KEY);
2023
+ case 'eth_call':
2024
+ {
2025
+ const [transactionObject, blockTag] = request.params;
2026
+ response.result = await provider.call(transactionObject, blockTag);
2027
+ break;
2028
+ }
2089
2029
 
2090
- try {
2091
- return session ? JSON.parse(session) : null;
2092
- } catch (err) {
2093
- console.error(`unable to parse transport session: ${session}`);
2094
- return null;
2095
- }
2096
- };
2030
+ case 'eth_getCode':
2031
+ {
2032
+ const [contractAddress, blockTag] = request.params;
2033
+ response.result = await provider.getCode(contractAddress, blockTag);
2034
+ break;
2035
+ }
2097
2036
 
2098
- this.walletRequestHandler = walletRequestHandler;
2099
- this._init = exports.InitState.NIL;
2100
- this.walletRequestHandler.on('connect', connectDetails => {
2101
- if (!this.registered) return; // means user has logged in and wallet is connected to the app
2037
+ case 'eth_estimateGas':
2038
+ {
2039
+ const [transactionObject] = request.params;
2040
+ response.result = await provider.estimateGas(transactionObject);
2041
+ break;
2042
+ }
2102
2043
 
2103
- this.notifyConnect(connectDetails);
2104
- });
2105
- this.walletRequestHandler.on('disconnect', error => {
2106
- if (!this.registered) return; // means user has logged out the app / disconnected wallet from the app
2044
+ case 'eth_gasPrice':
2045
+ {
2046
+ const gasPrice = await provider.getGasPrice();
2047
+ response.result = gasPrice.toHexString();
2048
+ break;
2049
+ }
2107
2050
 
2108
- this.notifyDisconnect(error);
2109
- });
2110
- this.walletRequestHandler.on('accountsChanged', (accounts, origin) => {
2111
- if (!this.registered) return;
2112
- this.notifyAccountsChanged(accounts, origin);
2113
- });
2114
- this.walletRequestHandler.on('networks', networks => {
2115
- if (!this.registered) return;
2116
- this.notifyNetworks(networks);
2051
+ case 'wallet_switchEthereumChain':
2052
+ {
2053
+ const [switchParams] = request.params;
2117
2054
 
2118
- if (!networks || networks.length === 0) {
2119
- this.notifyChainChanged('0x0');
2120
- } else {
2121
- this.notifyChainChanged(ethers.ethers.utils.hexlify(networks.find(network => network.isDefaultChain).chainId));
2122
- }
2123
- });
2124
- this.walletRequestHandler.on('walletContext', walletContext => {
2125
- if (!this.registered || !walletContext) return;
2126
- this.notifyWalletContext(walletContext);
2127
- });
2128
- this.walletRequestHandler.on('close', error => {
2129
- if (!this.registered) return;
2130
- this.notifyClose(error);
2131
- });
2132
- }
2055
+ if (!switchParams.chainId || switchParams.chainId.length === 0) {
2056
+ throw new Error('invalid chainId');
2057
+ }
2133
2058
 
2134
- get registered() {
2135
- return this._registered;
2136
- }
2059
+ const _chainId = ethers.ethers.BigNumber.from(switchParams.chainId);
2137
2060
 
2138
- register() {
2139
- throw new Error('abstract method');
2140
- }
2061
+ const ok = await _this.setDefaultNetwork(_chainId.toString(), true);
2141
2062
 
2142
- unregister() {
2143
- throw new Error('abstract method');
2144
- }
2063
+ if (!ok) {
2064
+ throw new Error(`unable to set chainId ${_chainId}`);
2065
+ }
2145
2066
 
2146
- sendMessage(message) {
2147
- throw new Error('abstract method');
2148
- }
2067
+ response.result = null; // success
2149
2068
 
2150
- notifyOpen(openInfo) {
2151
- const {
2152
- chainId,
2153
- sessionId,
2154
- session,
2155
- error
2156
- } = openInfo;
2157
- this.sendMessage({
2158
- idx: -1,
2159
- type: exports.EventType.OPEN,
2160
- data: {
2161
- chainId,
2162
- sessionId,
2163
- session,
2164
- error
2165
- }
2166
- });
2167
- }
2069
+ break;
2070
+ }
2071
+ // smart wallet method
2168
2072
 
2169
- notifyClose(error) {
2170
- this.sendMessage({
2171
- idx: -1,
2172
- type: exports.EventType.CLOSE,
2173
- data: error ? {
2174
- error
2175
- } : null
2176
- });
2177
- }
2073
+ case 'sequence_getWalletContext':
2074
+ {
2075
+ response.result = await signer.getWalletContext();
2076
+ break;
2077
+ }
2078
+ // smart wallet method
2178
2079
 
2179
- notifyConnect(connectDetails) {
2180
- this.sendMessage({
2181
- idx: -1,
2182
- type: exports.EventType.CONNECT,
2183
- data: connectDetails
2184
- });
2185
- }
2080
+ case 'sequence_getWalletConfig':
2081
+ {
2082
+ const [_chainId2] = request.params;
2083
+ response.result = await signer.getWalletConfig(_chainId2);
2084
+ break;
2085
+ }
2086
+ // smart wallet method
2186
2087
 
2187
- notifyDisconnect(error) {
2188
- this.sendMessage({
2189
- idx: -1,
2190
- type: exports.EventType.DISCONNECT,
2191
- data: error ? {
2192
- error
2193
- } : null
2194
- });
2195
- }
2088
+ case 'sequence_getConfigTracker':
2089
+ {
2090
+ var _this$prompter;
2196
2091
 
2197
- notifyAccountsChanged(accounts, origin) {
2198
- this.sendMessage({
2199
- idx: -1,
2200
- type: exports.EventType.ACCOUNTS_CHANGED,
2201
- data: accounts,
2202
- origin: origin
2203
- });
2204
- }
2092
+ response.result = await ((_this$prompter = _this.prompter) == null ? void 0 : _this$prompter.getShareableConfigTrackerData());
2093
+ break;
2094
+ }
2095
+ // smart wallet method
2205
2096
 
2206
- notifyChainChanged(chainIdHex) {
2207
- this.sendMessage({
2208
- idx: -1,
2209
- type: exports.EventType.CHAIN_CHANGED,
2210
- data: chainIdHex
2211
- });
2212
- }
2097
+ case 'sequence_getWalletState':
2098
+ {
2099
+ const [_chainId3] = request.params;
2100
+ response.result = await signer.getWalletState(_chainId3);
2101
+ break;
2102
+ }
2103
+ // smart wallet method
2213
2104
 
2214
- notifyNetworks(networks) {
2215
- this.sendMessage({
2216
- idx: -1,
2217
- type: exports.EventType.NETWORKS,
2218
- data: networks
2219
- });
2220
- }
2105
+ case 'sequence_getNetworks':
2106
+ {
2107
+ // NOTE: must ensure that the response result below returns clean serialized data, which is to omit
2108
+ // the provider and relayer objects and only return the urls so can be reinstantiated on dapp side.
2109
+ // This is handled by this.getNetworks() but noted here for future readers.
2110
+ response.result = await _this.getNetworks(true);
2111
+ break;
2112
+ }
2113
+ // smart wallet method
2221
2114
 
2222
- notifyWalletContext(walletContext) {
2223
- this.sendMessage({
2224
- idx: -1,
2225
- type: exports.EventType.WALLET_CONTEXT,
2226
- data: walletContext
2227
- });
2228
- }
2115
+ case 'sequence_updateConfig':
2116
+ {
2117
+ throw new Error('sequence_updateConfig method is not allowed from a dapp'); // NOTE: method is disabled as we don't need a dapp to request to update a config.
2118
+ // However, if we ever want this, we can enable it but must also use the prompter
2119
+ // for confirmation.
2120
+ //
2121
+ // const [newConfig] = request.params
2122
+ // response.result = await signer.updateConfig(newConfig)
2229
2123
 
2230
- isValidInitAck(message) {
2231
- if (this._init === exports.InitState.OK) {
2232
- // we're already in init state, we shouldn't handle this message
2233
- utils.logger.warn("isValidInitAck, already in init'd state, so inquiry is invalid.");
2234
- return false;
2235
- }
2124
+ break;
2125
+ }
2126
+ // smart wallet method
2236
2127
 
2237
- if (message.type !== exports.EventType.INIT) {
2238
- utils.logger.warn('isValidInitAck, invalid message type, expecting init');
2239
- return false;
2240
- }
2128
+ case 'sequence_publishConfig':
2129
+ {
2130
+ throw new Error('sequence_publishConfig method is not allowed from a dapp');
2131
+ break;
2132
+ }
2133
+ // relayer method
2241
2134
 
2242
- const {
2243
- sessionId,
2244
- nonce
2245
- } = message.data;
2135
+ case 'sequence_estimateGasLimits':
2136
+ {
2137
+ // TODO
2138
+ break;
2139
+ }
2140
+ // relayer method
2246
2141
 
2247
- if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
2248
- utils.logger.error('invalid init ack');
2249
- return false;
2250
- }
2142
+ case 'sequence_gasRefundOptions':
2143
+ {
2144
+ // TODO
2145
+ break;
2146
+ }
2147
+ // relayer method
2251
2148
 
2252
- if (sessionId !== this._sessionId || nonce !== this._initNonce) {
2253
- utils.logger.error('invalid init ack match');
2254
- return false;
2255
- } // all checks pass, its true
2149
+ case 'sequence_getNonce':
2150
+ {
2151
+ // TODO
2152
+ break;
2153
+ }
2154
+ // relayer method
2256
2155
 
2156
+ case 'sequence_relay':
2157
+ {
2158
+ // TODO
2159
+ break;
2160
+ }
2161
+ // set default network of wallet
2257
2162
 
2258
- return true;
2259
- }
2163
+ case 'sequence_setDefaultNetwork':
2164
+ {
2165
+ const [defaultNetworkId] = request.params;
2260
2166
 
2261
- init() {
2262
- return new Promise((resolve, reject) => {
2263
- // avoid re-init`ing, or if there is a transport which doesn't require
2264
- // it, then it may set this._init to OK in its constructor.
2265
- if (this._init === exports.InitState.OK) {
2266
- resolve();
2267
- return;
2268
- }
2167
+ if (!defaultNetworkId) {
2168
+ throw new Error('invalid request, method argument defaultNetworkId cannot be empty');
2169
+ }
2269
2170
 
2270
- if (this._init !== exports.InitState.NIL || this._initCallback) {
2271
- reject('transport init is in progress');
2272
- return;
2273
- } // start init timeout, if we don't receive confirmation
2274
- // from provider within this amount of time, then we timeout
2171
+ const ok = await _this.setDefaultNetwork(defaultNetworkId);
2275
2172
 
2173
+ if (!ok) {
2174
+ throw new Error(`unable to set default network ${defaultNetworkId}`);
2175
+ }
2276
2176
 
2277
- const initTimeout = setTimeout(() => {
2278
- utils.logger.warn('transport init timed out');
2177
+ response.result = await _this.getNetworks(true);
2178
+ break;
2179
+ }
2279
2180
 
2280
- if (this._initCallback) {
2281
- this._initCallback('transport init timed out');
2181
+ default:
2182
+ {
2183
+ // NOTE: provider here will be chain-bound if chainId is provided
2184
+ const providerResponse = await provider.send(request.method, request.params);
2185
+ response.result = providerResponse;
2186
+ }
2282
2187
  }
2283
- }, PROVIDER_OPEN_TIMEOUT / 2); // setup callback as we receive the init message async in the handleMessage function
2188
+ } catch (err) {
2189
+ utils.logger.error(err); // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#rpc-errors
2284
2190
 
2285
- this._initCallback = error => {
2286
- this._initCallback = undefined; // reset
2191
+ response.result = null;
2192
+ response.error = _extends({}, new Error(err), {
2193
+ code: 4001
2194
+ });
2195
+ }
2287
2196
 
2288
- clearTimeout(initTimeout);
2197
+ callback(undefined, response);
2198
+ };
2289
2199
 
2290
- if (error) {
2291
- reject(error);
2292
- } else {
2293
- this._init = exports.InitState.OK;
2294
- resolve();
2295
- }
2296
- }; // send init request with random nonce to the provider, where we expect
2297
- // for the provider to echo it back to us as complete handshake
2200
+ this.isSignedIn = async function () {
2201
+ await _this.signerReady();
2202
+ return !!_this.signer;
2203
+ };
2298
2204
 
2205
+ this.getSigner = async function () {
2206
+ await _this.signerReady();
2299
2207
 
2300
- this._initNonce = `${performance.now()}`;
2301
- this.sendMessage({
2302
- idx: -1,
2303
- type: exports.EventType.INIT,
2304
- data: {
2305
- nonce: this._initNonce
2306
- }
2307
- });
2308
- this._init = exports.InitState.SENT_NONCE; // NOTE: the promise will resolve in the _initCallback method
2309
- // which will be called from either handleMessage or the initTimeout
2310
- });
2311
- }
2208
+ if (_this.signer === undefined) {
2209
+ throw new Error('signerReady failed resolve');
2210
+ }
2312
2211
 
2313
- }
2212
+ return _this.signer;
2213
+ };
2314
2214
 
2315
- class ProxyMessageChannel {
2316
- constructor() {
2317
- this.app = void 0;
2318
- this.wallet = void 0;
2319
- const port1 = new ProxyMessageChannelPort();
2320
- const port2 = new ProxyMessageChannelPort();
2321
- port1.conn = port2;
2322
- port2.conn = port1;
2323
- this.app = port1;
2324
- this.wallet = port2;
2215
+ this.signer = _signer;
2216
+ this.prompter = prompter;
2217
+ this.mainnetNetworks = mainnetNetworks;
2218
+ this.testnetNetworks = testnetNetworks;
2325
2219
  }
2326
2220
 
2327
- }
2328
- class ProxyMessageChannelPort {
2329
- constructor() {
2330
- this.conn = void 0;
2331
- this.events = new eventemitter2.EventEmitter2();
2221
+ async signIn(signer, options = {}) {
2222
+ this.setSigner(signer);
2223
+ const {
2224
+ connect,
2225
+ mainnetNetworks,
2226
+ testnetNetworks,
2227
+ defaultNetworkId
2228
+ } = options;
2332
2229
 
2333
- this.handleMessage = message => {
2334
- throw new Error('ProxyMessageChannelPort is not registered');
2335
- };
2230
+ if (mainnetNetworks && mainnetNetworks.length > 0) {
2231
+ this.mainnetNetworks = mainnetNetworks;
2232
+ }
2336
2233
 
2337
- this.sendMessage = message => {
2338
- this.conn.handleMessage(message); // trigger events
2234
+ if (testnetNetworks && testnetNetworks.length > 0) {
2235
+ this.testnetNetworks = testnetNetworks;
2236
+ }
2339
2237
 
2340
- if (message.type === 'open') {
2341
- this.events.emit('open', message);
2238
+ if ((!this.mainnetNetworks || this.mainnetNetworks.length === 0) && (!this.testnetNetworks || this.testnetNetworks.length === 0)) {
2239
+ throw new Error('signIn failed as network configuration is empty');
2240
+ }
2241
+
2242
+ const networkId = defaultNetworkId || this._defaultNetworkId;
2243
+
2244
+ if (networkId) {
2245
+ if (!(await this.setDefaultNetwork(networkId, false))) {
2246
+ throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${networkId}`);
2342
2247
  }
2248
+ } // Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
2249
+ // necessary auth request, and then notify the dapp of the 'connect' details.
2250
+ //
2251
+ // NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
2252
+ // we don't trigger the promptConnect flow, as we consider the user just authenticated
2253
+ // for this dapp, so its safe to authorize in the connect() method without the prompt.
2254
+ //
2255
+ // NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
2343
2256
 
2344
- if (message.type === 'close') {
2345
- this.events.emit('close', message);
2346
- }
2347
2257
 
2348
- if (message.type === 'connect') {
2349
- this.events.emit('connect', message);
2350
- }
2258
+ if (connect) {
2259
+ const connectOptions = this._connectOptions;
2260
+ const connectDetails = await this.connect(connectOptions);
2261
+ this.notifyConnect(connectDetails);
2351
2262
 
2352
- if (message.type === 'disconnect') {
2353
- this.events.emit('disconnect', message);
2263
+ if (!connectOptions || connectOptions.keepWalletOpened !== true) {
2264
+ this.notifyClose();
2354
2265
  }
2355
- };
2266
+ }
2356
2267
  }
2357
2268
 
2358
- on(event, fn) {
2359
- this.events.on(event, fn);
2269
+ signOut() {
2270
+ // signed out state
2271
+ this.setSigner(null);
2360
2272
  }
2361
2273
 
2362
- once(event, fn) {
2363
- this.events.once(event, fn);
2274
+ accountReset() {
2275
+ // resetting signer puts the wallet in an uninitialized state, which requires the app to
2276
+ // re-initiatize and set the signer either as "null" (ie. no signer) or "Signer" (ie. signed in).
2277
+ this.signer = undefined;
2364
2278
  }
2365
2279
 
2366
- }
2367
-
2368
- class ProxyMessageProvider extends BaseProviderTransport {
2369
- constructor(port) {
2370
- super();
2371
- this.port = void 0;
2280
+ signerReady(timeout = SIGNER_READY_TIMEOUT) {
2281
+ return new Promise((resolve, reject) => {
2282
+ if (this.signer !== undefined) {
2283
+ resolve();
2284
+ } else {
2285
+ setTimeout(() => {
2286
+ if (this.signer === undefined) {
2287
+ this.signerReadyCallbacks = [];
2288
+ reject(`signerReady timed out`);
2289
+ }
2290
+ }, timeout);
2291
+ this.signerReadyCallbacks.push(resolve);
2292
+ }
2293
+ });
2294
+ }
2372
2295
 
2373
- this.register = () => {
2374
- this.port.handleMessage = message => {
2375
- this.handleMessage(message);
2296
+ async connect(options) {
2297
+ if (!this.signer) {
2298
+ return {
2299
+ connected: false,
2300
+ chainId: '0x0',
2301
+ error: 'unable to connect without signed in account'
2376
2302
  };
2303
+ }
2377
2304
 
2378
- this.on('open', (...args) => {
2379
- this.port.events.emit('open', ...args);
2380
- });
2381
- this.on('close', (...args) => {
2382
- this.port.events.emit('close', ...args);
2383
- });
2384
- this.on('connect', (...args) => {
2385
- this.port.events.emit('connect', ...args);
2386
- });
2387
- this.on('disconnect', (...args) => {
2388
- this.port.events.emit('disconnect', ...args);
2389
- });
2390
- this._registered = true;
2305
+ const connectDetails = {
2306
+ connected: true,
2307
+ chainId: ethers.ethers.utils.hexlify(await this.getChainId())
2391
2308
  };
2392
2309
 
2393
- this.unregister = () => {
2394
- this._registered = false;
2395
- this.closeWallet();
2396
- this.events.removeAllListeners(); // @ts-ignore
2397
-
2398
- this.port.handleMessage = undefined;
2399
- };
2310
+ if (options && options.authorize) {
2311
+ // Perform ethauth eip712 request and construct the ConnectDetails response
2312
+ // including the auth proof
2313
+ const authOptions = {
2314
+ app: options.app,
2315
+ origin: options.origin,
2316
+ expiry: options.expiry
2317
+ }; // if (typeof(options.authorize) === 'object') {
2318
+ // authOptions = { ...authOptions, ...options.authorize }
2319
+ // }
2400
2320
 
2401
- this.openWallet = (path, intent, networkId) => {
2402
- if (this.state === exports.OpenState.CLOSED) {
2403
- this.state = exports.OpenState.OPENING;
2404
- const sessionId = `${performance.now()}`;
2405
- this._sessionId = sessionId;
2406
- this.sendMessage({
2407
- idx: -1,
2408
- type: exports.EventType.OPEN,
2409
- data: {
2410
- path,
2411
- intent,
2412
- networkId,
2413
- sessionId
2414
- }
2415
- });
2321
+ try {
2322
+ connectDetails.proof = await auth.signAuthorization(this.signer, authOptions);
2323
+ } catch (err) {
2324
+ utils.logger.warn(`connect, signAuthorization failed for options: ${JSON.stringify(options)}, due to: ${err.message}`);
2325
+ return {
2326
+ connected: false,
2327
+ chainId: '0x0',
2328
+ error: `signAuthorization failed: ${err.message}`
2329
+ };
2416
2330
  }
2417
- };
2331
+ } // Build session response for connect details
2418
2332
 
2419
- this.state = exports.OpenState.CLOSED;
2420
- this.port = port;
2421
2333
 
2422
- if (!port) {
2423
- throw new Error('port argument cannot be empty');
2424
- } // disable init handshake for proxy-transport, we set it to OK, to
2425
- // consider it in completed state.
2334
+ connectDetails.session = await this.walletSession();
2335
+ return connectDetails;
2336
+ }
2426
2337
 
2338
+ // sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler
2339
+ // (aka, the signer in this instance) and then responds with a wrapped response of
2340
+ // ProviderMessageResponse to be sent over the transport
2341
+ sendMessageRequest(message) {
2342
+ return new Promise(resolve => {
2343
+ this.sendAsync(message.data, (error, response) => {
2344
+ // TODO: if response includes data.error, why do we need a separate error argument here?
2345
+ const responseMessage = _extends({}, message, {
2346
+ data: response
2347
+ }); // NOTE: we always resolve here, are the sendAsync call will wrap any exceptions
2348
+ // in the error field of the response to ensure we send back to the user
2427
2349
 
2428
- this._init = exports.InitState.OK;
2429
- }
2430
2350
 
2431
- closeWallet() {
2432
- this.sendMessage({
2433
- idx: -1,
2434
- type: exports.EventType.CLOSE,
2435
- data: null
2351
+ resolve(responseMessage);
2352
+ }, message.chainId);
2436
2353
  });
2437
- this.close();
2438
- }
2354
+ } // sendAsync implements the JsonRpcHandler interface for sending JsonRpcRequests to the wallet
2439
2355
 
2440
- sendMessage(message) {
2441
- if (!message.idx) {
2442
- throw new Error('message idx is empty');
2443
- }
2444
2356
 
2445
- this.port.sendMessage(message);
2357
+ on(event, fn) {
2358
+ this.events.on(event, fn);
2446
2359
  }
2447
2360
 
2448
- }
2361
+ once(event, fn) {
2362
+ this.events.once(event, fn);
2363
+ }
2449
2364
 
2450
- class ProxyMessageHandler extends BaseWalletTransport {
2451
- constructor(walletRequestHandler, port) {
2452
- super(walletRequestHandler);
2453
- this.port = void 0;
2454
- this.port = port;
2455
- this._init = exports.InitState.OK;
2365
+ async getAddress() {
2366
+ if (!this.signer) {
2367
+ return '';
2368
+ } else {
2369
+ return this.signer.getAddress();
2370
+ }
2456
2371
  }
2457
2372
 
2458
- register() {
2459
- this.port.handleMessage = message => {
2460
- this.handleMessage(message);
2461
- };
2373
+ async getChainId() {
2374
+ if (!this.signer) {
2375
+ return 0;
2376
+ } else {
2377
+ if (this._chainId) return this._chainId; // memoized
2462
2378
 
2463
- this._registered = true;
2464
- } // note: we can't decide whether to restore the session within register(), because session info is
2465
- // received asyncronously via EventType.OPEN after register() is executed.
2466
- // And in the case of a redirect/reload, EventType.OPEN is not sent at all,
2467
- // because the wallet is already open.
2468
- //
2469
- // call this method from wallet redirect hander when a session restore is needed
2379
+ this._chainId = await this.signer.getChainId();
2380
+ return this._chainId;
2381
+ }
2382
+ }
2470
2383
 
2384
+ get openIntent() {
2385
+ return this._openIntent;
2386
+ }
2471
2387
 
2472
- async restoreSession() {
2473
- const cachedSession = await this.getCachedTransportSession();
2388
+ setOpenIntent(intent) {
2389
+ this._openIntent = intent;
2390
+ }
2474
2391
 
2475
- if (cachedSession) {
2476
- this.open(cachedSession);
2477
- }
2392
+ get connectOptions() {
2393
+ return this._connectOptions;
2478
2394
  }
2479
2395
 
2480
- unregister() {
2481
- // @ts-ignore
2482
- this.port.handleMessage = undefined;
2483
- this._registered = false;
2396
+ setConnectOptions(options) {
2397
+ this._connectOptions = options;
2484
2398
  }
2485
2399
 
2486
- sendMessage(message) {
2487
- this.port.sendMessage(message);
2400
+ get defaultNetworkId() {
2401
+ return this._defaultNetworkId;
2488
2402
  }
2489
2403
 
2490
- }
2404
+ async setDefaultNetwork(chainId, notifyNetworks = true) {
2405
+ if (!chainId) return undefined;
2406
+ this._defaultNetworkId = chainId;
2407
+ this._chainId = undefined;
2491
2408
 
2492
- class MuxMessageProvider {
2493
- constructor(...messageProviders) {
2494
- var _this = this;
2409
+ if (this.signer) {
2410
+ // Find network id among available networks
2411
+ const networks = await this.signer.getNetworks();
2412
+ const maybeNameLowerCase = chainId.toString().toLowerCase();
2413
+ const cidString = chainId.toString();
2414
+ const network = networks.find(n => n.chainId.toString() === cidString || n.name.toLowerCase() === maybeNameLowerCase); // Can't set network if it doesn't exist in available networks
2495
2415
 
2496
- this.messageProviders = void 0;
2497
- this.provider = void 0;
2416
+ if (!network) return undefined;
2417
+ await this.signer.setDefaultNetwork(network.chainId);
2418
+ const defaultChainId = network.chainId;
2498
2419
 
2499
- this.register = () => {
2500
- if (this.messageProviders.length === 1) {
2501
- this.provider = this.messageProviders[0];
2502
- this.provider.register();
2503
- return;
2504
- } // REVIEW/NOTE: ........ this method does not work for the chrome-extension. The issue becomes
2505
- // when the browser quits or restarts, the "open" event is never triggered. Perhaps the code here is fine,
2506
- // or maybe its not. What should happen is when a dapp makes a request, it will call openWallet
2507
- // below, in which case one of the events will register. So perhaps this is fine.
2420
+ if (defaultChainId && notifyNetworks) {
2421
+ await this.notifyNetworks();
2422
+ }
2423
+
2424
+ return defaultChainId;
2425
+ } else {
2426
+ return undefined;
2427
+ }
2428
+ }
2429
+
2430
+ async getNetworks(jsonRpcResponse) {
2431
+ if (!this.signer) {
2432
+ utils.logger.warn('signer not set: getNetworks is returning an empty list');
2433
+ return [];
2434
+ }
2508
2435
 
2436
+ const networks = await this.signer.getNetworks();
2509
2437
 
2510
- this.messageProviders.forEach(m => {
2511
- m.register();
2512
- m.once('open', () => {
2513
- // the first one to open is the winner, and others will be unregistered
2514
- if (!this.provider) {
2515
- this.provider = m; // unregister other providers
2438
+ if (jsonRpcResponse) {
2439
+ // omit provider and relayer objects as they are not serializable
2440
+ return networks.map(n => {
2441
+ const network = _extends({}, n);
2516
2442
 
2517
- this.messageProviders.forEach(m => {
2518
- if (this.provider !== m) {
2519
- m.unregister();
2520
- }
2521
- });
2522
- }
2523
- });
2443
+ network.provider = undefined;
2444
+ network.relayer = undefined;
2445
+ return network;
2524
2446
  });
2525
- };
2526
-
2527
- this.unregister = () => {
2528
- this.messageProviders.forEach(m => m.unregister());
2529
- this.provider = undefined;
2530
- };
2531
-
2532
- this.openWallet = (path, intent, networkId) => {
2533
- if (this.provider) {
2534
- this.provider.openWallet(path, intent, networkId);
2535
- return;
2536
- }
2447
+ } else {
2448
+ return networks;
2449
+ }
2450
+ }
2537
2451
 
2538
- this.messageProviders.forEach(m => m.openWallet(path, intent, networkId));
2452
+ async walletSession() {
2453
+ return !this.signer ? undefined : {
2454
+ walletContext: await this.signer.getWalletContext(),
2455
+ accountAddress: await this.signer.getAddress(),
2456
+ networks: await this.getNetworks(true)
2539
2457
  };
2458
+ }
2540
2459
 
2541
- this.sendAsync = async function (request, callback, chainId) {
2542
- if (_this.provider) {
2543
- _this.provider.sendAsync(request, callback, chainId);
2460
+ notifyConnect(connectDetails, origin) {
2461
+ var _connectDetails$sessi;
2544
2462
 
2545
- return;
2546
- }
2463
+ this.events.emit('connect', connectDetails);
2547
2464
 
2548
- throw new Error('impossible state, wallet must be opened first');
2549
- };
2465
+ if ((_connectDetails$sessi = connectDetails.session) != null && _connectDetails$sessi.accountAddress) {
2466
+ var _connectDetails$sessi2;
2550
2467
 
2551
- this.sendMessageRequest = async function (message) {
2552
- if (_this.provider) {
2553
- return _this.provider.sendMessageRequest(message);
2554
- }
2468
+ this.events.emit('accountsChanged', [(_connectDetails$sessi2 = connectDetails.session) == null ? void 0 : _connectDetails$sessi2.accountAddress], origin);
2469
+ }
2470
+ }
2555
2471
 
2556
- throw new Error('impossible state, wallet must be opened first');
2557
- };
2472
+ notifyDisconnect(origin) {
2473
+ this.events.emit('accountsChanged', [], origin);
2474
+ this.events.emit('disconnect');
2475
+ }
2558
2476
 
2559
- this.waitUntilOpened = async function () {
2560
- if (_this.provider) {
2561
- return _this.provider.waitUntilOpened();
2562
- }
2477
+ async notifyNetworks(networks) {
2478
+ const n = networks || (await this.getNetworks(true));
2479
+ this.events.emit('networks', n);
2563
2480
 
2564
- return Promise.race(_this.messageProviders.map(p => p.waitUntilOpened()));
2565
- };
2481
+ if (n.length > 0) {
2482
+ const defaultNetwork = n.find(network => network.isDefaultChain);
2566
2483
 
2567
- this.waitUntilConnected = async function () {
2568
- if (_this.provider) {
2569
- return _this.provider.waitUntilConnected();
2484
+ if (defaultNetwork) {
2485
+ this.events.emit('chainChanged', ethers.ethers.utils.hexlify(defaultNetwork.chainId));
2570
2486
  }
2487
+ } else {
2488
+ this.events.emit('chainChanged', '0x0');
2489
+ }
2490
+ }
2571
2491
 
2572
- throw new Error('impossible state, wallet must be opened first');
2573
- };
2492
+ async notifyWalletContext() {
2493
+ if (!this.signer) {
2494
+ utils.logger.warn('signer not set: skipping to notify wallet context');
2495
+ return;
2496
+ }
2574
2497
 
2575
- this.messageProviders = messageProviders;
2576
- this.provider = undefined;
2498
+ const walletContext = await this.signer.getWalletContext();
2499
+ this.events.emit('walletContext', walletContext);
2577
2500
  }
2578
2501
 
2579
- add(...messageProviders) {
2580
- this.messageProviders.push(...messageProviders);
2502
+ notifyClose(error) {
2503
+ this.events.emit('close', error);
2581
2504
  }
2582
2505
 
2583
- closeWallet() {
2584
- if (this.provider) {
2585
- this.provider.closeWallet();
2506
+ setSigner(signer) {
2507
+ this.signer = signer;
2508
+
2509
+ if (signer !== undefined) {
2510
+ for (let i = 0; i < this.signerReadyCallbacks.length; i++) {
2511
+ this.signerReadyCallbacks[i]();
2512
+ }
2513
+
2514
+ this.signerReadyCallbacks = [];
2586
2515
  }
2587
2516
  }
2588
2517
 
2589
- isOpened() {
2590
- if (this.provider) {
2591
- return this.provider.isOpened();
2518
+ async handleConfirmWalletDeployPrompt(prompter, signer, chainId) {
2519
+ // check if wallet is deployed and up to date, if not, prompt user to deploy
2520
+ // if no chainId is provided, we'll assume the wallet is auth chain wallet and is up to date
2521
+ if (!chainId) {
2522
+ return true;
2592
2523
  }
2593
2524
 
2594
- return false;
2595
- }
2525
+ if (!wallet.isUpgradableSigner(signer)) {
2526
+ return true;
2527
+ }
2596
2528
 
2597
- isConnected() {
2598
- if (this.provider) {
2599
- return this.provider.isConnected();
2529
+ if (await signer.isSettled(chainId)) {
2530
+ return true;
2600
2531
  }
2601
2532
 
2602
- return false;
2603
- }
2533
+ const promptResult = await prompter.promptConfirmWalletDeploy(chainId, this.connectOptions); // if client returned true, check again to make sure wallet is deployed and up to date
2604
2534
 
2605
- on(event, fn) {
2606
- if (this.provider) {
2607
- this.provider.on(event, fn);
2608
- return;
2535
+ if (promptResult) {
2536
+ if (!(await signer.isSettled(chainId))) {
2537
+ utils.logger.error('WalletRequestHandler: result for promptConfirmWalletDeploy is not correct');
2538
+ return false;
2539
+ } else {
2540
+ return true;
2541
+ }
2609
2542
  }
2610
2543
 
2611
- this.messageProviders.forEach(m => {
2612
- m.on(event, fn);
2613
- });
2544
+ return false;
2614
2545
  }
2615
2546
 
2616
- once(event, fn) {
2617
- if (this.provider) {
2618
- this.provider.once(event, fn);
2619
- return;
2620
- }
2547
+ }
2548
+ const permittedJsonRpcMethods = ['net_version', 'eth_chainId', 'eth_getBalance', 'eth_getTransactionCount', 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getBlockByHash', 'eth_getTransactionByHash', 'eth_getCode', 'eth_estimateGas', 'eth_gasPrice', 'sequence_getWalletContext', 'sequence_getNetworks', 'sequence_setDefaultNetwork'];
2621
2549
 
2622
- this.messageProviders.forEach(m => {
2623
- m.once(event, fn);
2550
+ const CHANNEL_ID = 'sequence-extension-message-handler';
2551
+ class ExtensionMessageHandler extends BaseWalletTransport {
2552
+ constructor(walletRequestHandler, runtime) {
2553
+ super(walletRequestHandler);
2554
+ this.runtime = runtime;
2555
+ this.port = void 0;
2556
+ this._init = exports.InitState.OK;
2557
+ }
2558
+
2559
+ register() {
2560
+ this._registered = true;
2561
+ this.port = this.runtime.connect({
2562
+ name: CHANNEL_ID
2624
2563
  });
2625
2564
  }
2626
2565
 
2627
- emit(event, ...args) {
2628
- if (this.provider) {
2629
- return this.provider.emit(event, ...args);
2630
- }
2566
+ sendMessage(message) {
2567
+ utils.logger.info('[ExtensionMessageHandler send]', message);
2568
+ this.port.postMessage(message);
2569
+ }
2631
2570
 
2632
- for (let i = 0; i < this.messageProviders.length; i++) {
2633
- this.messageProviders[i].emit(event, ...args);
2634
- }
2571
+ }
2635
2572
 
2636
- return true;
2573
+ class ExtensionMessageProvider extends BaseProviderTransport {
2574
+ constructor(runtime) {
2575
+ super();
2576
+
2577
+ this.register = () => {
2578
+ this._registered = true;
2579
+ };
2580
+
2581
+ runtime.onConnect.addListener(port => {
2582
+ if (port.name === CHANNEL_ID) {
2583
+ this._init = exports.InitState.OK;
2584
+ port.onMessage.addListener(message => {
2585
+ this.handleMessage(message);
2586
+ });
2587
+ }
2588
+ });
2637
2589
  }
2638
2590
 
2639
- sendMessage(message) {
2640
- if (!message.idx || message.idx <= 0) {
2641
- throw new Error('message idx is empty');
2642
- }
2591
+ sendMessage(message) {//noop
2592
+ }
2643
2593
 
2644
- if (this.provider) {
2645
- this.provider.sendMessage(message);
2646
- } else {
2647
- throw new Error('impossible state, wallet must be opened first');
2648
- }
2594
+ unregister() {//noop
2649
2595
  }
2650
2596
 
2651
- handleMessage(message) {
2652
- if (this.provider) {
2653
- this.provider.handleMessage(message);
2654
- return;
2655
- }
2597
+ openWallet(path, intent, networkId) {//noop
2598
+ }
2656
2599
 
2657
- throw new Error('impossible state, wallet must be opened first');
2600
+ closeWallet() {//noop
2658
2601
  }
2659
2602
 
2660
2603
  }
2661
2604
 
2662
- let registeredWindowMessageProvider;
2663
- class WindowMessageProvider extends BaseProviderTransport {
2664
- constructor(walletAppURL) {
2605
+ // to be used on injected window.ethereum EIP1193 proxy
2606
+ class BaseInjectedTransport extends eventemitter2.EventEmitter2 {
2607
+ constructor(stream) {
2608
+ var _this;
2609
+
2665
2610
  super();
2666
- this.walletURL = void 0;
2667
- this.walletWindow = void 0;
2611
+ _this = this;
2612
+ this.stream = stream;
2613
+ this.responseCallbacks = new Map();
2614
+ this._messageIdx = 0;
2668
2615
 
2669
- this.register = () => {
2670
- if (registeredWindowMessageProvider) {
2671
- // overriding the registered message provider
2672
- registeredWindowMessageProvider.unregister();
2673
- registeredWindowMessageProvider = this;
2674
- } // listen for incoming messages from wallet
2616
+ this.nextMessageIdx = () => ++this._messageIdx;
2617
+
2618
+ this.handleMessage = message => {
2619
+ if (!message.type || !message.data) {
2620
+ return;
2621
+ }
2622
+
2623
+ utils.logger.info('[received message]', message);
2624
+ const requestIdx = message.idx;
2625
+ const responseCallback = this.responseCallbacks.get(requestIdx);
2626
+
2627
+ if (requestIdx) {
2628
+ this.responseCallbacks.delete(requestIdx);
2629
+ }
2630
+
2631
+ switch (message.type) {
2632
+ case exports.EventType.MESSAGE:
2633
+ if (responseCallback) {
2634
+ this.emit(exports.EventType.MESSAGE, message);
2635
+ responseCallback(message.data.error, message);
2636
+ } else {
2637
+ // NOTE: this would occur if 'idx' isn't set, which should never happen
2638
+ // or when we register two handler, or duplicate messages with the same idx are sent,
2639
+ // all of which should be prevented prior to getting to this point
2640
+ throw new Error('impossible state');
2641
+ }
2675
2642
 
2643
+ break;
2676
2644
 
2677
- window.addEventListener('message', this.onWindowEvent);
2678
- registeredWindowMessageProvider = this; // open heartbeat
2645
+ case exports.EventType.DISCONNECT:
2646
+ case exports.EventType.ACCOUNTS_CHANGED:
2647
+ case exports.EventType.CHAIN_CHANGED:
2648
+ this.emit(message.type, message.data);
2649
+ break;
2679
2650
 
2680
- this.on('open', () => {
2681
- // Heartbeat to track if window closed
2682
- const popup = this.walletWindow;
2683
- const interval = setInterval(() => {
2684
- if (popup && popup.closed) {
2685
- clearInterval(interval);
2686
- this.close();
2687
- }
2688
- }, 500);
2689
- }); // close clean up
2651
+ default:
2652
+ console.error('unknown message type', message);
2653
+ break;
2654
+ }
2655
+ };
2690
2656
 
2691
- this.on('close', () => {
2692
- if (this.walletWindow) {
2693
- this.walletWindow.close();
2694
- this.walletWindow = null;
2657
+ this.sendMessageRequest = async function (message) {
2658
+ return new Promise((resolve, reject) => {
2659
+ if (!message.idx || message.idx <= 0) {
2660
+ reject(new Error('message idx not set'));
2695
2661
  }
2696
- });
2697
- this._registered = true;
2698
- };
2699
2662
 
2700
- this.unregister = () => {
2701
- this._registered = false;
2702
- this.closeWallet(); // disable message listener
2663
+ const responseCallback = (error, response) => {
2664
+ if (error) {
2665
+ reject(error);
2666
+ } else if (response) {
2667
+ resolve(response);
2668
+ } else {
2669
+ throw new Error('no valid response to return');
2670
+ }
2671
+ };
2703
2672
 
2704
- if (registeredWindowMessageProvider === this) {
2705
- registeredWindowMessageProvider = undefined;
2706
- }
2673
+ const {
2674
+ idx
2675
+ } = message;
2707
2676
 
2708
- window.removeEventListener('message', this.onWindowEvent); // clear event listeners
2677
+ if (!_this.responseCallbacks.get(idx)) {
2678
+ _this.responseCallbacks.set(idx, responseCallback);
2679
+ } else {
2680
+ reject(new Error('duplicate message idx, should never happen'));
2681
+ }
2709
2682
 
2710
- this.events.removeAllListeners();
2683
+ _this.sendMessage(message);
2684
+ });
2711
2685
  };
2712
2686
 
2713
- this.openWallet = (path, intent, networkId) => {
2714
- if (this.walletWindow && this.isOpened()) {
2715
- // TODO: update the location of window to path
2716
- this.walletWindow.focus();
2717
- return;
2718
- } // Instantiate new walletURL for this call
2687
+ this.stream.on('data', this.handleMessage);
2688
+ }
2719
2689
 
2690
+ sendMessage(message) {
2691
+ if (!this.stream.writable) {
2692
+ console.error('window post message stream is not writable');
2693
+ }
2720
2694
 
2721
- const walletURL = new URL(this.walletURL.href);
2722
- const windowSessionParams = new WindowSessionParams();
2695
+ this.stream.write(message);
2696
+ }
2723
2697
 
2724
- if (path && path !== '') {
2725
- walletURL.pathname = path.toLowerCase();
2726
- } // Set session, intent and network id on walletURL
2698
+ }
2727
2699
 
2700
+ class Web3Provider extends providers.Web3Provider {
2701
+ static isSequenceProvider(cand) {
2702
+ return isSequenceProvider(cand);
2703
+ }
2728
2704
 
2729
- this._init = exports.InitState.NIL;
2730
- this._sessionId = `${performance.now()}`;
2731
- windowSessionParams.set('sid', this._sessionId);
2705
+ constructor(provider, defaultChainId) {
2706
+ const sender = new network.JsonRpcSender(provider, network.maybeChainId(defaultChainId));
2707
+ provider = sender;
2708
+ super(provider, 'any');
2709
+ this._sender = void 0;
2710
+ this._isSequenceProvider = void 0;
2711
+ this._defaultChainId = void 0;
2712
+ this._sender = sender;
2713
+ this._isSequenceProvider = true;
2714
+ this._defaultChainId = network.maybeChainId(defaultChainId);
2715
+ }
2732
2716
 
2733
- if (intent) {
2734
- // for the window-transport, we eagerly/optimistically set the origin host
2735
- // when connecting to the wallet, however, this will be verified and enforced
2736
- // on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
2737
- if (intent.type === 'connect') {
2738
- if (!intent.options) intent.options = {}; // skip setting origin host if we're in an browser extension execution context
2739
- // allow origin that is passed in
2717
+ sendAsync(request, callback, chainId) {
2718
+ this._sender.sendAsync(request, callback, chainId);
2719
+ }
2740
2720
 
2741
- if (!isBrowserExtension()) {
2742
- intent.options.origin = window.location.origin;
2743
- }
2744
- } // encode intent as base64 url-encoded param
2721
+ send(method, params, chainId) {
2722
+ return this._sender.send(method, params, chainId);
2723
+ }
2745
2724
 
2725
+ request(request) {
2726
+ return this.send(request.method, request.params || [], request.chainId);
2727
+ }
2746
2728
 
2747
- windowSessionParams.set('intent', utils.base64EncodeObject(intent));
2748
- }
2729
+ getSigner() {
2730
+ return new Web3Signer(this, this._defaultChainId);
2731
+ }
2749
2732
 
2750
- if (networkId) {
2751
- windowSessionParams.set('net', `${networkId}`);
2752
- } // Open popup window on center of the app window
2733
+ async getChainId() {
2734
+ // TODO: is it safe to memoize this?
2735
+ const result = await this.send('eth_chainId', []);
2736
+ const chainId = ethers.ethers.BigNumber.from(result).toNumber();
2753
2737
 
2738
+ if (this._defaultChainId && this._defaultChainId !== chainId) {
2739
+ throw new Error(`provider chainId (${chainId}) does not match provider-bound chainId ${this._defaultChainId}`);
2740
+ }
2754
2741
 
2755
- let windowSize;
2756
- let windowPos;
2742
+ return chainId;
2743
+ }
2757
2744
 
2758
- if (isBrowserExtension()) {
2759
- windowSize = [450, 750];
2760
- windowPos = [Math.abs(window.screen.width / 2 - windowSize[0] / 2), Math.abs(window.screen.height / 2 - windowSize[1] / 2)];
2761
- } else {
2762
- windowSize = [450, 750];
2763
- windowPos = [Math.abs(window.screenX + window.innerWidth / 2 - windowSize[0] / 2), Math.abs(window.screenY + window.innerHeight / 2 - windowSize[1] / 2)];
2764
- }
2745
+ }
2746
+ function isSequenceProvider(provider) {
2747
+ const cand = provider;
2748
+ return cand && cand.send !== undefined && cand._isSequenceProvider === true;
2749
+ }
2750
+ class LocalWeb3Provider extends Web3Provider {
2751
+ constructor(account, networks) {
2752
+ const walletRequestHandler = new WalletRequestHandler(account, null, networks || []);
2753
+ super(walletRequestHandler);
2754
+ }
2765
2755
 
2766
- const windowFeatures = `toolbar=0,location=0,menubar=0,scrollbars=yes,status=yes` + `,width=${windowSize[0]},height=${windowSize[1]}` + `,left=${windowPos[0]},top=${windowPos[1]}`; // serialize params
2756
+ }
2757
+ class Web3Signer extends wallet.Signer {
2758
+ constructor(provider, defaultChainId) {
2759
+ super();
2760
+ this.provider = void 0;
2761
+ this.defaultChainId = void 0;
2762
+ this._address = void 0;
2763
+ this._index = void 0;
2764
+ this._context = void 0;
2765
+ this._networks = void 0;
2766
+ this._providers = {};
2767
+ this.provider = provider;
2768
+ this.defaultChainId = defaultChainId;
2769
+ } // memoized
2767
2770
 
2768
- walletURL.search = windowSessionParams.toString();
2769
- this.walletWindow = window.open(walletURL.href, 'sequence.app', windowFeatures); // TODO: move this somewhere else
2770
- // TODO: perhaps we trigger a .on('openTimeout') event..? maybe.. could help.
2771
- // Popup blocking detection and notice
2772
- // let warned = false
2773
- // const warnPopupBlocked = () => {
2774
- // if (warned) return
2775
- // warned = true
2776
- // // alert('popup is blocked! hey yo') // NOTE: for debug purposes only
2777
- // throw new Error('popup is blocked')
2778
- // }
2779
- // const popupCheck = setTimeout(() => {
2780
- // if (!popup || popup.closed || typeof popup.closed === 'undefined') {
2781
- // // popup is definitely blocked if we reach here.
2782
- // warnPopupBlocked()
2783
- // }
2784
- // }, 1000)
2785
- // const popupBlocked = popup === null || popup === undefined
2786
- // if (popupBlocked) {
2787
- // warnPopupBlocked()
2788
- // return
2789
- // }
2790
- };
2791
2771
 
2792
- this.onWindowEvent = event => {
2793
- // Security check, ensure message is coming from wallet origin url
2794
- if (event.origin !== this.walletURL.origin) {
2795
- // Safetly can skip events not from the wallet
2796
- return;
2797
- }
2772
+ //
2773
+ // ethers AbstractSigner methods
2774
+ //
2775
+ async getAddress() {
2776
+ if (this._address) return this._address;
2777
+ const accounts = await this.provider.send('eth_accounts', []);
2778
+ this._address = accounts[0];
2779
+ this._index = 0;
2780
+ return ethers.ethers.utils.getAddress(this._address);
2781
+ }
2798
2782
 
2799
- let message;
2783
+ signTransaction(transaction) {
2784
+ // TODO .. since ethers isn't using this method, perhaps we will?
2785
+ throw new Error('signTransaction is unsupported, use signTransactions instead');
2786
+ }
2800
2787
 
2801
- try {
2802
- message = JSON.parse(event.data);
2803
- } catch (err) {
2804
- // event is not a ProviderMessage JSON object, skip
2805
- return;
2806
- }
2788
+ connect(provider) {
2789
+ throw new Error('unsupported: cannot alter JSON-RPC Signer connection');
2790
+ }
2807
2791
 
2808
- if (!message) {
2809
- throw new Error('ProviderMessage object is empty');
2810
- } // handle message with base message provider
2792
+ setDefaultNetwork(chainId) {
2793
+ return this.provider.send('sequence_setDefaultNetwork', [network.maybeChainId(chainId)]);
2794
+ } //
2795
+ // Sequence Signer methods
2796
+ //
2797
+ // getProvider returns a Web3Provider instance for the current chain. Note that this method
2798
+ // and signer is bound to a particular chain to prevent misuse. If you'd like a provider
2799
+ // for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
2811
2800
 
2812
2801
 
2813
- this.handleMessage(message);
2814
- };
2802
+ async getProvider(chainId) {
2803
+ if (chainId) {
2804
+ const currentChainId = await this.getChainId();
2815
2805
 
2816
- this.walletURL = new URL(walletAppURL);
2817
- }
2806
+ if (currentChainId !== chainId) {
2807
+ throw new Error(`signer is attempting to access chain ${chainId}, but is already bound to chain ${currentChainId}`);
2808
+ }
2809
+ }
2818
2810
 
2819
- closeWallet() {
2820
- var _this$walletWindow;
2811
+ return this.provider;
2812
+ } // getSender returns a Web3Provider instance via the signer transport. Note: for our case
2813
+ // the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
2814
+ // requests are sent to the window transport. Therefore, for anything non-signing related
2815
+ // you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
2816
+ // as we do in the WalletProvider.
2817
+ //
2818
+ // This method is primarily utilized internally when routing requests to a particular chainId.
2821
2819
 
2822
- this.close();
2823
- (_this$walletWindow = this.walletWindow) == null ? void 0 : _this$walletWindow.close();
2824
- } // onmessage, receives ProviderMessageResponse from the wallet post-message transport
2825
2820
 
2821
+ async getSender(chainId) {
2822
+ if (!chainId || chainId && chainId === this.defaultChainId) {
2823
+ return this.provider;
2824
+ }
2826
2825
 
2827
- sendMessage(message) {
2828
- if (!this.walletWindow) {
2829
- utils.logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable');
2830
- return;
2826
+ if (!this._providers[chainId]) {
2827
+ this._providers[chainId] = new Web3Provider(new network.JsonRpcSender(this.provider, chainId), chainId);
2831
2828
  }
2832
2829
 
2833
- const postedMessage = typeof message !== 'string' ? JSON.stringify(message) : message;
2834
- this.walletWindow.postMessage(postedMessage, this.walletURL.origin);
2830
+ return this._providers[chainId];
2835
2831
  }
2836
2832
 
2837
- }
2838
-
2839
- class WindowMessageHandler extends BaseWalletTransport {
2840
- constructor(walletRequestHandler) {
2841
- var _this;
2833
+ getRelayer(chainId) {
2834
+ // TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
2835
+ // sequence_estimateGasLimits
2836
+ // sequence_gasRefundOptions
2837
+ // sequence_getNonce
2838
+ // sequence_relay
2839
+ throw new Error('TODO');
2840
+ }
2842
2841
 
2843
- super(walletRequestHandler);
2844
- _this = this;
2845
- this.parentWindow = void 0;
2846
- this._isPopup = false;
2842
+ async getWalletContext() {
2843
+ if (!this._context) {
2844
+ this._context = await this.provider.send('sequence_getWalletContext', []);
2845
+ }
2847
2846
 
2848
- this.onWindowEvent = async function (event) {
2849
- if (!event.origin || event.origin === '') {
2850
- // skip same-origin or when event.origin is empty/undefined
2851
- return;
2852
- }
2847
+ return this._context;
2848
+ }
2853
2849
 
2854
- if (_this.appOrigin && event.origin !== _this.appOrigin) {
2855
- // skip message as not from expected app origin
2856
- return;
2857
- } // Wallet always expects json-rpc request messages from a dapp
2850
+ async getConfigTracker() {
2851
+ return this.provider.send('sequence_getConfigTracker', []);
2852
+ }
2858
2853
 
2854
+ async getWalletConfig(chainId) {
2855
+ return await this.provider.send('sequence_getWalletConfig', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
2856
+ }
2859
2857
 
2860
- let request;
2858
+ async getWalletState(chainId) {
2859
+ return await this.provider.send('sequence_getWalletState', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
2860
+ }
2861
2861
 
2862
- try {
2863
- request = JSON.parse(event.data);
2864
- } catch (err) {
2865
- // event is not a ProviderMessage JSON object, skip
2866
- return;
2867
- }
2862
+ async getNetworks() {
2863
+ if (!this._networks) {
2864
+ this._networks = await this.provider.send('sequence_getNetworks', []);
2865
+ }
2868
2866
 
2869
- utils.logger.debug('RECEIVED MESSAGE', request); // Record event origin for valid init ack
2867
+ return this._networks;
2868
+ }
2870
2869
 
2871
- if (_this._init !== exports.InitState.OK && _this.isValidInitAck(request)) {
2872
- _this.appOrigin = event.origin;
2873
- }
2870
+ async getSigners() {
2871
+ var _this = this;
2874
2872
 
2875
- if (_this._init === exports.InitState.OK && (!_this.appOrigin || _this.appOrigin.length < 8)) {
2876
- // impossible state
2877
- utils.logger.error('impossible state, init.OK and appOrigin required');
2878
- return;
2879
- } // Handle message via the base transport
2873
+ // Get all networks
2874
+ const networks = await this.getNetworks(); // Get all config for all networks
2880
2875
 
2876
+ const configs = await Promise.all(networks.map(async function (network) {
2877
+ return _this.getWalletConfig(network.chainId);
2878
+ })); // Get all addresses for all configs
2881
2879
 
2882
- _this.handleMessage(request);
2883
- };
2880
+ const addresses = configs.reduce((acc, config) => {
2881
+ return acc.concat(config.signers.map(s => s.address));
2882
+ }, []); // Remove duplicates
2884
2883
 
2885
- this.getWindowTransportSession = windowParams => {
2886
- const params = new WindowSessionParams(windowParams);
2887
- return {
2888
- sessionId: params.get('sid'),
2889
- networkId: params.get('net'),
2890
- intent: utils.base64DecodeObject(params.get('intent'))
2891
- };
2892
- };
2884
+ return Array.from(new Set(addresses));
2885
+ }
2893
2886
 
2894
- this._init = exports.InitState.NIL;
2887
+ buildDeployTransaction(chainId) {
2888
+ throw new Error('Method not implemented.');
2895
2889
  }
2896
2890
 
2897
- async register(windowHref) {
2898
- const isPopup = parent.window.opener !== null;
2899
- this._isPopup = isPopup;
2891
+ deploy(chainId) {
2892
+ throw new Error('Method not implemented.');
2893
+ } // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
2894
+ // multi-chain support.
2900
2895
 
2901
- if (isPopup !== true) {
2902
- return;
2903
- } // record open details (sessionId + default network) from the window url
2904
2896
 
2897
+ async signMessage(message, chainId, allSigners, counterfactual) {
2898
+ chainId = network.maybeChainId(chainId) || this.defaultChainId;
2899
+ const provider = await this.getSender(chainId);
2905
2900
 
2906
- const {
2907
- pathname,
2908
- search: rawParams
2909
- } = new URL(windowHref || window.location.href);
2910
- let session = this.getWindowTransportSession(rawParams); // provider should always include sid when opening a new window
2901
+ if (!provider) {
2902
+ throw new Error(`no provider for chain id ${chainId}`);
2903
+ }
2911
2904
 
2912
- const isNewWindowSession = !!session.sessionId; // attempt to restore previous session in the case of a redirect or window reload
2905
+ const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
2906
+ const address = await this.getAddress();
2913
2907
 
2914
- if (!isNewWindowSession) {
2915
- session = await this.getCachedTransportSession();
2908
+ if (counterfactual) {
2909
+ return provider.send('sequence_sign', [ethers.ethers.utils.hexlify(data), address, {
2910
+ counterfactual: true
2911
+ }]);
2912
+ } else {
2913
+ // NOTE: as of ethers v5.5, it switched to using personal_sign, see
2914
+ // https://github.com/ethers-io/ethers.js/pull/1542 and see
2915
+ // https://github.com/WalletConnect/walletconnect-docs/issues/32 for additional info.
2916
+ return provider.send('personal_sign', [ethers.ethers.utils.hexlify(data), address]);
2916
2917
  }
2917
-
2918
- if (!session) {
2919
- utils.logger.error('window session is undefined');
2920
- return;
2921
- } // record parent window instance for communication
2918
+ } // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
2919
+ // multi-chain support.
2922
2920
 
2923
2921
 
2924
- this.parentWindow = parent.window.opener; // listen for window-transport requests
2922
+ async signTypedData(domain, types, message, chainId, allSigners, counterfactual) {
2923
+ // Populate any ENS names (in-place)
2924
+ // const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
2925
+ // return this.provider.resolveName(name)
2926
+ // })
2927
+ if (counterfactual) {
2928
+ return this.provider.send('sequence_signTypedData_v4', [await this.getAddress(), ethers.ethers.utils._TypedDataEncoder.getPayload(domain, types, message), {
2929
+ counterfactual: true
2930
+ }], network.maybeChainId(chainId) || this.defaultChainId);
2931
+ } else {
2932
+ return this.provider.send('eth_signTypedData_v4', [await this.getAddress(), ethers.ethers.utils._TypedDataEncoder.getPayload(domain, types, message)], network.maybeChainId(chainId) || this.defaultChainId);
2933
+ }
2934
+ } // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
2935
+ // multi-chain support.
2925
2936
 
2926
- window.addEventListener('message', this.onWindowEvent, false);
2927
- this._registered = true; // send open event to the app which opened us
2928
2937
 
2929
- this.open(session).then(opened => {
2930
- if (!opened) {
2931
- var _session;
2938
+ async sendTransaction(transaction, chainId, allSigners) {
2939
+ const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
2940
+ const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
2941
+ return ethers.ethers.utils.poll(() => {
2942
+ return provider.getTransaction(hash).then(tx => {
2943
+ if (tx === null) {
2944
+ return undefined;
2945
+ }
2932
2946
 
2933
- const err = `failed to open to network ${(_session = session) == null ? void 0 : _session.networkId}`;
2934
- utils.logger.error(err);
2935
- this.notifyClose({
2936
- message: err
2947
+ return provider._wrapTransaction(tx, hash);
2937
2948
  });
2938
- window.close();
2939
- }
2940
- }).catch(e => {
2941
- var _session2;
2942
-
2943
- const err = `failed to open to network ${(_session2 = session) == null ? void 0 : _session2.networkId}, due to: ${e}`;
2944
- utils.logger.error(err);
2945
- this.notifyClose({
2946
- message: err
2949
+ }, {
2950
+ onceBlock: this.provider
2951
+ }).catch(error => {
2952
+ error.transactionHash = hash;
2953
+ throw error;
2947
2954
  });
2948
- window.close();
2949
- });
2950
- }
2955
+ }); // @ts-ignore
2951
2956
 
2952
- unregister() {
2953
- window.removeEventListener('message', this.onWindowEvent);
2954
- this._registered = false;
2955
- } // onmessage is called when (the wallet) receives request messages from the dapp
2956
- // over the window post-messaging transport
2957
+ return tx;
2958
+ } // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
2959
+ // send multiple transaction as a single payload and just one on-chain transaction.
2957
2960
 
2958
2961
 
2959
- // postMessage sends message to the dapp window
2960
- sendMessage(message) {
2961
- // prepare payload
2962
- const payload = JSON.stringify(message); // post-message to app.
2963
- // only for init requests, we send to '*' origin
2962
+ async sendTransactionBatch(transactions, chainId, allSigners) {
2963
+ const batch = await wallet.resolveArrayProperties(transactions);
2964
2964
 
2965
- if (message.type === exports.EventType.INIT) {
2966
- this.postMessage(payload, true);
2967
- } else {
2968
- this.postMessage(payload);
2969
- }
2970
- }
2965
+ if (!batch || batch.length === 0) {
2966
+ throw new Error('cannot send empty batch');
2967
+ } // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
2971
2968
 
2972
- get isPopup() {
2973
- return this._isPopup;
2974
- }
2975
2969
 
2976
- postMessage(message, init = false) {
2977
- if (init !== true && this._init !== exports.InitState.OK) {
2978
- utils.logger.error('impossible state, should not be calling postMessage until inited');
2979
- return;
2970
+ if (batch.find(v => v.wait !== undefined && v.wait !== null)) {
2971
+ throw new Error('transaction request expected for sendTransactionBatch, transaction response found');
2980
2972
  }
2981
2973
 
2982
- if (init) {
2983
- // init message transmission to global target -- for 'init' payloads only
2984
- this.parentWindow.postMessage(message, '*');
2985
- } else {
2986
- // open message transmission
2987
- if (this.appOrigin && this.appOrigin.length > 4) {
2988
- // just above '.com'
2989
- this.parentWindow.postMessage(message, this.appOrigin);
2990
- } else {
2991
- utils.logger.error('unable to postMessage as parentOrigin is invalid');
2992
- }
2993
- }
2994
- }
2974
+ const tx = _extends({}, batch[0]);
2995
2975
 
2996
- }
2976
+ if (batch.length > 1) {
2977
+ tx.auxiliary = batch.splice(1);
2978
+ }
2997
2979
 
2998
- const CHANNEL_ID = 'sequence-extension-message-handler';
2999
- class ExtensionMessageHandler extends BaseWalletTransport {
3000
- constructor(walletRequestHandler, runtime) {
3001
- super(walletRequestHandler);
3002
- this.runtime = runtime;
3003
- this.port = void 0;
3004
- this._init = exports.InitState.OK;
2980
+ return this.sendTransaction(tx, chainId, allSigners);
3005
2981
  }
3006
2982
 
3007
- register() {
3008
- this._registered = true;
3009
- this.port = this.runtime.connect({
3010
- name: CHANNEL_ID
3011
- });
3012
- }
2983
+ signTransactions(transaction, chainId, allSigners) {
2984
+ transaction = utils.shallowCopy(transaction); // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
2985
+ // the wire.. see sendUncheckedTransaction and resolveProperties
3013
2986
 
3014
- sendMessage(message) {
3015
- utils.logger.info('[ExtensionMessageHandler send]', message);
3016
- this.port.postMessage(message);
2987
+ return this.provider.send('eth_signTransaction', [transaction], network.maybeChainId(chainId) || this.defaultChainId);
3017
2988
  }
3018
2989
 
3019
- }
2990
+ sendSignedTransactions(signedBundle, chainId) {
2991
+ // sequence_relay
2992
+ throw new Error('TODO');
2993
+ } // updateConfig..
2994
+ // NOTE: this is not supported by the remote wallet by default.
3020
2995
 
3021
- class ExtensionMessageProvider extends BaseProviderTransport {
3022
- constructor(runtime) {
3023
- super();
3024
2996
 
3025
- this.register = () => {
3026
- this._registered = true;
3027
- };
2997
+ async updateConfig(newConfig) {
2998
+ // sequence_updateConfig
2999
+ const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId);
3000
+
3001
+ if (tx === null) {
3002
+ return [config, undefined];
3003
+ }
3028
3004
 
3029
- runtime.onConnect.addListener(port => {
3030
- if (port.name === CHANNEL_ID) {
3031
- this._init = exports.InitState.OK;
3032
- port.onMessage.addListener(message => {
3033
- this.handleMessage(message);
3034
- });
3035
- }
3036
- });
3037
- }
3005
+ const provider = await this.getSender(this.defaultChainId);
3006
+ return [config, provider._wrapTransaction(tx, tx.hash)];
3007
+ } // publishConfig..
3008
+ // NOTE: this is not supported by the remote wallet by default.
3038
3009
 
3039
- sendMessage(message) {//noop
3040
- }
3041
3010
 
3042
- unregister() {//noop
3043
- }
3011
+ async publishConfig() {
3012
+ const provider = await this.getSender(this.defaultChainId);
3013
+ const tx = await provider.send('sequence_publishConfig', []);
3044
3014
 
3045
- openWallet(path, intent, networkId) {//noop
3046
- }
3015
+ if (tx === null) {
3016
+ return undefined;
3017
+ }
3047
3018
 
3048
- closeWallet() {//noop
3019
+ return provider._wrapTransaction(tx, tx.hash);
3049
3020
  }
3050
3021
 
3051
- }
3022
+ async isDeployed(chainId) {
3023
+ const provider = await this.getSender(network.maybeChainId(chainId));
3024
+ const walletCode = await provider.getCode(await this.getAddress());
3025
+ return !!walletCode && walletCode !== '0x';
3026
+ } //
3027
+ // ethers JsonRpcSigner methods
3028
+ //
3052
3029
 
3053
- // to be used on injected window.ethereum EIP1193 proxy
3054
- class BaseInjectedTransport extends eventemitter2.EventEmitter2 {
3055
- constructor(stream) {
3056
- var _this;
3057
3030
 
3058
- super();
3059
- _this = this;
3060
- this.stream = stream;
3061
- this.responseCallbacks = new Map();
3062
- this._messageIdx = 0;
3031
+ async _legacySignMessage(message, chainId, allSigners) {
3032
+ const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
3033
+ const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
3034
+ const address = await this.getAddress(); // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
3035
+ // NOTE: ethers since 5.5 has switched to using personal_sign, we should review, etc.
3063
3036
 
3064
- this.nextMessageIdx = () => ++this._messageIdx;
3037
+ return await provider.send('eth_sign', [address, ethers.ethers.utils.hexlify(data)]);
3038
+ }
3065
3039
 
3066
- this.handleMessage = message => {
3067
- if (!message.type || !message.data) {
3068
- return;
3069
- }
3040
+ async _signTypedData(domain, types, message, chainId, allSigners) {
3041
+ return this.signTypedData(domain, types, message, chainId, allSigners);
3042
+ }
3070
3043
 
3071
- utils.logger.info('[received message]', message);
3072
- const requestIdx = message.idx;
3073
- const responseCallback = this.responseCallbacks.get(requestIdx);
3044
+ async sendUncheckedTransaction(transaction, chainId) {
3045
+ transaction = utils.shallowCopy(transaction);
3046
+ const fromAddress = this.getAddress(); // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
3047
+ //
3048
+ // TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
3049
+ // and send it to our relayer url instead for estimation..
3050
+ //
3051
+ // if (!transaction.gasLimit) {
3052
+ // const estimate = shallowCopy(transaction)
3053
+ // estimate.from = fromAddress
3054
+ // transaction.gasLimit = this.provider.estimateGas(estimate)
3055
+ // }
3074
3056
 
3075
- if (requestIdx) {
3076
- this.responseCallbacks.delete(requestIdx);
3057
+ const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
3058
+ return utils.resolveProperties({
3059
+ tx: utils.resolveProperties(transaction),
3060
+ sender: await fromAddress
3061
+ }).then(({
3062
+ tx,
3063
+ sender
3064
+ }) => {
3065
+ if (tx.from != null) {
3066
+ if (ethers.ethers.utils.getAddress(tx.from) !== sender) {
3067
+ // logger.throwArgumentError("from address mismatch", "transaction", transaction)
3068
+ throw new Error(`from address mismatch for transaction ${transaction}`);
3069
+ }
3070
+ } else {
3071
+ tx.from = sender;
3077
3072
  }
3078
3073
 
3079
- switch (message.type) {
3080
- case exports.EventType.MESSAGE:
3081
- if (responseCallback) {
3082
- this.emit(exports.EventType.MESSAGE, message);
3083
- responseCallback(message.data.error, message);
3084
- } else {
3085
- // NOTE: this would occur if 'idx' isn't set, which should never happen
3086
- // or when we register two handler, or duplicate messages with the same idx are sent,
3087
- // all of which should be prevented prior to getting to this point
3088
- throw new Error('impossible state');
3089
- }
3074
+ const hexTx = hexlifyTransaction(tx);
3075
+ return provider.send('eth_sendTransaction', [hexTx]).then(hash => {
3076
+ return hash;
3077
+ }, error => {
3078
+ // return checkError("sendTransaction", error, hexTx)
3079
+ throw new Error(`sendTransaction ${error}`);
3080
+ });
3081
+ });
3082
+ }
3090
3083
 
3091
- break;
3084
+ connectUnchecked() {
3085
+ throw new Error('connectUnchecked is unsupported');
3086
+ }
3092
3087
 
3093
- case exports.EventType.DISCONNECT:
3094
- case exports.EventType.ACCOUNTS_CHANGED:
3095
- case exports.EventType.CHAIN_CHANGED:
3096
- this.emit(message.type, message.data);
3097
- break;
3088
+ async unlock(password) {
3089
+ const address = await this.getAddress();
3090
+ return this.provider.send('personal_unlockAccount', [address, password, null]);
3091
+ }
3098
3092
 
3099
- default:
3100
- console.error('unknown message type', message);
3101
- break;
3102
- }
3103
- };
3093
+ } // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
3094
+ // Convert an ethers.js transaction into a JSON-RPC transaction
3104
3095
 
3105
- this.sendMessageRequest = async function (message) {
3106
- return new Promise((resolve, reject) => {
3107
- if (!message.idx || message.idx <= 0) {
3108
- reject(new Error('message idx not set'));
3109
- }
3096
+ const allowedTransactionKeys = {
3097
+ chainId: true,
3098
+ data: true,
3099
+ gasLimit: true,
3100
+ gasPrice: true,
3101
+ nonce: true,
3102
+ to: true,
3103
+ value: true,
3104
+ from: true,
3105
+ auxiliary: true,
3106
+ expiration: true,
3107
+ afterNonce: true,
3108
+ delegateCall: true,
3109
+ revertOnError: true
3110
+ };
3110
3111
 
3111
- const responseCallback = (error, response) => {
3112
- if (error) {
3113
- reject(error);
3114
- } else if (response) {
3115
- resolve(response);
3116
- } else {
3117
- throw new Error('no valid response to return');
3118
- }
3119
- };
3112
+ const hexlifyTransaction = (transaction, allowExtra) => {
3113
+ // Check only allowed properties are given
3114
+ const allowed = utils.shallowCopy(allowedTransactionKeys);
3120
3115
 
3121
- const {
3122
- idx
3123
- } = message;
3116
+ if (allowExtra) {
3117
+ for (const key in allowExtra) {
3118
+ if (allowExtra[key]) {
3119
+ allowed[key] = true;
3120
+ }
3121
+ }
3122
+ }
3124
3123
 
3125
- if (!_this.responseCallbacks.get(idx)) {
3126
- _this.responseCallbacks.set(idx, responseCallback);
3127
- } else {
3128
- reject(new Error('duplicate message idx, should never happen'));
3129
- }
3124
+ ethers.ethers.utils.checkProperties(transaction, allowed);
3125
+ const result = {} // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
3126
+ ;
3127
+ ['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
3128
+ if (!transaction[key]) {
3129
+ return;
3130
+ }
3130
3131
 
3131
- _this.sendMessage(message);
3132
- });
3133
- };
3132
+ const value = ethers.ethers.utils.hexValue(transaction[key]);
3134
3133
 
3135
- this.stream.on('data', this.handleMessage);
3136
- }
3134
+ if (key === 'gasLimit') {
3135
+ key = 'gas';
3136
+ }
3137
3137
 
3138
- sendMessage(message) {
3139
- if (!this.stream.writable) {
3140
- console.error('window post message stream is not writable');
3138
+ result[key] = value;
3139
+ });
3140
+ ['from', 'to', 'data'].forEach(key => {
3141
+ if (!transaction[key]) {
3142
+ return;
3141
3143
  }
3142
3144
 
3143
- this.stream.write(message);
3145
+ result[key] = ethers.ethers.utils.hexlify(transaction[key]);
3146
+ });
3147
+ const auxiliary = transaction['auxiliary'];
3148
+
3149
+ if (auxiliary && auxiliary.length > 0) {
3150
+ result['auxiliary'] = [];
3151
+ auxiliary.forEach(a => {
3152
+ result['auxiliary'].push(hexlifyTransaction(a));
3153
+ });
3144
3154
  }
3145
3155
 
3146
- }
3156
+ return result;
3157
+ };
3147
3158
 
3148
3159
  class WalletUtils {
3149
3160
  constructor(walletProvider) {
3150
3161
  var _this = this;
3151
3162
 
3152
3163
  this.wallet = void 0;
3164
+ this.configTracker = void 0;
3153
3165
 
3154
3166
  this.recoverWalletConfig = async function (address, digest, signature, chainId, walletContext) {
3155
3167
  walletContext = walletContext || (await _this.wallet.getWalletContext());
3156
- return recoverWalletConfig(address, digest, signature, chainId, walletContext);
3168
+
3169
+ const provider = _this.wallet.getProvider(chainId);
3170
+
3171
+ if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
3172
+ const configTracker = await _this._configTracker();
3173
+ return recoverWalletConfig(address, digest, signature, chainId, provider, configTracker, walletContext);
3157
3174
  };
3158
3175
 
3159
3176
  this.recoverWalletConfigFromMessage = async function (address, message, signature, chainId, walletContext) {
3160
3177
  walletContext = walletContext || (await _this.wallet.getWalletContext());
3161
- return recoverWalletConfig(address, utils.encodeMessageDigest(prefixEIP191Message(message)), signature, chainId, walletContext);
3178
+
3179
+ const provider = _this.wallet.getProvider(chainId);
3180
+
3181
+ if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
3182
+ const configTracker = await _this._configTracker();
3183
+ return recoverWalletConfig(address, utils.encodeMessageDigest(wallet.prefixEIP191Message(message)), signature, chainId, provider, configTracker, walletContext);
3162
3184
  };
3163
3185
 
3164
3186
  this.recoverWalletConfigFromTypedData = async function (address, typedData, signature, chainId, walletContext) {
3165
3187
  walletContext = walletContext || (await _this.wallet.getWalletContext());
3166
- return recoverWalletConfig(address, utils.encodeTypedDataDigest(typedData), signature, chainId, walletContext);
3188
+
3189
+ const provider = _this.wallet.getProvider(chainId);
3190
+
3191
+ if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
3192
+ const configTracker = await _this._configTracker();
3193
+ return recoverWalletConfig(address, utils.encodeTypedDataDigest(typedData), signature, chainId, provider, configTracker, walletContext);
3167
3194
  };
3168
3195
 
3169
3196
  this.wallet = walletProvider;
3197
+ }
3198
+
3199
+ async _configTracker() {
3200
+ if (this.configTracker) return this.configTracker;
3201
+ const res = await this.wallet.getConfigTracker();
3202
+ if (res) this.configTracker = res;
3203
+ return res;
3170
3204
  } // Sign message on a specified chain, or DefaultChain by default
3171
3205
 
3172
3206
 
@@ -3174,13 +3208,6 @@ class WalletUtils {
3174
3208
  const signer = this.wallet.getSigner();
3175
3209
  if (!signer) throw new Error('unable to get signer');
3176
3210
  return signer.signMessage(message, chainId, allSigners);
3177
- } // Sign message on the AuthChain
3178
-
3179
-
3180
- async signAuthMessage(message, allSigners) {
3181
- const signer = await this.wallet.getAuthSigner();
3182
- if (!signer) throw new Error('unable to get AuthChain signer');
3183
- return signer.signMessage(message, await signer.getChainId(), allSigners);
3184
3211
  } // Sign EIP-712 TypedData on a specified chain, or DefaultChain by default
3185
3212
 
3186
3213
 
@@ -3188,29 +3215,41 @@ class WalletUtils {
3188
3215
  const signer = this.wallet.getSigner();
3189
3216
  if (!signer) throw new Error('unable to get signer');
3190
3217
  return signer.signTypedData(domain, types, message, chainId, allSigners);
3191
- } // Sign EIP-712 TypedData on the AuthChain
3192
-
3193
-
3194
- async signAuthTypedData(domain, types, message, allSigners) {
3195
- const signer = await this.wallet.getAuthSigner();
3196
- if (!signer) throw new Error('unable to get AuthChain signer');
3197
- return signer.signTypedData(domain, types, message, await signer.getChainId(), allSigners);
3198
3218
  } // Verify signature of a digest, one of a message, typedData or other
3199
3219
 
3200
3220
 
3201
3221
  async isValidSignature(address, digest, signature, chainId, walletContext) {
3202
3222
  const provider = this.wallet.getProvider(chainId);
3203
3223
  if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
3204
- return isValidSignature(address, digest, signature, provider, chainId, walletContext);
3224
+ const configTracker = await this._configTracker(); // return isValidSignature(address, digest, signature, provider, chainId, walletContext, configTracker)
3225
+
3226
+ return wallet.isValidSignature({
3227
+ address,
3228
+ digest,
3229
+ signature,
3230
+ chainId,
3231
+ context: walletContext,
3232
+ provider,
3233
+ configTracker
3234
+ });
3205
3235
  } // Verify message signature
3206
3236
 
3207
3237
 
3208
3238
  async isValidMessageSignature(address, message, signature, chainId, walletContext) {
3209
3239
  const provider = this.wallet.getProvider(chainId);
3210
3240
  if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
3211
- const prefixed = prefixEIP191Message(message);
3241
+ const configTracker = await this._configTracker();
3242
+ const prefixed = wallet.prefixEIP191Message(message);
3212
3243
  const digest = utils.encodeMessageDigest(prefixed);
3213
- return isValidSignature(address, digest, signature, provider, chainId, walletContext);
3244
+ return wallet.isValidSignature({
3245
+ address,
3246
+ digest,
3247
+ signature,
3248
+ chainId,
3249
+ context: walletContext,
3250
+ provider,
3251
+ configTracker
3252
+ });
3214
3253
  } // Verify typedData signature
3215
3254
 
3216
3255
 
@@ -3469,20 +3508,6 @@ class Wallet {
3469
3508
  return network.chainId;
3470
3509
  };
3471
3510
 
3472
- this.getAuthChainId = async function () {
3473
- if (!_this.networks || _this.networks.length < 1) {
3474
- throw new Error('networks have not been set by session. connect first.');
3475
- }
3476
-
3477
- const network = _this.networks.find(network => network.isAuthChain);
3478
-
3479
- if (!network) {
3480
- throw new Error('networks must have an auth chain specified');
3481
- }
3482
-
3483
- return network.chainId;
3484
- };
3485
-
3486
3511
  this.openWallet = async function (path, intent, networkId) {
3487
3512
  if ((intent == null ? void 0 : intent.type) !== 'connect' && !_this.isConnected()) {
3488
3513
  throw new Error('connect first');
@@ -3689,14 +3714,6 @@ class Wallet {
3689
3714
  return provider;
3690
3715
  }
3691
3716
 
3692
- async getAuthProvider() {
3693
- return this.getProvider((await this.getAuthNetwork()).chainId);
3694
- }
3695
-
3696
- async getAuthNetwork() {
3697
- return (await this.getNetworks()).find(n => n.isAuthChain);
3698
- }
3699
-
3700
3717
  getAllProviders() {
3701
3718
  return this.providers;
3702
3719
  }
@@ -3705,10 +3722,6 @@ class Wallet {
3705
3722
  return this.getProvider(chainId).getSigner();
3706
3723
  }
3707
3724
 
3708
- async getAuthSigner() {
3709
- return (await this.getAuthProvider()).getSigner();
3710
- }
3711
-
3712
3725
  getWalletConfig(chainId) {
3713
3726
  return this.getSigner().getWalletConfig(chainId);
3714
3727
  }
@@ -3725,6 +3738,11 @@ class Wallet {
3725
3738
  return this.getSigner(chainId).isDeployed();
3726
3739
  }
3727
3740
 
3741
+ async getConfigTracker() {
3742
+ const options = await this.getSigner().getConfigTracker();
3743
+ return options && new config.SessionsApiConfigTracker(options);
3744
+ }
3745
+
3728
3746
  on(event, fn) {
3729
3747
  this.transport.messageProvider.on(event, fn);
3730
3748
  }
@@ -3752,8 +3770,10 @@ class Wallet {
3752
3770
  if (!this.config.networks && !this.config.networkRpcUrl) {
3753
3771
  this.networks = networks;
3754
3772
  return;
3755
- } // combine custom network config with networks in the session
3773
+ } // init networks
3774
+
3756
3775
 
3776
+ this.networks = networks; // combine custom network config with networks in the session
3757
3777
 
3758
3778
  if (this.config.networks) {
3759
3779
  this.networks = networks.map(n => _extends({}, n)); // copy
@@ -3768,7 +3788,11 @@ class Wallet {
3768
3788
 
3769
3789
 
3770
3790
  if (this.config.networkRpcUrl) {
3771
- this.networks.find(network => network.isDefaultChain).rpcUrl = this.config.networkRpcUrl;
3791
+ const network = this.networks.find(network => network.isDefaultChain);
3792
+
3793
+ if (network) {
3794
+ network.rpcUrl = this.config.networkRpcUrl;
3795
+ }
3772
3796
  }
3773
3797
  }
3774
3798
 
@@ -3824,11 +3848,5 @@ exports.WindowMessageProvider = WindowMessageProvider;
3824
3848
  exports.WindowSessionParams = WindowSessionParams;
3825
3849
  exports.isBrowserExtension = isBrowserExtension;
3826
3850
  exports.isSequenceProvider = isSequenceProvider;
3827
- exports.isValidMessageSignature = isValidMessageSignature;
3828
- exports.isValidSignature = isValidSignature;
3829
- exports.isValidTypedDataSignature = isValidTypedDataSignature;
3830
- exports.isWalletUpToDate = isWalletUpToDate;
3831
- exports.messageToBytes = messageToBytes;
3832
3851
  exports.nextMessageIdx = nextMessageIdx;
3833
- exports.prefixEIP191Message = prefixEIP191Message;
3834
3852
  exports.recoverWalletConfig = recoverWalletConfig;