BlockchainWalletService.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. import * as bip39 from 'bip39'
  2. import { BIP32Factory } from 'bip32'
  3. import * as bitcoin from 'bitcoinjs-lib'
  4. import { ethers } from 'ethers'
  5. import { TronWeb } from 'tronweb'
  6. import * as ecc from 'tiny-secp256k1'
  7. import Logger from '@ioc:Adonis/Core/Logger'
  8. import axios from 'axios'
  9. import { Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from '@solana/web3.js'
  10. import BigNumber from 'bignumber.js'
  11. interface WalletBalance {
  12. address: string
  13. balance: string
  14. }
  15. interface WalletAddresses {
  16. btc: WalletBalance
  17. eth: WalletBalance
  18. bsc: WalletBalance
  19. tron: WalletBalance
  20. sol: WalletBalance
  21. }
  22. const CONFIG = {
  23. ETH_API_KEY: process.env.ETH_API_KEY || 'JSYCSZEYYRI1G6CTYW8J6RPXEZGTMBFFMM',
  24. BSC_API_KEY: process.env.BSC_API_KEY || 'APB5SN5JUZM44V5MB9U1KGJDITPU2IJDD4',
  25. BITCOIN_API_URL: 'https://blockchain.info/rawaddr',
  26. ETHERSCAN_API_URL: 'https://api.etherscan.io/v2/api',
  27. BSCSCAN_API_URL: 'https://api.bscscan.com/api',
  28. TRON_API_URL: 'https://apilist.tronscan.org/api/account',
  29. TRON_FULL_HOST: 'https://api.trongrid.io',
  30. SOLANA_API_URL: 'https://api.mainnet-beta.solana.com',
  31. DECIMAL_PLACES: 8,
  32. CHAIN_IDS: {
  33. ETH: 1,
  34. BSC: 56
  35. },
  36. BIP_PATHS: {
  37. BTC: (accountIndex: number) => `m/44'/0'/${accountIndex}'/0/0`,
  38. ETH: (accountIndex: number) => `m/44'/60'/${accountIndex}'/0/0`,
  39. TRON: (accountIndex: number) => `m/44'/195'/${accountIndex}'/0/0`,
  40. SOL: (accountIndex: number) => `m/44'/501'/${accountIndex}'/0/0`
  41. }
  42. }
  43. const bip32 = BIP32Factory(ecc)
  44. class BlockchainWalletService {
  45. /**
  46. * 获取所有支持的区块链地址和余额
  47. * @param mnemonic 助记词字符串
  48. * @param accountIndex 账户索引,默认为0
  49. * @returns 包含所有地址和余额的对象
  50. */
  51. public async getAllAddresses(
  52. mnemonic: string,
  53. accountIndex: number = 0
  54. ): Promise<WalletAddresses> {
  55. // 设置默认返回值
  56. const defaultResult: WalletAddresses = {
  57. btc: { address: '-', balance: this.formatBalance(0) },
  58. eth: { address: '-', balance: this.formatBalance(0) },
  59. bsc: { address: '-', balance: this.formatBalance(0) },
  60. tron: { address: '-', balance: this.formatBalance(0) },
  61. sol: { address: '-', balance: this.formatBalance(0) }
  62. }
  63. try {
  64. // 验证助记词
  65. this.validateMnemonic(mnemonic)
  66. // 获取种子
  67. const seed = this.getSeedFromMnemonic(mnemonic)
  68. // 并行生成所有地址
  69. const [btcAddress, ethAddress, bscAddress, tronAddress, solAddress] = await Promise.all(
  70. [
  71. Promise.resolve(this.generateBTCAddress(seed, accountIndex)),
  72. Promise.resolve(this.generateETHAddress(seed, accountIndex)),
  73. Promise.resolve(this.generateBSCAddress(seed, accountIndex)),
  74. Promise.resolve(this.generateTRONAddress(seed, accountIndex)),
  75. Promise.resolve(this.generateSOLAddress(seed, accountIndex))
  76. ]
  77. )
  78. // 并行获取所有余额
  79. const [btcBalance, ethBalance, bscBalance, tronBalance, solBalance] = await Promise.all(
  80. [
  81. this.getBitcoinBalance(btcAddress),
  82. this.getEthereumBalance(ethAddress),
  83. this.getBscBalance(bscAddress),
  84. this.getTronBalance(tronAddress),
  85. this.getSolanaBalance(solAddress)
  86. ]
  87. )
  88. return {
  89. btc: { address: btcAddress, balance: btcBalance },
  90. eth: { address: ethAddress, balance: ethBalance },
  91. bsc: { address: bscAddress, balance: bscBalance },
  92. tron: { address: tronAddress, balance: tronBalance },
  93. sol: { address: solAddress, balance: solBalance }
  94. }
  95. } catch (error) {
  96. Logger.error(`Failed to get addresses: ${(error as Error).message}`)
  97. return defaultResult
  98. }
  99. }
  100. /**
  101. * 格式化余额为统一的8位小数格式
  102. * @param balance 数值余额
  103. * @returns 格式化后的字符串余额,固定8位小数
  104. */
  105. private formatBalance(balance: number): string {
  106. return balance.toFixed(CONFIG.DECIMAL_PLACES)
  107. }
  108. /**
  109. * 验证助记词是否有效
  110. * @param mnemonic 助记词字符串
  111. * @throws {Error} 如果助记词无效
  112. */
  113. private validateMnemonic(mnemonic: string): void {
  114. if (!bip39.validateMnemonic(mnemonic)) {
  115. throw new Error('Invalid mnemonic')
  116. }
  117. }
  118. /**
  119. * 获取助记词的种子
  120. * @param mnemonic 助记词字符串
  121. * @returns 种子Buffer
  122. */
  123. private getSeedFromMnemonic(mnemonic: string): Buffer {
  124. this.validateMnemonic(mnemonic)
  125. return bip39.mnemonicToSeedSync(mnemonic)
  126. }
  127. /**
  128. * 根据给定的路径派生子节点
  129. * @param seed 助记词生成的种子
  130. * @param path 路径
  131. * @returns 派生的子节点
  132. * @throws {Error} 如果派生过程失败
  133. */
  134. private deriveChildNodeFromSeed(seed: Buffer, path: string) {
  135. try {
  136. const root = bip32.fromSeed(seed)
  137. return root.derivePath(path)
  138. } catch (error) {
  139. Logger.error(`Failed to derive child node: ${(error as Error).message}`)
  140. throw new Error(`Key derivation failed: ${(error as Error).message}`)
  141. }
  142. }
  143. /**
  144. * 生成比特币(BTC)地址
  145. * @param seed 助记词生成的种子
  146. * @param accountIndex 账户索引,默认为0
  147. * @param network 网络类型,默认为主网
  148. * @returns BTC地址
  149. * @throws {Error} 如果地址生成失败
  150. */
  151. private generateBTCAddress(
  152. seed: Buffer,
  153. accountIndex: number = 0,
  154. network = bitcoin.networks.bitcoin
  155. ): string {
  156. const path = CONFIG.BIP_PATHS.BTC(accountIndex)
  157. const child = this.deriveChildNodeFromSeed(seed, path)
  158. if (!child.publicKey) {
  159. throw new Error('Failed to generate BTC public key')
  160. }
  161. const { address } = bitcoin.payments.p2pkh({
  162. pubkey: Buffer.from(child.publicKey),
  163. network
  164. })
  165. if (!address) {
  166. throw new Error('Failed to generate BTC address')
  167. }
  168. return address
  169. }
  170. /**
  171. * 生成以太坊(ETH)地址
  172. * @param seed 助记词生成的种子
  173. * @param accountIndex 账户索引,默认为0
  174. * @returns ETH地址
  175. * @throws {Error} 如果地址生成失败
  176. */
  177. private generateETHAddress(seed: Buffer, accountIndex: number = 0): string {
  178. const path = CONFIG.BIP_PATHS.ETH(accountIndex)
  179. const child = this.deriveChildNodeFromSeed(seed, path)
  180. if (!child.privateKey) {
  181. throw new Error('Failed to generate ETH private key')
  182. }
  183. const privateKeyHex = Buffer.from(child.privateKey).toString('hex')
  184. const wallet = new ethers.Wallet(`0x${privateKeyHex}`)
  185. return wallet.address
  186. }
  187. /**
  188. * 生成波场(TRON)地址
  189. * @param seed 助记词生成的种子
  190. * @param accountIndex 账户索引,默认为0
  191. * @returns TRON地址
  192. * @throws {Error} 如果地址生成失败
  193. */
  194. private generateTRONAddress(seed: Buffer, accountIndex: number = 0): string {
  195. const path = CONFIG.BIP_PATHS.TRON(accountIndex)
  196. const child = this.deriveChildNodeFromSeed(seed, path)
  197. if (!child.privateKey) {
  198. throw new Error('Failed to generate TRON private key')
  199. }
  200. const privateKeyHex = Buffer.from(child.privateKey).toString('hex')
  201. const tronWeb = new TronWeb({ fullHost: CONFIG.TRON_FULL_HOST })
  202. const account = tronWeb.address.fromPrivateKey(privateKeyHex)
  203. if (typeof account !== 'string') {
  204. throw new Error('Failed to generate TRON address')
  205. }
  206. return account
  207. }
  208. /**
  209. * 生成BSC地址
  210. * @param seed 助记词生成的种子
  211. * @param accountIndex 账户索引,默认为0
  212. * @returns BSC地址
  213. * @throws {Error} 如果地址生成失败
  214. */
  215. private generateBSCAddress(seed: Buffer, accountIndex: number = 0): string {
  216. const path = CONFIG.BIP_PATHS.ETH(accountIndex)
  217. const child = this.deriveChildNodeFromSeed(seed, path)
  218. if (!child.privateKey) {
  219. throw new Error('Failed to generate BSC private key')
  220. }
  221. const privateKeyHex = Buffer.from(child.privateKey).toString('hex')
  222. const wallet = new ethers.Wallet(`0x${privateKeyHex}`)
  223. return wallet.address
  224. }
  225. /**
  226. * 生成Solana(SOL)地址
  227. * @param seed 助记词生成的种子
  228. * @param accountIndex 账户索引,默认为0
  229. * @returns SOL地址
  230. * @throws {Error} 如果地址生成失败
  231. */
  232. private generateSOLAddress(seed: Buffer, accountIndex: number = 0): string {
  233. try {
  234. const path = CONFIG.BIP_PATHS.SOL(accountIndex)
  235. const child = this.deriveChildNodeFromSeed(seed, path)
  236. if (!child.privateKey) {
  237. throw new Error('Failed to generate SOL private key')
  238. }
  239. // 使用私钥创建 Solana 密钥对
  240. const keypair = Keypair.fromSeed(child.privateKey.slice(0, 32))
  241. return keypair.publicKey.toString()
  242. } catch (error) {
  243. Logger.error('Failed to generate SOL address:', error)
  244. if (error instanceof Error) {
  245. throw error
  246. }
  247. throw new Error('Failed to generate SOL address')
  248. }
  249. }
  250. /**
  251. * 生成比特币(BTC)地址 - 公共接口
  252. * @param mnemonic 助记词字符串
  253. * @param accountIndex 账户索引,默认为0
  254. * @param network 网络类型,默认为主网
  255. * @returns BTC地址
  256. */
  257. public getBTCAddress(
  258. mnemonic: string,
  259. accountIndex: number = 0,
  260. network = bitcoin.networks.bitcoin
  261. ): string {
  262. const seed = this.getSeedFromMnemonic(mnemonic)
  263. return this.generateBTCAddress(seed, accountIndex, network)
  264. }
  265. /**
  266. * 生成以太坊(ETH)地址 - 公共接口
  267. * @param mnemonic 助记词字符串
  268. * @param accountIndex 账户索引,默认为0
  269. * @returns ETH地址
  270. */
  271. public getETHAddress(mnemonic: string, accountIndex: number = 0): string {
  272. const seed = this.getSeedFromMnemonic(mnemonic)
  273. return this.generateETHAddress(seed, accountIndex)
  274. }
  275. /**
  276. * 生成波场(TRON)地址 - 公共接口
  277. * @param mnemonic 助记词字符串
  278. * @param accountIndex 账户索引,默认为0
  279. * @returns TRON地址
  280. */
  281. public getTRONAddress(mnemonic: string, accountIndex: number = 0): string {
  282. const seed = this.getSeedFromMnemonic(mnemonic)
  283. return this.generateTRONAddress(seed, accountIndex)
  284. }
  285. /**
  286. * 生成BSC地址 - 公共接口
  287. * @param mnemonic 助记词字符串
  288. * @param accountIndex 账户索引,默认为0
  289. * @returns BSC地址
  290. */
  291. public getBSCAddress(mnemonic: string, accountIndex: number = 0): string {
  292. const seed = this.getSeedFromMnemonic(mnemonic)
  293. return this.generateBSCAddress(seed, accountIndex)
  294. }
  295. /**
  296. * 生成Solana(SOL)地址 - 公共接口
  297. * @param mnemonic 助记词字符串
  298. * @param accountIndex 账户索引,默认为0
  299. * @returns SOL地址
  300. */
  301. public getSOLAddress(mnemonic: string, accountIndex: number = 0): string {
  302. const seed = this.getSeedFromMnemonic(mnemonic)
  303. return this.generateSOLAddress(seed, accountIndex)
  304. }
  305. /**
  306. * 获取比特币余额
  307. * @param address 比特币地址
  308. * @returns 比特币余额(8位小数格式字符串)
  309. */
  310. public async getBitcoinBalance(address: string): Promise<string> {
  311. try {
  312. const response = await axios.get(`${CONFIG.BITCOIN_API_URL}/${address}`)
  313. const balance = response.data.final_balance / 1e8
  314. return this.formatBalance(balance)
  315. } catch (error) {
  316. Logger.error(`Failed to get Bitcoin balance: ${(error as Error).message}`)
  317. return this.formatBalance(0)
  318. }
  319. }
  320. /**
  321. * 获取以太坊余额
  322. * @param address 以太坊地址
  323. * @returns 以太坊余额(8位小数格式字符串)
  324. */
  325. public async getEthereumBalance(address: string): Promise<string> {
  326. try {
  327. const response = await axios.get(CONFIG.ETHERSCAN_API_URL, {
  328. params: {
  329. chainid: 1,
  330. module: 'account',
  331. action: 'balance',
  332. address: address,
  333. tag: 'latest',
  334. apikey: CONFIG.ETH_API_KEY
  335. }
  336. })
  337. if (response.data.status !== '1') {
  338. Logger.warn(`Etherscan API error: ${response.data.message}`)
  339. return this.formatBalance(0)
  340. }
  341. const balance = Number(response.data.result) / 1e18
  342. return this.formatBalance(balance)
  343. } catch (error) {
  344. Logger.error(`Failed to get Ethereum balance: ${(error as Error).message}`)
  345. return this.formatBalance(0)
  346. }
  347. }
  348. /**
  349. * 获取波场USDT余额
  350. * @param address 波场地址
  351. * @returns 波场USDT余额(8位小数格式字符串)
  352. */
  353. public async getTronBalance(address: string): Promise<string> {
  354. try {
  355. const response = await axios.get(`${CONFIG.TRON_API_URL}?address=${address}`)
  356. const data = response.data
  357. // 查找USDT TRC20代币余额
  358. let usdtBalance = 0
  359. if (data.trc20token_balances && Array.isArray(data.trc20token_balances)) {
  360. const usdtToken = data.trc20token_balances.find(
  361. (token: any) => token.tokenAbbr === 'USDT'
  362. )
  363. if (usdtToken) {
  364. const decimals = Number(usdtToken.tokenDecimal) || 6
  365. usdtBalance = Number(usdtToken.balance) / Math.pow(10, decimals)
  366. }
  367. }
  368. return this.formatBalance(usdtBalance)
  369. } catch (error) {
  370. Logger.error(`Failed to get TRON balance: ${(error as Error).message}`)
  371. return this.formatBalance(0)
  372. }
  373. }
  374. /**
  375. * 获取BSC余额
  376. * @param address BSC地址
  377. * @returns BSC余额(8位小数格式字符串)
  378. */
  379. public async getBscBalance(address: string): Promise<string> {
  380. try {
  381. const response = await axios.get(CONFIG.BSCSCAN_API_URL, {
  382. params: {
  383. module: 'account',
  384. action: 'balance',
  385. address,
  386. tag: 'latest',
  387. apikey: CONFIG.BSC_API_KEY
  388. }
  389. })
  390. if (response.data.status === '1' && response.data.message === 'OK') {
  391. return new BigNumber(response.data.result)
  392. .dividedBy(new BigNumber(10).pow(18))
  393. .toFixed(CONFIG.DECIMAL_PLACES)
  394. }
  395. Logger.error(`Failed to get balance for BSC: ${response.data.message}`)
  396. return this.formatBalance(0)
  397. } catch (error) {
  398. Logger.error('Failed to get BSC balance:', error)
  399. return this.formatBalance(0)
  400. }
  401. }
  402. /**
  403. * 获取Solana余额
  404. * @param address Solana地址
  405. * @returns Solana余额(8位小数格式字符串)
  406. */
  407. public async getSolanaBalance(address: string): Promise<string> {
  408. try {
  409. const connection = new Connection(CONFIG.SOLANA_API_URL)
  410. const publicKey = new PublicKey(address)
  411. const balance = await connection.getBalance(publicKey)
  412. return this.formatBalance(balance / LAMPORTS_PER_SOL)
  413. } catch (error) {
  414. Logger.error('Failed to get Solana balance:', error)
  415. return this.formatBalance(0)
  416. }
  417. }
  418. }
  419. export default new BlockchainWalletService()