UNPKG

@smartinvoicexyz/utils

Version:

Unified source for utility functions used across the Smart Invoice protocol.

180 lines (179 loc) 6.76 kB
import { ARWEAVE_ENDPOINT, invoiceFactory, IPFS_ENDPOINT, isSupportedChainId, resolverInfo, resolvers, wrappedNativeToken, } from '@smartinvoicexyz/constants'; import _ from 'lodash'; import { formatUnits } from 'viem'; import { chainById } from './web3'; export const getResolverTypes = (chainId) => { if (!isSupportedChainId(chainId)) return []; return resolvers(chainId); }; export const getResolverInfo = (resolverType, chainId) => { if (!resolverType || !isSupportedChainId(chainId)) return undefined; return resolverInfo(chainId)[resolverType]; }; export const getResolverInfoByAddress = (resolverAddress, chainId) => { if (!isSupportedChainId(chainId)) return undefined; const resolverTypes = getResolverTypes(chainId); return resolverTypes .map(resolverType => getResolverInfo(resolverType, chainId)) .find(info => info?.address.toLowerCase() === resolverAddress.toLowerCase()); }; export const getResolverFee = (invoice, tokenBalance) => { const { resolutionRate } = _.pick(invoice, ['resolutionRate']); return tokenBalance?.value && resolutionRate ? formatUnits(!resolutionRate || resolutionRate === BigInt(0) ? BigInt(0) : tokenBalance.value / BigInt(resolutionRate), 18) : undefined; }; export const resolverFeeLabel = (fee, tokenMetadata) => (fee ? `${fee} ${tokenMetadata?.symbol}` : undefined); export const getWrappedNativeToken = (chainId) => { if (!isSupportedChainId(chainId)) return undefined; return wrappedNativeToken(chainId); }; export const getNativeTokenSymbol = (chainId) => { if (!isSupportedChainId(chainId)) return undefined; return chainById(chainId).nativeCurrency.symbol; }; export const getInvoiceFactoryAddress = (chainId) => { if (!isSupportedChainId(chainId)) return undefined; return invoiceFactory(chainId); }; const getExplorerUrl = (chainId) => { if (!isSupportedChainId(chainId)) return undefined; const chain = chainById(chainId); return _.get(chain, 'blockExplorers.etherscan.url', _.get(chain, 'blockExplorers.default.url')); }; export const getTxLink = (chainId, hash) => { if (!chainId || !hash) return undefined; return `${getExplorerUrl(chainId)}/tx/${hash}`; }; export const getAddressLink = (chainId, hash) => { if (!chainId || !hash) return undefined; return `${getExplorerUrl(chainId)}/address/${hash}`; }; // bytes58 QmNLei78zWmzUdbeRB3CiUfAizWUrbeeZh5K1rhAQKCh51 // is the same as // bytes64 12200000000000000000000000000000000000000000000000000000000000000000 // which means an all zeros bytes32 was input on the contract const EMPTY_CID = 'QmNLei78zWmzUdbeRB3CiUfAizWUrbeeZh5K1rhAQKCh51'; export const isEmptyIpfsHash = (hash) => hash === EMPTY_CID; export const getAccountString = (account) => { if (!account) return undefined; const len = account.length; return `0x${_.toUpper(account.slice(2, 4))}...${_.toUpper(account.slice(len - 5, len))}`; }; export const isKnownResolver = (resolverType, chainId) => { if (!isSupportedChainId(chainId)) return false; return _.includes(getResolverTypes(chainId), _.toLower(resolverType)); }; export const isKnownResolverAddress = (resolverAddress, chainId) => _.some(getResolverTypes(chainId), resolverType => getResolverInfo(resolverType, chainId)?.address.toLowerCase() === resolverAddress.toLowerCase()); export const getResolverString = (resolverType, chainId, resolverAddress) => { const info = getResolverInfo(resolverType, chainId); return info ? info.name : getAccountString(resolverAddress); }; const IPFS_HASH_REGEX = /^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,})([/?#][-a-zA-Z0-9@:%_+.~#?&//=]*)*$/; const isValidCID = (hash) => IPFS_HASH_REGEX.test(hash); const ARWEAVE_TXID_REGEX = /^([a-z0-9-_]{43})$/i; const isValidArweaveTxID = (hash) => ARWEAVE_TXID_REGEX.test(hash); const ALLOWED_PROTOCOLS = ['http:', 'https:', 'ipfs:', 'ar:']; export const PROTOCOL_OPTIONS = ['https://', 'ipfs://', 'ar://']; export const isValidURL = (url) => { if (!url) return false; try { // Parse the URL const parsedUrl = new URL(url); // Check if the protocol is one of the allowed protocols if (!ALLOWED_PROTOCOLS.includes(parsedUrl.protocol)) { return false; } if (parsedUrl.protocol === 'ipfs:') { return isValidCID(parsedUrl.href.slice(7)); } if (parsedUrl.protocol === 'ar:') { return isValidArweaveTxID(parsedUrl.href.slice(5)); } // Additional checks for valid formats, etc. return true; } catch { // If URL constructor throws, it's not a valid URL return false; } }; export const getDecimals = (value) => { const [, decimal] = value.split('.'); return decimal?.length || 0; }; export function commify(x, decimals) { if (_.toString(x).includes('.')) { const [whole, decimal] = x.toString().split('.'); return `${commify(whole)}.${decimal.substring(0, Math.min(decimals ?? decimal.length, decimal.length))}`; } return _.toString(x).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } export const resolutionFeePercentage = (resolutionRate) => { if (!resolutionRate || resolutionRate === '0') { return 0; } const feePercentage = 1 / parseInt(resolutionRate, 10); return feePercentage; }; export const uriToHttp = (uri) => { if (!isValidURL(uri)) { return undefined; } const parsedUrl = new URL(uri); if (parsedUrl.protocol === 'ipfs:') { return `${IPFS_ENDPOINT}/ipfs/${parsedUrl.href.slice(7)}`; } if (parsedUrl.protocol === 'ar:') { return `${ARWEAVE_ENDPOINT}/${parsedUrl.href.slice(5)}`; } return parsedUrl.href; }; export const getIpfsLink = (hash) => { if (!isValidCID(hash)) { return ''; } return `${IPFS_ENDPOINT}/ipfs/${hash}`; }; export const documentToHttp = (document) => uriToHttp(document.src); const protocolToType = (protocol) => { switch (protocol) { case 'ipfs:': return 'ipfs'; case 'ar:': return 'arweave'; case 'https:': case 'http:': default: return 'https'; } }; export const uriToDocument = (uri) => { if (!isValidURL(uri)) { return undefined; } const parsed = new URL(uri); const now = Math.floor(new Date().getTime() / 1000); const type = protocolToType(parsed.protocol); return { id: parsed.hostname + parsed.pathname + now, src: uri, type, createdAt: now, }; };