@0xsequence/provider 0.35.8 → 0.35.12

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,1833 +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
- throw new Error('A WindowMessageProvider is already registered. There can only be one.');
1380
- } // listen for incoming messages from wallet
1381
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.
1382
1455
 
1383
- window.addEventListener('message', this.onWindowEvent);
1384
- registeredWindowMessageProvider = this; // open heartbeat
1385
1456
 
1386
- this.on('open', () => {
1387
- // Heartbeat to track if window closed
1388
- const popup = this.walletWindow;
1389
- const interval = setInterval(() => {
1390
- if (popup && popup.closed) {
1391
- clearInterval(interval);
1392
- 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;
1393
1464
  }
1394
- }, 500);
1395
- }); // close clean up
1396
1465
 
1397
- this.on('close', () => {
1398
- if (this.walletWindow) {
1399
- this.walletWindow.close();
1400
- this.walletWindow = null;
1401
- }
1466
+ return provider._wrapTransaction(tx, hash);
1467
+ });
1468
+ }, {
1469
+ onceBlock: this.provider
1470
+ }).catch(error => {
1471
+ error.transactionHash = hash;
1472
+ throw error;
1402
1473
  });
1403
- this._registered = true;
1404
- };
1474
+ }); // @ts-ignore
1405
1475
 
1406
- this.unregister = () => {
1407
- this._registered = false;
1408
- 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.
1409
1479
 
1410
- if (registeredWindowMessageProvider === this) {
1411
- registeredWindowMessageProvider = undefined;
1412
- }
1413
1480
 
1414
- window.removeEventListener('message', this.onWindowEvent); // clear event listeners
1481
+ async sendTransactionBatch(transactions, chainId, allSigners) {
1482
+ const batch = await wallet.resolveArrayProperties(transactions);
1415
1483
 
1416
- this.events.removeAllListeners();
1417
- };
1418
-
1419
- this.openWallet = (path, intent, networkId) => {
1420
- if (this.walletWindow && this.isOpened()) {
1421
- // TODO: update the location of window to path
1422
- this.walletWindow.focus();
1423
- return;
1424
- } // Instantiate new walletURL for this call
1484
+ if (!batch || batch.length === 0) {
1485
+ throw new Error('cannot send empty batch');
1486
+ } // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
1425
1487
 
1426
1488
 
1427
- const walletURL = new URL(this.walletURL.href);
1428
- const windowSessionParams = new WindowSessionParams();
1429
-
1430
- if (path && path !== '') {
1431
- walletURL.pathname = path.toLowerCase();
1432
- } // 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
+ }
1433
1492
 
1493
+ const tx = _extends({}, batch[0]);
1434
1494
 
1435
- this._init = exports.InitState.NIL;
1436
- this._sessionId = `${performance.now()}`;
1437
- windowSessionParams.set('sid', this._sessionId);
1495
+ if (batch.length > 1) {
1496
+ tx.auxiliary = batch.splice(1);
1497
+ }
1438
1498
 
1439
- if (intent) {
1440
- // for the window-transport, we eagerly/optimistically set the origin host
1441
- // when connecting to the wallet, however, this will be verified and enforced
1442
- // on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
1443
- if (intent.type === 'connect') {
1444
- if (!intent.options) intent.options = {}; // skip setting origin host if we're in an browser extension execution context
1445
- // allow origin that is passed in
1499
+ return this.sendTransaction(tx, chainId, allSigners);
1500
+ }
1446
1501
 
1447
- if (!isBrowserExtension()) {
1448
- intent.options.origin = window.location.origin;
1449
- }
1450
- } // 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
1451
1505
 
1506
+ return this.provider.send('eth_signTransaction', [transaction], network.maybeChainId(chainId) || this.defaultChainId);
1507
+ }
1452
1508
 
1453
- windowSessionParams.set('intent', utils.base64EncodeObject(intent));
1454
- }
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.
1455
1514
 
1456
- if (networkId) {
1457
- windowSessionParams.set('net', `${networkId}`);
1458
- } // Open popup window on center of the app window
1459
1515
 
1516
+ async updateConfig(newConfig) {
1517
+ // sequence_updateConfig
1518
+ const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId);
1460
1519
 
1461
- let windowSize;
1462
- let windowPos;
1520
+ if (tx === null) {
1521
+ return [config, undefined];
1522
+ }
1463
1523
 
1464
- if (isBrowserExtension()) {
1465
- windowSize = [450, 750];
1466
- windowPos = [Math.abs(window.screen.width / 2 - windowSize[0] / 2), Math.abs(window.screen.height / 2 - windowSize[1] / 2)];
1467
- } else {
1468
- windowSize = [450, 750];
1469
- windowPos = [Math.abs(window.screenX + window.innerWidth / 2 - windowSize[0] / 2), Math.abs(window.screenY + window.innerHeight / 2 - windowSize[1] / 2)];
1470
- }
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.
1471
1528
 
1472
- 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
1473
1529
 
1474
- walletURL.search = windowSessionParams.toString();
1475
- this.walletWindow = window.open(walletURL.href, 'sequence.app', windowFeatures); // TODO: move this somewhere else
1476
- // TODO: perhaps we trigger a .on('openTimeout') event..? maybe.. could help.
1477
- // Popup blocking detection and notice
1478
- // let warned = false
1479
- // const warnPopupBlocked = () => {
1480
- // if (warned) return
1481
- // warned = true
1482
- // // alert('popup is blocked! hey yo') // NOTE: for debug purposes only
1483
- // throw new Error('popup is blocked')
1484
- // }
1485
- // const popupCheck = setTimeout(() => {
1486
- // if (!popup || popup.closed || typeof popup.closed === 'undefined') {
1487
- // // popup is definitely blocked if we reach here.
1488
- // warnPopupBlocked()
1489
- // }
1490
- // }, 1000)
1491
- // const popupBlocked = popup === null || popup === undefined
1492
- // if (popupBlocked) {
1493
- // warnPopupBlocked()
1494
- // return
1495
- // }
1496
- };
1530
+ async publishConfig() {
1531
+ const provider = await this.getSender(this.defaultChainId);
1532
+ const tx = await provider.send('sequence_publishConfig', []);
1497
1533
 
1498
- this.onWindowEvent = event => {
1499
- // Security check, ensure message is coming from wallet origin url
1500
- if (event.origin !== this.walletURL.origin) {
1501
- // Safetly can skip events not from the wallet
1502
- return;
1503
- }
1534
+ if (tx === null) {
1535
+ return undefined;
1536
+ }
1504
1537
 
1505
- let message;
1538
+ return provider._wrapTransaction(tx, tx.hash);
1539
+ }
1506
1540
 
1507
- try {
1508
- message = JSON.parse(event.data);
1509
- } catch (err) {
1510
- // event is not a ProviderMessage JSON object, skip
1511
- return;
1512
- }
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
+ //
1513
1548
 
1514
- if (!message) {
1515
- throw new Error('ProviderMessage object is empty');
1516
- } // handle message with base message provider
1517
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.
1518
1555
 
1519
- this.handleMessage(message);
1520
- };
1556
+ return await provider.send('eth_sign', [address, ethers.ethers.utils.hexlify(data)]);
1557
+ }
1521
1558
 
1522
- this.walletURL = new URL(walletAppURL);
1559
+ async _signTypedData(domain, types, message, chainId, allSigners) {
1560
+ return this.signTypedData(domain, types, message, chainId, allSigners);
1523
1561
  }
1524
1562
 
1525
- closeWallet() {
1526
- 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
+ // }
1527
1575
 
1528
- this.close();
1529
- (_this$walletWindow = this.walletWindow) == null ? void 0 : _this$walletWindow.close();
1530
- } // 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
+ }
1531
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
+ }
1532
1602
 
1533
- sendMessage(message) {
1534
- if (!this.walletWindow) {
1535
- utils.logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable');
1536
- return;
1537
- }
1603
+ connectUnchecked() {
1604
+ throw new Error('connectUnchecked is unsupported');
1605
+ }
1538
1606
 
1539
- const postedMessage = typeof message !== 'string' ? JSON.stringify(message) : message;
1540
- 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]);
1541
1610
  }
1542
1611
 
1543
- }
1612
+ } // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
1613
+ // Convert an ethers.js transaction into a JSON-RPC transaction
1544
1614
 
1545
- class WindowMessageHandler extends BaseWalletTransport {
1546
- constructor(walletRequestHandler) {
1547
- 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
+ };
1548
1630
 
1549
- super(walletRequestHandler);
1550
- _this = this;
1551
- this.parentWindow = void 0;
1552
- this._isPopup = false;
1631
+ const hexlifyTransaction = (transaction, allowExtra) => {
1632
+ // Check only allowed properties are given
1633
+ const allowed = utils.shallowCopy(allowedTransactionKeys);
1553
1634
 
1554
- this.onWindowEvent = async function (event) {
1555
- if (!event.origin || event.origin === '') {
1556
- // skip same-origin or when event.origin is empty/undefined
1557
- return;
1635
+ if (allowExtra) {
1636
+ for (const key in allowExtra) {
1637
+ if (allowExtra[key]) {
1638
+ allowed[key] = true;
1558
1639
  }
1640
+ }
1641
+ }
1559
1642
 
1560
- if (_this.appOrigin && event.origin !== _this.appOrigin) {
1561
- // skip message as not from expected app origin
1562
- return;
1563
- } // Wallet always expects json-rpc request messages from a dapp
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
+ }
1564
1650
 
1651
+ const value = ethers.ethers.utils.hexValue(transaction[key]);
1565
1652
 
1566
- let request;
1653
+ if (key === 'gasLimit') {
1654
+ key = 'gas';
1655
+ }
1567
1656
 
1568
- try {
1569
- request = JSON.parse(event.data);
1570
- } catch (err) {
1571
- // event is not a ProviderMessage JSON object, skip
1572
- return;
1573
- }
1657
+ result[key] = value;
1658
+ });
1659
+ ['from', 'to', 'data'].forEach(key => {
1660
+ if (!transaction[key]) {
1661
+ return;
1662
+ }
1574
1663
 
1575
- utils.logger.debug('RECEIVED MESSAGE', request); // Record event origin for valid init ack
1576
-
1577
- if (_this._init !== exports.InitState.OK && _this.isValidInitAck(request)) {
1578
- _this.appOrigin = event.origin;
1579
- }
1664
+ result[key] = ethers.ethers.utils.hexlify(transaction[key]);
1665
+ });
1666
+ const auxiliary = transaction['auxiliary'];
1580
1667
 
1581
- if (_this._init === exports.InitState.OK && (!_this.appOrigin || _this.appOrigin.length < 8)) {
1582
- // impossible state
1583
- utils.logger.error('impossible state, init.OK and appOrigin required');
1584
- return;
1585
- } // Handle message via the base transport
1668
+ if (auxiliary && auxiliary.length > 0) {
1669
+ result['auxiliary'] = [];
1670
+ auxiliary.forEach(a => {
1671
+ result['auxiliary'].push(hexlifyTransaction(a));
1672
+ });
1673
+ }
1586
1674
 
1675
+ return result;
1676
+ };
1587
1677
 
