| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- 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<WalletAddresses> {
- // 设置默认返回值
- 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<string> {
- 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<string> {
- 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<string> {
- 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<string> {
- 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<string> {
- 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()
|