@0xsequence/provider 0.35.7 → 0.35.11

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