1588
- _this.handleMessage(request);
1589
- };
1678
+ const isValidSignature = async (address, digest, sig, provider, chainId, walletContext) => {
1679
+ if (!chainId) {
1680
+ var _await$provider$getNe;
1590
1681
 
1591
- this.getWindowTransportSession = windowParams => {
1592
- const params = new WindowSessionParams(windowParams);
1593
- return {
1594
- sessionId: params.get('sid'),
1595
- networkId: params.get('net'),
1596
- intent: utils.base64DecodeObject(params.get('intent'))
1597
- };
1598
- };
1682
+ chainId = (_await$provider$getNe = await provider.getNetwork()) == null ? void 0 : _await$provider$getNe.chainId;
1683
+ }
1599
1684
 
1600
- this._init = exports.InitState.NIL;
1685
+ if (!walletContext && Web3Provider.isSequenceProvider(provider)) {
1686
+ walletContext = await provider.getSigner().getWalletContext();
1601
1687
  }
1602
1688
 
1603
- register(windowHref) {
1604
- const isPopup = parent.window.opener !== null;
1605
- this._isPopup = isPopup;
1689
+ return wallet.isValidSignature(address, digest, sig, provider, walletContext, chainId);
1690
+ };
1691
+ const isValidMessageSignature = async (address, message, signature, provider, chainId, walletContext) => {
1692
+ const msgDigest = ethers.ethers.utils.arrayify(ethers.ethers.utils.hashMessage(message));
1693
+ if (wallet.isValidEIP712Signature(address, msgDigest, signature) || wallet.isValidEthSignSignature(address, msgDigest, signature)) return true;
1694
+ return isValidSignature(address, utils.encodeMessageDigest(message), signature, provider, chainId, walletContext);
1695
+ };
1696
+ const isValidTypedDataSignature = (address, typedData, signature, provider, chainId, walletContext) => {
1697
+ return isValidSignature(address, utils.encodeTypedDataDigest(typedData), signature, provider, chainId, walletContext);
1698
+ };
1699
+ const recoverWalletConfig = async (address, digest, signature, chainId, walletContext) => {
1700
+ const subDigest = utils.packMessageData(address, chainId, digest);
1701
+ const config$1 = await wallet.recoverConfig(subDigest, signature);
1606
1702
 
1607
- if (isPopup !== true) {
1608
- return;
1609
- } // record open details (sessionId + default network) from the window url
1703
+ if (walletContext) {
1704
+ const recoveredWalletAddress = config.addressOf(config$1, walletContext);
1610
1705
 
1706
+ if (config$1.address && config$1.address !== recoveredWalletAddress) {
1707
+ throw new Error('recovered address does not match the WalletConfig address, check the WalletContext');
1708
+ } else {
1709
+ config$1.address = recoveredWalletAddress;
1710
+ }
1711
+ }
1611
1712
 
1612
- const {
1613
- pathname,
1614
- search: rawParams
1615
- } = new URL(windowHref || window.location.href);
1616
- let session = this.getWindowTransportSession(rawParams); // provider should always include sid when opening a new window
1713
+ return config$1;
1714
+ };
1715
+ const isBrowserExtension = () => window.location.protocol === 'chrome-extension:' || window.location.protocol === 'moz-extension:';
1716
+ /**
1717
+ * Returns the status of a signer's wallet on given chain by checking wallet deployment and config status
1718
+ *
1719
+ * @param {Signer} signer
1720
+ * @param {number} chainId
1721
+ * @return {Promise<boolean>} Promise that returns true if the wallet is up to date, false otherwise
1722
+ */
1617
1723
 
1618
- const isNewWindowSession = !!session.sessionId; // attempt to restore previous session in the case of a redirect or window reload
1724
+ const isWalletUpToDate = async (signer, chainId) => {
1725
+ var _walletState$find;
1619
1726
 
1620
- if (!isNewWindowSession) {
1621
- session = this.getCachedTransportSession();
1622
- }
1727
+ const walletState = await signer.getWalletState();
1728
+ const networks = await signer.getNetworks();
1729
+ const walletStateForRequiredChain = walletState.find(state => state.chainId === chainId);
1623
1730
 
1624
- if (!session) {
1625
- utils.logger.error('window session is undefined');
1626
- return;
1627
- } // record parent window instance for communication
1731
+ if (!walletStateForRequiredChain) {
1732
+ throw new Error(`WalletRequestHandler: could not find wallet state for chainId ${chainId}`);
1733
+ }
1628
1734
 
1735
+ const isDeployed = walletStateForRequiredChain.deployed;
1629
1736
 
1630
- this.parentWindow = parent.window.opener; // listen for window-transport requests
1737
+ if (!networks) {
1738
+ throw new Error(`isWalletUpToDate util: could not get networks from signer`);
1739
+ }
1631
1740
 
1632
- window.addEventListener('message', this.onWindowEvent, false);
1633
- this._registered = true; // send open event to the app which opened us
1741
+ const authChain = networks.find(network => network.isAuthChain);
1634
1742
 
1635
- this.open(session).then(opened => {
1636
- if (!opened) {
1637
- var _session;
1743
+ if (!authChain) {
1744
+ throw new Error(`isWalletUpToDate util: could not get auth chain network information`);
1745
+ }
1638
1746
 
1639
- const err = `failed to open to network ${(_session = session) == null ? void 0 : _session.networkId}`;
1640
- utils.logger.error(err);
1641
- this.notifyClose({
1642
- message: err
1643
- });
1644
- window.close();
1645
- }
1646
- }).catch(e => {
1647
- var _session2;
1747
+ const authChainId = authChain.chainId;
1748
+ const authChainConfig = (_walletState$find = walletState.find(state => state.chainId === authChainId)) == null ? void 0 : _walletState$find.config;
1648
1749
 
1649
- const err = `failed to open to network ${(_session2 = session) == null ? void 0 : _session2.networkId}, due to: ${e}`;
1650
- utils.logger.error(err);
1651
- this.notifyClose({
1652
- message: err
1653
- });
1654
- window.close();
1655
- });
1750
+ if (!authChainConfig) {
1751
+ throw new Error(`isWalletUpToDate util: could not get auth chain config`);
1656
1752
  }
1657
1753
 
1658
- unregister() {
1659
- window.removeEventListener('message', this.onWindowEvent);
1660
- this._registered = false;
1661
- } // onmessage is called when (the wallet) receives request messages from the dapp
1662
- // over the window post-messaging transport
1754
+ const requiredChainConfig = walletStateForRequiredChain.config;
1663
1755
 
1756
+ if (!requiredChainConfig) {
1757
+ throw new Error(`isWalletUpToDate util: could not get config for chainId ${chainId}`);
1758
+ }
1664
1759
 
1665
- // postMessage sends message to the dapp window
1666
- sendMessage(message) {
1667
- // prepare payload
1668
- const payload = JSON.stringify(message); // post-message to app.
1669
- // only for init requests, we send to '*' origin
1760
+ const isUpToDate = config.isConfigEqual(authChainConfig, requiredChainConfig);
1761
+ return isDeployed && isUpToDate;
1762
+ }; // window.localstorage helper
1670
1763
 
1671
- if (message.type === exports.EventType.INIT) {
1672
- this.postMessage(payload, true);
1673
- } else {
1674
- this.postMessage(payload);
1675
- }
1764
+ class LocalStore {
1765
+ constructor(key, def) {
1766
+ this.def = def;
1767
+ this.key = void 0;
1768
+ this.key = key;
1676
1769
  }
1677
1770
 
1678
- get isPopup() {
1679
- return this._isPopup;
1680
- }
1771
+ get() {
1772
+ const val = window.localStorage.getItem(this.key);
1681
1773
 
1682
- postMessage(message, init = false) {
1683
- if (init !== true && this._init !== exports.InitState.OK) {
1684
- utils.logger.error('impossible state, should not be calling postMessage until inited');
1685
- return;
1774
+ if (val === null) {
1775
+ return this.def;
1686
1776
  }
1687
1777
 
1688
- if (init) {
1689
- // init message transmission to global target -- for 'init' payloads only
1690
- this.parentWindow.postMessage(message, '*');
1691
- } else {
1692
- // open message transmission
1693
- if (this.appOrigin && this.appOrigin.length > 4) {
1694
- // just above '.com'
1695
- this.parentWindow.postMessage(message, this.appOrigin);
1696
- } else {
1697
- utils.logger.error('unable to postMessage as parentOrigin is invalid');
1698
- }
1778
+ try {
1779
+ return JSON.parse(val);
1780
+ } catch (err) {
1781
+ console.error(err);
1699
1782
  }
1783
+
1784
+ return;
1785
+ }
1786
+
1787
+ set(val) {
1788
+ val ? window.localStorage.setItem(this.key, JSON.stringify(val)) : window.localStorage.removeItem(this.key);
1789
+ }
1790
+
1791
+ del() {
1792
+ window.localStorage.removeItem(this.key);
1700
1793
  }
1701
1794
 
1702
1795
  }
1703
1796
 
1704
- const SIGNER_READY_TIMEOUT = 10000;
1705
- class WalletRequestHandler {
1706
- // signer interface of the wallet. A null value means there is no signer (ie. user not signed in). An undefined
1707
- // value means the signer state is unknown, usually meaning the wallet app is booting up and initializing. Of course
1708
- // a Signer value is the actually interface to a signed-in account
1709
- constructor(_signer, prompter, mainnetNetworks, testnetNetworks = []) {
1797
+ const TRANSPORT_SESSION_LS_KEY = '@sequence.transportSession';
1798
+ class BaseWalletTransport {
1799
+ // appOrigin identifies the dapp's origin which opened the app. A transport
1800
+ // will auto-detect and set this value if it can. This is determined
1801
+ // as the parent app/window which opened the wallet.
1802
+ constructor(walletRequestHandler) {
1710
1803
  var _this = this;
1711
1804
 
1712
- this.signer = void 0;
1713
- this.signerReadyCallbacks = [];
1714
- this.prompter = void 0;
1715
- this.mainnetNetworks = void 0;
1716
- this.testnetNetworks = void 0;
1717
- this._openIntent = void 0;
1718
- this._connectOptions = void 0;
1719
- this._defaultNetworkId = void 0;
1720
- this._chainId = void 0;
1721
- this.events = new eventemitter2.EventEmitter2();
1805
+ this.walletRequestHandler = void 0;
1806
+ this._sessionId = void 0;
1807
+ this._registered = void 0;
1808
+ this._init = void 0;
1809
+ this._initNonce = void 0;
1810
+ this._initCallback = void 0;
1811
+ this.appOrigin = void 0;
1722
1812
 
1723
- this.promptConnect = async function (options) {
1724
- if (!options && !_this._connectOptions) {
1725
- // this is an unexpected state and should not happen
1726
- throw new Error('prompter connect options are empty');
1727
- }
1813
+ this.sendAsync = async function (request, callback, chainId) {
1814
+ throw new Error('abstract method');
1815
+ };
1728
1816
 
1729
- if (!_this.prompter) {
1730
- // if prompter is null, we'll auto connect
1731
- return _this.connect(options);
1732
- }
1817
+ this.handleMessage = async function (message) {
1818
+ const request = message; // ensure initial handshake is complete before accepting
1819
+ // other kinds of messages.
1733
1820
 
1734
- const promptConnectDetails = await _this.prompter.promptConnect(options || _this._connectOptions).catch(_ => {
1735
- return {
1736
- connected: false
1737
- };
1738
- });
1739
- const connectDetails = promptConnectDetails;
1821
+ if (_this._init !== exports.InitState.OK) {
1822
+ if (request.type === exports.EventType.INIT) {
1823
+ if (_this.isValidInitAck(message)) {
1824
+ // successful init
1825
+ if (_this._initCallback) _this._initCallback();
1826
+ } else {
1827
+ // failed init
1828
+ if (_this._initCallback) _this._initCallback('invalid init');
1829
+ return;
1830
+ }
1831
+ }
1740
1832
 
1741
- if (connectDetails.connected && !connectDetails.session) {
1742
- connectDetails.session = await _this.walletSession();
1743
- }
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
1744
1839
 
1745
- return promptConnectDetails;
1746
- };
1747
1840
 
1748
- this.sendAsync = async function (request, callback, chainId) {
1749
- const response = {
1750
- jsonrpc: '2.0',
1751
- id: request.id,
1752
- result: null
1753
- };
1754
- await _this.getSigner();
1755
-
1756
- try {
1757
- // only allow public json rpc method to the provider when user is not logged in, aka signer is not set
1758
- if ((!_this.signer || _this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
1759
- // throw new Error(`not logged in. ${request.method} is unavailable`)
1760
- throw ErrSignedInRequired;
1761
- } // wallet signer
1762
-
1763
-
1764
- const signer = _this.signer;
1765
- if (!signer) throw new Error('WalletRequestHandler: wallet signer is not configured'); // fetch the provider for the specific chain, or undefined will select defaultChain
1766
-
1767
- const provider = await signer.getProvider(chainId);
1768
- if (!provider) throw new Error(`WalletRequestHandler: wallet provider is not configured for chainId ${chainId}`);
1769
-
1770
- switch (request.method) {
1771
- case 'net_version':
1772
- {
1773
- const result = await provider.send('net_version', []);
1774
- response.result = result;
1775
- break;
1776
- }
1777
-
1778
- case 'eth_chainId':
1779
- {
1780
- const result = await provider.send('eth_chainId', []);
1781
- response.result = result;
1782
- break;
1783
- }
1784
-
1785
- case 'eth_accounts':
1786
- {
1787
- const walletAddress = await signer.getAddress();
1788
- response.result = [walletAddress];
1789
- break;
1790
- }
1791
-
1792
- case 'eth_getBalance':
1793
- {
1794
- const [accountAddress, blockTag] = request.params;
1795
- const walletBalance = await provider.getBalance(accountAddress, blockTag);
1796
- response.result = walletBalance.toHexString();
1797
- break;
1798
- }
1799
-
1800
- case 'personal_sign':
1801
- case 'eth_sign':
1802
- {
1803
- // note: message from json-rpc input is in hex format
1804
- let message; // there is a difference in the order of the params:
1805
- // personal_sign: [data, address]
1806
- // eth_sign: [address, data]
1807
-
1808
- if (request.method === 'personal_sign') {
1809
- const [data, address] = request.params;
1810
- message = data;
1811
- } else {
1812
- const [address, data] = request.params;
1813
- message = data;
1814
- }
1815
-
1816
- let sig = ''; // TODO:
1817
- // if (process.env.TEST_MODE === 'true' && this.prompter === null) {
1818
-
1819
- if (_this.prompter === null) {
1820
- // prompter is null, so we'll sign from here
1821
- sig = await signer.signMessage(ethers.ethers.utils.arrayify(message), chainId);
1822
- } else {
1823
- const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
1824
-
1825
- if (promptResultForDeployment) {
1826
- sig = await _this.prompter.promptSignMessage({
1827
- chainId: chainId,
1828
- message: message
1829
- });
1830
- }
1831
- }
1832
-
1833
- if (sig && sig.length > 0) {
1834
- response.result = sig;
1835
- } else {
1836
- // The user has declined the request when value is null
1837
- throw new Error('declined by user');
1838
- }
1839
-
1840
- break;
1841
- }
1842
-
1843
- case 'eth_signTypedData':
1844
- case 'eth_signTypedData_v4':
1845
- {
1846
- // note: signingAddress from json-rpc input is in hex format, and typedDataObject
1847
- // should be an object, but in some instances may be double string encoded
1848
- const [signingAddress, typedDataObject] = request.params;
1849
- 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
+ }
1850
1853
 
1851
- if (typeof typedDataObject === 'string') {
1852
- try {
1853
- typedData = JSON.parse(typedDataObject);
1854
- } catch (e) {}
1855
- } else {
1856
- typedData = typedDataObject;
1857
- }
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
1858
1857
 
1859
- if (!typedData || !typedData.domain || !typedData.types || !typedData.message) {
1860
- throw new Error('invalid typedData object');
1861
- }
1858
+ return;
1859
+ }
1862
1860
 
1863
- let sig = '';
1861
+ case exports.EventType.MESSAGE:
1862
+ {
1863
+ const response = await _this.walletRequestHandler.sendMessageRequest(request);
1864
1864
 
1865
- if (_this.prompter === null) {
1866
- // prompter is null, so we'll sign from here
1867
- sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainId);
1868
- } else {
1869
- const promptResultForDeployment = await _this.handleConfirmWalletDeployPrompt(_this.prompter, signer, chainId);
1865
+ _this.sendMessage(response);
1870
1866
 
1871
- if (promptResultForDeployment) {
1872
- sig = await _this.prompter.promptSignMessage({
1873
- chainId: chainId,
1874
- typedData: typedData
1875
- });
1876
- }
1877
- }
1867
+ if (response.data.error) ;
1878
1868
 
1879
- if (sig && sig.length > 0) {
1880
- response.result = sig;
1881
- } else {
1882
- // The user has declined the request when value is null
1883
- throw new Error('declined by user');
1884
- }
1869
+ return;
1870
+ }
1885
1871
 
1886
- break;
1887
- }
1872
+ default:
1873
+ {
1874
+ utils.logger.error(`unexpected payload type ${request.type}`);
1875
+ }
1876
+ }
1877
+ };
1888
1878
 
1889
- case 'eth_sendTransaction':
1890
- {
1891
- // https://eth.wiki/json-rpc/API#eth_sendtransaction
1892
- const [transactionParams] = request.params;
1893
- let txnHash = '';
1879
+ this.sendMessageRequest = async function (message) {
1880
+ return _this.walletRequestHandler.sendMessageRequest(message);
1881
+ };
1894
1882
 
1895
- if (_this.prompter === null) {
1896
- // prompter is null, so we'll send from here
1897
- const txnResponse = await signer.sendTransaction(transactionParams, chainId);
1898
- txnHash = txnResponse.hash;
1899
- } else {
1900
- // prompt user to provide the response
1901
- txnHash = await _this.prompter.promptSendTransaction(transactionParams, chainId);
1902
- }
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
1903
1890
 
1904
- if (txnHash) {
1905
- response.result = txnHash;
1906
- } else {
1907
- // The user has declined the request when value is null
1908
- throw new Error('declined by user');
1909
- }
1891
+ _this.saveTransportSession({
1892
+ sessionId,
1893
+ intent,
1894
+ networkId
1895
+ });
1896
+ }
1910
1897
 
1911
- break;
1912
- }
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.
1913
1902
 
1914
- case 'eth_signTransaction':
1915
- {
1916
- // https://eth.wiki/json-rpc/API#eth_signTransaction
1917
- const [transaction] = request.params;
1918
- const sender = ethers.ethers.utils.getAddress(transaction.from);
1919
1903
 
1920
- if (sender !== (await signer.getAddress())) {
1921
- throw new Error('sender address does not match wallet');
1922
- }
1904
+ await _this.init(); // Prepare connect options from intent
1923
1905
 
1924
- if (_this.prompter === null) {
1925
- // The eth_signTransaction method expects a `string` return value we instead return a `SignedTransactions` object,
1926
- // this can only be broadcasted using an RPC provider with support for signed Sequence transactions, like this one.
1927
- //
1928
- // TODO: verify serializing / transporting the SignedTransaction object works as expected, most likely however
1929
- // we will want to resolveProperties the bignumber values to hex strings
1930
- response.result = await signer.signTransactions(transaction, chainId);
1931
- } else {
1932
- response.result = await _this.prompter.promptSignTransaction(transaction, chainId);
1933
- }
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.
1934
1911
 
1935
- 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');
1936
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
+ }
1937
1926
 
1938
- case 'eth_sendRawTransaction':
1939
- {
1940
- // NOTE: we're not using a prompter here as the transaction is already signed
1941
- // and would have prompted the user upon signing.
1942
- // https://eth.wiki/json-rpc/API#eth_sendRawTransaction
1943
- if (transactions.isSignedTransactions(request.params[0])) {
1944
- const txChainId = ethers.BigNumber.from(request.params[0].chainId).toNumber();
1945
- const tx = await (await signer.getRelayer(txChainId)).relay(request.params[0]);
1946
- response.result = (await tx).hash;
1947
- } else {
1948
- const tx = await provider.sendTransaction(request.params[0]);
1949
- response.result = tx.hash;
1950
- }
1927
+ if (connectOptions.app) {
1928
+ connectOptions.app = utils.sanitizeAlphanumeric(connectOptions.app);
1929
+ } // Set connect options on the walletRequestHandler as our primary
1930
+ // wallet controller
1951
1931
 
1952
- break;
1953
- }
1954
1932
 
1955
- case 'eth_getTransactionCount':
1956
- {
1957
- const address = ethers.ethers.utils.getAddress(request.params[0]);
1958
- const tag = request.params[1];
1959
- const walletAddress = ethers.ethers.utils.getAddress(await signer.getAddress());
1933
+ _this.walletRequestHandler.setConnectOptions(connectOptions);
1960
1934
 
1961
- if (address === walletAddress) {
1962
- const count = await signer.getTransactionCount(tag);
1963
- response.result = ethers.ethers.BigNumber.from(count).toHexString();
1964
- } else {
1965
- const count = await provider.getTransactionCount(address, tag);
1966
- response.result = ethers.ethers.BigNumber.from(count).toHexString();
1967
- }
1935
+ if (connectOptions.networkId) {
1936
+ networkId = connectOptions.networkId;
1937
+ }
1938
+ } else {
1939
+ _this.walletRequestHandler.setConnectOptions(undefined);
1940
+ } // ensure signer is ready
1968
1941
 
1969
- break;
1970
- }
1971
1942
 
1972
- case 'eth_blockNumber':
1973
- {
1974
- response.result = await provider.getBlockNumber();
1975
- break;
1976
- }
1943
+ await _this.walletRequestHandler.getSigner(); // Notify open and proceed to prompt for connection if intended
1977
1944
 
1978
- case 'eth_getBlockByNumber':
1979
- {
1980
- response.result = await provider.getBlock(request.params[0]
1981
- /* , jsonRpcRequest.params[1] */
1982
- );
1983
- break;
1984
- }
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
+ });
1985
1950
 
1986
- case 'eth_getBlockByHash':
1987
- {
1988
- response.result = await provider.getBlock(request.params[0]
1989
- /* , jsonRpcRequest.params[1] */
1990
- );
1991
- break;
1992
- }
1951
+ return true;
1952
+ } else {
1953
+ // Set default network, in case of error chainId will be undefined or 0
1954
+ let chainId = undefined;
1993
1955
 
1994
- case 'eth_getTransactionByHash':
1995
- {
1996
- response.result = await provider.getTransaction(request.params[0]);
1997
- break;
1998
- }
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
1999
1965
 
2000
- case 'eth_call':
2001
- {
2002
- const [transactionObject, blockTag] = request.params;
2003
- response.result = await provider.call(transactionObject, blockTag);
2004
- break;
2005
- }
2006
1966
 
2007
- case 'eth_getCode':
2008
- {
2009
- const [contractAddress, blockTag] = request.params;
2010
- response.result = await provider.getCode(contractAddress, blockTag);
2011
- break;
2012
- }
1967
+ if (!chainId || chainId <= 0) {
1968
+ _this.notifyOpen({
1969
+ sessionId: _this._sessionId,
1970
+ error: `failed to open wallet on network ${networkId}`
1971
+ });
2013
1972
 
2014
- case 'eth_estimateGas':
2015
- {
2016
- const [transactionObject] = request.params;
2017
- response.result = await provider.estimateGas(transactionObject);
2018
- break;
2019
- }
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
2020
1977
 
2021
- case 'eth_gasPrice':
2022
- {
2023
- const gasPrice = await provider.getGasPrice();
2024
- response.result = gasPrice.toHexString();
2025
- break;
2026
- }
2027
- // smart wallet method
2028
1978
 
2029
- case 'sequence_getWalletContext':
2030
- {
2031
- response.result = await signer.getWalletContext();
2032
- break;
2033
- }
2034
- // 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
+ });
2035
1984
 
2036
- case 'sequence_getWalletConfig':
2037
- {
2038
- const [_chainId] = request.params;
2039
- response.result = await signer.getWalletConfig(_chainId);
2040
- break;
2041
- }
2042
- // smart wallet method
1985
+ try {
1986
+ const connectDetails = await _this.walletRequestHandler.promptConnect(intent.options);
2043
1987
 
2044
- case 'sequence_getWalletState':
2045
- {
2046
- const [_chainId2] = request.params;
2047
- response.result = await signer.getWalletState(_chainId2);
2048
- break;
1988
+ if (connectDetails.connected) {
1989
+ _this.walletRequestHandler.notifyConnect(connectDetails);
2049
1990
  }
2050
- // smart wallet method
2051
-
2052
- case 'sequence_getNetworks':
2053
- {
2054
- // NOTE: must ensure that the response result below returns clean serialized data, which is to omit
2055
- // the provider and relayer objects and only return the urls so can be reinstantiated on dapp side.
2056
- // This is handled by this.getNetworks() but noted here for future readers.
2057
- response.result = await _this.getNetworks(true);
2058
- 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();
2059
1997
  }
2060
- // smart wallet method
2061
-
2062
- case 'sequence_updateConfig':
2063
- {
2064
- 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.
2065
- // However, if we ever want this, we can enable it but must also use the prompter
2066
- // for confirmation.
2067
- //
2068
- // const [newConfig] = request.params
2069
- // 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
+ }
2070
2010
 
2071
- break;
2072
- }
2073
- // smart wallet method
2011
+ return true;
2012
+ };
2074
2013
 
2075
- case 'sequence_publishConfig':
2076
- {
2077
- throw new Error('sequence_publishConfig method is not allowed from a dapp');
2078
- break;
2079
- }
2080
- // relayer method
2014
+ this.saveTransportSession = session => {
2015
+ window.localStorage.setItem(TRANSPORT_SESSION_LS_KEY, JSON.stringify(session));
2016
+ };
2081
2017
 
2082
- case 'sequence_estimateGasLimits':
2083
- {
2084
- // TODO
2085
- break;
2086
- }
2087
- // relayer method
2018
+ this.getCachedTransportSession = () => {
2019
+ const session = window.localStorage.getItem(TRANSPORT_SESSION_LS_KEY);
2088
2020
 
2089
- case 'sequence_gasRefundOptions':
2090
- {
2091
- // TODO
2092
- break;
2093
- }
2094
- // 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
+ };
2095
2028
 
2096
- case 'sequence_getNonce':
2097
- {
2098
- // TODO
2099
- break;
2100
- }
2101
- // 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
2102
2033
 
2103
- case 'sequence_relay':
2104
- {
2105
- // TODO
2106
- break;
2107
- }
2108
- // 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
2109
2038
 
2110
- case 'sequence_setDefaultNetwork':
2111
- {
2112
- 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);
2113
2048
 
2114
- if (!defaultNetworkId) {
2115
- throw new Error('invalid request, method argument defaultNetworkId cannot be empty');
2116
- }
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
+ }
2117
2064
 
2118
- const ok = await _this.setDefaultNetwork(defaultNetworkId);
2065
+ get registered() {
2066
+ return this._registered;
2067
+ }
2119
2068
 
2120
- if (!ok) {
2121
- throw new Error(`unable to set default network ${defaultNetworkId}`);
2122
- }
2069
+ register() {
2070
+ throw new Error('abstract method');
2071
+ }
2123
2072
 
2124
- response.result = await _this.getNetworks(true);
2125
- break;
2126
- }
2073
+ unregister() {
2074
+ throw new Error('abstract method');
2075
+ }
2127
2076
 
2128
- default:
2129
- {
2130
- // NOTE: provider here will be chain-bound if chainId is provided
2131
- const providerResponse = await provider.send(request.method, request.params);
2132
- response.result = providerResponse;
2133
- }
2134
- }
2135
- } catch (err) {
2136
- 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
+ }
2137
2080
 
2138
- response.result = null;
2139
- response.error = _extends({}, new Error(err), {
2140
- code: 4001
2141
- });
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
2142
2096
  }
