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