/** * Chain-specific address validators с CHECKSUM проверкой. * Принципиально: regex/length недостаточно — TRX/BTC используют base58check, * один испорченный символ может пройти regex, но кошелёк по такому адресу * не восстановим → funds permanently lost. */ import { ethers } from 'ethers'; import { createHash } from 'crypto'; import bs58 from 'bs58'; import * as bitcoin from 'bitcoinjs-lib'; import { PublicKey } from '@solana/web3.js'; const TRX_RE = /^T[1-9A-HJ-NP-Za-km-z]{33}$/; export type ChainCode = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC'; export function isValidAddress(chain: ChainCode, address: string): boolean { // Любой блокчейн-адрес помещается в ~64 chars. 256 был оверкилл и open vector // для DoS (тратим CPU на bs58.decode 200-char garbage). if (typeof address !== 'string' || address.length === 0 || address.length > 64) return false; switch (chain) { case 'BTC': return isValidBtcAddress(address); case 'TRX': return isValidTrxAddress(address); case 'ETH': case 'BSC': return ethers.utils.isAddress(address); case 'SOL': return isValidSolAddress(address); default: return false; } } // ── BTC: bitcoinjs-lib проверяет version byte + checksum (P2PKH/P2SH/bech32) ── function isValidBtcAddress(address: string): boolean { try { bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin); return true; } catch { return false; } } // ── TRX: base58check + первый байт 0x41 ── function isValidTrxAddress(address: string): boolean { if (!TRX_RE.test(address)) return false; let decoded: Uint8Array; try { decoded = bs58.decode(address); } catch { return false; } if (decoded.length !== 25) return false; // 1 prefix + 20 payload + 4 checksum if (decoded[0] !== 0x41) return false; // TRX mainnet prefix const payload = decoded.subarray(0, 21); const checksum = decoded.subarray(21); const h1 = createHash('sha256').update(payload).digest(); const h2 = createHash('sha256').update(h1).digest(); for (let i = 0; i < 4; i++) { if (h2[i] !== checksum[i]) return false; } return true; } // ── SOL: реальное base58-декодирование через PublicKey ── function isValidSolAddress(address: string): boolean { try { const pk = new PublicKey(address); // PublicKey принимает 32-байтовое значение; isOnCurve дополнительный sanity return pk.toBytes().length === 32; } catch { return false; } } export function isValidAmount(amount: string): boolean { if (typeof amount !== 'string' || amount.length === 0 || amount.length > 78) return false; if (!/^\d+$/.test(amount)) return false; try { return BigInt(amount) > 0n; } catch { return false; } }