@0xsequence/provider 0.35.8 → 0.35.12

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