@clonegod/ttd-bsc-common 1.0.36 → 1.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,27 @@
1
1
  import { AbastrcatTrade, AppConfig, TradeContext } from "@clonegod/ttd-core/dist";
2
2
  import { ethers } from "ethers";
3
3
  import { DexConfig, EvmChainConfig } from "../types";
4
+ import { SimpleRedisClient } from "../redis";
4
5
  export declare abstract class AbstractEvmDexTradePlus extends AbastrcatTrade {
5
6
  protected appConfig: AppConfig;
6
7
  protected group_wallets: ethers.Wallet[];
8
+ protected wallet: ethers.Wallet;
7
9
  protected provider: ethers.providers.JsonRpcProvider;
10
+ protected walletMode: 'single' | 'multi';
8
11
  protected approvedTokens: Map<string, Map<string, boolean>>;
9
12
  protected pairContracts: Map<string, ethers.Contract>;
10
13
  protected tokenContracts: Map<string, ethers.Contract>;
11
14
  protected routerContract: ethers.Contract;
12
15
  protected chainConfig: EvmChainConfig;
13
16
  protected dexConfig: DexConfig;
17
+ protected redisClient: SimpleRedisClient;
18
+ protected chainNameLower: string;
14
19
  constructor(appConfig: AppConfig);
15
20
  protected abstract initConfigs(): void;
16
21
  init(): Promise<void>;
22
+ protected getWalletMode(): 'single' | 'multi';
23
+ protected getSingleWallet(): ethers.Wallet;
24
+ protected getGroupWallets(): ethers.Wallet[];
17
25
  protected getTokenContract(tokenAddress: string): ethers.Contract;
18
26
  protected getTokenContractWithWallet(tokenAddress: string, wallet: ethers.Wallet): ethers.Contract;
19
27
  protected getGasPriceGwei(context: TradeContext): string;
@@ -27,4 +35,9 @@ export declare abstract class AbstractEvmDexTradePlus extends AbastrcatTrade {
27
35
  protected getWrappedNativeAddress(): string;
28
36
  abstract execute(context: TradeContext, retryCount?: number): Promise<string>;
29
37
  protected buildTipTransferTx(to: string, transfer_amount_gwei: string, gas_price_gwei: string, transfer_nonce: number, wallet: ethers.Wallet): Promise<string>;
38
+ protected chooseWallet(context: TradeContext): Promise<ethers.Wallet>;
39
+ protected determineInputOutputTokens(order_msg: any, pool_info: any): {
40
+ inputToken: any;
41
+ outputToken: any;
42
+ };
30
43
  }
@@ -8,15 +8,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.AbstractEvmDexTradePlus = void 0;
13
16
  const dist_1 = require("@clonegod/ttd-core/dist");
14
17
  const ethers_1 = require("ethers");
15
18
  const common_1 = require("../common");
19
+ const decimal_js_1 = __importDefault(require("decimal.js"));
20
+ const redis_1 = require("../redis");
16
21
  class AbstractEvmDexTradePlus extends dist_1.AbastrcatTrade {
17
22
  constructor(appConfig) {
18
23
  super();
19
24
  this.appConfig = appConfig;
25
+ this.walletMode = 'multi';
20
26
  this.approvedTokens = new Map();
21
27
  this.pairContracts = new Map();
22
28
  this.tokenContracts = new Map();
@@ -24,21 +30,42 @@ class AbstractEvmDexTradePlus extends dist_1.AbastrcatTrade {
24
30
  this.pairContracts = new Map();
25
31
  this.tokenContracts = new Map();
26
32
  this.initConfigs();
33
+ this.chainNameLower = this.appConfig.env_args.chain_id.toLowerCase();
34
+ this.redisClient = new redis_1.SimpleRedisClient(`${this.chainNameLower}:tx`);
27
35
  }
28
36
  init() {
29
37
  return __awaiter(this, void 0, void 0, function* () {
30
- var _a;
31
38
  this.provider = new ethers_1.ethers.providers.JsonRpcProvider(this.chainConfig.rpcEndpoint);
32
- if (!process.env.WALLET_GROUP_IDS) {
33
- throw new Error('必须配置 WALLET_GROUP_IDS 环境变量来启用多钱包功能');
39
+ const walletGroupIds = process.env.WALLET_GROUP_IDS || '';
40
+ this.walletMode = walletGroupIds && walletGroupIds.trim().split(',').length > 0 ? 'multi' : 'single';
41
+ if (this.walletMode === 'multi') {
42
+ let wallet_infos = (0, dist_1.load_wallet_multi)(walletGroupIds.split(','), false);
43
+ this.group_wallets = wallet_infos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.provider));
44
+ (0, dist_1.log_info)(`组钱包已初始化,数量: ${this.group_wallets.length}`, this.group_wallets.map(e => e.address));
45
+ }
46
+ else {
47
+ this.wallet = new ethers_1.ethers.Wallet(this.appConfig.trade_runtime.wallet.private_key, this.provider);
48
+ (0, dist_1.log_info)(`单钱包已初始化,walletAddress= ${this.wallet.address}`);
34
49
  }
35
- let wallet_infos = (0, dist_1.load_wallet_multi)(((_a = process.env.WALLET_GROUP_IDS) === null || _a === void 0 ? void 0 : _a.split(',')) || [], false);
36
- this.group_wallets = wallet_infos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.provider));
37
- (0, dist_1.log_info)(`组钱包已初始化,数量: ${this.group_wallets.length}`, this.group_wallets.map(e => e.address));
38
50
  this.routerContract = new ethers_1.ethers.Contract(this.dexConfig.routerAddress, this.dexConfig.routerAbi, this.provider);
39
- (0, dist_1.log_info)(`${this.dexConfig.dexName} Router已初始化, 地址: ${this.dexConfig.routerAddress}, 钱包数量: ${this.group_wallets.length}`);
51
+ (0, dist_1.log_info)(`${this.dexConfig.dexName} Router已初始化, routerAddress= ${this.dexConfig.routerAddress}`);
40
52
  });
41
53
  }
