@0xsequence/provider 0.35.10 → 0.36.1

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