2097
+ });
2098
+ }
2143
2099
 
2144
- callback(undefined, response);
2145
- };
2100
+ notifyClose(error) {
2101
+ this.sendMessage({
2102
+ idx: -1,
2103
+ type: exports.EventType.CLOSE,
2104
+ data: error ? {
2105
+ error
2106
+ } : null
2107
+ });
2108
+ }
2146
2109
 
2147
- this.isSignedIn = async function () {
2148
- await _this.signerReady();
2149
- return !!_this.signer;
2150
- };
2110
+ notifyConnect(connectDetails) {
2111
+ this.sendMessage({
2112
+ idx: -1,
2113
+ type: exports.EventType.CONNECT,
2114
+ data: connectDetails
2115
+ });
2116
+ }
2151
2117
 
2152
- this.getSigner = async function () {
2153
- 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
+ }
2154
2127
 
2155
- if (_this.signer === undefined) {
2156
- throw new Error('signerReady failed resolve');
2157
- }
2128
+ notifyAccountsChanged(accounts) {
2129
+ this.sendMessage({
2130
+ idx: -1,
2131
+ type: exports.EventType.ACCOUNTS_CHANGED,
2132
+ data: accounts
2133
+ });
2134
+ }
2158
2135
 
2159
- return _this.signer;
2160
- };
2136
+ notifyChainChanged(chainIdHex) {
2137
+ this.sendMessage({
2138
+ idx: -1,
2139
+ type: exports.EventType.CHAIN_CHANGED,
2140
+ data: chainIdHex
2141
+ });
2142
+ }
2161
2143
 
