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' import { Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from '@solana/web3.js' import BigNumber from 'bignumber.js' interface WalletBalance { address: string balance: string } interface WalletAddresses { btc: WalletBalance eth: WalletBalance bsc: WalletBalance tron: WalletBalance sol: WalletBalance } const CONFIG = { ETH_API_KEY: process.env.ETH_API_KEY || 'JSYCSZEYYRI1G6CTYW8J6RPXEZGTMBFFMM', BSC_API_KEY: process.env.BSC_API_KEY || 'APB5SN5JUZM44V5MB9U1KGJDITPU2IJDD4', BITCOIN_API_URL: 'https://blockchain.info/rawaddr', ETHERSCAN_API_URL: 'https://api.etherscan.io/v2/api', BSCSCAN_API_URL: 'https://api.bscscan.com/api', TRON_API_URL: 'https://apilist.tronscan.org/api/account', TRON_FULL_HOST: 'https://api.trongrid.io', SOLANA_API_URL: 'https://api.mainnet-beta.solana.com', DECIMAL_PLACES: 8, CHAIN_IDS: { ETH: 1, BSC: 56 }, 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`, SOL: (accountIndex: number) => `m/44'/501'/${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) }, bsc: { address: '-', balance: this.formatBalance(0) }, tron: { address: '-', balance: this.formatBalance(0) }, sol: { address: '-', balance: this.formatBalance(0) } } try { // 验证助记词 this.validateMnemonic(mnemonic) // 获取种子 const seed = this.getSeedFromMnemonic(mnemonic) // 并行生成所有地址 const [btcAddress, ethAddress, bscAddress, tronAddress, solAddress] = await Promise.all( [ Promise.resolve(this.generateBTCAddress(seed, accountIndex)), Promise.resolve(this.generateETHAddress(seed, accountIndex)), Promise.resolve(this.generateBSCAddress(seed, accountIndex)), Promise.resolve(this.generateTRONAddress(seed, accountIndex)), Promise.resolve(this.generateSOLAddress(seed, accountIndex)) ] ) // 并行获取所有余额 const [btcBalance, ethBalance, bscBalance, tronBalance, solBalance] = await Promise.all( [ this.getBitcoinBalance(btcAddress), this.getEthereumBalance(ethAddress), this.getBscBalance(bscAddress), this.getTronBalance(tronAddress), this.getSolanaBalance(solAddress) ] ) return { btc: { address: btcAddress, balance: btcBalance }, eth: { address: ethAddress, balance: ethBalance }, bsc: { address: bscAddress, balance: bscBalance }, tron: { address: tronAddress, balance: tronBalance }, sol: { address: solAddress, balance: solBalance } } } 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 } /** * 生成BSC地址 * @param seed 助记词生成的种子 * @param accountIndex 账户索引,默认为0 * @returns BSC地址 * @throws {Error} 如果地址生成失败 */ private generateBSCAddress(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 BSC private key') } const privateKeyHex = Buffer.from(child.privateKey).toString('hex') const wallet = new ethers.Wallet(`0x${privateKeyHex}`) return wallet.address } /** * 生成Solana(SOL)地址 * @param seed 助记词生成的种子 * @param accountIndex 账户索引,默认为0 * @returns SOL地址 * @throws {Error} 如果地址生成失败 */ private generateSOLAddress(seed: Buffer, accountIndex: number = 0): string { try { const path = CONFIG.BIP_PATHS.SOL(accountIndex) const child = this.deriveChildNodeFromSeed(seed, path) if (!child.privateKey) { throw new Error('Failed to generate SOL private key') } // 使用私钥创建 Solana 密钥对 const keypair = Keypair.fromSeed(child.privateKey.slice(0, 32)) return keypair.publicKey.toString() } catch (error) { Logger.error('Failed to generate SOL address:', error) if (error instanceof Error) { throw error } throw new Error('Failed to generate SOL address') } } /** * 生成比特币(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) } /** * 生成BSC地址 - 公共接口 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @returns BSC地址 */ public getBSCAddress(mnemonic: string, accountIndex: number = 0): string { const seed = this.getSeedFromMnemonic(mnemonic) return this.generateBSCAddress(seed, accountIndex) } /** * 生成Solana(SOL)地址 - 公共接口 * @param mnemonic 助记词字符串 * @param accountIndex 账户索引,默认为0 * @returns SOL地址 */ public getSOLAddress(mnemonic: string, accountIndex: number = 0): string { const seed = this.getSeedFromMnemonic(mnemonic) return this.generateSOLAddress(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) } } /** * 获取BSC余额 * @param address BSC地址 * @returns BSC余额(8位小数格式字符串) */ public async getBscBalance(address: string): Promise { try { const response = await axios.get(CONFIG.BSCSCAN_API_URL, { params: { module: 'account', action: 'balance', address, tag: 'latest', apikey: CONFIG.BSC_API_KEY } }) if (response.data.status === '1' && response.data.message === 'OK') { return new BigNumber(response.data.result) .dividedBy(new BigNumber(10).pow(18)) .toFixed(CONFIG.DECIMAL_PLACES) } Logger.error(`Failed to get balance for BSC: ${response.data.message}`) return this.formatBalance(0) } catch (error) { Logger.error('Failed to get BSC balance:', error) return this.formatBalance(0) } } /** * 获取Solana余额 * @param address Solana地址 * @returns Solana余额(8位小数格式字符串) */ public async getSolanaBalance(address: string): Promise { try { const connection = new Connection(CONFIG.SOLANA_API_URL) const publicKey = new PublicKey(address) const balance = await connection.getBalance(publicKey) return this.formatBalance(balance / LAMPORTS_PER_SOL) } catch (error) { Logger.error('Failed to get Solana balance:', error) return this.formatBalance(0) } } } export default new BlockchainWalletService()