UNPKG

@enclavemoney/enclave-wallet-sdk

Version:

A simple enclave wallet SDK for React applications

288 lines (264 loc) 8.56 kB
// Add this mapping at the top (after TOKEN_NETWORKS) export const NETWORK_NAME_TO_CHAIN_ID: Record<string, number> = { Arbitrum: 42161, Optimism: 10, Base: 8453, Solana: 792703809, Bitcoin: 8253038, Sonic: 146, Ethereum: 1, Unichain: 130, Avalanche: 43114, Polygon: 137, BNB: 56, }; export function getNetworkName(chainId: string | number): string { const chainIdNumber = typeof chainId === "string" ? parseInt(chainId) : chainId; const networkName = Object.entries(NETWORK_NAME_TO_CHAIN_ID).find( ([_, id]) => id === chainIdNumber )?.[0]; return networkName || chainId.toString(); } export const NETWORK_LOGO_URLS: Record<string, string> = { 42161: "https://storage.googleapis.com/enclave_assets/arb-logo.svg", 10: "https://storage.googleapis.com/enclave_assets/optimism-logo.svg", 8453: "https://storage.googleapis.com/enclave_assets/base.svg", 792703809: "https://storage.googleapis.com/enclave_assets/sol.png", 8253038: "https://assets.coingecko.com/coins/images/1/standard/bitcoin.png?1696501400", 56: "https://assets.coingecko.com/coins/images/825/standard/bnb-icon2_2x.png?1696501970", 137: "https://assets.coingecko.com/coins/images/4713/standard/polygon.png?1698233745", 43114: "https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png?1696512369", 130: "https://icons.llamao.fi/icons/chains/rsz_unichain.jpg", 146: "https://assets.coingecko.com/coins/images/38108/standard/200x200_Sonic_Logo.png?1734679256", 1: "https://media.socket.tech/tokens/all/ETH", }; export function formatAmount(amount: string) { const num = parseFloat(amount); const decimalPlaces = amount.split(".")[1]?.length || 0; return decimalPlaces > 6 ? num.toFixed(6) : amount; } // Simple debounce implementation export const debounce = <T extends (...args: any[]) => any>( func: T, wait: number ): ((...args: Parameters<T>) => void) => { let timeout: NodeJS.Timeout; return (...args: Parameters<T>) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; }; /** * Validates and sanitizes numeric input to prevent invalid formats * @param value The input value to validate * @param maxDecimals Maximum number of decimal places allowed (optional) * @returns The sanitized value or empty string if invalid */ export function validateNumericInput( value: string, maxDecimals?: number ): string { if (!value) return ""; // Remove any non-digit, non-decimal point characters // This prevents things like "1+1", "1--", etc. let sanitized = value.replace(/[^0-9.]/g, ""); // Handle multiple decimal points - keep only the first one const parts = sanitized.split("."); if (parts.length > 2) { sanitized = parts[0] + "." + parts.slice(1).join(""); } // Prevent starting with decimal point for better UX if (sanitized.startsWith(".")) { sanitized = "0" + sanitized; } // Prevent multiple leading zeros (but allow single zero before decimal) if ( sanitized.length > 1 && sanitized.startsWith("0") && !sanitized.startsWith("0.") ) { sanitized = sanitized.replace(/^0+/, "0"); } // Enforce maximum decimal places if specified if (maxDecimals !== undefined && sanitized.includes(".")) { const [integerPart, decimalPart] = sanitized.split("."); if (decimalPart.length > maxDecimals) { sanitized = integerPart + "." + decimalPart.slice(0, maxDecimals); } } // Final validation - ensure it's a valid number format if (sanitized && (isNaN(Number(sanitized)) || sanitized.includes(".."))) { return ""; } return sanitized; } /** * Handles keydown events to prevent invalid characters from being typed * @param e The keyboard event * @param currentValue The current input value * @param maxDecimals Maximum number of decimal places allowed (optional) */ export function handleNumericKeyDown( e: React.KeyboardEvent<HTMLInputElement>, currentValue: string, maxDecimals?: number ) { // Allow basic navigation and editing keys const allowedKeys = [ "Backspace", "Delete", "Tab", "Escape", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown", ]; // Allow Ctrl/Cmd combinations (copy, paste, select all, etc.) if (e.ctrlKey || e.metaKey) { return; } // Allow allowed keys if (allowedKeys.includes(e.key)) { return; } // Allow digits, but check decimal limit if applicable if (/^[0-9]$/.test(e.key)) { // If maxDecimals is specified and we're after a decimal point, check limit if (maxDecimals !== undefined && currentValue.includes(".")) { const decimalPart = currentValue.split(".")[1]; if (decimalPart && decimalPart.length >= maxDecimals) { // Check if cursor is in the decimal part const target = e.target as HTMLInputElement; const cursorPosition = target.selectionStart || 0; const decimalIndex = currentValue.indexOf("."); // If cursor is after decimal and we've reached the limit, prevent input if ( cursorPosition > decimalIndex && decimalPart.length >= maxDecimals ) { e.preventDefault(); return; } } } return; } // Allow decimal point only if there isn't one already if (e.key === "." && !currentValue.includes(".")) { return; } // Prevent all other characters including +, -, e, E, etc. e.preventDefault(); } export const availableNetworks = [ { name: "Solana", icon: "https://storage.googleapis.com/enclave_assets/sol.png", }, { name: "Arbitrum", icon: "https://storage.googleapis.com/enclave_assets/arb-logo.svg", }, { name: "Optimism", icon: "https://storage.googleapis.com/enclave_assets/optimism-logo.svg", }, { name: "Base", icon: "https://storage.googleapis.com/enclave_assets/base.svg", }, { name: "BNB", icon: "https://assets.coingecko.com/coins/images/825/standard/bnb-icon2_2x.png?1696501970", }, { name: "Polygon", icon: "https://assets.coingecko.com/coins/images/4713/standard/polygon.png?1698233745", }, { name: "Avalanche", icon: "https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png?1696512369", }, { name: "Unichain", icon: "https://icons.llamao.fi/icons/chains/rsz_unichain.jpg", }, { name: "Sonic", icon: "https://assets.coingecko.com/coins/images/38108/standard/200x200_Sonic_Logo.png?1734679256", }, { name: "Ethereum", icon: "https://media.socket.tech/tokens/all/ETH", }, ]; export enum ProtocolProvider { LIFI = "LIFI", RELAY = "RELAY", BUNGEE = "BUNGEE", GARDEN = "GARDEN", ONEINCH = "1INCH", INCOMING = "INCOMING", CCTP_V1 = "CCTP_V1", CCTP_V2_FAST = "CCTP_V2_FAST", CCTP_V2_STANDARD = "CCTP_V2_SLOW", } /** * Gets the appropriate icon URL for a protocol provider * @param provider The protocol provider * @returns The icon URL for the provider */ export function getProviderIcon( provider: ProtocolProvider | string ): string | null { switch (provider) { case ProtocolProvider.LIFI: return "https://storage.googleapis.com/enclave_assets/lifi.png"; case ProtocolProvider.BUNGEE: return "https://storage.googleapis.com/enclave_assets/bungee.png"; case ProtocolProvider.RELAY: return "https://storage.googleapis.com/enclave_assets/relay.png"; case ProtocolProvider.CCTP_V1: case ProtocolProvider.CCTP_V2_FAST: case ProtocolProvider.CCTP_V2_STANDARD: return "https://storage.googleapis.com/enclave_assets/circle.png"; default: return null; } } /** * Gets the human-readable name for a protocol provider * @param provider The protocol provider * @returns The display name for the provider */ export function getProviderName(provider: ProtocolProvider | string): string { switch (provider) { case ProtocolProvider.BUNGEE: return "Bungee"; case ProtocolProvider.RELAY: return "Relay"; case ProtocolProvider.CCTP_V1: return "Circle CCTP V1"; case ProtocolProvider.CCTP_V2_FAST: return "Circle CCTP V2 Fast"; case ProtocolProvider.CCTP_V2_STANDARD: return "Circle CCTP V2 Standard"; case ProtocolProvider.LIFI: return "LiFi"; case ProtocolProvider.GARDEN: return "Garden"; case ProtocolProvider.ONEINCH: return "1inch"; default: return provider || "Unknown Provider"; } }