/** * Wallet generation: BIP39 mnemonic + multi-chain address derivation. * Server-side для custodial-флоу. */ import { ethers } from 'ethers'; import { createHash } from 'crypto'; import bs58 from 'bs58'; import * as bip39 from 'bip39'; import { BIP32Factory } from 'bip32'; import * as ecc from 'tiny-secp256k1'; import * as bitcoin from 'bitcoinjs-lib'; import { Keypair } from '@solana/web3.js'; import { derivePath } from 'ed25519-hd-key'; import type { ChainCode } from '../lib/address-validators'; const bip32 = BIP32Factory(ecc); export const DERIVATION_PATHS: Record = { ETH: "m/44'/60'/0'/0/0", BSC: "m/44'/60'/0'/0/0", BTC: "m/84'/0'/0'/0/0", TRX: "m/44'/195'/0'/0/0", SOL: "m/44'/501'/0'/0'", }; export const ALL_CHAINS: ChainCode[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL']; export interface DerivedWallet { chain: ChainCode; address: string; derivationPath: string; } export function generateMnemonic(): string { return bip39.generateMnemonic(128); } export function validateMnemonic(m: string): boolean { return bip39.validateMnemonic(m); } export async function deriveAllAddresses(mnemonic: string): Promise { if (!bip39.validateMnemonic(mnemonic)) { throw new Error('Invalid mnemonic'); } const seed = await bip39.mnemonicToSeed(mnemonic); const seedHex = seed.toString('hex'); // ETH (BSC shares) const ethWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.ETH); const ethAddress = ethers.utils.getAddress(ethWallet.address); // BTC P2WPKH bech32 const btcRoot = bip32.fromSeed(seed); const btcChild = btcRoot.derivePath(DERIVATION_PATHS.BTC); if (!btcChild.publicKey) throw new Error('BTC derivation failed'); const btcPayment = bitcoin.payments.p2wpkh({ pubkey: Buffer.from(btcChild.publicKey), network: bitcoin.networks.bitcoin, }); if (!btcPayment.address) throw new Error('BTC payment derivation failed'); // TRX (same secp256k1 + keccak256 as ETH, different encoding) const trxWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.TRX); const trxAddress = ethAddressToTron(trxWallet.address); // SOL (ed25519) const { key: solKey } = derivePath(DERIVATION_PATHS.SOL, seedHex); if (!solKey || solKey.length !== 32) { throw new Error('SOL derivation produced invalid seed length'); } const solKeypair = Keypair.fromSeed(solKey); const solAddress = solKeypair.publicKey.toBase58(); return [ { chain: 'ETH', address: ethAddress, derivationPath: DERIVATION_PATHS.ETH }, { chain: 'BSC', address: ethAddress, derivationPath: DERIVATION_PATHS.BSC }, { chain: 'BTC', address: btcPayment.address, derivationPath: DERIVATION_PATHS.BTC }, { chain: 'TRX', address: trxAddress, derivationPath: DERIVATION_PATHS.TRX }, { chain: 'SOL', address: solAddress, derivationPath: DERIVATION_PATHS.SOL }, ]; } /** * ETH address (0x...) → TRX base58check (T...). * Используют одну curve и keccak256-derivation; различается только prefix (0x41) + encoding. */ export function ethAddressToTron(ethAddr: string): string { const hex = ethAddr.toLowerCase().replace(/^0x/, ''); if (hex.length !== 40) { throw new Error('ethAddressToTron: invalid input length'); } const bytes = Buffer.from(hex, 'hex'); const prefixed = Buffer.concat([Buffer.from([0x41]), bytes]); const h1 = createHash('sha256').update(prefixed).digest(); const h2 = createHash('sha256').update(h1).digest(); const checksum = h2.subarray(0, 4); return bs58.encode(new Uint8Array(Buffer.concat([prefixed, checksum]))); }