import * as bip39 from 'bip39' import { BIP32Factory } from 'bip32' import * as bitcoin from 'bitcoinjs-lib' import { ethers } from 'ethers' import { TronWeb } from 'tronweb' import * as ecc from 'tiny-secp256k1' import Logger from '@ioc:Adonis/Core/Logger' import axios from 'axios' interface WalletBalance { address: string balance: string } interface WalletAddresses { btc: WalletBalance eth: WalletBalance tron: WalletBalance } const CONFIG = { ETH_API_KEY: process.env.ETH_API_KEY || 'JSYCSZEYYRI1G6CTYW8J6RPXEZGTMBFFMM', BITCOIN_API_URL: 'https://blockchain.info/rawaddr', ETHERSCAN_API_URL: 'https://api.etherscan.io/v2/api', TRON_API_URL: 'https://apilist.tronscan.org/api/account', TRON_FULL_HOST: 'https://api.trongrid.io', DECIMAL_PLACES: 8, BIP_PATHS: { BTC: (accountIndex: number) => `m/44'/0'/${accountIndex}'/0/0`, ETH: (accountIndex: number) => `m/44'/60'/${accountIndex}'/0/0`, TRON: (accountIndex: number) => `m/44'/195'/${accountIndex}'/0/0` } } const bip32 = BIP32Factory(ecc) class BlockchainWalletService { /** * 获取所有支持的区块链地址和余额 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @returns 包含所有地址和余额的对象 */ public async getAllAddresses( mnemonic: string, accountIndex: number = 0 ): Promise { // 设置默认返回值 const defaultResult: WalletAddresses = { btc: { address: '-', balance: this.formatBalance(0) }, eth: { address: '-', balance: this.formatBalance(0) }, tron: { address: '-', balance: this.formatBalance(0) } } try { // 验证助记词 this.validateMnemonic(mnemonic) // 获取种子 const seed = this.getSeedFromMnemonic(mnemonic) // 并行生成所有地址 const [btcAddress, ethAddress, tronAddress] = await Promise.all([ Promise.resolve(this.generateBTCAddress(seed, accountIndex)), Promise.resolve(this.generateETHAddress(seed, accountIndex)), Promise.resolve(this.generateTRONAddress(seed, accountIndex)) ]) // 并行获取所有余额 const [btcBalance, ethBalance, tronBalance] = await Promise.all([ this.getBitcoinBalance(btcAddress), this.getEthereumBalance(ethAddress), this.getTronBalance(tronAddress) ]) return { btc: { address: btcAddress, balance: btcBalance }, eth: { address: ethAddress, balance: ethBalance }, tron: { address: tronAddress, balance: tronBalance } } } catch (error) { Logger.error(`Failed to get addresses: ${(error as Error).message}`) return defaultResult } } /** * 格式化余额为统一的8位小数格式 * @param balance 数值余额 * @returns 格式化后的字符串余额,固定8位小数 */ private formatBalance(balance: number): string { return balance.toFixed(CONFIG.DECIMAL_PLACES) } /** * 验证助记词是否有效 * @param mnemonic 助记词字符串 * @throws {Error} 如果助记词无效 */ private validateMnemonic(mnemonic: string): void { if (!bip39.validateMnemonic(mnemonic)) { throw new Error('Invalid mnemonic') } } /** * 获取助记词的种子 * @param mnemonic 助记词字符串 * @returns 种子Buffer */ private getSeedFromMnemonic(mnemonic: string): Buffer { this.validateMnemonic(mnemonic) return bip39.mnemonicToSeedSync(mnemonic) } /** * 根据给定的路径派生子节点 * @param seed 助记词生成的种子 * @param path 路径 * @returns 派生的子节点 * @throws {Error} 如果派生过程失败 */ private deriveChildNodeFromSeed(seed: Buffer, path: string) { try { const root = bip32.fromSeed(seed) return root.derivePath(path) } catch (error) { Logger.error(`Failed to derive child node: ${(error as Error).message}`) throw new Error(`Key derivation failed: ${(error as Error).message}`) } } /** * 生成比特币(BTC)地址 * @param seed 助记词生成的种子 * @param accountIndex 账户索引,默认为0 * @param network 网络类型,默认为主网 * @returns BTC地址 * @throws {Error} 如果地址生成失败 */ private generateBTCAddress( seed: Buffer, accountIndex: number = 0, network = bitcoin.networks.bitcoin ): string { const path = CONFIG.BIP_PATHS.BTC(accountIndex) const child = this.deriveChildNodeFromSeed(seed, path) if (!child.publicKey) { throw new Error('Failed to generate BTC public key') } const { address } = bitcoin.payments.p2pkh({ pubkey: Buffer.from(child.publicKey), network }) if (!address) { throw new Error('Failed to generate BTC address') } return address } /** * 生成以太坊(ETH)地址 * @param seed 助记词生成的种子 * @param accountIndex 账户索引,默认为0 * @returns ETH地址 * @throws {Error} 如果地址生成失败 */ private generateETHAddress(seed: Buffer, accountIndex: number = 0): string { const path = CONFIG.BIP_PATHS.ETH(accountIndex) const child = this.deriveChildNodeFromSeed(seed, path) if (!child.privateKey) { throw new Error('Failed to generate ETH private key') } const privateKeyHex = Buffer.from(child.privateKey).toString('hex') const wallet = new ethers.Wallet(`0x${privateKeyHex}`) return wallet.address } /** * 生成波场(TRON)地址 * @param seed 助记词生成的种子 * @param accountIndex 账户索引,默认为0 * @returns TRON地址 * @throws {Error} 如果地址生成失败 */ private generateTRONAddress(seed: Buffer, accountIndex: number = 0): string { const path = CONFIG.BIP_PATHS.TRON(accountIndex) const child = this.deriveChildNodeFromSeed(seed, path) if (!child.privateKey) { throw new Error('Failed to generate TRON private key') } const privateKeyHex = Buffer.from(child.privateKey).toString('hex') const tronWeb = new TronWeb({ fullHost: CONFIG.TRON_FULL_HOST }) const account = tronWeb.address.fromPrivateKey(privateKeyHex) if (typeof account !== 'string') { throw new Error('Failed to generate TRON address') } return account } /** * 生成比特币(BTC)地址 - 公共接口 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @param network 网络类型,默认为主网 * @returns BTC地址 */ public getBTCAddress( mnemonic: string, accountIndex: number = 0, network = bitcoin.networks.bitcoin ): string { const seed = this.getSeedFromMnemonic(mnemonic) return this.generateBTCAddress(seed, accountIndex, network) } /** * 生成以太坊(ETH)地址 - 公共接口 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @returns ETH地址 */ public getETHAddress(mnemonic: string, accountIndex: number = 0): string { const seed = this.getSeedFromMnemonic(mnemonic) return this.generateETHAddress(seed, accountIndex) } /** * 生成波场(TRON)地址 - 公共接口 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @returns TRON地址 */ public getTRONAddress(mnemonic: string, accountIndex: number = 0): string { const seed = this.getSeedFromMnemonic(mnemonic) return this.generateTRONAddress(seed, accountIndex) } /** * 获取比特币余额 * @param address 比特币地址 * @returns 比特币余额(8位小数格式字符串) */ public async getBitcoinBalance(address: string): Promise { try { const response = await axios.get(`${CONFIG.BITCOIN_API_URL}/${address}`) const balance = response.data.final_balance / 1e8 return this.formatBalance(balance) } catch (error) { Logger.error(`Failed to get Bitcoin balance: ${(error as Error).message}`) return this.formatBalance(0) } } /** * 获取以太坊余额 * @param address 以太坊地址 * @returns 以太坊余额(8位小数格式字符串) */ public async getEthereumBalance(address: string): Promise { try { const response = await axios.get(CONFIG.ETHERSCAN_API_URL, { params: { chainid: 1, module: 'account', action: 'balance', address: address, tag: 'latest', apikey: CONFIG.ETH_API_KEY } }) if (response.data.status !== '1') { Logger.warn(`Etherscan API error: ${response.data.message}`) return this.formatBalance(0) } const balance = Number(response.data.result) / 1e18 return this.formatBalance(balance) } catch (error) { Logger.error(`Failed to get Ethereum balance: ${(error as Error).message}`) return this.formatBalance(0) } } /** * 获取波场USDT余额 * @param address 波场地址 * @returns 波场USDT余额(8位小数格式字符串) */ public async getTronBalance(address: string): Promise { try { const response = await axios.get(`${CONFIG.TRON_API_URL}?address=${address}`) const data = response.data // 查找USDT TRC20代币余额 let usdtBalance = 0 if (data.trc20token_balances && Array.isArray(data.trc20token_balances)) { const usdtToken = data.trc20token_balances.find( (token: any) => token.tokenAbbr === 'USDT' ) if (usdtToken) { const decimals = Number(usdtToken.tokenDecimal) || 6 usdtBalance = Number(usdtToken.balance) / Math.pow(10, decimals) } } return this.formatBalance(usdtBalance) } catch (error) { Logger.error(`Failed to get TRON balance: ${(error as Error).message}`) return this.formatBalance(0) } } } export default new BlockchainWalletService()