|
|
@@ -6,6 +6,8 @@ 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
|
|
|
@@ -15,20 +17,30 @@ interface WalletBalance {
|
|
|
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`
|
|
|
+ TRON: (accountIndex: number) => `m/44'/195'/${accountIndex}'/0/0`,
|
|
|
+ SOL: (accountIndex: number) => `m/44'/501'/${accountIndex}'/0/0`
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -49,7 +61,9 @@ class BlockchainWalletService {
|
|
|
const defaultResult: WalletAddresses = {
|
|
|
btc: { address: '-', balance: this.formatBalance(0) },
|
|
|
eth: { address: '-', balance: this.formatBalance(0) },
|
|
|
- tron: { address: '-', balance: this.formatBalance(0) }
|
|
|
+ bsc: { address: '-', balance: this.formatBalance(0) },
|
|
|
+ tron: { address: '-', balance: this.formatBalance(0) },
|
|
|
+ sol: { address: '-', balance: this.formatBalance(0) }
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
@@ -60,23 +74,33 @@ class BlockchainWalletService {
|
|
|
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 [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, tronBalance] = await Promise.all([
|
|
|
- this.getBitcoinBalance(btcAddress),
|
|
|
- this.getEthereumBalance(ethAddress),
|
|
|
- this.getTronBalance(tronAddress)
|
|
|
- ])
|
|
|
+ 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 },
|
|
|
- tron: { address: tronAddress, balance: tronBalance }
|
|
|
+ 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}`)
|
|
|
@@ -210,6 +234,55 @@ class BlockchainWalletService {
|
|
|
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 助记词字符串
|
|
|
@@ -248,6 +321,28 @@ class BlockchainWalletService {
|
|
|
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 比特币地址
|
|
|
@@ -324,6 +419,55 @@ class BlockchainWalletService {
|
|
|
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()
|