54
+ getWalletMode() {
55
+ return this.walletMode;
56
+ }
57
+ getSingleWallet() {
58
+ if (this.walletMode !== 'single' || !this.wallet) {
59
+ throw new Error('Single wallet not available in multi-wallet mode or not initialized');
60
+ }
61
+ return this.wallet;
62
+ }
63
+ getGroupWallets() {
64
+ if (this.walletMode !== 'multi' || !this.group_wallets) {
65
+ throw new Error('Group wallets not available in single-wallet mode or not initialized');
66
+ }
67
+ return this.group_wallets;
68
+ }
42
69
  getTokenContract(tokenAddress) {
43
70
  if (!this.tokenContracts.has(tokenAddress)) {
44
71
  const tokenContract = new ethers_1.ethers.Contract(tokenAddress, common_1.ERC20_ABI, this.provider);
@@ -68,37 +95,70 @@ class AbstractEvmDexTradePlus extends dist_1.AbastrcatTrade {
68
95
  }
69
96
  preApproveAllWallets() {
70
97
  return __awaiter(this, arguments, void 0, function* (token_list = []) {
71
- if (!this.group_wallets || this.group_wallets.length === 0) {
72
- (0, dist_1.log_info)('No wallets available for pre-approval');
73
- return;
74
- }
75
- if (!token_list || token_list.length === 0) {
76
- token_list = Array.from(this.tokenContracts.keys());
77
- }
78
- (0, dist_1.log_info)(`Pre-approve: ${this.group_wallets.length} wallets, ${token_list.length} tokens`, token_list);
79
- const tokenAddresses = new Set();
80
- for (const token_address of token_list) {
81
- tokenAddresses.add(token_address.toLowerCase());
82
- }
83
- const tokens = Array.from(tokenAddresses);
84
- for (const wallet of this.group_wallets) {
98
+ if (this.walletMode === 'single') {
99
+ if (!this.wallet) {
100
+ (0, dist_1.log_info)('Single wallet not available for pre-approval');
101
+ return;
102
+ }
103
+ if (!token_list || token_list.length === 0) {
104
+ token_list = Array.from(this.tokenContracts.keys());
105
+ }
106
+ (0, dist_1.log_info)(`Pre-approve: single wallet ${this.wallet.address}, ${token_list.length} tokens`, token_list);
107
+ const tokenAddresses = new Set();
108
+ for (const token_address of token_list) {
109
+ tokenAddresses.add(token_address.toLowerCase());
110
+ }
111
+ const tokens = Array.from(tokenAddresses);
85
112
  try {
86
- (0, dist_1.log_info)(`Pre-approve: wallet ${wallet.address}`);
113
+ (0, dist_1.log_info)(`Pre-approve: wallet ${this.wallet.address}`);
87
114
  for (const tokenAddress of tokens) {
88
115
  try {
89
- yield this.approveTokenIfNeeded(wallet, tokenAddress);
116
+ yield this.approveTokenIfNeeded(this.wallet, tokenAddress);
90
117
  }
91
118
  catch (error) {
92
- console.warn(`Failed to pre-approve token ${tokenAddress} for wallet ${wallet.address}:`, error);
119
+ console.warn(`Failed to pre-approve token ${tokenAddress} for wallet ${this.wallet.address}:`, error);
93
120
  }
94
121
  }
95
- (0, dist_1.log_info)(`Pre-approve: wallet ${wallet.address} completed`);
122
+ (0, dist_1.log_info)(`Pre-approve: wallet ${this.wallet.address} completed`);
96
123
  }
97
124
  catch (error) {
98
- console.warn(`Failed to pre-approve tokens for wallet ${wallet.address}:`, error);
125
+ console.warn(`Failed to pre-approve tokens for wallet ${this.wallet.address}:`, error);
99
126
  }
127
+ (0, dist_1.log_info)('Pre-approve: single wallet completed');
128
+ }
129
+ else {
130
+ if (!this.group_wallets || this.group_wallets.length === 0) {
131
+ (0, dist_1.log_info)('No wallets available for pre-approval');
132
+ return;
133
+ }
134
+ if (!token_list || token_list.length === 0) {
135
+ token_list = Array.from(this.tokenContracts.keys());
136
+ }
137
+ (0, dist_1.log_info)(`Pre-approve: ${this.group_wallets.length} wallets, ${token_list.length} tokens`, token_list);
138
+ const tokenAddresses = new Set();
139
+ for (const token_address of token_list) {
140
+ tokenAddresses.add(token_address.toLowerCase());
141
+ }
142
+ const tokens = Array.from(tokenAddresses);
143
+ for (const wallet of this.group_wallets) {
144
+ try {
145
+ (0, dist_1.log_info)(`Pre-approve: wallet ${wallet.address}`);
146
+ for (const tokenAddress of tokens) {
147
+ try {
148
+ yield this.approveTokenIfNeeded(wallet, tokenAddress);
149
+ }
150
+ catch (error) {
151
+ console.warn(`Failed to pre-approve token ${tokenAddress} for wallet ${wallet.address}:`, error);
152
+ }
153
+ }
154
+ (0, dist_1.log_info)(`Pre-approve: wallet ${wallet.address} completed`);
155
+ }
156
+ catch (error) {
157
+ console.warn(`Failed to pre-approve tokens for wallet ${wallet.address}:`, error);
158
+ }
159
+ }
160
+ (0, dist_1.log_info)('Pre-approve: all wallets completed');
100
161
  }
101
- (0, dist_1.log_info)('Pre-approve: all wallets completed');
102
162
  });
103
163
  }
104
164
  approveTokenIfNeeded(wallet, tokenAddress) {
@@ -223,5 +283,139 @@ class AbstractEvmDexTradePlus extends dist_1.AbastrcatTrade {
223
283
  return signedTx;
224
284
  });
225
285
  }
286
+ chooseWallet(context) {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ var _a, _b;
289
+ if (this.getWalletMode() === 'single') {
290
+ return this.getSingleWallet();
291
+ }
292
+ const { inputToken } = this.determineInputOutputTokens(context.order_msg, context.pool_info);
293
+ console.log('inputToken', inputToken);
294
+ const requiredAmount = new decimal_js_1.default(context.order_msg.amount);
295
+ const wallet_assets = yield this.redisClient.hgetall(`${this.chainNameLower}:wallet:assets`);
296
+ if (!wallet_assets || Object.keys(wallet_assets).length === 0) {
297
+ throw new Error('没有找到钱包资产信息,请确保钱包监控服务正在运行');
298
+ }
299
+ const wallet_assets_map = {};
300
+ for (const [walletAddress, assetsJson] of Object.entries(wallet_assets)) {
301
+ try {
302
+ wallet_assets_map[walletAddress] = JSON.parse(assetsJson);
303
+ }
304
+ catch (error) {
305
+ console.warn(`解析钱包资产数据失败: ${walletAddress}`, error);
306
+ }
307
+ }
308
+ const availableWallets = [];
309
+ const allWallets = [];
310
+ for (const [walletAddress, assets] of Object.entries(wallet_assets_map)) {
311
+ try {
312
+ const wallet = (_a = this.group_wallets) === null || _a === void 0 ? void 0 : _a.find(w => w.address.toLowerCase() === walletAddress.toLowerCase());
313
+ if (!wallet) {
314
+ continue;
315
+ }
316
+ const tokenAsset = (_b = assets.tokens) === null || _b === void 0 ? void 0 : _b.find((token) => token.tokenaddr.toLowerCase() === inputToken.address.toLowerCase());
317
+ let tokenBalance = (tokenAsset === null || tokenAsset === void 0 ? void 0 : tokenAsset.balance) || '0';
318
+ const balanceDecimal = new decimal_js_1.default(tokenBalance);
319
+ if (balanceDecimal.gte(requiredAmount)) {
320
+ availableWallets.push({
321
+ wallet,
322
+ balance: tokenBalance
323
+ });
324
+ }
325
+ else {
326
+ console.log(`钱包 ${walletAddress} 余额不足: ${tokenBalance} ${inputToken.symbol} < ${requiredAmount} ${inputToken.symbol}`);
327
+ }
328
+ allWallets.push({
329
+ wallet,
330
+ balance: tokenBalance
331
+ });
332
+ }
333
+ catch (error) {
334
+ console.warn(`处理钱包资产时出错: ${walletAddress}`, error);
335
+ }
336
+ }
337
+ if (availableWallets.length === 0) {
338
+ let error_msg = `all wallets are insufficient, required: ${requiredAmount} ${inputToken.symbol}`;
339
+ (0, dist_1.log_warn)(error_msg);
340
+ allWallets.forEach((walletInfo, index) => {
341
+ const shortAddress = walletInfo.wallet.address.substring(0, 6) + '...' + walletInfo.wallet.address.substring(walletInfo.wallet.address.length - 4);
342
+ console.log(`${index + 1}. ${shortAddress}: ${walletInfo.balance} ${inputToken.symbol}`);
343
+ });
344
+ throw new Error(error_msg);
345
+ }
346
+ console.log('availableWallets', availableWallets.map(w => w.wallet.address));
347
+ const wallet_last_used = yield this.redisClient.hgetall(`${this.chainNameLower}:wallet:last_used`);
348
+ for (const availableWallet of availableWallets) {
349
+ const lastUsedData = wallet_last_used === null || wallet_last_used === void 0 ? void 0 : wallet_last_used[availableWallet.wallet.address];
350
+ if (lastUsedData) {
351
+ try {
352
+ const lastUsedInfo = JSON.parse(lastUsedData);
353
+ availableWallet.lastUsedAt = lastUsedInfo.lastUsedAt;
354
+ }
355
+ catch (error) {
356
+ console.warn(`解析钱包最后使用时间失败: ${availableWallet.wallet.address}`, error);
357
+ }
358
+ }
359
+ else {
360
+ yield this.redisClient.hsetValue(`${this.chainNameLower}:wallet:last_used`, availableWallet.wallet.address, JSON.stringify({
361
+ lastUsedAt: 0,
362
+ lastUsedTime: new Date(0).toISOString()
363
+ }), 24 * 60 * 60);
364
+ availableWallet.lastUsedAt = 0;
365
+ }
366
+ }
367
+ let selectedWallet;
368
+ let selectionStrategy;
369
+ if (availableWallets.length === 1) {
370
+ selectedWallet = availableWallets[0].wallet;
371
+ selectionStrategy = 'OnlyOne';
372
+ }
373
+ else {
374
+ const hasUsageHistory = wallet_last_used && Object.keys(wallet_last_used).length > 0;
375
+ if (hasUsageHistory) {
376
+ availableWallets.sort((a, b) => {
377
+ const aTime = a.lastUsedAt || 0;
378
+ const bTime = b.lastUsedAt || 0;
379
+ return aTime - bTime;
380
+ });
381
+ selectedWallet = availableWallets[0].wallet;
382
+ selectionStrategy = 'MaxLastUsed';
383
+ }
384
+ else {
385
+ availableWallets.sort((a, b) => {
386
+ const aBalance = new decimal_js_1.default(a.balance);
387
+ const bBalance = new decimal_js_1.default(b.balance);
388
+ return bBalance.cmp(aBalance);
389
+ });
390
+ selectedWallet = availableWallets[0].wallet;
391
+ selectionStrategy = 'MaxBalance';
392
+ }
393
+ }
394
+ yield this.redisClient.hsetValue(`${this.chainNameLower}:wallet:last_used`, selectedWallet.address, JSON.stringify({
395
+ lastUsedAt: Date.now(),
396
+ lastUsedTime: new Date().toISOString()
397
+ }), 24 * 60 * 60);
398
+ const selectedWalletInfo = availableWallets.find(w => w.wallet.address === selectedWallet.address);
399
+ (0, dist_1.log_info)(`Choose wallet: ${selectionStrategy} -> ${selectedWallet.address}, balance: ${selectedWalletInfo === null || selectedWalletInfo === void 0 ? void 0 : selectedWalletInfo.balance} ${inputToken.symbol}, required: ${requiredAmount} ${inputToken.symbol}`);
400
+ return selectedWallet;
401
+ });
402
+ }
403
+ determineInputOutputTokens(order_msg, pool_info) {
404
+ const { aToB } = order_msg;
405
+ const { tokenA, tokenB, quote_token } = pool_info;
406
+ const quoteToken = [tokenA, tokenB].find(token => token.symbol === quote_token);
407
+ const nonQuoteToken = [tokenA, tokenB].find(token => token.symbol !== quote_token);
408
+ const inputToken = aToB ? nonQuoteToken : quoteToken;
409
+ const outputToken = aToB ? quoteToken : nonQuoteToken;
410
+ if (!inputToken || !outputToken) {
411
+ throw new Error(`无法确定输入/输出代币: ${JSON.stringify({
412
+ tokenA: tokenA.symbol,
413
+ tokenB: tokenB.symbol,
414
+ quote: quote_token,
415
+ aToB
416
+ })}`);
417
+ }
418
+ return { inputToken, outputToken };
419
+ }
226
420
  }
