UNPKG

@toruslabs/torus-embed

Version:
1,462 lines (1,404 loc) 78.8 kB
import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { get, setAPIKey } from '@toruslabs/http-helpers'; import { SafeEventEmitter, ObjectMultiplex, createStreamMiddleware, JRPCEngine, createIdRemapMiddleware, BasePostMessageStream, setupMultiplex } from '@toruslabs/openlogin-jrpc'; import deepmerge from 'lodash.merge'; import { rpcErrors, EthereumProviderError } from '@metamask/rpc-errors'; import dequal from 'fast-deep-equal'; import pump from 'pump'; import loglevel from 'loglevel'; const WALLET_VERIFIERS = { GOOGLE: "google", FACEBOOK: "facebook", TWITCH: "twitch", REDDIT: "reddit", DISCORD: "discord", EMAIL_PASSWORDLESS: "torus-auth0-email-passwordless" }; const WALLET_OPENLOGIN_VERIFIER_MAP = { [WALLET_VERIFIERS.GOOGLE]: "tkey-google", [WALLET_VERIFIERS.FACEBOOK]: "tkey-facebook", [WALLET_VERIFIERS.TWITCH]: "tkey-twitch", [WALLET_VERIFIERS.REDDIT]: "tkey-reddit", [WALLET_VERIFIERS.DISCORD]: "tkey-discord", [WALLET_VERIFIERS.EMAIL_PASSWORDLESS]: "tkey-auth0-email-passwordless" }; const PAYMENT_PROVIDER = { MOONPAY: "moonpay", RAMPNETWORK: "rampnetwork", MERCURYO: "mercuryo", TRANSAK: "transak", BANXA: "banxa" }; const SUPPORTED_PAYMENT_NETWORK = { MAINNET: "mainnet", MATIC: "matic", BSC_MAINNET: "bsc_mainnet", AVALANCHE_MAINNET: "avalanche_mainnet", XDAI: "xdai", ARBITRUM_MAINNET: "arbitrum_mainnet", OPTIMISM_MAINNET: "optimism_mainnet" }; const TORUS_BUILD_ENV = { PRODUCTION: "production", DEVELOPMENT: "development", BINANCE: "binance", TESTING: "testing", LRC: "lrc", BETA: "beta", BNB: "bnb", POLYGON: "polygon", ALPHA: "alpha" }; const BUTTON_POSITION = { BOTTOM_LEFT: "bottom-left", TOP_LEFT: "top-left", BOTTOM_RIGHT: "bottom-right", TOP_RIGHT: "top-right" }; /** * From https://min-api.cryptocompare.com/data/v2/pair/mapping/fsym?fsym=BTC&extraParams=YourSite * GET https://min-api.cryptocompare.com/data/v2/pair/mapping/fsym?fsym=BTC * Then map over returned entries, picking tsym * * Last updated: Date of commit */ const CRYPTO_COMPARE_CURRENCIES = ["ETH", "USDT", "USDC", "TUSD", "EOSDT", "USD", "DAI", "GUSD", "DKKT", "PAX", "ILS", "RUB", "BYN", "EUR", "GBP", "JPY", "KRW", "PLN", "MXN", "AUD", "BRL", "CAD", "CHF", "KPW", "LAK", "LBP", "LKR", "XOF", "CNHT", "DOGE", "UAH", "TRY", "HKD", "XJP", "SGD", "USC", "NZD", "NGN", "RUR", "COP", "GHS", "EGP", "IDR", "BHD", "CRC", "PEN", "AED", "DOP", "PKR", "HUF", "VND", "XAR", "LTC", "RON", "OMR", "MYR", "DKK", "UGX", "ZMW", "SAR", "SEK", "GEL", "RWF", "IRR", "TZS", "CNY", "VEF", "BDT", "HRK", "CLP", "THB", "XAF", "ARS", "UYU", "SZL", "KZT", "NOK", "KES", "PAB", "INR", "CZK", "MAD", "TWD", "PHP", "ZAR", "BOB", "CDF", "DASH", "VES", "ISK", "MWK", "BAM", "TTD", "XRP", "JOD", "RSD", "HNL", "BGN", "GTQ", "BWP", "XMR", "MMK", "QAR", "AOA", "KWD", "MUR", "WUSD", "WEUR", "WAVES", "WTRY", "LRD", "LSL", "LYD", "AWG", "MDL", "BTO", "EURS", "CHFT", "MKD", "MNT", "MOP", "MRO", "MVR", "VOLLAR", "CKUSD", "KHR", "VUV", "BITCNY", "QC", "BBD", "NAD", "NPR", "PGK", "PYG", "BIF", "BMD", "BND", "XLM", "BNB", "SCR", "BAT", "CRO", "HT", "KCS", "LEO", "LINK", "MKR", "NPXS", "OMG", "REP", "ZB", "ZIL", "ZRX", "BCH", "BZD", "CUP", "CVE", "DJF", "DZD", "ERN", "ETB", "FJD", "FKP", "BUSD", "ANCT", "ALL", "AMD", "ANG", "CNYX", "IQD", "UZS", "TND", "GGP", "XAU", "KGS", "GIP", "JMD", "ZEC", "USDP", "BSV", "EMC2", "SNT", "GTO", "POWR", "EUSD", "EURT", "BCY", "BTS", "ATM", "BLOCKPAY", "ARDR", "AMP", "B2X", "BITGOLD", "BITEUR", "ATB", "BITUSD", "AGRS", "DFXT", "HIKEN", "BIX", "KNC", "EOS", "COB", "COSS", "BMH", "NANO", "BDG", "BNT", "XVG", "LKK1Y", "LKK", "USDK", "EURN", "NZDT", "JSE", "GMD", "GNF", "GYD", "YER", "XPF", "HTG", "SLL", "SOS", "WST", "SVC", "SYP", "NEO", "KMF", "JUMP", "AYA", "BLAST", "WGR", "BCN", "BTG", "URALS", "INN", "USDQ", "CNH", "HUSD", "BKRW", "NZDX", "EURX", "CADX", "USDEX", "JPYX", "AUDX", "VNDC", "EON", "GBPX", "CHFX", "USDJ", "IDRT", "USDS", "USDN", "BIDR", "IDK", "BSD", "BTN", "KYD", "NIO", "SBD", "SDG", "SHP", "TOP", "XCD", "XCHF", "CNYT", "GYEN", "ZUSD", "GOLD", "TRX", "TRYB", "PLATC", "STRAX", "UST", "GLM", "VAI", "BRZ", "DDRST", "XAUT", "MIM"]; /** * currencies supported by the payment provider * Last updated: Date of commit */ const PROVIDER_SUPPORTED_FIAT_CURRENCIES = { // https://integrations.simplex.com/supported_currencies // https://support.moonpay.com/hc/en-gb/articles/360011931457-Which-fiat-currencies-are-supported- [PAYMENT_PROVIDER.MOONPAY]: ["AUD", "BGN", "BRL", "CAD", "CHF", "CNY", "COP", "CZK", "DKK", "DOP", "EGP", "EUR", "GBP", "HKD", "HRK", "IDR", "ILS", "JPY", "JOD", "KES", "KRW", "KWD", "LKR", "MAD", "MXN", "MYR", "NGN", "NOK", "NZD", "OMR", "PEN", "PKR", "PLN", "RON", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "USD", "VND", "ZAR"], // https://support.ramp.network/en/articles/471-supported-fiat-currencies [PAYMENT_PROVIDER.RAMPNETWORK]: ["USD", "EUR", "GBP", "BMD", "BAM", "BWP", "BRL", "BGN", "COP", "CRC", "CZK", "DKK", "DOP", "GEL", "GTQ", "HNL", "HUF", "ISK", "INR", "ILS", "KZT", "KES", "KWD", "LAK", "MKD", "MYR", "MXN", "MDL", "MZN", "NZD", "NGN", "PYG", "PEN", "PLN", "RON", "RSD", "SGD", "ZAR", "LKR", "SEK", "CHF", "TJS", "THB", "UYU"], // https://help.mercuryo.io/en/articles/6121246-which-fiat-currencies-are-supported // RUB / UAH currently not supported [PAYMENT_PROVIDER.MERCURYO]: ["EUR", "USD", "GBP", "TRY", "JPY", "BRL", "NGN", "VND", "MXN", "KRW", "PLN", "SEK", "CHF", "CAD", "CZK", "DKK", "BGN", "HKD", "AUD", "INR"], /** * https://support.transak.com/hc/en-us/articles/360020615578-Credit-and-Debit-Card-Payments-through-Transak * or * https://transak.stoplight.io/docs/transak-docs/b3A6OTk1ODQ0-2-get-fiat-currencies */ [PAYMENT_PROVIDER.TRANSAK]: ["ARS", "AUD", "BBD", "BGN", "BMD", "BRL", "CAD", "CHF", "CLP", "CRC", "CZK", "DKK", "DOP", "EUR", "FJD", "FKP", "GBP", "GIP", "HRK", "HUF", "IDR", "ILS", "ISK", "JMD", "JPY", "KES", "KRW", "MDL", "MXN", "MYR", "NOK", "NZD", "PEN", "PHP", "PLN", "PYG", "RON", "SEK", "SGD", "THB", "TRY", "TZS", "USD", "ZAR"], [PAYMENT_PROVIDER.BANXA]: ["AUD", "CAD", "CZK", "DKK", "EUR", "GBP", "HKD", "JPY", "NOK", "NZD", "NZD", "PLN", "RUB", "SEK", "SGD", "TRY", "USD"] }; const cryptoCompareCurrenciesSet = new Set(CRYPTO_COMPARE_CURRENCIES); /** * Fiat currencies that we support */ function supportedFiatCurrencies(provider) { const providerSupportedFiatCurrencies = PROVIDER_SUPPORTED_FIAT_CURRENCIES[provider]; return providerSupportedFiatCurrencies.filter(currency => cryptoCompareCurrenciesSet.has(currency)); } const paymentProviders$1 = { [PAYMENT_PROVIDER.MOONPAY]: { line1: "Credit/ Debit Card/ Apple Pay", line2: "4.5% or 5 USD", line3: "2,000€/day, 10,000€/mo", supportPage: "https://help.moonpay.io/en/", minOrderValue: 24.99, maxOrderValue: 50000, validCurrencies: supportedFiatCurrencies(PAYMENT_PROVIDER.MOONPAY), validCryptoCurrenciesByChain: { [SUPPORTED_PAYMENT_NETWORK.MAINNET]: [{ value: "aave", display: "AAVE" }, { value: "bat", display: "BAT" }, { value: "dai", display: "DAI" }, { value: "eth", display: "ETH" }, { value: "mkr", display: "MKR" }, { value: "matic", display: "MATIC" }, { value: "usdt", display: "USDT" }, { value: "uni", display: "UNI" }, { value: "usdc", display: "USDC" }, { value: "weth", display: "WETH" }], [SUPPORTED_PAYMENT_NETWORK.MATIC]: [{ value: "eth_polygon", display: "ETH" }, { value: "matic_polygon", display: "MATIC" }, { value: "usdc_polygon", display: "USDC" }, { value: "usdt_polygon", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.BSC_MAINNET]: [{ value: "bnb_bsc", display: "BNB" }, { value: "busd_bsc", display: "BUSD" }], [SUPPORTED_PAYMENT_NETWORK.AVALANCHE_MAINNET]: [{ value: "avax_cchain", display: "AVAX" }, { value: "usdc_cchain", display: "USDC" }], [SUPPORTED_PAYMENT_NETWORK.ARBITRUM_MAINNET]: [{ value: "eth_arbitrum", display: "ETH" }, { value: "usdc_arbitrum", display: "USDC" }], [SUPPORTED_PAYMENT_NETWORK.OPTIMISM_MAINNET]: [{ value: "eth_optimism", display: "ETH" }, { value: "usdc_optimism", display: "USDC" }] }, includeFees: true, api: true, enforceMax: false }, [PAYMENT_PROVIDER.RAMPNETWORK]: { line1: "Debit Card/ <br>Apple Pay/ Bank transfer", line2: "0.49% - 2.9%", line3: "5,000€/purchase, 20,000€/mo", supportPage: "https://instant.ramp.network/", minOrderValue: 50, maxOrderValue: 20000, validCurrencies: supportedFiatCurrencies(PAYMENT_PROVIDER.RAMPNETWORK), validCryptoCurrenciesByChain: { [SUPPORTED_PAYMENT_NETWORK.MAINNET]: [{ value: "ETH", display: "ETH" }, { value: "DAI", display: "DAI" }, { value: "BAT", display: "BAT" }, { value: "USDC", display: "USDC" }, { value: "USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.MATIC]: [{ value: "MATIC_BAT", display: "BAT" }, { value: "MATIC_DAI", display: "DAI" }, { value: "MATIC_MATIC", display: "MATIC" }, { value: "MATIC_USDC", display: "USDC" }, { value: "MATIC_USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.AVALANCHE_MAINNET]: [{ value: "AVAX_AVAX", display: "AVAX" }, { value: "AVAX_USDC", display: "USDC" }, { value: "AVAX_USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.ARBITRUM_MAINNET]: [{ value: "ARBITRUM_ETH", display: "ETH" }, { value: "ARBITRUM_USDC.e ", display: "USDC" }, { value: "ARBITRUM_USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.OPTIMISM_MAINNET]: [{ value: "OPTIMISM_DAI", display: "DAI" }, { value: "OPTIMISM_OPTIMISM", display: "OPTIMISM" }, { value: "OPTIMISM_USDC", display: "USDC" }, { value: "OPTIMISM_USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.BSC_MAINNET]: [{ value: "BSC_BNB", display: "BNB" }, { value: "BSC_BUSD", display: "BUSD" }] }, includeFees: true, api: true, receiveHint: "walletTopUp.receiveHintRamp", enforceMax: false }, [PAYMENT_PROVIDER.MERCURYO]: { line1: "Credit/ Debit Card/ Apple Pay", line2: "3.95% or 4 USD", line3: "10,000€/day, 25,000€/mo", supportPage: "mailto:support@mercuryo.io", minOrderValue: 30, maxOrderValue: 5000, validCurrencies: supportedFiatCurrencies(PAYMENT_PROVIDER.MERCURYO), validCryptoCurrenciesByChain: { [SUPPORTED_PAYMENT_NETWORK.MAINNET]: [{ value: "ETH", display: "ETH" }, { value: "BAT", display: "BAT" }, { value: "USDT", display: "USDT" }, { value: "DAI", display: "DAI" }], [SUPPORTED_PAYMENT_NETWORK.BSC_MAINNET]: [{ value: "BNB", display: "BNB" }, { value: "BUSD", display: "BUSD" }, { value: "1INCH", display: "1INCH" }], [SUPPORTED_PAYMENT_NETWORK.AVALANCHE_MAINNET]: [{ value: "AVAX", display: "AVAX" }], [SUPPORTED_PAYMENT_NETWORK.MATIC]: [{ value: "MATIC", display: "MATIC" }] }, includeFees: true, api: true, enforceMax: false }, [PAYMENT_PROVIDER.TRANSAK]: { line1: "Apple & Google Pay / Credit/Debit Card<br/>Bangkok Bank Mobile & iPay<br/>Bank Transfer (sepa/gbp) / SCB Mobile & Easy", line2: "0.99% - 5.5% or 5 USD", line3: "$5,000/day, $28,000/mo", supportPage: "https://support.transak.com/hc/en-US", minOrderValue: 30, maxOrderValue: 500, validCurrencies: supportedFiatCurrencies(PAYMENT_PROVIDER.TRANSAK), validCryptoCurrenciesByChain: { [SUPPORTED_PAYMENT_NETWORK.MAINNET]: [{ value: "1INCH", display: "1INCH" }, { value: "BAT", display: "BAT" }, { value: "AAVE", display: "AAVE" }, { value: "DAI", display: "DAI" }, { value: "ETH", display: "ETH" }, { value: "USDC", display: "USDC" }, { value: "USDT", display: "USDT" }], [SUPPORTED_PAYMENT_NETWORK.MATIC]: [{ value: "BAT", display: "BAT" }, { value: "AAVE", display: "AAVE" }, { value: "DAI", display: "DAI" }, { value: "MATIC", display: "MATIC" }, { value: "USDC", display: "USDC" }, { value: "USDT", display: "USDT" }, { value: "WETH", display: "WETH" }], [SUPPORTED_PAYMENT_NETWORK.BSC_MAINNET]: [{ value: "BAT", display: "BAT" }, { value: "BNB", display: "BNB" }, { value: "BUSD", display: "BUSD" }], [SUPPORTED_PAYMENT_NETWORK.AVALANCHE_MAINNET]: [{ value: "AVAX", display: "AVAX" }], [SUPPORTED_PAYMENT_NETWORK.OPTIMISM_MAINNET]: [{ value: "ETH", display: "ETH" }, { value: "USDC", display: "USDC" }], [SUPPORTED_PAYMENT_NETWORK.ARBITRUM_MAINNET]: [{ value: "USDC", display: "USDC" }, { value: "ETH", display: "ETH" }] }, includeFees: true, enforceMax: true }, [PAYMENT_PROVIDER.BANXA]: { line1: "Debit Card/ <br>Apple Pay/ Bank transfer", line2: "0.49% - 2.9%", line3: "5,000€/purchase, 20,000€/mo", supportPage: "https://support.banxa.com", minOrderValue: 20, maxOrderValue: 15000, validCurrencies: supportedFiatCurrencies(PAYMENT_PROVIDER.BANXA), validCryptoCurrenciesByChain: { [SUPPORTED_PAYMENT_NETWORK.MAINNET]: [{ value: "ETH", display: "ETH" }, { value: "DAI", display: "DAI" }, { value: "MKR", display: "MKR" }, { value: "USDT", display: "USDT" }, { value: "BUSD", display: "BUSD" }, { value: "USDC", display: "USDC" }, { value: "BAT", display: "BAT" }, { value: "AAVE", display: "AAVE" }, { value: "COMP", display: "COMP" }, { value: "UNI", display: "UNI" }], [SUPPORTED_PAYMENT_NETWORK.MATIC]: [{ value: "MATIC", display: "MATIC" }] // [BSC_MAINNET]: [{ value: 'BNB', display: 'BNB' }], }, includeFees: true, enforceMax: true } }; const translations = { en: { embed: { continue: "Continue", actionRequired: "Authorization required", pendingAction: "Click continue to proceed with your request in a popup", cookiesRequired: "Cookies Required", enableCookies: "Please enable cookies in your browser preferences to access Torus", clickHere: "More Info" } }, de: { embed: { continue: "Fortsetzen", actionRequired: "Autorisierung erforderlich", pendingAction: "Klicken Sie in einem Popup auf Weiter, um mit Ihrer Anfrage fortzufahren", cookiesRequired: "Cookies benötigt", enableCookies: "Bitte aktivieren Sie Cookies in Ihren Browsereinstellungen, um auf Torus zuzugreifen", clickHere: "Mehr Info" } }, ja: { embed: { continue: "継続する", actionRequired: "認証が必要です", pendingAction: "続行をクリックして、ポップアップでリクエストを続行します", cookiesRequired: "必要なクッキー", enableCookies: "Torusにアクセスするには、ブラウザの設定でCookieを有効にしてください。", clickHere: "詳しくは" } }, ko: { embed: { continue: "계속하다", actionRequired: "승인 필요", pendingAction: "팝업에서 요청을 진행하려면 계속을 클릭하십시오.", cookiesRequired: "쿠키 필요", enableCookies: "브라우저 환경 설정에서 쿠키를 활성화하여 Torus에 액세스하십시오.", clickHere: "더 많은 정보" } }, zh: { embed: { continue: "继续", actionRequired: "需要授权", pendingAction: "单击继续以在弹出窗口中继续您的请求", cookiesRequired: "必填Cookie", enableCookies: "请在您的浏览器首选项中启用cookie以访问Torus。", clickHere: "更多信息" } } }; var configuration = { supportedVerifierList: Object.values(WALLET_VERIFIERS), paymentProviders: paymentProviders$1, api: "https://api.tor.us", translations, prodTorusUrl: "", localStorageKeyPrefix: `torus-` }; const htmlToElement = html => { const template = window.document.createElement("template"); const trimmedHtml = html.trim(); // Never return a text node of whitespace as the result template.innerHTML = trimmedHtml; return template.content.firstChild; }; const handleStream = (handle, eventName, handler) => { const handlerWrapper = chunk => { handler(chunk); handle.removeListener(eventName, handlerWrapper); }; handle.on(eventName, handlerWrapper); }; /* eslint-disable @typescript-eslint/no-explicit-any */ function isStream(stream) { return stream !== null && typeof stream === "object" && typeof stream.pipe === "function"; } function isWritableStream(stream) { return isStream(stream) && stream.writable !== false && typeof stream._write === "function" && typeof stream._writableState === "object"; } function isReadableStream(stream) { return isStream(stream) && stream.readable !== false && typeof stream._read === "function" && typeof stream._readableState === "object"; } function isDuplexStream(stream) { return isWritableStream(stream) && isReadableStream(stream); } var log = loglevel.getLogger("torus-embed"); var messages = { errors: { disconnected: () => "Torus: Lost connection to Torus.", permanentlyDisconnected: () => "Torus: Disconnected from iframe. Page reload required.", sendSiteMetadata: () => "Torus: Failed to send site metadata. This is an internal error, please report this bug.", unsupportedSync: method => `Torus: The Torus Ethereum provider does not support synchronous methods like ${method} without a callback parameter.`, invalidDuplexStream: () => "Must provide a Node.js-style duplex stream.", invalidOptions: (maxEventListeners, shouldSendMetadata) => `Invalid options. Received: { maxEventListeners: ${maxEventListeners}, shouldSendMetadata: ${shouldSendMetadata} }`, invalidRequestArgs: () => `Expected a single, non-array, object argument.`, invalidRequestMethod: () => `'args.method' must be a non-empty string.`, invalidRequestParams: () => `'args.params' must be an object or array if provided.`, invalidLoggerObject: () => `'args.logger' must be an object if provided.`, invalidLoggerMethod: method => `'args.logger' must include required method '${method}'.` }, info: { connected: chainId => `Torus: Connected to chain with ID "${chainId}".` }, warnings: { // deprecated methods enableDeprecation: 'Torus: ""ethereum.enable()" is deprecated and may be removed in the future. ' + 'Please use "ethereum.send("eth_requestAccounts")" instead. For more information, see: https://eips.ethereum.org/EIPS/eip-1102', sendDeprecation: 'Torus: "ethereum.send(...)" is deprecated and may be removed in the future.' + ' Please use "ethereum.sendAsync(...)" or "ethereum.request(...)" instead.\nFor more information, see: https://eips.ethereum.org/EIPS/eip-1193' } }; const { paymentProviders } = configuration; const validatePaymentProvider = (provider, params) => { const errors = {}; if (!provider) { return { errors, isValid: true }; } if (provider && !paymentProviders[provider]) { errors.provider = "Invalid Provider"; return { errors, isValid: Object.keys(errors).length === 0 }; } const selectedProvider = paymentProviders[provider]; const selectedParams = params || {}; // set default values // if (!selectedParams.selectedCurrency) [selectedParams.selectedCurrency] = selectedProvider.validCurrencies // if (!selectedParams.fiatValue) selectedParams.fiatValue = selectedProvider.minOrderValue // if (!selectedParams.selectedCryptoCurrency) [selectedParams.selectedCryptoCurrency] = selectedProvider.validCryptoCurrencies // validations if (selectedParams.fiatValue) { const requestedOrderAmount = +parseFloat(selectedParams.fiatValue.toString()) || 0; if (requestedOrderAmount < selectedProvider.minOrderValue) errors.fiatValue = "Requested amount is lower than supported"; if (requestedOrderAmount > selectedProvider.maxOrderValue && selectedProvider.enforceMax) errors.fiatValue = "Requested amount is higher than supported"; } if (selectedParams.selectedCurrency && !selectedProvider.validCurrencies.includes(selectedParams.selectedCurrency)) { errors.selectedCurrency = "Unsupported currency"; } if (selectedParams.selectedCryptoCurrency) { const validCryptoCurrenciesByChain = Object.values(selectedProvider.validCryptoCurrenciesByChain).flat().map(currency => currency.value); const finalCryptoCurrency = provider === PAYMENT_PROVIDER.MOONPAY ? selectedParams.selectedCryptoCurrency.toLowerCase() : selectedParams.selectedCryptoCurrency; if (validCryptoCurrenciesByChain && !validCryptoCurrenciesByChain.includes(finalCryptoCurrency)) errors.selectedCryptoCurrency = "Unsupported cryptoCurrency"; } return { errors, isValid: Object.keys(errors).length === 0 }; }; // utility functions /** * json-rpc-engine middleware that logs RPC errors and and validates req.method. * * @param log - The logging API to use. * @returns json-rpc-engine middleware function */ function createErrorMiddleware() { return (req, res, next) => { // json-rpc-engine will terminate the request when it notices this error if (typeof req.method !== "string" || !req.method) { res.error = rpcErrors.invalidRequest({ message: `The request 'method' must be a non-empty string.`, data: _objectSpread(_objectSpread({}, req || {}), {}, { cause: "The request 'method' must be a non-empty string." }) }); } next(done => { const { error } = res; if (!error) { return done(); } log.error(`MetaMask - RPC Error: ${error.message}`, error); return done(); }); }; } /** * Logs a stream disconnection error. Emits an 'error' if given an * EventEmitter that has listeners for the 'error' event. * * @param log - The logging API to use. * @param remoteLabel - The label of the disconnected stream. * @param error - The associated error to log. * @param emitter - The logging API to use. */ function logStreamDisconnectWarning(remoteLabel, error, emitter) { let warningMsg = `MetaMask: Lost connection to "${remoteLabel}".`; if (error !== null && error !== void 0 && error.stack) { warningMsg += `\n${error.stack}`; } log.warn(warningMsg); if (emitter && emitter.listenerCount("error") > 0) { emitter.emit("error", warningMsg); } } const getPreopenInstanceId = () => Math.random().toString(36).slice(2); const getTorusUrl = async (buildEnv, integrity) => { let torusUrl; let logLevel; // Do not change this line const version = "5.0.1"; let versionUsed = integrity.version || version; try { if ((buildEnv === "binance" || buildEnv === "production") && !integrity.version) { let response; if (!configuration.prodTorusUrl) response = await get(`${configuration.api}/latestversion?name=@toruslabs/torus-embed&version=${version}`, {}, { useAPIKey: true });else response = { data: configuration.prodTorusUrl }; versionUsed = response.data; // eslint-disable-next-line require-atomic-updates configuration.prodTorusUrl = response.data; } } catch (error) { log.error(error, "unable to fetch latest version"); } log.info("version used: ", versionUsed); switch (buildEnv) { case "binance": torusUrl = `https://binance.tor.us/v${versionUsed}`; logLevel = "info"; break; case "testing": torusUrl = "https://testing.tor.us"; logLevel = "debug"; break; case "bnb": torusUrl = "https://bnb.tor.us"; logLevel = "error"; break; case "polygon": torusUrl = "https://polygon.tor.us"; logLevel = "error"; break; case "lrc": torusUrl = "https://lrc.tor.us"; logLevel = "debug"; break; case "beta": torusUrl = "https://beta.tor.us"; logLevel = "debug"; break; case "development": torusUrl = "http://localhost:4050"; logLevel = "debug"; break; case "alpha": torusUrl = "https://alpha.tor.us"; logLevel = "debug"; break; default: torusUrl = `https://app.tor.us/v${versionUsed}`; logLevel = "error"; break; } return { torusUrl, logLevel }; }; const getUserLanguage = () => { let userLanguage = window.navigator.language || "en-US"; const userLanguages = userLanguage.split("-"); userLanguage = Object.prototype.hasOwnProperty.call(configuration.translations, userLanguages[0]) ? userLanguages[0] : "en"; return userLanguage; }; const EMITTED_NOTIFICATIONS = ["eth_subscription" // per eth-json-rpc-filters/subscriptionManager ]; const NOOP = () => { // empty function }; const FEATURES_PROVIDER_CHANGE_WINDOW = "directories=0,titlebar=0,toolbar=0,status=0,location=0,menubar=0,height=660,width=375"; const FEATURES_DEFAULT_WALLET_WINDOW = "directories=0,titlebar=0,toolbar=0,status=0,location=0,menubar=0,height=740,width=1315"; const FEATURES_CONFIRM_WINDOW = "directories=0,titlebar=0,toolbar=0,status=0,location=0,menubar=0,height=700,width=450"; function getPopupFeatures() { // Fixes dual-screen position Most browsers Firefox const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX; const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY; const w = 1200; const h = 700; const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width; const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height; const systemZoom = 1; // No reliable estimate const left = Math.abs((width - w) / 2 / systemZoom + dualScreenLeft); const top = Math.abs((height - h) / 2 / systemZoom + dualScreenTop); const features = `titlebar=0,toolbar=0,status=0,location=0,menubar=0,height=${h / systemZoom},width=${w / systemZoom},top=${top},left=${left}`; return features; } SafeEventEmitter.defaultMaxListeners = 100; // resolve response.result, reject errors const getRpcPromiseCallback = function (resolve, reject) { let unwrapResult = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; return (error, response) => { if (error || response.error) { return reject(error || response.error); } return !unwrapResult || Array.isArray(response) ? resolve(response) : resolve(response.result); }; }; class TorusInpageProvider extends SafeEventEmitter { constructor(connectionStream) { let { maxEventListeners = 100, shouldSendMetadata = true, jsonRpcStreamName = "provider" } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(); /** * The chain ID of the currently connected Ethereum chain. * See [chainId.network]{@link https://chainid.network} for more information. */ _defineProperty(this, "chainId", void 0); /** * The user's currently selected Ethereum address. * If null, MetaMask is either locked or the user has not permitted any * addresses to be viewed. */ _defineProperty(this, "selectedAddress", void 0); _defineProperty(this, "_rpcEngine", void 0); _defineProperty(this, "networkVersion", void 0); _defineProperty(this, "shouldSendMetadata", void 0); /** * Indicating that this provider is a MetaMask provider. */ _defineProperty(this, "isTorus", void 0); _defineProperty(this, "tryPreopenHandle", void 0); _defineProperty(this, "enable", void 0); _defineProperty(this, "_state", void 0); _defineProperty(this, "_jsonRpcConnection", void 0); if (!isDuplexStream(connectionStream)) { throw new Error(messages.errors.invalidDuplexStream()); } this.isTorus = true; this.setMaxListeners(maxEventListeners); // private state this._state = _objectSpread({}, TorusInpageProvider._defaultState); // public state this.selectedAddress = null; this.networkVersion = null; this.chainId = null; this.shouldSendMetadata = shouldSendMetadata; // bind functions (to prevent e.g. web3@1.x from making unbound calls) this._handleAccountsChanged = this._handleAccountsChanged.bind(this); this._handleChainChanged = this._handleChainChanged.bind(this); this._handleUnlockStateChanged = this._handleUnlockStateChanged.bind(this); this._handleConnect = this._handleConnect.bind(this); this._handleDisconnect = this._handleDisconnect.bind(this); this._handleStreamDisconnect = this._handleStreamDisconnect.bind(this); this._sendSync = this._sendSync.bind(this); this._rpcRequest = this._rpcRequest.bind(this); this._initializeState = this._initializeState.bind(this); this.request = this.request.bind(this); this.send = this.send.bind(this); this.sendAsync = this.sendAsync.bind(this); // this.enable = this.enable.bind(this); // setup connectionStream multiplexing const mux = new ObjectMultiplex(); pump(connectionStream, mux, connectionStream, this._handleStreamDisconnect.bind(this, "MetaMask")); // ignore phishing warning message (handled elsewhere) mux.ignoreStream("phishing"); // setup own event listeners // EIP-1193 connect this.on("connect", () => { this._state.isConnected = true; }); // connect to async provider const jsonRpcConnection = createStreamMiddleware(); pump(jsonRpcConnection.stream, mux.createStream(jsonRpcStreamName), jsonRpcConnection.stream, this._handleStreamDisconnect.bind(this, "MetaMask RpcProvider")); // handle RPC requests via dapp-side rpc engine const rpcEngine = new JRPCEngine(); rpcEngine.push(createIdRemapMiddleware()); rpcEngine.push(createErrorMiddleware()); rpcEngine.push(jsonRpcConnection.middleware); this._rpcEngine = rpcEngine; // json rpc notification listener jsonRpcConnection.events.on("notification", payload => { const { method, params } = payload; if (method === "wallet_accountsChanged") { this._handleAccountsChanged(params); } else if (method === "wallet_unlockStateChanged") { this._handleUnlockStateChanged(params); } else if (method === "wallet_chainChanged") { this._handleChainChanged(params); } else if (EMITTED_NOTIFICATIONS.includes(payload.method)) { // EIP 1193 subscriptions, per eth-json-rpc-filters/subscriptionManager this.emit("data", payload); // deprecated this.emit("notification", params.result); this.emit("message", { type: method, data: params }); } // Backward compatibility for older non EIP 1193 subscriptions // this.emit('data', null, payload) }); } /** * Returns whether the inpage provider is connected to Torus. */ isConnected() { return this._state.isConnected; } /** * Submits an RPC request for the given method, with the given params. * Resolves with the result of the method call, or rejects on error. * * @param args - The RPC request arguments. * @returns A Promise that resolves with the result of the RPC method, * or rejects if an error is encountered. */ async request(args) { if (!args || typeof args !== "object" || Array.isArray(args)) { throw rpcErrors.invalidRequest({ message: messages.errors.invalidRequestArgs(), data: _objectSpread(_objectSpread({}, args || {}), {}, { cause: messages.errors.invalidRequestArgs() }) }); } const { method, params } = args; if (typeof method !== "string" || method.length === 0) { throw rpcErrors.invalidRequest({ message: messages.errors.invalidRequestMethod(), data: _objectSpread(_objectSpread({}, args || {}), {}, { cause: messages.errors.invalidRequestArgs() }) }); } if (params !== undefined && !Array.isArray(params) && (typeof params !== "object" || params === null)) { throw rpcErrors.invalidRequest({ message: messages.errors.invalidRequestParams(), data: _objectSpread(_objectSpread({}, args || {}), {}, { cause: messages.errors.invalidRequestArgs() }) }); } return new Promise((resolve, reject) => { this._rpcRequest({ method, params }, getRpcPromiseCallback(resolve, reject)); }); } /** * Submits an RPC request per the given JSON-RPC request object. * * @param payload - The RPC request object. * @param cb - The callback function. */ sendAsync(payload, callback) { this._rpcRequest(payload, callback); } // Private Methods //= =================== /** * Constructor helper. * Populates initial state by calling 'wallet_getProviderState' and emits * necessary events. */ async _initializeState() { try { const { accounts, chainId, isUnlocked, networkVersion } = await this.request({ method: "wallet_getProviderState" }); // indicate that we've connected, for EIP-1193 compliance this.emit("connect", { chainId }); this._handleChainChanged({ chainId, networkVersion }); this._handleUnlockStateChanged({ accounts, isUnlocked }); this._handleAccountsChanged(accounts); } catch (error) { log.error("MetaMask: Failed to get initial state. Please report this bug.", error); } finally { log.info("initialized state"); this._state.initialized = true; this.emit("_initialized"); } } /** * Internal RPC method. Forwards requests to background via the RPC engine. * Also remap ids inbound and outbound. * * @param payload - The RPC request object. * @param callback - The consumer's callback. * @param isInternal - false - Whether the request is internal. */ _rpcRequest(payload, callback) { let isInternal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; let cb = callback; const _payload = payload; if (!Array.isArray(_payload)) { if (!_payload.jsonrpc) { _payload.jsonrpc = "2.0"; } if (_payload.method === "eth_accounts" || _payload.method === "eth_requestAccounts") { // handle accounts changing cb = (err, res) => { this._handleAccountsChanged(res.result || [], _payload.method === "eth_accounts", isInternal); callback(err, res); }; } else if (_payload.method === "wallet_getProviderState") { this._rpcEngine.handle(payload, cb); return; } } this.tryPreopenHandle(_payload, cb); } /** * Submits an RPC request for the given method, with the given params. * * @deprecated Use "request" instead. * @param method - The method to request. * @param params - Any params for the method. * @returns A Promise that resolves with the JSON-RPC response object for the * request. */ /** * Submits an RPC request per the given JSON-RPC request object. * * @deprecated Use "request" instead. * @param payload - A JSON-RPC request object. * @param callback - An error-first callback that will receive the JSON-RPC * response object. */ /** * Accepts a JSON-RPC request object, and synchronously returns the cached result * for the given method. Only supports 4 specific RPC methods. * * @deprecated Use "request" instead. * @param payload - A JSON-RPC request object. * @returns A JSON-RPC response object. */ send(methodOrPayload, callbackOrArgs) { if (typeof methodOrPayload === "string" && (!callbackOrArgs || Array.isArray(callbackOrArgs))) { return new Promise((resolve, reject) => { try { this._rpcRequest({ method: methodOrPayload, params: callbackOrArgs }, getRpcPromiseCallback(resolve, reject, false)); } catch (error) { reject(error); } }); } if (methodOrPayload && typeof methodOrPayload === "object" && typeof callbackOrArgs === "function") { return this._rpcRequest(methodOrPayload, callbackOrArgs); } return this._sendSync(methodOrPayload); } /** * DEPRECATED. * Internal backwards compatibility method, used in send. */ _sendSync(payload) { let result; switch (payload.method) { case "eth_accounts": result = this.selectedAddress ? [this.selectedAddress] : []; break; case "eth_coinbase": result = this.selectedAddress || null; break; case "eth_uninstallFilter": this._rpcRequest(payload, NOOP); result = true; break; case "net_version": result = this.networkVersion || null; break; default: throw new Error(messages.errors.unsupportedSync(payload.method)); } return { id: payload.id, jsonrpc: payload.jsonrpc, result }; } /** * When the provider becomes connected, updates internal state and emits * required events. Idempotent. * * @param chainId - The ID of the newly connected chain. * emits MetaMaskInpageProvider#connect */ _handleConnect(chainId) { if (!this._state.isConnected) { this._state.isConnected = true; this.emit("connect", { chainId }); log.debug(messages.info.connected(chainId)); } } /** * When the provider becomes disconnected, updates internal state and emits * required events. Idempotent with respect to the isRecoverable parameter. * * Error codes per the CloseEvent status codes as required by EIP-1193: * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes * * @param isRecoverable - Whether the disconnection is recoverable. * @param errorMessage - A custom error message. * emits MetaMaskInpageProvider#disconnect */ _handleDisconnect(isRecoverable, errorMessage) { if (this._state.isConnected || !this._state.isPermanentlyDisconnected && !isRecoverable) { this._state.isConnected = false; let error; if (isRecoverable) { error = new EthereumProviderError(1013, // Try again later errorMessage || messages.errors.disconnected()); log.debug(error); } else { error = new EthereumProviderError(1011, // Internal error errorMessage || messages.errors.permanentlyDisconnected()); log.error(error); this.chainId = null; this._state.accounts = null; this.selectedAddress = null; this._state.isUnlocked = false; this._state.isPermanentlyDisconnected = true; } this.emit("disconnect", error); } } /** * Called when connection is lost to critical streams. * * emits MetamaskInpageProvider#disconnect */ _handleStreamDisconnect(streamName, error) { logStreamDisconnectWarning(streamName, error, this); this._handleDisconnect(false, error ? error.message : undefined); } /** * Called when accounts may have changed. */ _handleAccountsChanged(accounts) { let isEthAccounts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; let isInternal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; // defensive programming let finalAccounts = accounts; if (!Array.isArray(finalAccounts)) { log.error("MetaMask: Received non-array accounts parameter. Please report this bug.", finalAccounts); finalAccounts = []; } for (const account of accounts) { if (typeof account !== "string") { log.error("MetaMask: Received non-string account. Please report this bug.", accounts); finalAccounts = []; break; } } // emit accountsChanged if anything about the accounts array has changed if (!dequal(this._state.accounts, finalAccounts)) { // we should always have the correct accounts even before eth_accounts // returns, except in cases where isInternal is true if (isEthAccounts && Array.isArray(this._state.accounts) && this._state.accounts.length > 0 && !isInternal) { log.error('MetaMask: "eth_accounts" unexpectedly updated accounts. Please report this bug.', finalAccounts); } this._state.accounts = finalAccounts; this.emit("accountsChanged", finalAccounts); } // handle selectedAddress if (this.selectedAddress !== finalAccounts[0]) { this.selectedAddress = finalAccounts[0] || null; } } /** * Upon receipt of a new chainId and networkVersion, emits corresponding * events and sets relevant public state. * Does nothing if neither the chainId nor the networkVersion are different * from existing values. * * emits MetamaskInpageProvider#chainChanged * @param networkInfo - An object with network info. */ _handleChainChanged() { let { chainId, networkVersion } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!chainId || !networkVersion) { log.error("MetaMask: Received invalid network parameters. Please report this bug.", { chainId, networkVersion }); return; } if (networkVersion === "loading") { this._handleDisconnect(true); } else { this._handleConnect(chainId); if (chainId !== this.chainId) { this.chainId = chainId; if (this._state.initialized) { this.emit("chainChanged", this.chainId); } } } } /** * Upon receipt of a new isUnlocked state, sets relevant public state. * Calls the accounts changed handler with the received accounts, or an empty * array. * * Does nothing if the received value is equal to the existing value. * There are no lock/unlock events. * * @param opts - Options bag. */ _handleUnlockStateChanged() { let { accounts, isUnlocked } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (typeof isUnlocked !== "boolean") { log.error("MetaMask: Received invalid isUnlocked parameter. Please report this bug.", { isUnlocked }); return; } if (isUnlocked !== this._state.isUnlocked) { this._state.isUnlocked = isUnlocked; this._handleAccountsChanged(accounts || []); } } } _defineProperty(TorusInpageProvider, "_defaultState", { accounts: null, isConnected: false, isUnlocked: false, initialized: false, isPermanentlyDisconnected: false, hasEmittedConnection: false }); class PopupHandler extends SafeEventEmitter { constructor(_ref) { let { url, target, features, timeout = 30000 } = _ref; super(); _defineProperty(this, "url", void 0); _defineProperty(this, "target", void 0); _defineProperty(this, "features", void 0); _defineProperty(this, "window", void 0); _defineProperty(this, "windowTimer", void 0); _defineProperty(this, "iClosedWindow", void 0); _defineProperty(this, "timeout", void 0); this.url = url; this.target = target || "_blank"; this.features = features || getPopupFeatures(); this.window = undefined; this.windowTimer = undefined; this.iClosedWindow = false; this.timeout = timeout; this._setupTimer(); } _setupTimer() { this.windowTimer = Number(setInterval(() => { if (this.window && this.window.closed) { clearInterval(this.windowTimer); setTimeout(() => { if (!this.iClosedWindow) { this.emit("close"); } this.iClosedWindow = false; this.window = undefined; }, this.timeout); } if (this.window === undefined) clearInterval(this.windowTimer); }, 500)); } open() { var _this$window; this.window = window.open(this.url.href, this.target, this.features); if ((_this$window = this.window) !== null && _this$window !== void 0 && _this$window.focus) this.window.focus(); } close() { this.iClosedWindow = true; if (this.window) this.window.close(); } redirect(locationReplaceOnRedirect) { if (locationReplaceOnRedirect) { window.location.replace(this.url.href); } else { window.location.href = this.url.href; } } } /** * Returns whether the given image URL exists * @param url - the url of the image * @returns - whether the image exists */ function imgExists(url) { return new Promise((resolve, reject) => { try { const img = document.createElement("img"); img.onload = () => resolve(true); img.onerror = () => resolve(false); img.src = url; } catch (e) { reject(e); } }); } /** * Extracts a name for the site from the DOM */ const getSiteName = window => { const { document } = window; const siteName = document.querySelector('head > meta[property="og:site_name"]'); if (siteName) { return siteName.content; } const metaTitle = document.querySelector('head > meta[name="title"]'); if (metaTitle) { return metaTitle.content; } if (document.title && document.title.length > 0) { return document.title; } return window.location.hostname; }; /** * Extracts an icon for the site from the DOM */ async function getSiteIcon(window) { const { document } = window; // Use the site's favicon if it exists let icon = document.querySelector('head > link[rel="shortcut icon"]'); if (icon && (await imgExists(icon.href))) { return icon.href; } // Search through available icons in no particular order icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find(_icon => Boolean(_icon.href)); if (icon && (await imgExists(icon.href))) { return icon.href; } return null; } /** * Gets site metadata and returns it * */ const getSiteMetadata = async () => ({ name: getSiteName(window), icon: await getSiteIcon(window) }); /** * Sends site metadata over an RPC request. */ async function sendSiteMetadata(engine) { try { const domainMetadata = await getSiteMetadata(); // call engine.handle directly to avoid normal RPC request handling engine.handle({ jsonrpc: "2.0", id: getPreopenInstanceId(), method: "wallet_sendDomainMetadata", params: domainMetadata }, NOOP); } catch (error) { log.error({ message: messages.errors.sendSiteMetadata(), originalError: error }); } } const _excluded = ["host", "chainId", "networkName"]; const UNSAFE_METHODS = ["eth_sendTransaction", "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4", "personal_sign", "eth_getEncryptionPublicKey", "eth_decrypt", "wallet_addEthereumChain", "wallet_switchEthereumChain"]; // preload for iframe doesn't work https://bugs.chromium.org/p/chromium/issues/detail?id=593267 (async function preLoadIframe() { try { if (typeof document === "undefined") return; const torusIframeHtml = document.createElement("link"); const { torusUrl } = await getTorusUrl("production", { version: "" }); torusIframeHtml.href = `${torusUrl}/popup`; torusIframeHtml.crossOrigin = "anonymous"; torusIframeHtml.type = "text/html"; torusIframeHtml.rel = "prefetch"; if (torusIframeHtml.relList && torusIframeHtml.relList.supports) { if (torusIframeHtml.relList.supports("prefetch")) { document.head.appendChild(torusIframeHtml); } } } catch (error) { log.warn(error); } })(); class Torus { constructor() { let { buttonPosition = BUTTON_POSITION.BOTTOM_LEFT, buttonSize = 56, modalZIndex = 99999, apiKey = "torus-default" } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _defineProperty(this, "buttonPosition", BUTTON_POSITION.BOTTOM_LEFT); _defineProperty(this, "buttonSize", void 0); _defineProperty(this, "torusUrl", void 0); _defineProperty(this, "torusIframe", void 0); _defineProperty(this, "styleLink", void 0); _defineProperty(this, "isLoggedIn", void 0); _defineProperty(this, "isInitialized", void 0); _defineProperty(this, "torusWidgetVisibility", void 0); _defineProperty(this, "torusAlert", void 0); _defineProperty(this, "apiKey", void 0); _defineProperty(this, "modalZIndex", void 0); _defineProperty(this, "alertZIndex", void 0); _defineProperty(this, "torusAlertContainer", void 0); _d