2162
- this.signer = _signer;
2163
- this.prompter = prompter;
2164
- this.mainnetNetworks = mainnetNetworks;
2165
- this.testnetNetworks = testnetNetworks;
2144
+ notifyNetworks(networks) {
2145
+ this.sendMessage({
2146
+ idx: -1,
2147
+ type: exports.EventType.NETWORKS,
2148
+ data: networks
2149
+ });
2166
2150
  }
2167
2151
 
2168
- async signIn(signer, options = {}) {
2169
- this.setSigner(signer);
2170
- const {
2171
- connect,
2172
- mainnetNetworks,
2173
- testnetNetworks,
2174
- defaultNetworkId
2175
- } = options;
2152
+ notifyWalletContext(walletContext) {
2153
+ this.sendMessage({
2154
+ idx: -1,
2155
+ type: exports.EventType.WALLET_CONTEXT,
2156
+ data: walletContext
2157
+ });
2158
+ }
2176
2159
 
2177
- if (mainnetNetworks && mainnetNetworks.length > 0) {
2178
- 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;
2179
2165
  }
2180
2166
 
2181
- if (testnetNetworks && testnetNetworks.length > 0) {
2182
- this.testnetNetworks = testnetNetworks;
2167
+ if (message.type !== exports.EventType.INIT) {
2168
+ utils.logger.warn('isValidInitAck, invalid message type, expecting init');
2169
+ return false;
2183
2170
  }
2184
2171
 
2185
- if ((!this.mainnetNetworks || this.mainnetNetworks.length === 0) && (!this.testnetNetworks || this.testnetNetworks.length === 0)) {
2186
- 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;
2187
2180
  }
2188
2181
 
2189
- 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
2190
2186
 
2191
- if (networkId) {
2192
- if (!(await this.setDefaultNetwork(networkId, false))) {
2193
- 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;
2194
2198
  }
2195
- } // Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
2196
- // necessary auth request, and then notify the dapp of the 'connect' details.
2197
- //
2198
- // NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
2199
- // we don't trigger the promptConnect flow, as we consider the user just authenticated
2200
- // for this dapp, so its safe to authorize in the connect() method without the prompt.
2201
- //
2202
- // NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
2203
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
2204
2205
 
2205
- if (connect) {
2206
- const connectOptions = this._connectOptions;
2207
- const connectDetails = await this.connect(connectOptions);
2208
- this.notifyConnect(connectDetails);
2209
2206
 
2210
- if (!connectOptions || connectOptions.keepWalletOpened !== true) {
2211
- this.notifyClose();
2212
- }
2213
- }
2214
- }
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
2228
+
2215
2229
 
2216
- signOut() {
2217
- // signed out state
2218
- this.setSigner(null);
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
+ });
2219
2241
  }
