UNPKG

@mobile-wallet-protocol/client

Version:
215 lines (214 loc) 9.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MWPClient = void 0; const KeyManager_1 = require("./components/key/KeyManager"); const cipher_1 = require("./core/cipher/cipher"); const error_1 = require("./core/error"); const ScopedAsyncStorage_1 = require("./core/storage/ScopedAsyncStorage"); const util_1 = require("./core/type/util"); const ACCOUNTS_KEY = 'accounts'; const ACTIVE_CHAIN_STORAGE_KEY = 'activeChain'; const AVAILABLE_CHAINS_STORAGE_KEY = 'availableChains'; const WALLET_CAPABILITIES_STORAGE_KEY = 'walletCapabilities'; const postRequestToWallet_1 = require("./components/communication/postRequestToWallet"); const version_1 = require("./version"); const utils_1 = require("./core/util/utils"); class MWPClient { constructor({ metadata, wallet }) { var _a, _b; this.metadata = Object.assign(Object.assign({}, metadata), { name: metadata.name || 'Dapp', customScheme: (0, utils_1.appendMWPResponsePath)(metadata.customScheme) }); this.wallet = wallet; this.keyManager = new KeyManager_1.KeyManager({ wallet: this.wallet }); this.storage = new ScopedAsyncStorage_1.ScopedAsyncStorage(this.wallet.name, 'MWPClient'); // default values this.accounts = []; this.chain = { id: (_b = (_a = metadata.chainIds) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, }; this.handshake = this.handshake.bind(this); this.request = this.request.bind(this); this.reset = this.reset.bind(this); } async initialize() { const storedAccounts = await this.storage.loadObject(ACCOUNTS_KEY); if (storedAccounts) { this.accounts = storedAccounts; } const storedChain = await this.storage.loadObject(ACTIVE_CHAIN_STORAGE_KEY); if (storedChain) { this.chain = storedChain; } } static async createInstance(params) { const instance = new MWPClient(params); await instance.initialize(); return instance; } async handshake() { if (this.accounts.length > 0) return this.accounts; const handshakeMessage = await this.createRequestMessage({ handshake: { method: 'eth_requestAccounts', params: { appName: this.metadata.name, appLogoUrl: this.metadata.logoUrl, }, }, }); const response = await (0, postRequestToWallet_1.postRequestToWallet)(handshakeMessage, this.metadata.customScheme, this.wallet); // store peer's public key if ('failure' in response.content) throw response.content.failure; const peerPublicKey = await (0, cipher_1.importKeyFromHexString)('public', response.sender); await this.keyManager.setPeerPublicKey(peerPublicKey); const decrypted = await this.decryptResponseMessage(response); const result = decrypted.result; if ('error' in result) throw result.error; const accounts = result.value; this.accounts = accounts; await this.storage.storeObject(ACCOUNTS_KEY, accounts); return accounts; } async request(request) { if (this.accounts.length === 0) { throw error_1.standardErrors.provider.unauthorized(); } (0, utils_1.checkErrorForInvalidRequestArgs)(request); switch (request.method) { case 'eth_requestAccounts': return this.accounts; case 'eth_accounts': return this.accounts; case 'eth_coinbase': return this.accounts[0]; case 'net_version': return this.chain.id; case 'eth_chainId': return (0, util_1.hexStringFromNumber)(this.chain.id); case 'wallet_getCapabilities': return this.storage.loadObject(WALLET_CAPABILITIES_STORAGE_KEY); case 'wallet_switchEthereumChain': return this.handleSwitchChainRequest(request); case 'eth_ecRecover': case 'personal_sign': case 'personal_ecRecover': case 'eth_signTransaction': case 'eth_sendTransaction': case 'eth_signTypedData_v1': case 'eth_signTypedData_v3': case 'eth_signTypedData_v4': case 'eth_signTypedData': case 'wallet_addEthereumChain': case 'wallet_watchAsset': case 'wallet_sendCalls': case 'wallet_showCallsStatus': case 'wallet_grantPermissions': return this.sendRequestToPopup(request); default: if (!this.chain.rpcUrl) throw error_1.standardErrors.rpc.internal('No RPC URL set for chain'); return (0, utils_1.fetchRPCRequest)(request, this.chain.rpcUrl); } } async sendRequestToPopup(request) { const response = await this.sendEncryptedRequest(request); const decrypted = await this.decryptResponseMessage(response); const result = decrypted.result; if ('error' in result) throw result.error; return result.value; } async reset() { var _a, _b; await this.storage.clear(); await this.keyManager.clear(); this.accounts = []; this.chain = { id: (_b = (_a = this.metadata.chainIds) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, }; } /** * @returns `null` if the request was successful. * https://eips.ethereum.org/EIPS/eip-3326#wallet_switchethereumchain */ async handleSwitchChainRequest(request) { var _a; const params = request.params; if (!params || !((_a = params[0]) === null || _a === void 0 ? void 0 : _a.chainId)) { throw error_1.standardErrors.rpc.invalidParams(); } const chainId = (0, util_1.ensureIntNumber)(params[0].chainId); const localResult = await this.updateChain(chainId); if (localResult) return null; const popupResult = await this.sendRequestToPopup(request); if (popupResult === null) { this.updateChain(chainId); } return popupResult; } async sendEncryptedRequest(request) { const sharedSecret = await this.keyManager.getSharedSecret(); if (!sharedSecret) { throw error_1.standardErrors.provider.unauthorized('No valid session found, try requestAccounts before other methods'); } const encrypted = await (0, cipher_1.encryptContent)({ action: request, chainId: this.chain.id, }, sharedSecret); const message = await this.createRequestMessage({ encrypted }); return (0, postRequestToWallet_1.postRequestToWallet)(message, this.metadata.customScheme, this.wallet); } async createRequestMessage(content) { const publicKey = await (0, cipher_1.exportKeyToHexString)('public', await this.keyManager.getOwnPublicKey()); return { id: crypto.randomUUID(), sender: publicKey, content, sdkVersion: version_1.LIB_VERSION, timestamp: new Date(), callbackUrl: this.metadata.customScheme, }; } async decryptResponseMessage(message) { var _a, _b; const content = message.content; // throw protocol level error if ('failure' in content) { throw content.failure; } const sharedSecret = await this.keyManager.getSharedSecret(); if (!sharedSecret) { throw error_1.standardErrors.provider.unauthorized('Invalid session'); } const response = await (0, cipher_1.decryptContent)(content.encrypted, sharedSecret); const availableChains = (_a = response.data) === null || _a === void 0 ? void 0 : _a.chains; if (availableChains) { const chains = Object.entries(availableChains).map(([id, rpcUrl]) => ({ id: Number(id), rpcUrl, })); await this.storage.storeObject(AVAILABLE_CHAINS_STORAGE_KEY, chains); await this.updateChain(this.chain.id, chains); } const walletCapabilities = (_b = response.data) === null || _b === void 0 ? void 0 : _b.capabilities; if (walletCapabilities) { await this.storage.storeObject(WALLET_CAPABILITIES_STORAGE_KEY, walletCapabilities); } return response; } async updateChain(chainId, newAvailableChains) { const chains = newAvailableChains !== null && newAvailableChains !== void 0 ? newAvailableChains : (await this.storage.loadObject(AVAILABLE_CHAINS_STORAGE_KEY)); const chain = chains === null || chains === void 0 ? void 0 : chains.find((chain) => chain.id === chainId); if (!chain) return false; if (chain !== this.chain) { this.chain = chain; await this.storage.storeObject(ACTIVE_CHAIN_STORAGE_KEY, chain); } return true; } } exports.MWPClient = MWPClient;