@clonegod/ttd-bsc-common 3.0.10 → 3.0.11
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.
|
@@ -81,89 +81,93 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
81
81
|
slippage: `${slippage_bps / 100}%`,
|
|
82
82
|
});
|
|
83
83
|
let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
nonce_from_error = -1;
|
|
97
|
-
(0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
nonce = yield this.provider.getTransactionCount(caller.address, 'pending');
|
|
101
|
-
(0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from chain: ${nonce}`, {}, order_trace_id);
|
|
102
|
-
}
|
|
84
|
+
const { wallet: caller, nonce: initialNonce } = yield this.callerManager.acquireCaller();
|
|
85
|
+
let nonce = initialNonce;
|
|
86
|
+
let nonce_from_error = -1;
|
|
87
|
+
let txid = '';
|
|
88
|
+
let i = 1;
|
|
89
|
+
do {
|
|
90
|
+
try {
|
|
91
|
+
if (i > 1) {
|
|
92
|
+
if (nonce_from_error >= 0) {
|
|
93
|
+
nonce = nonce_from_error;
|
|
94
|
+
nonce_from_error = -1;
|
|
95
|
+
(0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
|
|
103
96
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
97
|
+
else {
|
|
98
|
+
nonce = yield this.provider.getTransactionCount(caller.address, 'pending');
|
|
99
|
+
(0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from chain: ${nonce}`, {}, order_trace_id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const { executorId, data } = this.encodeTradeData(context);
|
|
103
|
+
const deadline = Math.floor(Date.now() / 1000) + 60;
|
|
104
|
+
const vaultCalldata = this.vaultInterface.encodeFunctionData('delegatecallExecutorToTrade', [executorId, data, deadline]);
|
|
105
|
+
const gasPriceGwei = this.getGasPriceGwei(context);
|
|
106
|
+
const realGasPriceGwei = Math.min(Number(gasPriceGwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
|
|
107
|
+
const tipNonce = nonce + 1;
|
|
108
|
+
const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
|
|
109
|
+
context.ui_tip_amount = new decimal_js_1.default(transfer_amount_gwei).div(Math.pow(10, 9)).toNumber();
|
|
110
|
+
const mainTx = {
|
|
111
|
+
to: this.tradeConfig.vaultAddress,
|
|
112
|
+
data: vaultCalldata,
|
|
113
|
+
gasLimit: this.chainConfig.gasOptions.gasLimit,
|
|
114
|
+
gasPrice: ethers_1.ethers.utils.parseUnits(realGasPriceGwei, 'gwei'),
|
|
115
|
+
nonce,
|
|
116
|
+
chainId: this.chainConfig.chainId,
|
|
117
|
+
value: 0,
|
|
118
|
+
};
|
|
119
|
+
const builderAddresses = [
|
|
120
|
+
ttd_bsc_send_tx_1.BSC_EOA_ADDRESS.BLOCKRAZOR,
|
|
121
|
+
ttd_bsc_send_tx_1.BSC_EOA_ADDRESS._48CLUB,
|
|
122
|
+
ttd_bsc_send_tx_1.BSC_EOA_ADDRESS.BLXR,
|
|
123
|
+
];
|
|
124
|
+
const [signedMainTx, ...signedTips] = yield Promise.all([
|
|
125
|
+
caller.signTransaction(mainTx),
|
|
126
|
+
...builderAddresses.map(addr => this.buildTipTransferTx(addr, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller)),
|
|
127
|
+
]);
|
|
128
|
+
txid = ethers_1.ethers.utils.keccak256(signedMainTx);
|
|
129
|
+
const tipTxMap = new Map();
|
|
130
|
+
builderAddresses.forEach((addr, idx) => tipTxMap.set(addr, signedTips[idx]));
|
|
131
|
+
(0, ttd_core_1.log_info)(`交易已签名`, { txid, nonce, caller: caller.address, gasPriceGwei: realGasPriceGwei }, order_trace_id);
|
|
132
|
+
const only_bundle = order_msg.is_dex_maker;
|
|
133
|
+
const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
return tipTxMap.get(eoa_address) || (yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller));
|
|
135
|
+
});
|
|
136
|
+
yield this.transactionSender.sendTransaction(signedMainTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
|
|
137
|
+
(0, ttd_core_1.log_info)(`交易发送成功`, {
|
|
138
|
+
pair, direction: isBuy ? 'BUY' : 'SELL',
|
|
139
|
+
txid, attempt: i, caller: caller.address
|
|
140
|
+
}, order_trace_id);
|
|
141
|
+
return txid;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
145
|
+
if (errorMessage.includes('rate limit')) {
|
|
146
|
+
(0, ttd_core_1.log_warn)(`Rate limit error, no retry! i=${i}`, {}, order_trace_id);
|
|
133
147
|
return txid;
|
|
134
148
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
(0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
|
|
151
|
-
if (errorMessage.includes('nonce too')) {
|
|
152
|
-
const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
|
|
153
|
-
if (correctNonce !== null && correctNonce >= 0) {
|
|
154
|
-
nonce_from_error = correctNonce;
|
|
155
|
-
}
|
|
149
|
+
if (errorMessage.includes('replacement transaction underpriced')) {
|
|
150
|
+
(0, ttd_core_1.log_warn)(`Replacement tx underpriced, continue! i=${i}`, {}, order_trace_id);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const isNonceError = errorMessage.includes('nonce');
|
|
154
|
+
if (!isNonceError || i >= maxAttempts) {
|
|
155
|
+
(0, ttd_core_1.log_warn)(`Non-nonce error or max retries, i=${i}, error: ${errorMessage}`, {}, order_trace_id);
|
|
156
|
+
return txid;
|
|
157
|
+
}
|
|
158
|
+
(0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
|
|
159
|
+
if (errorMessage.includes('nonce too')) {
|
|
160
|
+
const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
|
|
161
|
+
if (correctNonce !== null && correctNonce >= 0) {
|
|
162
|
+
nonce_from_error = correctNonce;
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
|
-
} while (++i <= maxAttempts);
|
|
159
|
-
if (!txid) {
|
|
160
|
-
throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
|
|
161
165
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
yield callerHandle.release();
|
|
166
|
+
} while (++i <= maxAttempts);
|
|
167
|
+
if (!txid) {
|
|
168
|
+
throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
|
|
166
169
|
}
|
|
170
|
+
return txid;
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
getGasPriceGwei(context) {
|
|
@@ -2,16 +2,12 @@ import { ethers } from "ethers";
|
|
|
2
2
|
export interface CallerHandle {
|
|
3
3
|
wallet: ethers.Wallet;
|
|
4
4
|
nonce: number;
|
|
5
|
-
release: () => Promise<void>;
|
|
6
5
|
}
|
|
7
6
|
export interface CallerManagerConfig {
|
|
8
7
|
chainName: string;
|
|
9
8
|
provider: ethers.providers.JsonRpcProvider;
|
|
10
9
|
callerGroupIds: string[];
|
|
11
10
|
chainId: number;
|
|
12
|
-
lockExpireSeconds?: number;
|
|
13
|
-
acquireTimeoutMs?: number;
|
|
14
|
-
acquireRetryIntervalMs?: number;
|
|
15
11
|
}
|
|
16
12
|
export declare class CallerManager {
|
|
17
13
|
private callers;
|
|
@@ -26,10 +22,8 @@ export declare class CallerManager {
|
|
|
26
22
|
getCallerAddresses(): string[];
|
|
27
23
|
private getNonceRedisKey;
|
|
28
24
|
private getLastUsedRedisKey;
|
|
29
|
-
private getLockKey;
|
|
30
25
|
private getNonce;
|
|
31
26
|
private setNonce;
|
|
32
|
-
private advanceNonce;
|
|
33
27
|
private getCallersSortedByLRU;
|
|
34
28
|
private updateLastUsed;
|
|
35
29
|
}
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.CallerManager = void 0;
|
|
13
|
-
const
|
|
13
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
14
14
|
const ethers_1 = require("ethers");
|
|
15
15
|
const redis_1 = require("../redis");
|
|
16
16
|
const CALLER_NONCE_KEY = 'caller:nonce';
|
|
@@ -19,29 +19,28 @@ const VAULT_CALLERS_KEY = 'vault:callers';
|
|
|
19
19
|
class CallerManager {
|
|
20
20
|
constructor(config) {
|
|
21
21
|
this.callers = [];
|
|
22
|
-
this.config =
|
|
22
|
+
this.config = config;
|
|
23
23
|
this.redis = new redis_1.SimpleRedisClient(`${config.chainName}:caller`);
|
|
24
24
|
}
|
|
25
25
|
init() {
|
|
26
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
-
const walletInfos = (0,
|
|
27
|
+
const walletInfos = (0, dist_1.load_wallet_multi)(this.config.callerGroupIds, false);
|
|
28
28
|
const allWallets = walletInfos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.config.provider));
|
|
29
|
-
(0,
|
|
29
|
+
(0, dist_1.log_info)(`CallerManager: loaded ${allWallets.length} wallets from CALLER_GROUP_IDS`, allWallets.map(w => w.address));
|
|
30
30
|
const vaultCallersKey = `${this.config.chainName}:${VAULT_CALLERS_KEY}`;
|
|
31
31
|
const allowedAddresses = yield this.redis.lrange(vaultCallersKey);
|
|
32
32
|
if (allowedAddresses && allowedAddresses.length > 0) {
|
|
33
33
|
const allowedSet = new Set(allowedAddresses.map(a => a.toLowerCase()));
|
|
34
34
|
this.callers = allWallets.filter(w => allowedSet.has(w.address.toLowerCase()));
|
|
35
|
-
|
|
36
|
-
const skipped = allWallets.filter(w => !allowedSet.has(w.address.toLowerCase()))
|
|
37
|
-
(0, ttd_core_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, matched);
|
|
35
|
+
(0, dist_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, this.callers.map(w => w.address));
|
|
36
|
+
const skipped = allWallets.filter(w => !allowedSet.has(w.address.toLowerCase()));
|
|
38
37
|
if (skipped.length > 0) {
|
|
39
|
-
(0,
|
|
38
|
+
(0, dist_1.log_warn)(`CallerManager: skipped ${skipped.length} wallets not in Vault whitelist`, skipped.map(w => w.address));
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
else {
|
|
43
42
|
this.callers = allWallets;
|
|
44
|
-
(0,
|
|
43
|
+
(0, dist_1.log_warn)(`CallerManager: Vault whitelist not found in Redis (${vaultCallersKey}), using all ${this.callers.length} loaded wallets`);
|
|
45
44
|
}
|
|
46
45
|
if (this.callers.length === 0) {
|
|
47
46
|
throw new Error('CallerManager: no valid callers after whitelist matching');
|
|
@@ -50,45 +49,25 @@ class CallerManager {
|
|
|
50
49
|
const address = caller.address.toLowerCase();
|
|
51
50
|
const nonceKey = this.getNonceRedisKey();
|
|
52
51
|
const existing = yield this.redis.hgetvalue(nonceKey, address);
|
|
53
|
-
if (existing
|
|
54
|
-
(0,
|
|
55
|
-
|
|
52
|
+
if (existing === null || existing === undefined) {
|
|
53
|
+
(0, dist_1.log_warn)(`Caller ${caller.address} nonce not found in Redis, stream-trade may not be running`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
(0, dist_1.log_info)(`Caller ${caller.address} nonce=${existing} (from Redis)`);
|
|
56
57
|
}
|
|
57
|
-
const nonce = yield this.config.provider.getTransactionCount(caller.address, 'pending');
|
|
58
|
-
yield this.redis.hsetValue(nonceKey, address, String(nonce), 24 * 60 * 60);
|
|
59
|
-
(0, ttd_core_1.log_info)(`Caller ${caller.address} nonce from chain: ${nonce}`);
|
|
60
58
|
})));
|
|
61
|
-
(0,
|
|
59
|
+
(0, dist_1.log_info)(`CallerManager initialized, ${this.callers.length} active callers`, this.callers.map(w => w.address));
|
|
62
60
|
});
|
|
63
61
|
}
|
|
64
62
|
acquireCaller() {
|
|
65
63
|
return __awaiter(this, void 0, void 0, function* () {
|
|
66
64
|
const startTime = Date.now();
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const lockValue = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
74
|
-
const acquired = yield this.redis.acquireLock(lockKey, lockValue, this.config.lockExpireSeconds);
|
|
75
|
-
if (!acquired)
|
|
76
|
-
continue;
|
|
77
|
-
const nonce = yield this.getNonce(caller.address);
|
|
78
|
-
yield this.updateLastUsed(caller.address);
|
|
79
|
-
(0, ttd_core_1.log_info)(`acquireCaller: ${caller.address}, nonce=${nonce}, took ${Date.now() - startTime}ms`);
|
|
80
|
-
const address = caller.address;
|
|
81
|
-
return {
|
|
82
|
-
wallet: caller,
|
|
83
|
-
nonce,
|
|
84
|
-
release: () => __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
yield this.redis.releaseLock(lockKey, lockValue);
|
|
86
|
-
})
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
yield (0, ttd_core_1.sleep)(retryInterval);
|
|
90
|
-
}
|
|
91
|
-
throw new Error(`acquireCaller failed: all ${this.callers.length} callers are busy, waited ${Date.now() - startTime}ms`);
|
|
65
|
+
const sortedCallers = yield this.getCallersSortedByLRU();
|
|
66
|
+
const caller = sortedCallers[0];
|
|
67
|
+
const nonce = yield this.getNonce(caller.address);
|
|
68
|
+
yield this.updateLastUsed(caller.address);
|
|
69
|
+
(0, dist_1.log_info)(`acquireCaller: ${caller.address}, nonce=${nonce}, took ${Date.now() - startTime}ms`);
|
|
70
|
+
return { wallet: caller, nonce };
|
|
92
71
|
});
|
|
93
72
|
}
|
|
94
73
|
confirmNonce(address, confirmedNonce) {
|
|
@@ -96,7 +75,7 @@ class CallerManager {
|
|
|
96
75
|
const current = yield this.getNonce(address);
|
|
97
76
|
if (confirmedNonce > current) {
|
|
98
77
|
yield this.setNonce(address, confirmedNonce);
|
|
99
|
-
(0,
|
|
78
|
+
(0, dist_1.log_info)(`confirmNonce: ${address}, ${current} → ${confirmedNonce}`);
|
|
100
79
|
}
|
|
101
80
|
});
|
|
102
81
|
}
|
|
@@ -105,7 +84,7 @@ class CallerManager {
|
|
|
105
84
|
const current = yield this.getNonce(address);
|
|
106
85
|
if (nonce !== current) {
|
|
107
86
|
yield this.setNonce(address, nonce);
|
|
108
|
-
(0,
|
|
87
|
+
(0, dist_1.log_info)(`forceSetNonce: ${address}, ${current} → ${nonce}`);
|
|
109
88
|
}
|
|
110
89
|
});
|
|
111
90
|
}
|
|
@@ -121,9 +100,6 @@ class CallerManager {
|
|
|
121
100
|
getLastUsedRedisKey() {
|
|
122
101
|
return `${this.config.chainName}:${CALLER_LAST_USED_KEY}`;
|
|
123
102
|
}
|
|
124
|
-
getLockKey(address) {
|
|
125
|
-
return `${this.config.chainName}:lock:caller:${address.toLowerCase()}`;
|
|
126
|
-
}
|
|
127
103
|
getNonce(address) {
|
|
128
104
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
105
|
const nonceKey = this.getNonceRedisKey();
|
|
@@ -140,14 +116,6 @@ class CallerManager {
|
|
|
140
116
|
yield this.redis.hsetValue(nonceKey, address.toLowerCase(), String(nonce), 24 * 60 * 60);
|
|
141
117
|
});
|
|
142
118
|
}
|
|
143
|
-
advanceNonce(address, newNonce) {
|
|
144
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
-
const current = yield this.getNonce(address);
|
|
146
|
-
if (newNonce > current) {
|
|
147
|
-
yield this.setNonce(address, newNonce);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
119
|
getCallersSortedByLRU() {
|
|
152
120
|
return __awaiter(this, void 0, void 0, function* () {
|
|
153
121
|
const lastUsedKey = this.getLastUsedRedisKey();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clonegod/ttd-bsc-common",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.11",
|
|
4
4
|
"description": "BSC common library",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"push": "npm run build && npm publish"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@clonegod/ttd-core": "3.0.
|
|
17
|
+
"@clonegod/ttd-core": "3.0.7",
|
|
18
18
|
"@clonegod/ttd-bsc-send-tx": "1.0.0",
|
|
19
19
|
"axios": "^1.12.0",
|
|
20
20
|
"dotenv": "^16.4.7",
|