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