2220
2242
 
2221
- signerReset() {
2222
- // resetting signer puts the wallet in an uninitialized state, which requires the app to
2223
- // re-initiatize and set the signer either as "null" (ie. no signer) or "Signer" (ie. signed in).
2224
- this.signer = undefined;
2225
- }
2243
+ }
2226
2244
 
2227
- signerReady(timeout = SIGNER_READY_TIMEOUT) {
2228
- return new Promise((resolve, reject) => {
2229
- if (this.signer !== undefined) {
2230
- resolve();
2231
- } else {
2232
- setTimeout(() => {
2233
- if (this.signer === undefined) {
2234
- this.signerReadyCallbacks = [];
2235
- reject(`signerReady timed out`);
2236
- }
2237
- }, timeout);
2238
- this.signerReadyCallbacks.push(resolve);
2239
- }
2240
- });
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;
2241
2255
  }
2242
2256
 
2243
- async connect(options) {
2244
- if (!this.signer) {
2245
- return {
2246
- connected: false,
2247
- chainId: '0x0',
2248
- error: 'unable to connect without signed in account'
2249
- };
2250
- }
2257
+ }
2258
+ class ProxyMessageChannelPort {
2259
+ constructor() {
2260
+ this.conn = void 0;
2261
+ this.events = new eventemitter2.EventEmitter2();
2251
2262
 
2252
- const connectDetails = {
2253
- connected: true,
2254
- chainId: ethers.ethers.utils.hexlify(await this.getChainId())
2263
+ this.handleMessage = message => {
2264
+ throw new Error('ProxyMessageChannelPort is not registered');
2255
2265
  };
2256
2266
 
2257
- if (options && options.authorize) {
2258
- // Perform ethauth eip712 request and construct the ConnectDetails response
2259
- // including the auth proof
2260
- const authOptions = {
2261
- app: options.app,
2262
- origin: options.origin,
2263
- expiry: options.expiry
2264
- }; // if (typeof(options.authorize) === 'object') {
2265
- // authOptions = { ...authOptions, ...options.authorize }
2266
- // }
2267
+ this.sendMessage = message => {
2268
+ this.conn.handleMessage(message); // trigger events
2267
2269
 
2268
- try {
2269
- connectDetails.proof = await auth.signAuthorization(this.signer, authOptions);
2270
- } catch (err) {
2271
- utils.logger.warn(`connect, signAuthorization failed for options: ${JSON.stringify(options)}, due to: ${err.message}`);
2272
- return {
2273
- connected: false,
2274
- chainId: '0x0',
2275
- error: `signAuthorization failed: ${err.message}`
2276
- };
2270
+ if (message.type === 'open') {
2271
+ this.events.emit('open', message);
2277
2272
  }
2278
- } // Build session response for connect details
2279
-
2280
-
2281
- connectDetails.session = await this.walletSession();
2282
- return connectDetails;
2283
- }
2284
-
2285
- // sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler
2286
- // (aka, the signer in this instance) and then responds with a wrapped response of
2287
- // ProviderMessageResponse to be sent over the transport
2288
- sendMessageRequest(message) {
2289
- return new Promise(resolve => {
2290
- this.sendAsync(message.data, (error, response) => {
2291
- // TODO: if response includes data.error, why do we need a separate error argument here?
2292
- const responseMessage = _extends({}, message, {
2293
- data: response
2294
- }); // NOTE: we always resolve here, are the sendAsync call will wrap any exceptions
2295
- // in the error field of the response to ensure we send back to the user
2296
2273
 
2274
+ if (message.type === 'close') {
2275
+ this.events.emit('close', message);
2276
+ }
2297
2277
 
2298
- resolve(responseMessage);
2299
- }, message.chainId);
2300
- });
2301
- } // sendAsync implements the JsonRpcHandler interface for sending JsonRpcRequests to the wallet
2278
+ if (message.type === 'connect') {
2279
+ this.events.emit('connect', message);
2280
+ }
2302
2281
 
2282
+ if (message.type === 'disconnect') {
2283
+ this.events.emit('disconnect', message);
2284
+ }
2285
+ };
2286
+ }
2303
2287
 
2304
2288
  on(event, fn) {
2305
2289
  this.events.on(event, fn);
@@ -2309,750 +2293,785 @@ class WalletRequestHandler {
2309
2293
  this.events.once(event, fn);
2310
2294
  }
2311
2295
 
2312
- async getAddress() {
2313
- if (!this.signer) {
2314
- return '';
2315
- } else {
2316
- return this.signer.getAddress();
2317
- }
2318
- }
2296
+ }
2319
2297
 
2320
- async getChainId() {
2321
- if (!this.signer) {
2322
- return 0;
2323
- } else {
2324
- if (this._chainId) return this._chainId; // memoized
2298
+ class ProxyMessageProvider extends BaseProviderTransport {
2299
+ constructor(port) {
2300
+ super();
2301
+ this.port = void 0;
2325
2302
 
2326
- this._chainId = await this.signer.getChainId();
2327
- return this._chainId;
2328
- }
2329
- }
2303
+ this.register = () => {
2304
+ this.port.handleMessage = message => {
2305
+ this.handleMessage(message);
2306
+ };
2330
2307
 
2331
- get openIntent() {
2332
- return this._openIntent;
2333
- }
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
+ };
2334
2322
 
2335
- setOpenIntent(intent) {
2336
- this._openIntent = intent;
2337
- }
2323
+ this.unregister = () => {
2324
+ this._registered = false;
2325
+ this.closeWallet();
2326
+ this.events.removeAllListeners(); // @ts-ignore
2338
2327
 
2339
- get connectOptions() {
2340
- return this._connectOptions;
2341
- }
2328
+ this.port.handleMessage = undefined;
2329
+ };
2342
2330
 
2343
- setConnectOptions(options) {
2344
- this._connectOptions = options;
2345
- }
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
+ };
2346
2348
 
2347
- get defaultNetworkId() {
2348
- return this._defaultNetworkId;
2349
- }
2349
+ this.state = exports.OpenState.CLOSED;
2350
+ this.port = port;
2350
2351
 
2351
- async setDefaultNetwork(chainId, notifyNetworks = true) {
2352
- if (!chainId) return undefined;
2353
- this._defaultNetworkId = chainId;
2354
- 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.
2355
2356
 
2356
- if (this.signer && this.signer.setNetworks) {
2357
- const defaultChainId = this.signer.setNetworks(this.mainnetNetworks, this.testnetNetworks, chainId);
2358
2357
 
2359
- if (defaultChainId && notifyNetworks) {
2360
- await this.notifyNetworks();
2361
- }
2358
+ this._init = exports.InitState.OK;
2359
+ }
2362
2360
 
2363
- return defaultChainId;
2364
- } else {
2365
- return undefined;
2366
- }
2361
+ closeWallet() {
2362
+ this.sendMessage({
2363
+ idx: -1,
2364
+ type: exports.EventType.CLOSE,
2365
+ data: null
2366
+ });
2367
+ this.close();
2367
2368
  }
2368
2369
 
2369
- async getNetworks(jsonRpcResponse) {
2370
- if (!this.signer) {
2371
- utils.logger.warn('signer not set: getNetworks is returning an empty list');
2372
- return [];
2370
+ sendMessage(message) {
2371
+ if (!message.idx) {
2372
+ throw new Error('message idx is empty');
2373
2373
  }
2374
2374
 
2375
- const networks = await this.signer.getNetworks();
2375
+ this.port.sendMessage(message);
2376
+ }
2376
2377
 
2377
- if (jsonRpcResponse) {
2378
- // omit provider and relayer objects as they are not serializable
2379
- return networks.map(n => {
2380
- const network = _extends({}, n);
2378
+ }
2381
2379
 
2382
- network.provider = undefined;
2383
- network.relayer = undefined;
2384
- return network;
2385
- });
2386
- } else {
2387
- return networks;
2388
- }
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;
2389
2386
  }
2390
2387
 
2391
- async walletSession() {
2392
- return !this.signer ? undefined : {
2393
- walletContext: await this.signer.getWalletContext(),
2394
- accountAddress: await this.signer.getAddress(),
2395
- networks: await this.getNetworks(true)
2388
+ register() {
2389
+ this.port.handleMessage = message => {
2390
+ this.handleMessage(message);
2396
2391
  };
2397
- }
2398
2392
 
2399
- notifyConnect(connectDetails) {
2400
- var _connectDetails$sessi;
2393
+ this._registered = true;
2394
+ } // note: we can't decide whether to restore the session within register(), because session info is
2395
+ // received asyncronously via EventType.OPEN after register() is executed.
2396
+ // And in the case of a redirect/reload, EventType.OPEN is not sent at all,
2397
+ // because the wallet is already open.
2398
+ //
2399
+ // call this method from wallet redirect hander when a session restore is needed
2401
2400
 
2402
- this.events.emit('connect', connectDetails);
2403
2401
 
2404
- if ((_connectDetails$sessi = connectDetails.session) != null && _connectDetails$sessi.accountAddress) {
2405
- var _connectDetails$sessi2;
2402
+ restoreSession() {
2403
+ const cachedSession = this.getCachedTransportSession();
2406
2404
 
2407
- this.events.emit('accountsChanged', [(_connectDetails$sessi2 = connectDetails.session) == null ? void 0 : _connectDetails$sessi2.accountAddress]);
2405
+ if (cachedSession) {
2406
+ this.open(cachedSession);
2408
2407
  }
2409
2408
  }
2410
2409
 
2411
- notifyDisconnect() {
2412
- this.events.emit('accountsChanged', []);
2413
- this.events.emit('networks', []);
2414
- this.events.emit('disconnect');
2410
+ unregister() {
2411
+ // @ts-ignore
2412
+ this.port.handleMessage = undefined;
2413
+ this._registered = false;
2415
2414
  }
2416
2415
 
2417
- async notifyNetworks(networks) {
2418
- const n = networks || (await this.getNetworks(true));
2419
- this.events.emit('networks', n);
2420
-
2421
- if (n && n.length > 0) {
2422
- this.events.emit('chainChanged', ethers.ethers.utils.hexlify(n[0].chainId));
2423
- } else {
2424
- this.events.emit('chainChanged', '0x0');
2425
- }
2416
+ sendMessage(message) {
2417
+ this.port.sendMessage(message);
2426
2418
  }
2427
2419
 
2428
- async notifyWalletContext() {
2429
- if (!this.signer) {
2430
- utils.logger.warn('signer not set: skipping to notify wallet context');
2431
- return;
2432
- }
2420
+ }
2433
2421
 
2434
- const walletContext = await this.signer.getWalletContext();
2435
- this.events.emit('walletContext', walletContext);
2436
- }
2422
+ class MuxMessageProvider {
2423
+ constructor(...messageProviders) {
2424
+ var _this = this;
2437
2425
 
2438
- notifyClose(error) {
2439
- this.events.emit('close', error);
2440
- }
2426
+ this.messageProviders = void 0;
2427
+ this.provider = void 0;
2441
2428
 
2442
- setSigner(signer) {
2443
- 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
+ }
2444
2435
 
2445
- if (signer !== undefined) {
2446
- for (let i = 0; i < this.signerReadyCallbacks.length; i++) {
2447
- 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;
2448
2462
  }
2449
2463
 
2450
- this.signerReadyCallbacks = [];
2451
- }
2452
- }
2464
+ this.messageProviders.forEach(m => m.openWallet(path, intent, networkId));
2465
+ };
2453
2466
 
2454
- async handleConfirmWalletDeployPrompt(prompter, signer, chainId) {
2455
- // check if wallet is deployed and up to date, if not, prompt user to deploy
2456
- // if no chainId is provided, we'll assume the wallet is auth chain wallet and is up to date
2457
- if (!chainId) {
2458
- return true;
2459
- }
2467
+ this.sendAsync = async function (request, callback, chainId) {
2468
+ if (_this.provider) {
2469
+ _this.provider.sendAsync(request, callback, chainId);
2460
2470
 
2461
- const isUpToDate = await isWalletUpToDate(signer, chainId);
2471
+ return;
2472
+ }
2462
2473
 
2463
- if (isUpToDate) {
2464
- return true;
2465
- }
2474
+ throw new Error('impossible state, wallet must be opened first');
2475
+ };
2466
2476
 
2467
- 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
+ }
2468
2481
 
2469
- if (promptResult) {
2470
- const isPromptResultCorrect = await isWalletUpToDate(signer, chainId);
2482
+ throw new Error('impossible state, wallet must be opened first');
2483
+ };
2471
2484
 
2472
- if (!isPromptResultCorrect) {
2473
- utils.logger.error('WalletRequestHandler: result for promptConfirmWalletDeploy is not correct');
2474
- return false;
2475
- } else {
2476
- return true;
2485
+ this.waitUntilOpened = async function () {
2486
+ if (_this.provider) {
2487
+ return _this.provider.waitUntilOpened();
2477
2488
  }
2478
- }
2479
2489
 
2480
- return false;
2481
- }
2490
+ return Promise.race(_this.messageProviders.map(p => p.waitUntilOpened()));
2491
+ };
2482
2492
 
2483
- }
2484
- 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
+ }
2485
2497
 
2486
- const CHANNEL_ID = 'sequence-extension-message-handler';
2487
- class ExtensionMessageHandler extends BaseWalletTransport {
2488
- constructor(walletRequestHandler, runtime) {
2489
- super(walletRequestHandler);
2490
- this.runtime = runtime;
2491
- this.port = void 0;
2492
- 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;
2493
2503
  }
2494
2504
 
2495
- register() {
2496
- this._registered = true;
2497
- this.port = this.runtime.connect({
2498
- name: CHANNEL_ID
2499
- });
2505
+ add(...messageProviders) {
2506
+ this.messageProviders.push(...messageProviders);
2500
2507
  }
2501
2508
 
2502
- sendMessage(message) {
2503
- console.log('[ExtensionMessageHandler send]', message);
2504
- this.port.postMessage(message);
2509
+ closeWallet() {
2510
+ if (this.provider) {
2511
+ this.provider.closeWallet();
2512
+ }
2505
2513
  }
2506
2514
 
2507
- }
2515
+ isOpened() {
2516
+ if (this.provider) {
2517
+ return this.provider.isOpened();
2518
+ }
2508
2519
 
2509
- class ExtensionMessageProvider extends BaseProviderTransport {
2510
- constructor(runtime) {
2511
- super();
2520
+ return false;
2521
+ }
2512
2522
 
2513
- this.register = () => {
2514
- this._registered = true;
2515
- };
2523
+ isConnected() {
2524
+ if (this.provider) {
2525
+ return this.provider.isConnected();
2526
+ }
2516
2527
 
2517
- runtime.onConnect.addListener(port => {
2518
- if (port.name === CHANNEL_ID) {
2519
- this._init = exports.InitState.OK;
2520
- port.onMessage.addListener(message => {
2521
- this.handleMessage(message);
2522
- });
2523
- }
2524
- });
2528
+ return false;
2525
2529
  }
2526
2530
 
2527
- sendMessage(message) {//noop
2531
+ on(event, fn) {
2532
+ if (this.provider) {
2533
+ this.provider.on(event, fn);
2534
+ return;
2535
+ }
2536
+
2537
+ this.messageProviders.forEach(m => {
2538
+ m.on(event, fn);
2539
+ });
2528
2540
  }
2529
2541
 
2530
- unregister() {//noop
2542
+ once(event, fn) {
2543
+ if (this.provider) {
2544
+ this.provider.once(event, fn);
2545
+ return;
2546
+ }
2547
+
2548
+ this.messageProviders.forEach(m => {
2549
+ m.once(event, fn);
2550
+ });
2531
2551
  }
2532
2552
 
2533
- openWallet(path, intent, networkId) {//noop
2553
+ emit(event, ...args) {
2554
+ if (this.provider) {
2555
+ return this.provider.emit(event, ...args);
2556
+ }
2557
+
2558
+ for (let i = 0; i < this.messageProviders.length; i++) {
2559
+ this.messageProviders[i].emit(event, ...args);
2560
+ }
2561
+
2562
+ return true;
2534
2563
  }
2535
2564
 
2536
- closeWallet() {//noop
2565
+ sendMessage(message) {
2566
+ if (!message.idx || message.idx <= 0) {
2567
+ throw new Error('message idx is empty');
2568
+ }
2569
+
2570
+ if (this.provider) {
2571
+ this.provider.sendMessage(message);
2572
+ } else {
2573
+ throw new Error('impossible state, wallet must be opened first');
2574
+ }
2537
2575
  }
2538
2576
 
2539
- }
2577
+ handleMessage(message) {
2578
+ if (this.provider) {
2579
+ this.provider.handleMessage(message);
2580
+ return;
2581
+ }
2540
2582
 
2541
- // to be used on injected window.ethereum EIP1193 proxy
2542
- class BaseInjectedTransport extends eventemitter2.EventEmitter2 {
2543
- constructor(stream) {
2544
- var _this;
2583
+ throw new Error('impossible state, wallet must be opened first');
2584
+ }
2545
2585
 
2546
- super();
2547
- _this = this;
2548
- this.stream = stream;
2549
- this.responseCallbacks = new Map();
2550
- this._messageIdx = 0;
2586
+ }
2551
2587
 
2552
- this.nextMessageIdx = () => ++this._messageIdx;
2588
+ let registeredWindowMessageProvider;
2589
+ class WindowMessageProvider extends BaseProviderTransport {
2590
+ constructor(walletAppURL) {
2591
+ super();
2592
+ this.walletURL = void 0;
2593
+ this.walletWindow = void 0;
2553
2594
 
2554
- this.handleMessage = message => {
2555
- if (!message.type || !message.data) {
2556
- return;
2557
- }
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
2558
2601
 
2559
- console.log('[received message]', message);
2560
- const requestIdx = message.idx;
2561
- const responseCallback = this.responseCallbacks.get(requestIdx);
2562
2602
 
2563
- if (requestIdx) {
2564
- this.responseCallbacks.delete(requestIdx);
2565
- }
2603
+ window.addEventListener('message', this.onWindowEvent);
2604
+ registeredWindowMessageProvider = this; // open heartbeat
2566
2605
 
2567
- switch (message.type) {
2568
- case exports.EventType.MESSAGE:
2569
- if (responseCallback) {
2570
- this.emit(exports.EventType.MESSAGE, message);
2571
- responseCallback(message.data.error, message);
2572
- } else {
2573
- // NOTE: this would occur if 'idx' isn't set, which should never happen
2574
- // or when we register two handler, or duplicate messages with the same idx are sent,
2575
- // all of which should be prevented prior to getting to this point
2576
- throw new Error('impossible state');
2606
+ this.on('open', () => {
2607
+ // Heartbeat to track if window closed
2608
+ const popup = this.walletWindow;
2609
+ const interval = setInterval(() => {
2610
+ if (popup && popup.closed) {
2611
+ clearInterval(interval);
2612
+ this.close();
2577
2613
  }
2614
+ }, 500);
2615
+ }); // close clean up
2578
2616
 
2579
- break;
2617
+ this.on('close', () => {
2618
+ if (this.walletWindow) {
2619
+ this.walletWindow.close();
2620
+ this.walletWindow = null;
2621
+ }
2622
+ });
2623
+ this._registered = true;
2624
+ };
2580
2625
 
2581
- case exports.EventType.DISCONNECT:
2582
- case exports.EventType.ACCOUNTS_CHANGED:
2583
- case exports.EventType.CHAIN_CHANGED:
2584
- this.emit(message.type, message.data);
2585
- break;
2626
+ this.unregister = () => {
2627
+ this._registered = false;
2628
+ this.closeWallet(); // disable message listener
2586
2629
 
2587
- default:
2588
- console.error('unknown message type', message);
2589
- break;
2630
+ if (registeredWindowMessageProvider === this) {
2631
+ registeredWindowMessageProvider = undefined;
2590
2632
  }
2633
+
2634
+ window.removeEventListener('message', this.onWindowEvent); // clear event listeners
2635
+
2636
+ this.events.removeAllListeners();
2591
2637
  };
2592
2638
 
2593
- this.sendMessageRequest = async function (message) {
2594
- return new Promise((resolve, reject) => {
2595
- if (!message.idx || message.idx <= 0) {
2596
- reject(new Error('message idx not set'));
2597
- }
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
2598
2645
 
2599
- const responseCallback = (error, response) => {
2600
- if (error) {
2601
- reject(error);
2602
- } else if (response) {
2603
- resolve(response);
2604
- } else {
2605
- throw new Error('no valid response to return');
2606
- }
2607
- };
2608
2646
 
2609
- const {
2610
- idx
2611
- } = message;
2647
+ const walletURL = new URL(this.walletURL.href);
2648
+ const windowSessionParams = new WindowSessionParams();
2612
2649
 
2613
- if (!_this.responseCallbacks.get(idx)) {
2614
- _this.responseCallbacks.set(idx, responseCallback);
2615
- } else {
2616
- reject(new Error('duplicate message idx, should never happen'));
2617
- }
2650
+ if (path && path !== '') {
2651
+ walletURL.pathname = path.toLowerCase();
2652
+ } // Set session, intent and network id on walletURL
2618
2653
 
2619
- _this.sendMessage(message);
2620
- });
2621
- };
2622
2654
 
2623
- this.stream.on('data', this.handleMessage);
2624
- }
2655
+ this._init = exports.InitState.NIL;
2656
+ this._sessionId = `${performance.now()}`;
2657
+ windowSessionParams.set('sid', this._sessionId);
2625
2658
 
2626
- sendMessage(message) {
2627
- if (!this.stream.writable) {
2628
- console.error('window post message stream is not writable');
2629
- }
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
2630
2666
 
2631
- this.stream.write(message);
2632
- }
2667
+ if (!isBrowserExtension()) {
2668
+ intent.options.origin = window.location.origin;
2669
+ }
2670
+ } // encode intent as base6 url-encoded param
2633
2671
 
2634
- }
2635
2672
 
2636
- class Web3Provider extends providers.Web3Provider {
2637
- static isSequenceProvider(cand) {
2638
- return isSequenceProvider(cand);
2639
- }
2673
+ windowSessionParams.set('intent', utils.base64EncodeObject(intent));
2674
+ }
2640
2675
 
2641
- constructor(provider, defaultChainId) {
2642
- const sender = new network.JsonRpcSender(provider, network.maybeChainId(defaultChainId));
2643
- provider = sender;
2644
- super(provider, 'any');
2645
- this._sender = void 0;
2646
- this._isSequenceProvider = void 0;
2647
- this._defaultChainId = void 0;
2648
- this._sender = sender;
2649
- this._isSequenceProvider = true;
2650
- this._defaultChainId = network.maybeChainId(defaultChainId);
2651
- }
2676
+ if (networkId) {
2677
+ windowSessionParams.set('net', `${networkId}`);
2678
+ } // Open popup window on center of the app window
2652
2679
 
2653
- sendAsync(request, callback, chainId) {
2654
- this._sender.sendAsync(request, callback, chainId);
2655
- }
2656
2680
 
2657
- send(method, params, chainId) {
2658
- return this._sender.send(method, params, chainId);
2659
- }
2681
+ let windowSize;
2682
+ let windowPos;
2660
2683
 
2661
- getSigner() {
2662
- return new Web3Signer(this, this._defaultChainId);
2663
- }
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
+ }
2664
2691
 
2665
- async getChainId() {
2666
- const result = await this.send('eth_chainId', []);
2667
- 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
2668
2693
 
2669
- if (this._defaultChainId && this._defaultChainId !== chainId) {
2670
- throw new Error(`provider chainId (${chainId}) does not match provider-bound chainId ${this._defaultChainId}`);
2671
- }
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
+ };
2672
2717
 
2673
- return chainId;
2674
- }
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
+ }
2675
2724
 
2676
- }
2677
- function isSequenceProvider(provider) {
2678
- const cand = provider;
2679
- return cand && cand.send !== undefined && cand._isSequenceProvider === true;
2680
- }
2681
- class LocalWeb3Provider extends Web3Provider {
2682
- constructor(signer, networks) {
2683
- const walletRequestHandler = new WalletRequestHandler(signer, null, networks || []);
2684
- super(walletRequestHandler);
2685
- }
2725
+ let message;
2686
2726
 
2687
- }
2688
- class Web3Signer extends wallet.Signer {
2689
- constructor(provider, defaultChainId) {
2690
- super();
2691
- this.provider = void 0;
2692
- this.defaultChainId = void 0;
2693
- this._address = void 0;
2694
- this._index = void 0;
2695
- this._context = void 0;
2696
- this._networks = void 0;
2697
- this._providers = {};
2698
- this.provider = provider;
2699
- this.defaultChainId = defaultChainId;
2700
- } // 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
2701
2737
 
2702
2738
 
2703
- //
2704
- // ethers AbstractSigner methods
2705
- //
2706
- async getAddress() {
2707
- if (this._address) return this._address;
2708
- const accounts = await this.provider.send('eth_accounts', []);
2709
- this._address = accounts[0];
2710
- this._index = 0;
2711
- return ethers.ethers.utils.getAddress(this._address);
2739
+ this.handleMessage(message);
2740
+ };
2741
+
2742
+ this.walletURL = new URL(walletAppURL);
2712
2743
  }
2713
2744
 
2714
- signTransaction(transaction) {
2715
- // TODO .. since ethers isn't using this method, perhaps we will?
2716
- 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);
2717
2761
  }
2718
2762
 
2719
- connect(provider) {
2720
- throw new Error('unsupported: cannot alter JSON-RPC Signer connection');
2721
- } //
2722
- // Sequence Signer methods
2723
- //
2724
- // getProvider returns a Web3Provider instance for the current chain. Note that this method
2725
- // and signer is bound to a particular chain to prevent misuse. If you'd like a provider
2726
- // for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
2763
+ }
2727
2764
 
2765
+ class WindowMessageHandler extends BaseWalletTransport {
2766
+ constructor(walletRequestHandler) {
2767
+ var _this;
2728
2768
 
2729
- async getProvider(chainId) {
2730
- if (chainId) {
2731
- const currentChainId = await this.getChainId();
2769
+ super(walletRequestHandler);
2770
+ _this = this;
2771
+ this.parentWindow = void 0;
2772
+ this._isPopup = false;
2732
2773
 
2733
- if (currentChainId !== chainId) {
2734
- 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;
2735
2778
  }
2736
- }
2737
-
2738
- return this.provider;
2739
- } // getSender returns a Web3Provider instance via the signer transport. Note: for our case
2740
- // the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
2741
- // requests are sent to the window transport. Therefore, for anything non-signing related
2742
- // you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
2743
- // as we do in the WalletProvider.
2744
- //
2745
- // This method is primarily utilized internally when routing requests to a particular chainId.
2746
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
2747
2784
 
2748
- async getSender(chainId) {
2749
- if (!chainId || chainId && chainId === this.defaultChainId) {
2750
- return this.provider;
2751
- }
2752
2785
 
2753
- if (!this._providers[chainId]) {
2754
- this._providers[chainId] = new Web3Provider(new network.JsonRpcSender(this.provider, chainId), chainId);
2755
- }
2786
+ let request;
2756
2787
 
2757
- return this._providers[chainId];
2758
- }
2788
+ try {
2789
+ request = JSON.parse(event.data);
2790
+ } catch (err) {
2791
+ // event is not a ProviderMessage JSON object, skip
2792
+ return;
2793
+ }
2759
2794
 
2760
- getRelayer(chainId) {
2761
- // TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
2762
- // sequence_estimateGasLimits
2763
- // sequence_gasRefundOptions
2764
- // sequence_getNonce
2765
- // sequence_relay
2766
- throw new Error('TODO');
2767
- }
2795
+ utils.logger.debug('RECEIVED MESSAGE', request); // Record event origin for valid init ack
2768
2796
 
2769
- async getWalletContext() {
2770
- if (!this._context) {
2771
- this._context = await this.provider.send('sequence_getWalletContext', []);
2772
- }
2797
+ if (_this._init !== exports.InitState.OK && _this.isValidInitAck(request)) {
2798
+ _this.appOrigin = event.origin;
2799
+ }
2773
2800
 
2774
- return this._context;
2775
- }
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
2776
2806
 
2777
- async getWalletConfig(chainId) {
2778
- return await this.provider.send('sequence_getWalletConfig', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
2779
- }
2780
2807
 
2781
- async getWalletState(chainId) {
2782
- return await this.provider.send('sequence_getWalletState', [network.maybeChainId(chainId)], network.maybeChainId(chainId) || this.defaultChainId);
2783
- }
2808
+ _this.handleMessage(request);
2809
+ };
2784
2810
 
2785
- async getNetworks() {
2786
- if (!this._networks) {
2787
- this._networks = await this.provider.send('sequence_getNetworks', []);
2788
- }
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
+ };
2789
2819
 
2790
- return this._networks;
2820
+ this._init = exports.InitState.NIL;
2791
2821
  }
2792
2822
 
2793
- async getSigners() {
2794
- const networks = await this.getNetworks();
2795
- const authChainId = networks.find(n => n.isAuthChain);
2796
-
2797
- if (!authChainId) {
2798
- throw new Error('authChainId could not be determined from network list');
2799
- }
2823
+ register(windowHref) {
2824
+ const isPopup = parent.window.opener !== null;
2825
+ this._isPopup = isPopup;
2800
2826
 
2801
- const walletConfig = await this.getWalletConfig(authChainId);
2827
+ if (isPopup !== true) {
2828
+ return;
2829
+ } // record open details (sessionId + default network) from the window url
2802
2830
 
2803
- if (!walletConfig || walletConfig.length === 0) {
2804
- throw new Error(`walletConfig returned zero results for authChainId {authChainId}`);
2805
- }
2806
2831
 
2807
- return walletConfig[0].signers.map(s => s.address);
2808
- } // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
2809
- // 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
2810
2837
 
2838
+ const isNewWindowSession = !!session.sessionId; // attempt to restore previous session in the case of a redirect or window reload
2811
2839
 
2812
- async signMessage(message, chainId, allSigners) {
2813
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
2814
- const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
2815
- const address = await this.getAddress(); // NOTE: as of ethers v5.5, it switched to using personal_sign, see
2816
- // https://github.com/ethers-io/ethers.js/pull/1542 and see
2817
- // https://github.com/WalletConnect/walletconnect-docs/issues/32 for additional info.
2840
+ if (!isNewWindowSession) {
2841
+ session = this.getCachedTransportSession();
2842
+ }
2818
2843
 
2819
- return await provider.send('personal_sign', [ethers.ethers.utils.hexlify(data), address]);
2820
- } // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
2821
- // multi-chain support.
2844
+ if (!session) {
2845
+ utils.logger.error('window session is undefined');
2846
+ return;
2847
+ } // record parent window instance for communication
2822
2848
 
2823
2849
 
2824
- async signTypedData(domain, types, message, chainId, allSigners) {
2825
- // Populate any ENS names (in-place)
2826
- // const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
2827
- // return this.provider.resolveName(name)
2828
- // })
2829
- return await this.provider.send('eth_signTypedData_v4', [await this.getAddress(), ethers.ethers.utils._TypedDataEncoder.getPayload(domain, types, message)], network.maybeChainId(chainId) || this.defaultChainId);
2830
- } // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
2831
- // multi-chain support.
2850
+ this.parentWindow = parent.window.opener; // listen for window-transport requests
2832
2851
 
2852
+ window.addEventListener('message', this.onWindowEvent, false);
2853
+ this._registered = true; // send open event to the app which opened us
2833
2854
 
2834
- async sendTransaction(transaction, chainId, allSigners) {
2835
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
2836
- const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
2837
- return ethers.ethers.utils.poll(() => {
2838
- return provider.getTransaction(hash).then(tx => {
2839
- if (tx === null) {
2840
- return undefined;
2841
- }
2855
+ this.open(session).then(opened => {
2856
+ if (!opened) {
2857
+ var _session;
2842
2858
 
2843
- 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
2844
2863
  });
2845
- }, {
2846
- onceBlock: this.provider
2847
- }).catch(error => {
2848
- error.transactionHash = hash;
2849
- throw error;
2850
- });
2851
- }); // @ts-ignore
2852
-
2853
- return tx;
2854
- } // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
2855
- // send multiple transaction as a single payload and just one on-chain transaction.
2864
+ window.close();
2865
+ }
2866
+ }).catch(e => {
2867
+ var _session2;
2856
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
+ }
2857
2877
 
2858
- async sendTransactionBatch(transactions, chainId, allSigners) {
2859
- 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
2860
2883
 
2861
- if (!batch || batch.length === 0) {
2862
- throw new Error('cannot send empty batch');
2863
- } // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
2864
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
2865
2890
 
2866
- if (batch.find(v => v.wait !== undefined && v.wait !== null)) {
2867
- 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);
2868
2895
  }
2896
+ }
2869
2897
 
2870
- const tx = _extends({}, batch[0]);
2898
+ get isPopup() {
2899
+ return this._isPopup;
2900
+ }
2871
2901
 
2872
- if (batch.length > 1) {
2873
- 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;
2874
2906
  }
2875
2907
 
2876
- 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
+ }
2877
2920
  }
2878
2921
 
2879
- signTransactions(transaction, chainId, allSigners) {
2880
- transaction = utils.shallowCopy(transaction); // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
2881
- // the wire.. see sendUncheckedTransaction and resolveProperties
2922
+ }
2882
2923
 
2883
- 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;
2884
2931
  }
2885
2932
 
2886
- sendSignedTransactions(signedTxs, chainId) {
2887
- // sequence_relay
2888
- throw new Error('TODO');
2889
- } // updateConfig..
2890
- // 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
+ }
2891
2939
 
2940
+ sendMessage(message) {
2941
+ console.log('[ExtensionMessageHandler send]', message);
2942
+ this.port.postMessage(message);
2943
+ }
2892
2944
 
2893
- async updateConfig(newConfig) {
2894
- // sequence_updateConfig
2895
- const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId);
2945
+ }
2896
2946
 
2897
- if (tx === null) {
2898
- return [config, undefined];
2899
- }
2947
+ class ExtensionMessageProvider extends BaseProviderTransport {
2948
+ constructor(runtime) {
2949
+ super();
2900
2950
 
2901
- const provider = await this.getSender(this.defaultChainId);
2902
- return [config, provider._wrapTransaction(tx, tx.hash)];
2903
- } // publishConfig..
2904
- // NOTE: this is not supported by the remote wallet by default.
2951
+ this.register = () => {
2952
+ this._registered = true;
2953
+ };
2905
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
+ }
2906
2964
 
2907
- async publishConfig() {
2908
- const provider = await this.getSender(this.defaultChainId);
2909
- const tx = await provider.send('sequence_publishConfig', []);
2965
+ sendMessage(message) {//noop
2966
+ }
2910
2967
 
2911
- if (tx === null) {
2912
- return undefined;
2913
- }
2968
+ unregister() {//noop
2969
+ }
2914
2970
 
2915
- return provider._wrapTransaction(tx, tx.hash);
2971
+ openWallet(path, intent, networkId) {//noop
2916
2972
  }
2917
2973
 
2918
- async isDeployed(chainId) {
2919
- const provider = await this.getSender(network.maybeChainId(chainId));
2920
- const walletCode = await provider.getCode(await this.getAddress());
2921
- return !!walletCode && walletCode !== '0x';
2922
- } //
2923
- // ethers JsonRpcSigner methods
2924
- //
2974
+ closeWallet() {//noop
2975
+ }
2925
2976
 
2977
+ }
2926
2978
 
2927
- async _legacySignMessage(message, chainId, allSigners) {
2928
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
2929
- const data = typeof message === 'string' ? ethers.ethers.utils.toUtf8Bytes(message) : message;
2930
- const address = await this.getAddress(); // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
2931
- // 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;
2932
2983
 
2933
- return await provider.send('eth_sign', [address, ethers.ethers.utils.hexlify(data)]);
2934
- }
2984
+ super();
2985
+ _this = this;
2986
+ this.stream = stream;
2987
+ this.responseCallbacks = new Map();
2988
+ this._messageIdx = 0;
2935
2989
 
2936
- async _signTypedData(domain, types, message, chainId, allSigners) {
2937
- return this.signTypedData(domain, types, message, chainId, allSigners);
2938
- }
2990
+ this.nextMessageIdx = () => ++this._messageIdx;
2939
2991
 
2940
- async sendUncheckedTransaction(transaction, chainId) {
2941
- transaction = utils.shallowCopy(transaction);
2942
- const fromAddress = this.getAddress(); // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
2943
- //
2944
- // TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
2945
- // and send it to our relayer url instead for estimation..
2946
- //
2947
- // if (!transaction.gasLimit) {
2948
- // const estimate = shallowCopy(transaction)
2949
- // estimate.from = fromAddress
2950
- // transaction.gasLimit = this.provider.estimateGas(estimate)
2951
- // }
2992
+ this.handleMessage = message => {
2993
+ if (!message.type || !message.data) {
2994
+ return;
2995
+ }
2952
2996
 
2953
- const provider = await this.getSender(network.maybeChainId(chainId) || this.defaultChainId);
2954
- return utils.resolveProperties({
2955
- tx: utils.resolveProperties(transaction),
2956
- sender: await fromAddress
2957
- }).then(({
2958
- tx,
2959
- sender
2960
- }) => {
2961
- if (tx.from != null) {
2962
- if (ethers.ethers.utils.getAddress(tx.from) !== sender) {
2963
- // logger.throwArgumentError("from address mismatch", "transaction", transaction)
2964
- throw new Error(`from address mismatch for transaction ${transaction}`);
2965
- }
2966
- } else {
2967
- 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);
2968
3003
  }
2969
3004
 
2970
- const hexTx = hexlifyTransaction(tx);
2971
- return provider.send('eth_sendTransaction', [hexTx]).then(hash => {
2972
- return hash;
2973
- }, error => {
2974
- // return checkError("sendTransaction", error, hexTx)
2975
- throw new Error(`sendTransaction ${error}`);
2976
- });
2977
- });
2978
- }
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
+ }
2979
3016
 
2980
- connectUnchecked() {
2981
- throw new Error('connectUnchecked is unsupported');
2982
- }
3017
+ break;
2983
3018
 
2984
- async unlock(password) {
2985
- const address = await this.getAddress();
2986
- return this.provider.send('personal_unlockAccount', [address, password, null]);
2987
- }
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;
2988
3024
 
2989
- } // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
2990
- // Convert an ethers.js transaction into a JSON-RPC transaction
3025
+ default:
3026
+ console.error('unknown message type', message);
3027
+ break;
3028
+ }
3029
+ };
2991
3030
 
