UNPKG

@oasisprotocol/sapphire-paratime

Version:
219 lines 9.86 kB
"use strict"; // SPDX-License-Identifier: Apache-2.0 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeTaggedProxyObject = exports.makeSapphireRequestFn = exports.isCallDataPublicKeyQuery = exports.isWrappedRequestFn = exports.notifySapphireSnap = exports.detectSapphireSnap = exports.wrapEthereumProvider = exports.isWrappedEthereumProvider = exports.fillOptions = exports.isLegacyProvider = exports.isEthereumProvider = void 0; const ethersutils_js_1 = require("./ethersutils.cjs"); const calldatapublickey_js_1 = require("./calldatapublickey.cjs"); const constants_js_1 = require("./constants.cjs"); function isEthereumProvider(p) { return 'request' in p && typeof p.request === 'function'; } exports.isEthereumProvider = isEthereumProvider; function isLegacyProvider(p) { return 'send' in p && typeof p.send === 'function'; } exports.isLegacyProvider = isLegacyProvider; function fillOptions(options) { if (!options) { options = {}; } if (!options.fetcher) { options.fetcher = new calldatapublickey_js_1.KeyFetcher(); } return options; } exports.fillOptions = fillOptions; // ----------------------------------------------------------------------------- // Wrap an Ethereum compatible provider to expose a consistent request() iface const SAPPHIRE_WRAPPED_ETHEREUMPROVIDER = '#SAPPHIRE_WRAPPED_ETHEREUMPROVIDER'; function isWrappedEthereumProvider(p) { return p && SAPPHIRE_WRAPPED_ETHEREUMPROVIDER in p; } exports.isWrappedEthereumProvider = isWrappedEthereumProvider; /** * Wrap an EIP-1193 or EIP-2696 compatible provider with Sapphire encryption * * ```typescript * const provider = wrapEthereumProvider(window.ethereum); * ``` * * @param upstream Provides a send() or request() function * @param options (optional) Re-use parameters from other providers * @returns Sapphire wrapped provider */ function wrapEthereumProvider(upstream, options) { if (isWrappedEthereumProvider(upstream)) { return upstream; } if (!isEthereumProvider(upstream) && !isLegacyProvider(upstream)) { throw new Error('It is neither an Ethereum nor a Legacy provider'); } const filled_options = fillOptions(options); // if upstream provides a send() function but not request function // then derive a request() function from the send() function // if we do this, don't then re-wrap the send() function // only wrap the send() function if there was a request() function const request = makeSapphireRequestFn(upstream, filled_options); const hooks = { request }; // We prefer a request() method, but a provider may expose a send() method // Like Hardhat's LazyInitializationProviderAdapter, which is used with Ethers // So, everything gets sent through the Sapphire-wrapped request() function if ('send' in upstream) hooks.send = (method, params) => { return request({ method, params }); }; // sendAsync implementations vary too widely to be used as a standard if ('sendAsync' in upstream) hooks.sendAsync = () => { throw new Error('sendAsync not supported!'); }; return makeTaggedProxyObject(upstream, SAPPHIRE_WRAPPED_ETHEREUMPROVIDER, filled_options, hooks); } exports.wrapEthereumProvider = wrapEthereumProvider; const SAPPHIRE_SNAP_ID = 'npm:@oasisprotocol/sapphire-snap'; function detectSapphireSnap(provider) { return __awaiter(this, void 0, void 0, function* () { try { const installedSnaps = (yield provider.request({ method: 'wallet_getSnaps', })); for (const snap of Object.values(installedSnaps)) { if (snap.id === SAPPHIRE_SNAP_ID) { return snap.id; } } } catch (e) { return undefined; } }); } exports.detectSapphireSnap = detectSapphireSnap; function notifySapphireSnap(snapId, cipher, transactionData, options, provider) { return __awaiter(this, void 0, void 0, function* () { if (cipher.ephemeralKey) { const peerPublicKey = yield options.fetcher.fetch(provider); yield provider.request({ method: 'wallet_invokeSnap', params: { snapId: snapId, request: { method: 'setTransactionDecryptKeys', params: { id: transactionData, ephemeralSecretKey: (0, ethersutils_js_1.hexlify)(cipher.ephemeralKey), peerPublicKey: (0, ethersutils_js_1.hexlify)(peerPublicKey.key), peerPublicKeyEpoch: peerPublicKey.epoch, }, }, }, }); } }); } exports.notifySapphireSnap = notifySapphireSnap; const SAPPHIRE_EIP1193_REQUESTFN = '#SAPPHIRE_EIP1193_REQUESTFN'; function isWrappedRequestFn(p) { return p && SAPPHIRE_EIP1193_REQUESTFN in p; } exports.isWrappedRequestFn = isWrappedRequestFn; function isCallDataPublicKeyQuery(params) { return (params && Array.isArray(params) && params.length > 0 && params[0].to === constants_js_1.SUBCALL_ADDR && params[0].data === constants_js_1.CALLDATAPUBLICKEY_CALLDATA); } exports.isCallDataPublicKeyQuery = isCallDataPublicKeyQuery; /** * Creates an EIP-1193 compatible request() function * @param provider Upstream EIP-1193 provider to forward requests to * @param options * @returns */ function makeSapphireRequestFn(provider, options) { if (isWrappedRequestFn(provider.request)) { return provider.request; } const filled_options = fillOptions(options); const f = (args) => __awaiter(this, void 0, void 0, function* () { const snapId = filled_options.enableSapphireSnap ? yield detectSapphireSnap(provider) : undefined; const { method, params } = args; let transactionData = undefined; // Encrypt requests which can be encrypted if (params && Array.isArray(params) && /^eth_((send|sign)Transaction|call|estimateGas)$/.test(method) && params[0].data // Ignore balance transfers without calldata ) { // TODO: should we attempt to detect `if (not sapphire) throw` instead of // failing to fetch public key in the next line? const cipher = yield filled_options.fetcher.cipher(provider); transactionData = params[0].data = cipher.encryptCall(params[0].data); if (snapId !== undefined && transactionData !== undefined) { // Run in background so as to not delay results notifySapphireSnap(snapId, cipher, transactionData, filled_options, provider); } const res = yield provider.request({ method, params: params !== null && params !== void 0 ? params : [], }); // Decrypt responses which return encrypted data if (method === 'eth_call') { // If it's an unencrypted core.CallDataPublicKey query, don't attempt to decrypt the response if (!isCallDataPublicKeyQuery(params)) { return cipher.decryptResult(res); } } return res; } else { const res = yield provider.request({ method, params: params !== null && params !== void 0 ? params : [], }); return res; } }); return makeTaggedProxyObject(f, SAPPHIRE_EIP1193_REQUESTFN, filled_options); } exports.makeSapphireRequestFn = makeSapphireRequestFn; // ----------------------------------------------------------------------------- function makeTaggedProxyObject(upstream, propname, options, hooks) { return new Proxy(upstream, { has(target, p) { if (p === propname) return true; return Reflect.has(target, p); }, get(upstream, prop) { var _a; if (prop === propname) return options; if (hooks && prop in hooks) return Reflect.get(hooks, prop); const value = Reflect.get(upstream, prop); // Brave wallet web3provider properties are read only and throw typeerror // https://github.com/brave/brave-core/blob/74bf470a0291ea3719f1a75af066ee10b7057dbd/components/brave_wallet/resources/ethereum_provider.js#L13-L27 // https://github.com/wevm/wagmi/blob/86c42248c2f34260a52ee85183c607315ae63ce8/packages/core/src/connectors/injected.ts#L327-L335 const propWritable = ((_a = Object.getOwnPropertyDescriptor(upstream, prop)) === null || _a === void 0 ? void 0 : _a.writable) !== false; if (typeof value === 'function' && propWritable) { return value.bind(upstream); } return value; }, }); } exports.makeTaggedProxyObject = makeTaggedProxyObject; //# sourceMappingURL=provider.js.map