227
421
  exports.AbstractEvmDexTradePlus = AbstractEvmDexTradePlus;
@@ -1,4 +1,4 @@
1
- export declare class BscRpc {
1
+ export declare class BscMainnetRpc {
2
2
  private url;
3
3
  private headers;
4
4
  constructor(rpc_endpoint: string);
@@ -9,8 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.BscRpc = void 0;
13
- class BscRpc {
12
+ exports.BscMainnetRpc = void 0;
13
+ const dist_1 = require("@clonegod/ttd-core/dist");
14
+ class BscMainnetRpc {
14
15
  constructor(rpc_endpoint) {
15
16
  this.url = rpc_endpoint || process.env.BSC_RPC_ENDPOINT || '';
16
17
  this.headers = new Headers();
@@ -18,26 +19,29 @@ class BscRpc {
18
19
  }
19
20
  eth_sendRawTransaction(signedTx) {
20
21
  return __awaiter(this, void 0, void 0, function* () {
21
- console.log(`Sending transaction to ${this.url}`);
22
- const raw = JSON.stringify({
23
- jsonrpc: "2.0",
24
- method: "eth_sendRawTransaction",
25
- params: [signedTx],
26
- id: 1
27
- });
28
- const requestOptions = {
29
- method: 'POST',
30
- headers: this.headers,
31
- body: raw,
32
- redirect: 'follow'
33
- };
34
- const response = yield fetch(this.url, requestOptions);
35
- const result = yield response.json();
36
- if (result.error) {
37
- throw new Error(result.error.message);
38
- }
39
- return result.result;
22
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
23
+ console.log(`Sending transaction to ${this.url}`);
24
+ const raw = JSON.stringify({
25
+ jsonrpc: "2.0",
26
+ method: "eth_sendRawTransaction",
27
+ params: [signedTx],
28
+ id: 1
29
+ });
30
+ const requestOptions = {
31
+ method: 'POST',
32
+ headers: this.headers,
33
+ body: raw,
34
+ redirect: 'follow'
35
+ };
36
+ const response = yield fetch(this.url, requestOptions);
37
+ const result = yield response.json();
38
+ if (result.error) {
39
+ (0, dist_1.log_warn)(result.error.message);
40
+ }
41
+ return result.result;
42
+ }), 0);
43
+ return '';
40
44
  });
41
45
  }
42
46
  }
43
- exports.BscRpc = BscRpc;
47
+ exports.BscMainnetRpc = BscMainnetRpc;
@@ -17,7 +17,7 @@ const _48club_1 = require("./48club");
17
17
  const _1 = require(".");
18
18
  class TransactionSender {
19
19
  constructor(appConfig) {
20
- this.rpc = new bsc_rpc_1.BscRpc(appConfig.env_args.rpc_endpoint);
20
+ this.rpc = new bsc_rpc_1.BscMainnetRpc(appConfig.env_args.rpc_endpoint);
21
21
  this.blockRazor = new blockrazor_1.BlockRazorTrade();
22
22
  this._48Club = new _48club_1._48ClubTrade();
23
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",