@clonegod/ttd-core 2.0.1 → 2.0.2
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.
- package/dist/app_config/app_config.d.ts +2 -2
- package/dist/app_config/app_config.js +12 -3
- package/dist/app_config/env_args.d.ts +15 -4
- package/dist/app_config/env_args.js +19 -7
- package/dist/cache/arb_cache.js +6 -7
- package/dist/token/fixed_symbol_address.js +1 -0
- package/dist/token/price/gecko_terminal.js +3 -1
- package/dist/token/price/get_bsc_token_price.js +13 -2
- package/dist/token/price/get_solana_token_price.js +124 -1
- package/dist/token/price/get_tron_token_price.js +174 -1
- package/dist/token/types.d.ts +2 -1
- package/package.json +1 -1
- package/types/index.d.ts +34 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import EventEmitter from "events";
|
|
2
|
-
import {
|
|
2
|
+
import { TradeRuntimeType } from "../../types";
|
|
3
3
|
import { ArbCache, ArbEventSubscriber } from "../cache";
|
|
4
4
|
import { EnvArgs } from "./env_args";
|
|
5
5
|
export declare class AppConfig extends EventEmitter {
|
|
6
6
|
env_args: EnvArgs;
|
|
7
7
|
app_full_name: string;
|
|
8
8
|
arb_cache: ArbCache;
|
|
9
|
-
dex_id: DEX_ID;
|
|
10
9
|
arb_event_subscriber: ArbEventSubscriber;
|
|
10
|
+
trade_runtime: TradeRuntimeType;
|
|
11
11
|
query_token_market_price_url: string;
|
|
12
12
|
constructor();
|
|
13
13
|
init(): Promise<void>;
|
|
@@ -22,12 +22,12 @@ class AppConfig extends events_1.default {
|
|
|
22
22
|
this.env_args = new env_args_1.EnvArgs();
|
|
23
23
|
let { chain_id, app_name, dex_id } = this.env_args;
|
|
24
24
|
this.app_full_name = `${chain_id}_${dex_id}_${app_name}`;
|
|
25
|
-
this.dex_id = dex_id;
|
|
26
25
|
}
|
|
27
26
|
init() {
|
|
28
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29
28
|
this.arb_cache = (0, __1.getArbCache)(this.env_args);
|
|
30
29
|
yield this.arb_cache.init();
|
|
30
|
+
this.arb_cache.listen_trade_result(this);
|
|
31
31
|
let common_service_config = yield this.arb_cache.get_common_service();
|
|
32
32
|
if ((0, __1.isEmpty)(common_service_config)) {
|
|
33
33
|
throw new Error(`get_common_service from cache failed! read from cache=${(0, __1.to_json_str)(common_service_config)}`);
|
|
@@ -36,14 +36,23 @@ class AppConfig extends events_1.default {
|
|
|
36
36
|
this.query_token_market_price_url = this.env_args.token_market_price_url
|
|
37
37
|
.replace('${config_center_http_port}', config_center.http_port + '')
|
|
38
38
|
.replace('${chain_id}', this.env_args.chain_id);
|
|
39
|
-
yield this.subscribe_config_change();
|
|
40
39
|
});
|
|
41
40
|
}
|
|
42
41
|
subscribe_config_change() {
|
|
43
42
|
return __awaiter(this, void 0, void 0, function* () {
|
|
44
43
|
this.arb_event_subscriber = (0, __1.getArbEventSubscriber)(this.arb_cache);
|
|
45
44
|
yield this.arb_event_subscriber.pre_load_config_cache();
|
|
46
|
-
|
|
45
|
+
const refresh_trade_runtime = (is_sussess, event) => __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
(0, __1.log_info)(`subscribe_config_change_event, callback`, { is_sussess, event });
|
|
47
|
+
if (event.event_type === __1.REDIS_EVENT_TYPE_CONFIG_CHANGE.GROUP_CONFIG_CHANGE) {
|
|
48
|
+
if (!this.trade_runtime) {
|
|
49
|
+
(0, __1.log_warn)(`refresh_trade_runtime, skip...`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.trade_runtime = yield this.arb_cache.create_trade_runtime(this.trade_runtime.chain_id, this.trade_runtime.group.id, this.trade_runtime.dex_id, this.trade_runtime.pair_name);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
yield this.arb_event_subscriber.subscribe_config_change_event(refresh_trade_runtime);
|
|
47
56
|
});
|
|
48
57
|
}
|
|
49
58
|
}
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { CHAIN_ID, DEX_ID } from "..";
|
|
2
2
|
export declare class EnvArgs {
|
|
3
3
|
chain_id: CHAIN_ID;
|
|
4
|
-
app_name: string;
|
|
5
4
|
dex_id: DEX_ID;
|
|
6
|
-
|
|
5
|
+
app_base_dir: string;
|
|
6
|
+
app_name: string;
|
|
7
7
|
redis_host: string;
|
|
8
8
|
redis_port: string;
|
|
9
9
|
rpc_endpoint: string;
|
|
10
10
|
ws_endpoint: string;
|
|
11
|
+
grpc_endpoint: string;
|
|
12
|
+
grpc_token: string;
|
|
13
|
+
config_center_host: string;
|
|
11
14
|
token_market_price_url: string;
|
|
15
|
+
trade_analyze_url: string;
|
|
16
|
+
quote_pair: string;
|
|
17
|
+
quote_amount_usd: number;
|
|
12
18
|
auto_quote_enable: boolean;
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
auto_quote_interval_mills: number;
|
|
20
|
+
trade_group_id: string;
|
|
21
|
+
trade_pair: string;
|
|
22
|
+
trade_dex_id: DEX_ID;
|
|
23
|
+
trade_pool_address: string;
|
|
24
|
+
trade_pool_name: string;
|
|
25
|
+
trade_pool_fee_rate: number;
|
|
15
26
|
constructor();
|
|
16
27
|
}
|
|
@@ -4,25 +4,37 @@ exports.EnvArgs = void 0;
|
|
|
4
4
|
const __1 = require("..");
|
|
5
5
|
class EnvArgs {
|
|
6
6
|
constructor() {
|
|
7
|
+
var _a, _b, _c;
|
|
7
8
|
this.app_name = process.env.APP_NAME || '';
|
|
8
|
-
this.chain_id = process.env.CHAIN_ID.toUpperCase();
|
|
9
|
+
this.chain_id = (_a = process.env.CHAIN_ID) === null || _a === void 0 ? void 0 : _a.toUpperCase();
|
|
9
10
|
if ((0, __1.isEmpty)(this.chain_id)) {
|
|
10
11
|
throw new Error(`environment: CHAIN_ID is empty!!!`);
|
|
11
12
|
}
|
|
12
|
-
this.dex_id = process.env.DEX_ID.toUpperCase();
|
|
13
|
+
this.dex_id = (_b = process.env.DEX_ID) === null || _b === void 0 ? void 0 : _b.toUpperCase();
|
|
13
14
|
if ((0, __1.isEmpty)(this.dex_id)) {
|
|
14
15
|
throw new Error(`Invalid config: DEX_ID=${process.env.DEX_ID}!!!`);
|
|
15
16
|
}
|
|
16
|
-
this.
|
|
17
|
+
this.app_base_dir = process.env.APP_BASE_DIR || '';
|
|
17
18
|
this.redis_host = process.env.REDIS_HOST;
|
|
18
19
|
this.redis_port = process.env.REDIS_PORT;
|
|
19
20
|
this.rpc_endpoint = process.env.RPC_ENDPOINT;
|
|
20
21
|
this.ws_endpoint = process.env.WS_ENDPOINT;
|
|
21
|
-
this.
|
|
22
|
+
this.grpc_endpoint = process.env.GRPC_ENDPOINT;
|
|
23
|
+
this.grpc_token = process.env.GRPC_TOKEN;
|
|
24
|
+
this.config_center_host = process.env.CONFIG_CENTER_HOST;
|
|
25
|
+
this.token_market_price_url = process.env.TOKEN_PRICE_URL || process.env.TOKEN_MARKET_PRICE_URL;
|
|
26
|
+
this.trade_analyze_url = process.env.TRADE_ANALYZE_URL;
|
|
27
|
+
this.quote_pair = process.env.QUOTE_PAIR;
|
|
28
|
+
this.quote_amount_usd = parseInt(process.env.QUOTE_AMOUNT_USD || '100');
|
|
22
29
|
this.auto_quote_enable = process.env.AUTO_QUOTE_ENABLE === 'true';
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
|
|
30
|
+
this.auto_quote_interval_mills = parseInt(process.env.AUTO_QUOTE_INTERVAL_MILLS || '60000');
|
|
31
|
+
this.trade_group_id = process.env.TRADE_GROUP_ID;
|
|
32
|
+
this.trade_pair = process.env.TRADE_PAIR;
|
|
33
|
+
this.trade_dex_id = (_c = process.env.TRADE_DEX_ID) === null || _c === void 0 ? void 0 : _c.toUpperCase();
|
|
34
|
+
this.trade_pool_address = process.env.TRADE_POOL_ADDRESS;
|
|
35
|
+
this.trade_pool_name = process.env.TRADE_POOL_NAME;
|
|
36
|
+
this.trade_pool_fee_rate = parseInt(process.env.TRADE_POOL_FEE_RATE || '0');
|
|
37
|
+
(0, __1.log_info)('EnvArgs init finish', this);
|
|
26
38
|
}
|
|
27
39
|
}
|
|
28
40
|
exports.EnvArgs = EnvArgs;
|
package/dist/cache/arb_cache.js
CHANGED
|
@@ -684,13 +684,12 @@ class ArbCache {
|
|
|
684
684
|
public_key: trade_runtime.wallet.public_key,
|
|
685
685
|
private_key: '******'
|
|
686
686
|
};
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}, 0);
|
|
687
|
+
_trade_runtime.wallet_token_accounts = new Map();
|
|
688
|
+
(0, index_1.new_line)(3);
|
|
689
|
+
console.log(`=================================================================================`);
|
|
690
|
+
console.log(`| TRADE GROUP RUNTIME |`);
|
|
691
|
+
console.log(`=================================================================================`);
|
|
692
|
+
console.dir(_trade_runtime, { depth: 4 });
|
|
694
693
|
(0, index_1.log_trace)(`create_trade_runtime, end`);
|
|
695
694
|
return trade_runtime;
|
|
696
695
|
});
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.fixed_symbol_address = void 0;
|
|
7
|
+
require('dotenv').config();
|
|
7
8
|
const os_1 = __importDefault(require("os"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const index_1 = require("../index");
|
|
@@ -24,6 +24,7 @@ function fetchPriceFromGeckoTerminal(network, addresses) {
|
|
|
24
24
|
const retrysleepMs = 1000;
|
|
25
25
|
const addressString = addresses.join(',');
|
|
26
26
|
const url = `https://api.geckoterminal.com/api/v2/simple/networks/${network.toLowerCase()}/token_price/${addressString}`;
|
|
27
|
+
console.log(url);
|
|
27
28
|
(0, index_1.log_debug)(`[fetchPriceFromGeckoTerminal] Requesting prices for ${addresses.length} tokens`);
|
|
28
29
|
let retryCount = 0;
|
|
29
30
|
while (retryCount <= maxRetries) {
|
|
@@ -41,9 +42,10 @@ function fetchPriceFromGeckoTerminal(network, addresses) {
|
|
|
41
42
|
for (const address of addresses) {
|
|
42
43
|
if (tokenPrices[address]) {
|
|
43
44
|
result.set(address, {
|
|
44
|
-
symbol: '',
|
|
45
45
|
address: address,
|
|
46
46
|
price: tokenPrices[address],
|
|
47
|
+
symbol: '',
|
|
48
|
+
name: '',
|
|
47
49
|
update_time: currentTime
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.get_bsc_token_price_info = get_bsc_token_price_info;
|
|
16
|
+
require('dotenv').config();
|
|
16
17
|
const axios_1 = __importDefault(require("axios"));
|
|
17
18
|
const index_1 = require("../../index");
|
|
18
19
|
const gecko_terminal_1 = require("./gecko_terminal");
|
|
@@ -127,5 +128,15 @@ function fetchPriceFromPancakeSwap(addresses) {
|
|
|
127
128
|
return result;
|
|
128
129
|
});
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
if (require.main === module) {
|
|
132
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
133
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
+
let addresses = [
|
|
135
|
+
"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
|
|
136
|
+
"0x55d398326f99059ff775485246999027b3197955",
|
|
137
|
+
"0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82",
|
|
138
|
+
];
|
|
139
|
+
console.log(yield get_bsc_token_price_info(addresses));
|
|
140
|
+
}))();
|
|
141
|
+
}))();
|
|
142
|
+
}
|
|
@@ -8,10 +8,133 @@ 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.get_solana_token_price_info = get_solana_token_price_info;
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
const axios_1 = __importDefault(require("axios"));
|
|
18
|
+
const index_1 = require("../../index");
|
|
19
|
+
const gecko_terminal_1 = require("./gecko_terminal");
|
|
13
20
|
function get_solana_token_price_info(addresses) {
|
|
14
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
-
|
|
22
|
+
const result = new Map();
|
|
23
|
+
const PRICE_CHANNELS = [
|
|
24
|
+
{
|
|
25
|
+
name: 'Jupiter',
|
|
26
|
+
fetchFn: fetchPriceFromJupiter,
|
|
27
|
+
batchSize: 10,
|
|
28
|
+
batchDelay: 1000,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'GeckoTerminal',
|
|
32
|
+
fetchFn: (address_list) => (0, gecko_terminal_1.fetchPriceFromGeckoTerminal)(index_1.CHAIN_ID.SOLANA, address_list),
|
|
33
|
+
batchSize: 10,
|
|
34
|
+
batchDelay: 2000,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
try {
|
|
38
|
+
for (const channel of PRICE_CHANNELS) {
|
|
39
|
+
if (addresses.length === 0)
|
|
40
|
+
break;
|
|
41
|
+
(0, index_1.log_debug)(`[get_token_price_info] Processing ${addresses.length} tokens using ${channel.name}`);
|
|
42
|
+
const batches = (0, index_1.chunkArray)(addresses, channel.batchSize);
|
|
43
|
+
(0, index_1.log_debug)(`[get_token_price_info] Split into ${batches.length} batches of size ${channel.batchSize}`);
|
|
44
|
+
let remainingAddresses = [...addresses];
|
|
45
|
+
for (let i = 0; i < batches.length; i++) {
|
|
46
|
+
const batch = batches[i];
|
|
47
|
+
if (batch.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
(0, index_1.log_debug)(`[get_token_price_info] Processing batch ${i + 1}/${batches.length} (${batch.length} tokens) with ${channel.name}`);
|
|
50
|
+
try {
|
|
51
|
+
const channelResult = yield channel.fetchFn(batch);
|
|
52
|
+
for (const [address, priceInfo] of channelResult.entries()) {
|
|
53
|
+
result.set(address, priceInfo);
|
|
54
|
+
remainingAddresses = remainingAddresses.filter(addr => addr !== address);
|
|
55
|
+
}
|
|
56
|
+
(0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
(0, index_1.log_warn)(`[get_token_price_info] Error processing batch ${i + 1} with ${channel.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
}
|
|
61
|
+
if (i < batches.length - 1) {
|
|
62
|
+
yield (0, index_1.sleep)(channel.batchDelay);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
addresses = [...remainingAddresses];
|
|
66
|
+
if (addresses.length === 0) {
|
|
67
|
+
(0, index_1.log_debug)(`[get_token_price_info] All token prices retrieved using ${channel.name}`);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (result.size === 0) {
|
|
72
|
+
throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
if (addresses.length > 0) {
|
|
75
|
+
(0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
(0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
16
83
|
});
|
|
17
84
|
}
|
|
85
|
+
function fetchPriceFromJupiter(addresses) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const result = new Map();
|
|
88
|
+
const currentTime = (0, index_1.getCurDateTime)();
|
|
89
|
+
const timeout = 10000;
|
|
90
|
+
const maxRetries = 2;
|
|
91
|
+
const retrysleepMs = 500;
|
|
92
|
+
const url = `https://api.jup.ag/price/v2?ids=${addresses.join(',')}&showExtraInfo=false`;
|
|
93
|
+
(0, index_1.log_debug)(`[fetchPriceFromJupiter] Requesting Jupiter API for ${addresses.length} tokens`);
|
|
94
|
+
let retryCount = 0;
|
|
95
|
+
while (retryCount <= maxRetries) {
|
|
96
|
+
try {
|
|
97
|
+
const response = yield axios_1.default.get(url, { timeout });
|
|
98
|
+
if (response.data.data) {
|
|
99
|
+
for (const address of addresses) {
|
|
100
|
+
const key = address;
|
|
101
|
+
if (response.data.data[key] !== undefined) {
|
|
102
|
+
result.set(address, {
|
|
103
|
+
address: address,
|
|
104
|
+
price: response.data.data[key]['price'].toString(),
|
|
105
|
+
symbol: '',
|
|
106
|
+
name: '',
|
|
107
|
+
update_time: currentTime
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
(0, index_1.log_debug)(`[fetchPriceFromJupiter] Retrieved prices for ${result.size}/${addresses.length} tokens`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
(0, index_1.log_debug)(`[fetchPriceFromJupiter] Invalid response from Jupiter for ${addresses.length} tokens`);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
retryCount++;
|
|
120
|
+
(0, index_1.log_warn)(`[fetchPriceFromJupiter] Request failed (attempt ${retryCount}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
if (retryCount <= maxRetries) {
|
|
122
|
+
const currentRetrysleep = retrysleepMs * retryCount;
|
|
123
|
+
(0, index_1.log_debug)(`[fetchPriceFromJupiter] Retrying in ${currentRetrysleep}ms...`);
|
|
124
|
+
yield (0, index_1.sleep)(currentRetrysleep);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (require.main === module) {
|
|
132
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
133
|
+
let addresses = [
|
|
134
|
+
"So11111111111111111111111111111111111111112",
|
|
135
|
+
"JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
|
|
136
|
+
"MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5",
|
|
137
|
+
];
|
|
138
|
+
console.log(yield get_solana_token_price_info(addresses));
|
|
139
|
+
}))();
|
|
140
|
+
}
|
|
@@ -8,10 +8,183 @@ 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.get_tron_token_price_info = get_tron_token_price_info;
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
const axios_1 = __importDefault(require("axios"));
|
|
18
|
+
const index_1 = require("../../index");
|
|
19
|
+
const gecko_terminal_1 = require("./gecko_terminal");
|
|
13
20
|
function get_tron_token_price_info(addresses) {
|
|
14
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
-
|
|
22
|
+
const result = new Map();
|
|
23
|
+
const PRICE_CHANNELS = [
|
|
24
|
+
{
|
|
25
|
+
name: 'TronscanALL',
|
|
26
|
+
fetchFn: fetchPriceFromTronscanALL,
|
|
27
|
+
batchSize: 100,
|
|
28
|
+
batchDelay: 1000,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Tronscan',
|
|
32
|
+
fetchFn: fetchPriceFromTronscan,
|
|
33
|
+
batchSize: 1,
|
|
34
|
+
batchDelay: 1000,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'GeckoTerminal',
|
|
38
|
+
fetchFn: (address_list) => (0, gecko_terminal_1.fetchPriceFromGeckoTerminal)(index_1.CHAIN_ID.TRON, address_list),
|
|
39
|
+
batchSize: 10,
|
|
40
|
+
batchDelay: 2000,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
try {
|
|
44
|
+
for (const channel of PRICE_CHANNELS) {
|
|
45
|
+
if (addresses.length === 0)
|
|
46
|
+
break;
|
|
47
|
+
(0, index_1.log_debug)(`[get_token_price_info] Processing ${addresses.length} tokens using ${channel.name}`);
|
|
48
|
+
const batches = (0, index_1.chunkArray)(addresses, channel.batchSize);
|
|
49
|
+
(0, index_1.log_debug)(`[get_token_price_info] Split into ${batches.length} batches of size ${channel.batchSize}`);
|
|
50
|
+
let remainingAddresses = [...addresses];
|
|
51
|
+
for (let i = 0; i < batches.length; i++) {
|
|
52
|
+
const batch = batches[i];
|
|
53
|
+
if (batch.length === 0)
|
|
54
|
+
continue;
|
|
55
|
+
(0, index_1.log_debug)(`[get_token_price_info] Processing batch ${i + 1}/${batches.length} (${batch.length} tokens) with ${channel.name}`);
|
|
56
|
+
try {
|
|
57
|
+
const channelResult = yield channel.fetchFn(batch);
|
|
58
|
+
for (const [address, priceInfo] of channelResult.entries()) {
|
|
59
|
+
result.set(address, priceInfo);
|
|
60
|
+
remainingAddresses = remainingAddresses.filter(addr => addr !== address);
|
|
61
|
+
}
|
|
62
|
+
(0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
(0, index_1.log_warn)(`[get_token_price_info] Error processing batch ${i + 1} with ${channel.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
66
|
+
}
|
|
67
|
+
if (i < batches.length - 1) {
|
|
68
|
+
yield (0, index_1.sleep)(channel.batchDelay);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
addresses = [...remainingAddresses];
|
|
72
|
+
if (addresses.length === 0) {
|
|
73
|
+
(0, index_1.log_debug)(`[get_token_price_info] All token prices retrieved using ${channel.name}`);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (result.size === 0) {
|
|
78
|
+
throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
if (addresses.length > 0) {
|
|
81
|
+
(0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
(0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function fetchPriceFromTronscan(addresses) {
|
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
const result = new Map();
|
|
94
|
+
const currentTime = (0, index_1.getCurDateTime)();
|
|
95
|
+
const timeout = 10000;
|
|
96
|
+
const maxRetries = 2;
|
|
97
|
+
const retrysleepMs = 500;
|
|
98
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscan] Requesting Tronscan API for ${addresses.length} tokens`);
|
|
99
|
+
for (const address of addresses) {
|
|
100
|
+
let url = `https://apilist.tronscanapi.com/api/token_trc20?contract=${address}&showAll=1&source=search`;
|
|
101
|
+
let retryCount = 0;
|
|
102
|
+
while (retryCount <= maxRetries) {
|
|
103
|
+
try {
|
|
104
|
+
const response = yield axios_1.default.get(url, { timeout });
|
|
105
|
+
let token_info = response.data['trc20_tokens'][0];
|
|
106
|
+
if (token_info) {
|
|
107
|
+
result.set(address, {
|
|
108
|
+
address: address,
|
|
109
|
+
price: token_info['market_info']['priceInUsd'].toString(),
|
|
110
|
+
symbol: '',
|
|
111
|
+
name: token_info['name'],
|
|
112
|
+
update_time: currentTime
|
|
113
|
+
});
|
|
114
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscan] Retrieved prices for ${result.size}/${addresses.length} tokens`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscan] Invalid response from Tronscan for ${addresses.length} tokens`);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
retryCount++;
|
|
123
|
+
(0, index_1.log_warn)(`[fetchPriceFromTronscan] Request failed (attempt ${retryCount}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
if (retryCount <= maxRetries) {
|
|
125
|
+
const currentRetrysleep = retrysleepMs * retryCount;
|
|
126
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscan] Retrying in ${currentRetrysleep}ms...`);
|
|
127
|
+
yield (0, index_1.sleep)(currentRetrysleep);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
16
133
|
});
|
|
17
134
|
}
|
|
135
|
+
function fetchPriceFromTronscanALL(addresses) {
|
|
136
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
const result = new Map();
|
|
138
|
+
const currentTime = (0, index_1.getCurDateTime)();
|
|
139
|
+
const timeout = 10000;
|
|
140
|
+
const maxRetries = 2;
|
|
141
|
+
const retrysleepMs = 500;
|
|
142
|
+
let url = `https://apilist.tronscanapi.com/api/getAssetWithPriceList`;
|
|
143
|
+
let retryCount = 0;
|
|
144
|
+
while (retryCount <= maxRetries) {
|
|
145
|
+
try {
|
|
146
|
+
const response = yield axios_1.default.get(url, { timeout });
|
|
147
|
+
const token_price_list = response.data.data;
|
|
148
|
+
if (token_price_list) {
|
|
149
|
+
for (const address of addresses) {
|
|
150
|
+
let price_info = token_price_list.find(e => e.id.toLowerCase() === address.toLowerCase());
|
|
151
|
+
if (price_info) {
|
|
152
|
+
result.set(address, {
|
|
153
|
+
address: address,
|
|
154
|
+
price: price_info.priceInUsd.toString(),
|
|
155
|
+
symbol: '',
|
|
156
|
+
name: price_info.name,
|
|
157
|
+
update_time: currentTime
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscanALL] Retrieved prices for ${result.size}/${addresses.length} tokens`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscanALL] Invalid response from Tronscan for ${addresses.length} tokens`);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
retryCount++;
|
|
170
|
+
(0, index_1.log_warn)(`[fetchPriceFromTronscanALL] Request failed (attempt ${retryCount}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
if (retryCount <= maxRetries) {
|
|
172
|
+
const currentRetrysleep = retrysleepMs * retryCount;
|
|
173
|
+
(0, index_1.log_debug)(`[fetchPriceFromTronscanALL] Retrying in ${currentRetrysleep}ms...`);
|
|
174
|
+
yield (0, index_1.sleep)(currentRetrysleep);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (require.main === module) {
|
|
182
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
+
let addresses = [
|
|
184
|
+
"TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
|
|
185
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
|
|
186
|
+
"TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9",
|
|
187
|
+
];
|
|
188
|
+
console.log(yield get_tron_token_price_info(addresses));
|
|
189
|
+
}))();
|
|
190
|
+
}
|
package/dist/token/types.d.ts
CHANGED
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -371,6 +371,38 @@ export interface OrderSubmitResponseType {
|
|
|
371
371
|
txid: string
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
/*********************************************************************
|
|
378
|
+
* SWAP RESULT TYPE *
|
|
379
|
+
* *******************************************************************/
|
|
380
|
+
export interface StandardSwapDetailType {
|
|
381
|
+
success: boolean
|
|
382
|
+
error_code: string
|
|
383
|
+
|
|
384
|
+
wallet: string,
|
|
385
|
+
txid: string,
|
|
386
|
+
block_time: number
|
|
387
|
+
block_number: number
|
|
388
|
+
|
|
389
|
+
pool_address: string
|
|
390
|
+
tokenA: TokenBalanceChangeType
|
|
391
|
+
tokenB: TokenBalanceChangeType
|
|
392
|
+
tx_price: string
|
|
393
|
+
|
|
394
|
+
gas_fee: TradeGasFeeType
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
export interface TokenBalChangeType extends StandardTokenInfoType {
|
|
400
|
+
pre_bal: number
|
|
401
|
+
post_bal: number
|
|
402
|
+
change: string
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
374
406
|
/*********************************************************************
|
|
375
407
|
* TRADE RESULT TYPE *
|
|
376
408
|
* *******************************************************************/
|
|
@@ -481,6 +513,8 @@ export interface SendTxLogType {
|
|
|
481
513
|
duration: number // 发送交易 -> 获取到响应码的耗时
|
|
482
514
|
}
|
|
483
515
|
|
|
516
|
+
|
|
517
|
+
|
|
484
518
|
/*********************************************************************
|
|
485
519
|
* WALLET TYPE *
|
|
486
520
|
* *******************************************************************/
|