@clonegod/ttd-bsc-common 3.0.4 → 3.0.6
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/trade/abstract_dex_trade.d.ts +0 -1
- package/dist/trade/abstract_dex_trade.js +20 -39
- package/dist/trade/caller_manager.js +0 -1
- package/dist/trade/check/base_tx_result_checker.d.ts +14 -0
- package/dist/trade/check/base_tx_result_checker.js +150 -0
- package/dist/trade/check/index.d.ts +1 -1
- package/dist/trade/check/index.js +1 -1
- package/dist/trade/parse/base_parser.js +11 -11
- package/package.json +1 -1
|
@@ -28,7 +28,6 @@ export declare abstract class AbstractDexTrade extends AbastrcatTrade {
|
|
|
28
28
|
abstract encodeTradeData(context: TradeContext): TradeCalldata;
|
|
29
29
|
init(): Promise<void>;
|
|
30
30
|
execute(context: TradeContext): Promise<string>;
|
|
31
|
-
private subscribeTradeMonitor;
|
|
32
31
|
protected getGasPriceGwei(context: TradeContext): string;
|
|
33
32
|
protected getBuilderTipAmoutGwei(context: TradeContext): string;
|
|
34
33
|
protected buildTipTransferTx(to: string, transfer_amount_gwei: string, gas_price_gwei: string, nonce: number, wallet: ethers.Wallet): Promise<string>;
|
|
@@ -61,7 +61,6 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
61
61
|
chainId: this.chainConfig.chainId,
|
|
62
62
|
});
|
|
63
63
|
yield this.callerManager.init();
|
|
64
|
-
this.subscribeTradeMonitor();
|
|
65
64
|
(0, ttd_core_1.log_info)(`AbstractDexTrade initialized, vault=${this.tradeConfig.vaultAddress}, callers=${this.callerManager.getCallerCount()}`);
|
|
66
65
|
});
|
|
67
66
|
}
|
|
@@ -70,33 +69,36 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
70
69
|
var _a;
|
|
71
70
|
const { price_msg, order_msg, slippage_bps, order_trace_id, pool_info } = context;
|
|
72
71
|
const { pair } = price_msg;
|
|
73
|
-
const {
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const { isBuy, amount } = order_msg;
|
|
73
|
+
const { inputToken, outputToken } = this.determineInputOutputTokens(order_msg, pool_info);
|
|
74
|
+
const amountOutMin = this.calculateAmountOutMin(context, inputToken, outputToken);
|
|
75
|
+
(0, ttd_core_1.log_info)(`执行交易`, {
|
|
76
|
+
orderId: order_trace_id,
|
|
76
77
|
pair,
|
|
77
|
-
|
|
78
|
-
amount
|
|
79
|
-
|
|
78
|
+
direction: isBuy ? 'BUY' : 'SELL',
|
|
79
|
+
input: `${amount} ${inputToken.symbol}`,
|
|
80
|
+
outputMin: `${ethers_1.ethers.utils.formatUnits(amountOutMin, outputToken.decimals)} ${outputToken.symbol}`,
|
|
81
|
+
slippage: `${slippage_bps / 100}%`,
|
|
80
82
|
});
|
|
81
83
|
let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
|
|
82
84
|
const callerHandle = yield this.callerManager.acquireCaller();
|
|
83
85
|
const { wallet: caller, nonce: initialNonce } = callerHandle;
|
|
84
86
|
try {
|
|
85
87
|
let nonce = initialNonce;
|
|
86
|
-
let nonce_from_error =
|
|
88
|
+
let nonce_from_error = -1;
|
|
87
89
|
let txid = '';
|
|
88
90
|
let i = 1;
|
|
89
91
|
do {
|
|
90
92
|
try {
|
|
91
93
|
if (i > 1) {
|
|
92
|
-
if (nonce_from_error
|
|
94
|
+
if (nonce_from_error >= 0) {
|
|
93
95
|
nonce = nonce_from_error;
|
|
94
|
-
nonce_from_error =
|
|
96
|
+
nonce_from_error = -1;
|
|
95
97
|
(0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
|
|
96
98
|
}
|
|
97
99
|
else {
|
|
98
|
-
|
|
99
|
-
|
|
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);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
const { executorId, data } = this.encodeTradeData(context);
|
|
@@ -115,7 +117,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
115
117
|
};
|
|
116
118
|
const signedTx = yield caller.signTransaction(tx);
|
|
117
119
|
txid = ethers_1.ethers.utils.keccak256(signedTx);
|
|
118
|
-
(0, ttd_core_1.log_info)(
|
|
120
|
+
(0, ttd_core_1.log_info)(`交易已签名`, { txid, nonce, caller: caller.address, gasPriceGwei: realGasPriceGwei }, order_trace_id);
|
|
119
121
|
const tipNonce = nonce + 1;
|
|
120
122
|
const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
|
|
121
123
|
const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
|
|
@@ -124,9 +126,9 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
124
126
|
});
|
|
125
127
|
const only_bundle = order_msg.is_dex_maker;
|
|
126
128
|
yield this.transactionSender.sendTransaction(signedTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
|
|
127
|
-
(0, ttd_core_1.log_info)(
|
|
128
|
-
pair,
|
|
129
|
-
attempt: i, caller: caller.address
|
|
129
|
+
(0, ttd_core_1.log_info)(`交易发送成功`, {
|
|
130
|
+
pair, direction: isBuy ? 'BUY' : 'SELL',
|
|
131
|
+
txid, attempt: i, caller: caller.address
|
|
130
132
|
}, order_trace_id);
|
|
131
133
|
return txid;
|
|
132
134
|
}
|
|
@@ -148,14 +150,14 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
148
150
|
(0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
|
|
149
151
|
if (errorMessage.includes('nonce too')) {
|
|
150
152
|
const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
|
|
151
|
-
if (correctNonce !== null && correctNonce
|
|
153
|
+
if (correctNonce !== null && correctNonce >= 0) {
|
|
152
154
|
nonce_from_error = correctNonce;
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
} while (++i <= maxAttempts);
|
|
157
159
|
if (!txid) {
|
|
158
|
-
throw new Error(
|
|
160
|
+
throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
|
|
159
161
|
}
|
|
160
162
|
return txid;
|
|
161
163
|
}
|
|
@@ -164,27 +166,6 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
164
166
|
}
|
|
165
167
|
});
|
|
166
168
|
}
|
|
167
|
-
subscribeTradeMonitor() {
|
|
168
|
-
const host = process.env.STREAM_TRADE_HOST || '127.0.0.1';
|
|
169
|
-
const wsUrl = `ws://${host}:${ttd_core_1.SERVICE_PORT.STREAM_TRADE_WS}`;
|
|
170
|
-
const wsClient = new ttd_core_1.WebSocketClient(wsUrl);
|
|
171
|
-
wsClient.onOpen(() => {
|
|
172
|
-
wsClient.send(JSON.stringify({
|
|
173
|
-
address: this.tradeConfig.vaultAddress,
|
|
174
|
-
}));
|
|
175
|
-
(0, ttd_core_1.log_info)(`Subscribed to trade-monitor for ${this.tradeConfig.vaultAddress}`);
|
|
176
|
-
});
|
|
177
|
-
wsClient.onMessage((event) => {
|
|
178
|
-
if (event.type === 'TradeResult' && event.data) {
|
|
179
|
-
const { caller, callerNonce } = event.data;
|
|
180
|
-
if (caller) {
|
|
181
|
-
const nextNonce = callerNonce + 2;
|
|
182
|
-
this.callerManager.confirmNonce(caller, nextNonce).catch(err => (0, ttd_core_1.log_warn)(`Failed to confirm nonce from trade-monitor`, err));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
wsClient.connect();
|
|
187
|
-
}
|
|
188
169
|
getGasPriceGwei(context) {
|
|
189
170
|
let { evm_gas_price_gwei } = context.trade_runtime.settings.strategy;
|
|
190
171
|
if (!evm_gas_price_gwei || evm_gas_price_gwei <= 0) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EnvArgs } from '@clonegod/ttd-core';
|
|
2
|
+
import { AbstractTransactionResultCheck } from "@clonegod/ttd-core/dist/trade";
|
|
3
|
+
import { ethers } from 'ethers';
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
export declare abstract class BaseTxResultChecker extends AbstractTransactionResultCheck {
|
|
6
|
+
protected provider: ethers.providers.JsonRpcProvider;
|
|
7
|
+
constructor(env_args: EnvArgs, event_emitter: EventEmitter);
|
|
8
|
+
protected abstract createParser(): {
|
|
9
|
+
parseTransaction(txReceipt: any, poolInfo: any): Promise<any>;
|
|
10
|
+
};
|
|
11
|
+
check_tx_result_interval(): Promise<void>;
|
|
12
|
+
on_subscibe_transaction(): Promise<void>;
|
|
13
|
+
private processTransactionResult;
|
|
14
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BaseTxResultChecker = void 0;
|
|
13
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
14
|
+
const trade_1 = require("@clonegod/ttd-core/dist/trade");
|
|
15
|
+
const ethers_1 = require("ethers");
|
|
16
|
+
class TradeResultSubscriber {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.ws = null;
|
|
19
|
+
this.connected = false;
|
|
20
|
+
this.listeners = new Map();
|
|
21
|
+
}
|
|
22
|
+
static getInstance() {
|
|
23
|
+
if (!TradeResultSubscriber.instance) {
|
|
24
|
+
TradeResultSubscriber.instance = new TradeResultSubscriber();
|
|
25
|
+
}
|
|
26
|
+
return TradeResultSubscriber.instance;
|
|
27
|
+
}
|
|
28
|
+
listen(txHash, callback, timeoutMs = 30000) {
|
|
29
|
+
const key = txHash.toLowerCase();
|
|
30
|
+
this.listeners.set(key, callback);
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
this.listeners.delete(key);
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
this.ensureConnected();
|
|
35
|
+
}
|
|
36
|
+
remove(txHash) {
|
|
37
|
+
this.listeners.delete(txHash.toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
ensureConnected() {
|
|
40
|
+
if (this.ws && this.connected)
|
|
41
|
+
return;
|
|
42
|
+
const host = process.env.STREAM_TRADE_WS_HOST || '127.0.0.1';
|
|
43
|
+
const wsUrl = `ws://${host}:${ttd_core_1.SERVICE_PORT.STREAM_TRADE_WS}`;
|
|
44
|
+
this.ws = new ttd_core_1.WebSocketClient(wsUrl);
|
|
45
|
+
this.ws.onOpen(() => {
|
|
46
|
+
this.connected = true;
|
|
47
|
+
const addresses = (process.env.VAULT_ADDRESS || '').split(',').map(a => a.trim()).filter(Boolean);
|
|
48
|
+
for (const addr of addresses) {
|
|
49
|
+
this.ws.send(JSON.stringify({ address: addr }));
|
|
50
|
+
}
|
|
51
|
+
(0, ttd_core_1.log_info)(`TradeResultSubscriber connected: ${wsUrl}`);
|
|
52
|
+
});
|
|
53
|
+
this.ws.onMessage((msg) => {
|
|
54
|
+
if (msg.type !== 'TradeResult' || !msg.data)
|
|
55
|
+
return;
|
|
56
|
+
const { txHash, receipt } = msg.data;
|
|
57
|
+
if (!txHash || !receipt)
|
|
58
|
+
return;
|
|
59
|
+
const key = txHash.toLowerCase();
|
|
60
|
+
const callback = this.listeners.get(key);
|
|
61
|
+
if (callback) {
|
|
62
|
+
this.listeners.delete(key);
|
|
63
|
+
callback(receipt);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
this.ws.connect();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
TradeResultSubscriber.instance = null;
|
|
70
|
+
class BaseTxResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
71
|
+
constructor(env_args, event_emitter) {
|
|
72
|
+
super(env_args, event_emitter);
|
|
73
|
+
this.provider = new ethers_1.ethers.providers.JsonRpcProvider(this.env_args.rpc_endpoint);
|
|
74
|
+
}
|
|
75
|
+
check_tx_result_interval() {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
const check_start_time = Date.now();
|
|
78
|
+
const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
|
|
79
|
+
const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '15000');
|
|
80
|
+
if (check_interval >= check_timeout)
|
|
81
|
+
return;
|
|
82
|
+
const intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
this.check_count += 1;
|
|
84
|
+
(0, ttd_core_1.log_info)(`check transaction start: seq=[${this.check_count}], txhash=${this.txid}, trace_id=${this.trace_id}`);
|
|
85
|
+
try {
|
|
86
|
+
if (Date.now() - check_start_time < check_timeout) {
|
|
87
|
+
let txReceipt = yield this.provider.getTransactionReceipt(this.txid);
|
|
88
|
+
if (!txReceipt)
|
|
89
|
+
return;
|
|
90
|
+
(0, ttd_core_1.log_info)(`Received transaction result via polling: ${this.txid}`);
|
|
91
|
+
clearInterval(intervalId);
|
|
92
|
+
yield this.processTransactionResult(txReceipt, 'interval');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
clearInterval(intervalId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
clearInterval(intervalId);
|
|
100
|
+
(0, ttd_core_1.log_error)('parse transaction error!', err);
|
|
101
|
+
}
|
|
102
|
+
}), check_interval);
|
|
103
|
+
this.intervalId = intervalId;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
on_subscibe_transaction() {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
(0, ttd_core_1.log_info)(`Subscribing trade result, txid=${this.txid}`);
|
|
109
|
+
TradeResultSubscriber.getInstance().listen(this.txid, (receipt) => {
|
|
110
|
+
(0, ttd_core_1.log_info)(`Received transaction result via stream-trade: ${this.txid}`);
|
|
111
|
+
this.processTransactionResult(receipt, 'websocket')
|
|
112
|
+
.catch(err => (0, ttd_core_1.log_error)(`Error processing trade result: ${this.txid}`, err));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
processTransactionResult(txReceipt, source) {
|
|
117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
if (ttd_core_1.LOG.debug) {
|
|
119
|
+
(0, ttd_core_1.writeFile)(`./dist/tx_receipt_${this.txid}_${source}.json`, JSON.stringify(txReceipt, null, 2));
|
|
120
|
+
}
|
|
121
|
+
if (this.trade_result_already_processed) {
|
|
122
|
+
(0, ttd_core_1.log_warn)(`trade_result_already_processed, ignore result from ${source}!`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const parser = this.createParser();
|
|
126
|
+
const swap_detail = yield parser.parseTransaction(txReceipt, this.pool_info);
|
|
127
|
+
let trade_result = this.map_swap_result_to_tx_result(swap_detail);
|
|
128
|
+
this.trade_result_already_processed = true;
|
|
129
|
+
if (this.intervalId) {
|
|
130
|
+
clearInterval(this.intervalId);
|
|
131
|
+
this.intervalId = null;
|
|
132
|
+
}
|
|
133
|
+
if (trade_result.success) {
|
|
134
|
+
this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_SUCCESS, trade_result);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_FAILED, trade_result);
|
|
138
|
+
}
|
|
139
|
+
if (source === 'interval') {
|
|
140
|
+
console.log('--------------------- Transaction Result from Polling ---------------------');
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
console.log('===================== Transaction Result from stream-trade =====================');
|
|
144
|
+
}
|
|
145
|
+
console.log(JSON.stringify(trade_result, null, 2));
|
|
146
|
+
console.log('-----------------------------------------------------------------------------');
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.BaseTxResultChecker = BaseTxResultChecker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./base_tx_result_checker";
|
|
@@ -14,4 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./
|
|
17
|
+
__exportStar(require("./base_tx_result_checker"), exports);
|
|
@@ -44,17 +44,17 @@ class BaseTxParser {
|
|
|
44
44
|
}
|
|
45
45
|
calculateGasFee(txReceipt) {
|
|
46
46
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
const toBN = (val) => {
|
|
48
|
+
if (!val)
|
|
49
|
+
return ethers_1.ethers.BigNumber.from(0);
|
|
50
|
+
if (ethers_1.ethers.BigNumber.isBigNumber(val))
|
|
51
|
+
return val;
|
|
52
|
+
if (val.hex)
|
|
53
|
+
return ethers_1.ethers.BigNumber.from(val.hex);
|
|
54
|
+
return ethers_1.ethers.BigNumber.from(val);
|
|
55
|
+
};
|
|
56
|
+
const gasUsed = toBN(txReceipt.gasUsed);
|
|
57
|
+
const effectiveGasPrice = toBN(txReceipt.effectiveGasPrice || txReceipt.gasPrice);
|
|
58
58
|
const gasFeeBN = gasUsed.mul(effectiveGasPrice);
|
|
59
59
|
const base_fee = Number(ethers_1.ethers.utils.formatEther(gasFeeBN));
|
|
60
60
|
const priority_fee = 0;
|