UNPKG

@0xfutbol/id

Version:

React component library with shared providers for 0xFutbol ID

486 lines (485 loc) 19.1 kB
import {B as BaseError,q as stringify,u as getSavedConnectParamsFromStorage,v as normalizeChainId,h as getCachedChain,x as getWalletInfo,y as saveConnectParamsToStorage,z as getDefaultAppMetadata,D as DEFAULT_PROJECT_ID,A as formatWalletConnectUrl,E as getRpcUrlForChain,s as stringify$1,d as getAddress,F as getChainMetadata,G as getValidPublicRPCUrl,H as numberToHex,p as parseTypedData,J as getTypesForEIP712Domain,K as validateTypedData,L as serializeTypedData,M as stringToHex,N as uint8ArrayToHex,t as trackTransaction,O as NAMESPACE}from'./index-BJCJzM2_.js';import'react';import'react/jsx-runtime';import'@0xfutbol/id-sign';import'react-use';import'@0xfutbol/constants';import'thirdweb';import'@matchain/matchid-sdk-react';import'@tanstack/react-query';import'@matchain/matchid-sdk-react/index.css';import'react-dom';const getUrl = (url) => url;class RpcRequestError extends BaseError { constructor({ body, error, url, }) { super('RPC Request failed.', { cause: error, details: error.message, metaMessages: [`URL: ${getUrl(url)}`, `Request body: ${stringify(body)}`], name: 'RpcRequestError', }); Object.defineProperty(this, "code", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "data", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.code = error.code; this.data = error.data; } }const unknownErrorCode = -1; class RpcError extends BaseError { constructor(cause, { code, docsPath, metaMessages, name, shortMessage, }) { super(shortMessage, { cause, docsPath, metaMessages: metaMessages || cause?.metaMessages, name: name || 'RpcError', }); Object.defineProperty(this, "code", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.name = name || cause.name; this.code = (cause instanceof RpcRequestError ? cause.code : (code ?? unknownErrorCode)); } } class ProviderRpcError extends RpcError { constructor(cause, options) { super(cause, options); Object.defineProperty(this, "data", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.data = options.data; } } class UserRejectedRequestError extends ProviderRpcError { constructor(cause) { super(cause, { code: UserRejectedRequestError.code, name: 'UserRejectedRequestError', shortMessage: 'User rejected the request.', }); } } Object.defineProperty(UserRejectedRequestError, "code", { enumerable: true, configurable: true, writable: true, value: 4001 }); class SwitchChainError extends ProviderRpcError { constructor(cause) { super(cause, { code: SwitchChainError.code, name: 'SwitchChainError', shortMessage: 'An error occurred when attempting to switch chain.', }); } } Object.defineProperty(SwitchChainError, "code", { enumerable: true, configurable: true, writable: true, value: 4902 });const ADD_ETH_CHAIN_METHOD = "wallet_addEthereumChain"; const defaultShowQrModal = true; const storageKeys = { requestedChains: "tw.wc.requestedChains", lastUsedChainId: "tw.wc.lastUsedChainId", }; /** * @internal */ async function connectWC(options, emitter, walletId, storage, sessionHandler) { const provider = await initProvider(options, walletId, sessionHandler); const wcOptions = options.walletConnect; let { onDisplayUri } = wcOptions || {}; // use default sessionHandler unless onDisplayUri is explicitly provided if (!onDisplayUri && sessionHandler) { const walletInfo = await getWalletInfo(walletId); const deeplinkHandler = (uri) => { const appUrl = walletInfo.mobile.native || walletInfo.mobile.universal; if (!appUrl) { // generic wc uri sessionHandler(uri); return; } const fullUrl = formatWalletConnectUrl(appUrl, uri).redirect; sessionHandler(fullUrl); }; onDisplayUri = deeplinkHandler; } if (onDisplayUri) { provider.events.addListener("display_uri", onDisplayUri); } let optionalChains = wcOptions?.optionalChains; let chainToRequest = options.chain; // ignore the given options chains - and set the safe supported chains if (walletId === "global.safe") { optionalChains = chainsToRequestForSafe.map(getCachedChain); if (chainToRequest && !optionalChains.includes(chainToRequest)) { chainToRequest = undefined; } } const { rpcMap, requiredChain, optionalChains: chainsToRequest, } = getChainsToRequest({ client: options.client, chain: chainToRequest, optionalChains: optionalChains, }); if (provider.session) { await provider.connect({ ...(wcOptions?.pairingTopic ? { pairingTopic: wcOptions?.pairingTopic } : {}), optionalChains: chainsToRequest, chains: requiredChain ? [requiredChain.id] : undefined, rpcMap: rpcMap, }); } setRequestedChainsIds(chainsToRequest, storage); // If session exists and chains are authorized, enable provider for required chain const addresses = await provider.enable(); const address = addresses[0]; if (!address) { throw new Error("No accounts found on provider."); } const providerChainId = normalizeChainId(provider.chainId); const chain = options.chain && options.chain.id === providerChainId ? options.chain : getCachedChain(providerChainId); if (options) { const savedParams = { optionalChains: options.walletConnect?.optionalChains, chain: options.chain, pairingTopic: options.walletConnect?.pairingTopic, }; if (storage) { saveConnectParamsToStorage(storage, walletId, savedParams); } } if (wcOptions?.onDisplayUri) { provider.events.removeListener("display_uri", wcOptions.onDisplayUri); } return onConnect(address, chain, provider, emitter, storage, options.client); } /** * Auto connect to already connected wallet connect session. * @internal */ async function autoConnectWC(options, emitter, walletId, storage, sessionHandler) { const savedConnectParams = storage ? await getSavedConnectParamsFromStorage(storage, walletId) : null; const provider = await initProvider(savedConnectParams ? { chain: savedConnectParams.chain, client: options.client, walletConnect: { pairingTopic: savedConnectParams.pairingTopic, optionalChains: savedConnectParams.optionalChains, }, } : { client: options.client, walletConnect: {}, }, walletId, sessionHandler, true); const address = provider.accounts[0]; if (!address) { throw new Error("No accounts found on provider."); } const providerChainId = normalizeChainId(provider.chainId); const chain = options.chain && options.chain.id === providerChainId ? options.chain : getCachedChain(providerChainId); return onConnect(address, chain, provider, emitter, storage, options.client); } // Connection utils ----------------------------------------------------------------------------------------------- async function initProvider(options, walletId, sessionRequestHandler, isAutoConnect = false) { const walletInfo = await getWalletInfo(walletId); const wcOptions = options.walletConnect; const { EthereumProvider, OPTIONAL_EVENTS, OPTIONAL_METHODS } = await import('./index.es-DZr7RChF.js'); let optionalChains = wcOptions?.optionalChains; let chainToRequest = options.chain; // ignore the given options chains - and set the safe supported chains if (walletId === "global.safe") { optionalChains = chainsToRequestForSafe.map(getCachedChain); if (chainToRequest && !optionalChains.includes(chainToRequest)) { chainToRequest = undefined; } } const { rpcMap, requiredChain, optionalChains: chainsToRequest, } = getChainsToRequest({ client: options.client, chain: chainToRequest, optionalChains: optionalChains, }); const provider = await EthereumProvider.init({ showQrModal: wcOptions?.showQrModal === undefined ? sessionRequestHandler ? false : defaultShowQrModal : wcOptions.showQrModal, projectId: wcOptions?.projectId || DEFAULT_PROJECT_ID, optionalMethods: OPTIONAL_METHODS, optionalEvents: OPTIONAL_EVENTS, optionalChains: chainsToRequest, chains: requiredChain ? [requiredChain.id] : undefined, metadata: { name: wcOptions?.appMetadata?.name || getDefaultAppMetadata().name, description: wcOptions?.appMetadata?.description || getDefaultAppMetadata().description, url: wcOptions?.appMetadata?.url || getDefaultAppMetadata().url, icons: [ wcOptions?.appMetadata?.logoUrl || getDefaultAppMetadata().logoUrl, ], }, rpcMap: rpcMap, qrModalOptions: wcOptions?.qrModalOptions, disableProviderPing: true, }); provider.events.setMaxListeners(Number.POSITIVE_INFINITY); // disconnect the provider if chains are stale when (if not auto connecting) if (!isAutoConnect) { // const isStale = await isChainsStale(provider, chainsToRequest); if (provider.session) { await provider.disconnect(); } } if (walletId !== "walletConnect") { async function handleSessionRequest() { const walletLinkToOpen = provider.session?.peer?.metadata?.redirect?.native || walletInfo.mobile.native || walletInfo.mobile.universal; if (sessionRequestHandler && walletLinkToOpen) { // TODO: propagate error when this fails await sessionRequestHandler(walletLinkToOpen); } } provider.signer.client.on("session_request_sent", handleSessionRequest); provider.events.addListener("disconnect", () => { provider.signer.client.off("session_request_sent", handleSessionRequest); }); } return provider; } function createAccount({ provider, address, client, }) { const account = { address: address, async sendTransaction(tx) { const transactionHash = (await provider.request({ method: "eth_sendTransaction", params: [ { gas: tx.gas ? numberToHex(tx.gas) : undefined, value: tx.value ? numberToHex(tx.value) : undefined, from: getAddress(address), to: tx.to, data: tx.data, }, ], })); trackTransaction({ client: client, walletAddress: getAddress(address), walletType: "walletConnect", transactionHash, chainId: tx.chainId, contractAddress: tx.to ?? undefined, gasPrice: tx.gasPrice, }); return { transactionHash, }; }, async signMessage({ message }) { const messageToSign = (() => { if (typeof message === "string") { return stringToHex(message); } if (message.raw instanceof Uint8Array) { return uint8ArrayToHex(message.raw); } return message.raw; })(); return provider.request({ method: "personal_sign", params: [messageToSign, this.address], }); }, async signTypedData(_data) { const data = parseTypedData(_data); const { domain, message, primaryType } = data; const types = { EIP712Domain: getTypesForEIP712Domain({ domain }), ...data.types, }; // Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc // as we can't statically check this with TypeScript. validateTypedData({ domain, message, primaryType, types }); const typedData = serializeTypedData({ domain: domain ?? {}, message, primaryType, types, }); return await provider.request({ method: "eth_signTypedData_v4", params: [this.address, typedData], }); }, }; return account; } function onConnect(address, chain, provider, emitter, storage, client) { const account = createAccount({ provider, address, client }); async function disconnect() { provider.removeListener("accountsChanged", onAccountsChanged); provider.removeListener("chainChanged", onChainChanged); provider.removeListener("disconnect", onDisconnect); await provider.disconnect(); } function onDisconnect() { setRequestedChainsIds([], storage); storage?.removeItem(storageKeys.lastUsedChainId); disconnect(); emitter.emit("disconnect", undefined); } function onAccountsChanged(accounts) { if (accounts[0]) { const newAccount = createAccount({ provider, address: getAddress(accounts[0]), client, }); emitter.emit("accountChanged", newAccount); emitter.emit("accountsChanged", accounts); } else { onDisconnect(); } } function onChainChanged(newChainId) { const newChain = getCachedChain(normalizeChainId(newChainId)); emitter.emit("chainChanged", newChain); storage?.setItem(storageKeys.lastUsedChainId, String(newChainId)); } provider.on("accountsChanged", onAccountsChanged); provider.on("chainChanged", onChainChanged); provider.on("disconnect", onDisconnect); provider.on("session_delete", onDisconnect); return [ account, chain, disconnect, (newChain) => switchChainWC(provider, newChain, storage), ]; } // Storage utils ----------------------------------------------------------------------------------------------- function getNamespaceMethods(provider) { return provider.session?.namespaces[NAMESPACE]?.methods || []; } function getNamespaceChainsIds(provider) { const chainIds = provider.session?.namespaces[NAMESPACE]?.chains?.map((chain) => Number.parseInt(chain.split(":")[1] || "")); return chainIds ?? []; } async function switchChainWC(provider, chain, storage) { const chainId = chain.id; try { const namespaceChains = getNamespaceChainsIds(provider); const namespaceMethods = getNamespaceMethods(provider); const isChainApproved = namespaceChains.includes(chainId); if (!isChainApproved && namespaceMethods.includes(ADD_ETH_CHAIN_METHOD)) { const apiChain = await getChainMetadata(chain); const blockExplorerUrls = [ ...new Set([ ...(chain.blockExplorers?.map((x) => x.url) || []), ...(apiChain.explorers?.map((x) => x.url) || []), ]), ]; await provider.request({ method: ADD_ETH_CHAIN_METHOD, params: [ { chainId: numberToHex(apiChain.chainId), chainName: apiChain.name, nativeCurrency: apiChain.nativeCurrency, rpcUrls: getValidPublicRPCUrl(apiChain), // no clientId on purpose blockExplorerUrls: blockExplorerUrls.length > 0 ? blockExplorerUrls : undefined, }, ], }); const requestedChains = await getRequestedChainsIds(storage); requestedChains.push(chainId); setRequestedChainsIds(requestedChains, storage); } await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: numberToHex(chainId) }], }); } catch (error) { const message = typeof error === "string" ? error : error?.message; if (/user rejected request/i.test(message)) { throw new UserRejectedRequestError(error); } throw new SwitchChainError(error); } } /** * Set the requested chains to the storage. * @internal */ function setRequestedChainsIds(chains, storage) { storage?.setItem(storageKeys.requestedChains, stringify$1(chains)); } /** * Get the last requested chains from the storage. * @internal */ async function getRequestedChainsIds(storage) { const data = await storage.getItem(storageKeys.requestedChains); return data ? JSON.parse(data) : []; } function getChainsToRequest(options) { const rpcMap = {}; if (options.chain) { rpcMap[options.chain.id] = getRpcUrlForChain({ chain: options.chain, client: options.client, }); } // limit optional chains to 10 const optionalChains = (options?.optionalChains || []).slice(0, 10); for (const chain of optionalChains) { rpcMap[chain.id] = getRpcUrlForChain({ chain: chain, client: options.client, }); } if (!options.chain && optionalChains.length === 0) { rpcMap[1] = getCachedChain(1).rpc; } return { rpcMap, requiredChain: options.chain ? options.chain : undefined, optionalChains: optionalChains.length > 0 ? optionalChains.map((x) => x.id) : [1], }; } const chainsToRequestForSafe = [ 1, // Ethereum Mainnet 11155111, // Sepolia Testnet 42161, // Arbitrum One Mainnet 43114, // Avalanche Mainnet 8453, // Base Mainnet 1313161554, // Aurora Mainnet 84532, // Base Sepolia Testnet 56, // Binance Smart Chain Mainnet 42220, // Celo Mainnet 100, // Gnosis Mainnet 10, // Optimism Mainnet 137, // Polygon Mainnet 1101, // Polygon zkEVM Mainnet 324, // zkSync Era mainnet 534352, // Scroll mainnet ];export{autoConnectWC,connectWC};//# sourceMappingURL=controller-BPc6F7sR.js.map