@0xsequence/provider 0.35.7 → 0.35.11

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