@0xsequence/provider 0.0.0-20220610134843 → 0.0.0-20220614152529

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