@0xsequence/provider 0.0.0-2021827201516 → 0.0.0-20220418165517

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