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