2992
- const allowedTransactionKeys = {
2993
- chainId: true,
2994
- data: true,
2995
- gasLimit: true,
2996
- gasPrice: true,
2997
- nonce: true,
2998
- to: true,
2999
- value: true,
3000
- from: true,
3001
- auxiliary: true,
3002
- expiration: true,
3003
- afterNonce: true,
3004
- delegateCall: true,
3005
- revertOnError: true
3006
- };
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
+ }
3007
3036
 
3008
- const hexlifyTransaction = (transaction, allowExtra) => {
3009
- // Check only allowed properties are given
3010
- 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
+ };
3011
3046
 
3012
- if (allowExtra) {
3013
- for (const key in allowExtra) {
3014
- if (allowExtra[key]) {
3015
- allowed[key] = true;
3016
- }
3017
- }
3018
- }
3047
+ const {
3048
+ idx
3049
+ } = message;
3019
3050
 
3020
- ethers.ethers.utils.checkProperties(transaction, allowed);
3021
- const result = {} // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
3022
- ;
3023
- ['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
3024
- if (!transaction[key]) {
3025
- return;
3026
- }
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
+ }
3027
3056
 
3028
- const value = ethers.ethers.utils.hexValue(transaction[key]);
3057
+ _this.sendMessage(message);
3058
+ });
3059
+ };
3029
3060
 
3030
- if (key === 'gasLimit') {
3031
- key = 'gas';
3032
- }
3061
+ this.stream.on('data', this.handleMessage);
3062
+ }
3033
3063
 
3034
- result[key] = value;
3035
- });
3036
- ['from', 'to', 'data'].forEach(key => {
3037
- if (!transaction[key]) {
3038
- return;
3064
+ sendMessage(message) {
3065
+ if (!this.stream.writable) {
3066
+ console.error('window post message stream is not writable');
3039
3067
  }
3040
3068
 
3041
- result[key] = ethers.ethers.utils.hexlify(transaction[key]);
3042
- });
3043
- const auxiliary = transaction['auxiliary'];
3044
-
3045
- if (auxiliary && auxiliary.length > 0) {
3046
- result['auxiliary'] = [];
3047
- auxiliary.forEach(a => {
3048
- result['auxiliary'].push(hexlifyTransaction(a));
3049
- });
3069
+ this.stream.write(message);
3050
3070
  }
3051
3071
 
3052
- return result;
3053
- };
3072
+ }
3054
3073
 
3055
- class WalletCommands {
3074
+ class WalletUtils {
3056
3075
  constructor(walletProvider) {
3057
3076
  var _this = this;
3058
3077
 
@@ -3112,7 +3131,9 @@ class WalletCommands {
3112
3131
  } // Verify message signature
3113
3132
 
3114
3133
 
3115
- 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;
3116
3137
  return this.isValidSignature(address, utils.encodeMessageDigest(message), signature, chainId, walletContext);
3117
3138
  } // Verify typedData signature
3118
3139
 
@@ -3144,7 +3165,7 @@ class Wallet {
3144
3165
  constructor(defaultNetworkId, config) {
3145
3166
  var _this = this;
3146
3167
 
3147
- this.commands = void 0;
3168
+ this.utils = void 0;
3148
3169
  this.config = void 0;
3149
3170
  this.session = void 0;
3150
3171
  this.connectedSites = void 0;
@@ -3470,7 +3491,7 @@ class Wallet {
3470
3491
  this.networks = [];
3471
3492
  this.providers = {};
3472
3493
  this.connectedSites = new LocalStore('@sequence.connectedSites', []);
3473
- this.commands = new WalletCommands(this);
3494
+ this.utils = new WalletUtils(this);
3474
3495
  this.init();
3475
3496
  }
3476
3497
 
@@ -3713,7 +3734,9 @@ exports.WindowMessageProvider = WindowMessageProvider;
3713
3734
  exports.WindowSessionParams = WindowSessionParams;
3714
3735
  exports.isBrowserExtension = isBrowserExtension;
3715
3736
  exports.isSequenceProvider = isSequenceProvider;
3737
+ exports.isValidMessageSignature = isValidMessageSignature;
3716
3738
  exports.isValidSignature = isValidSignature;
3739
+ exports.isValidTypedDataSignature = isValidTypedDataSignature;
3717
3740
  exports.isWalletUpToDate = isWalletUpToDate;
3718
3741
  exports.nextMessageIdx = nextMessageIdx;
3719
3742
  exports.recoverWalletConfig = recoverWalletConfig;