@clonegod/ttd-sol-common 2.0.83 → 2.0.85
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.
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.QuotePriceVerify = void 0;
|
|
4
4
|
const trade_direction_1 = require("../../utils/trade_direction");
|
|
5
5
|
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
6
|
+
const USD_STABLECOINS = new Set(['USDT', 'USDC', 'USD1']);
|
|
6
7
|
function pickMatchingTier(tiers, actualAmountIn) {
|
|
7
8
|
if (!tiers || tiers.length === 0)
|
|
8
9
|
return null;
|
|
@@ -183,25 +184,24 @@ class QuotePriceVerify {
|
|
|
183
184
|
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, refPrice, execPriceNum, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash, sources, primaryTierInfo) {
|
|
184
185
|
if (inputAmountUi <= 0 || outputAmountUi <= 0)
|
|
185
186
|
return;
|
|
186
|
-
let swapUsd = 0;
|
|
187
|
-
if (token0PriceUsd && token1PriceUsd && token0Address) {
|
|
188
|
-
const inputIsToken0 = inputTokenAddress.toLowerCase() === token0Address.toLowerCase();
|
|
189
|
-
swapUsd = inputIsToken0
|
|
190
|
-
? inputAmountUi * token0PriceUsd
|
|
191
|
-
: inputAmountUi * token1PriceUsd;
|
|
192
|
-
}
|
|
193
187
|
const direction = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
|
|
194
188
|
const baseToken = direction.baseToken;
|
|
195
189
|
const quoteToken = direction.quoteToken;
|
|
196
190
|
const inputIsQuoteToken = inputTokenAddress.toLowerCase() === quoteToken.address.toLowerCase();
|
|
197
191
|
const isBuy = inputIsQuoteToken;
|
|
198
192
|
const side = isBuy ? 'BUY' : 'SELL';
|
|
193
|
+
const quoteIsToken0 = quoteToken.address === token0Address;
|
|
194
|
+
const quotePriceUsd = USD_STABLECOINS.has((quoteToken.symbol || '').toUpperCase())
|
|
195
|
+
? 1
|
|
196
|
+
: (quoteIsToken0 ? (token0PriceUsd || 0) : (token1PriceUsd || 0));
|
|
197
|
+
const quoteLegAmount = isBuy ? inputAmountUi : outputAmountUi;
|
|
198
|
+
const swapUsd = quotePriceUsd > 0 ? quoteLegAmount * quotePriceUsd : 0;
|
|
199
199
|
if (refPrice <= 0 || execPriceNum <= 0)
|
|
200
200
|
return;
|
|
201
201
|
const diffBps = (refPrice - execPriceNum) / refPrice * 10000;
|
|
202
202
|
const absDiffBps = Math.abs(diffBps);
|
|
203
203
|
const status = absDiffBps < 5 ? '✅' : absDiffBps < 10 ? '⚠️' : '❌';
|
|
204
|
-
const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(
|
|
204
|
+
const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(2)}` : '$?';
|
|
205
205
|
const maxUsd = cached.quoteAmountUsd * 2;
|
|
206
206
|
const inRange = swapUsd <= 0 || swapUsd <= maxUsd;
|
|
207
207
|
const rangeTag = inRange ? '' : ' [out]';
|
|
@@ -229,7 +229,7 @@ class QuotePriceVerify {
|
|
|
229
229
|
ref_price: refPrice,
|
|
230
230
|
exec_price: execPriceNum,
|
|
231
231
|
diff_bps: parseFloat(diffBps.toFixed(1)),
|
|
232
|
-
swap_usd: parseFloat(swapUsd.toFixed(
|
|
232
|
+
swap_usd: parseFloat(swapUsd.toFixed(2)),
|
|
233
233
|
in_range: inRange,
|
|
234
234
|
input_amount: inputAmountUi,
|
|
235
235
|
output_amount: outputAmountUi,
|
|
@@ -9,6 +9,7 @@ export declare class SolTransactionBuilder {
|
|
|
9
9
|
recentBlockheight: number;
|
|
10
10
|
constructor(appConfig: SolanaTradeAppConfig);
|
|
11
11
|
init(): Promise<void>;
|
|
12
|
+
private warmupSigner;
|
|
12
13
|
private handleBlockUpdateEvent;
|
|
13
14
|
private getPreInstructions;
|
|
14
15
|
private createComputeUnitLimitWithJitoDontFront;
|
package/dist/trade/tx_builder.js
CHANGED
|
@@ -41,9 +41,11 @@ const jitodontfrontAccounts = [
|
|
|
41
41
|
'jitodontfront1111111111111111111111111111wP',
|
|
42
42
|
'jitodontfront111111111111234565432123456RX5',
|
|
43
43
|
];
|
|
44
|
+
const jitodontfrontPubkeys = jitodontfrontAccounts.map(s => new web3_js_1.PublicKey(s));
|
|
45
|
+
const heliusTipPubkeys = helius_1.HELIUS_TIP_ACCOUNTS.map(s => new web3_js_1.PublicKey(s));
|
|
44
46
|
function getJitoDontFrontAccount() {
|
|
45
|
-
const randomIndex = Math.floor(Math.random() *
|
|
46
|
-
return
|
|
47
|
+
const randomIndex = Math.floor(Math.random() * jitodontfrontPubkeys.length);
|
|
48
|
+
return jitodontfrontPubkeys[randomIndex];
|
|
47
49
|
}
|
|
48
50
|
class SolTransactionBuilder {
|
|
49
51
|
constructor(appConfig) {
|
|
@@ -54,6 +56,24 @@ class SolTransactionBuilder {
|
|
|
54
56
|
}
|
|
55
57
|
async init() {
|
|
56
58
|
this.appConfig.arb_event_subscriber.subscribe_new_block(dist_1.CHAIN_ID.SOLANA, this.handleBlockUpdateEvent.bind(this));
|
|
59
|
+
this.warmupSigner();
|
|
60
|
+
}
|
|
61
|
+
warmupSigner() {
|
|
62
|
+
try {
|
|
63
|
+
for (let i = 0; i < 2; i++) {
|
|
64
|
+
const tx = new web3_js_1.Transaction();
|
|
65
|
+
tx.add(web3_js_1.SystemProgram.transfer({
|
|
66
|
+
fromPubkey: this.keypair.publicKey,
|
|
67
|
+
toPubkey: this.keypair.publicKey,
|
|
68
|
+
lamports: 0,
|
|
69
|
+
}));
|
|
70
|
+
tx.recentBlockhash = '11111111111111111111111111111111';
|
|
71
|
+
tx.feePayer = this.keypair.publicKey;
|
|
72
|
+
tx.sign(this.keypair);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
57
77
|
}
|
|
58
78
|
async handleBlockUpdateEvent(eventData) {
|
|
59
79
|
let blockUpdateEvent = JSON.parse(eventData);
|
|
@@ -88,6 +108,7 @@ class SolTransactionBuilder {
|
|
|
88
108
|
return [];
|
|
89
109
|
}
|
|
90
110
|
buildTransactionForSwap(context, swapInstructions) {
|
|
111
|
+
const _t0 = Date.now();
|
|
91
112
|
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset;
|
|
92
113
|
const swapTx = new web3_js_1.Transaction();
|
|
93
114
|
const preInstructions = this.getPreInstructions(context);
|
|
@@ -106,11 +127,14 @@ class SolTransactionBuilder {
|
|
|
106
127
|
}
|
|
107
128
|
let tip_instruction = web3_js_1.SystemProgram.transfer({
|
|
108
129
|
fromPubkey: this.keypair.publicKey,
|
|
109
|
-
toPubkey:
|
|
130
|
+
toPubkey: heliusTipPubkeys[Math.floor(Math.random() * heliusTipPubkeys.length)],
|
|
110
131
|
lamports: tip_lamports,
|
|
111
132
|
});
|
|
112
133
|
swapTx.instructions.push(tip_instruction);
|
|
134
|
+
const _t1 = Date.now();
|
|
113
135
|
swapTx.sign(this.keypair);
|
|
136
|
+
const _t2 = Date.now();
|
|
137
|
+
(0, dist_1.log_debug)(`[txBuilder] build breakdown: assemble=${_t1 - _t0}ms sign=${_t2 - _t1}ms total=${_t2 - _t0}ms`);
|
|
114
138
|
return swapTx;
|
|
115
139
|
}
|
|
116
140
|
}
|
package/package.json
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
import { resolveTradeDirection, calculateStandardPrice } from '../../utils/trade_direction';
|
|
17
17
|
import { report_data_to_analyze, QuoteTier } from '@clonegod/ttd-core';
|
|
18
18
|
|
|
19
|
+
/** USD 稳定币:作 quote 时直接按 $1,无需查价。扩展时在此加。 */
|
|
20
|
+
const USD_STABLECOINS = new Set(['USDT', 'USDC', 'USD1']);
|
|
21
|
+
|
|
19
22
|
// ========== 类型定义 ==========
|
|
20
23
|
|
|
21
24
|
interface CachedQuote {
|
|
@@ -441,25 +444,24 @@ export class QuotePriceVerify {
|
|
|
441
444
|
): string | void {
|
|
442
445
|
if (inputAmountUi <= 0 || outputAmountUi <= 0) return;
|
|
443
446
|
|
|
444
|
-
//
|
|
445
|
-
let swapUsd = 0;
|
|
446
|
-
if (token0PriceUsd && token1PriceUsd && token0Address) {
|
|
447
|
-
const inputIsToken0 = inputTokenAddress.toLowerCase() === token0Address.toLowerCase();
|
|
448
|
-
swapUsd = inputIsToken0
|
|
449
|
-
? inputAmountUi * token0PriceUsd
|
|
450
|
-
: inputAmountUi * token1PriceUsd;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// 判断是 买入 base 还是 卖出 base
|
|
447
|
+
// 判断 买入/卖出 base(USD 估值也要用 quote 侧,提前算)
|
|
454
448
|
const direction = resolveTradeDirection(poolInfo, true);
|
|
455
449
|
const baseToken = direction.baseToken;
|
|
456
450
|
const quoteToken = direction.quoteToken;
|
|
457
451
|
|
|
458
452
|
const inputIsQuoteToken = inputTokenAddress.toLowerCase() === quoteToken.address.toLowerCase();
|
|
459
453
|
const isBuy = inputIsQuoteToken;
|
|
460
|
-
|
|
461
454
|
const side = isBuy ? 'BUY' : 'SELL';
|
|
462
455
|
|
|
456
|
+
// 估算 USD:只看 **quote 侧那条腿**(swap 必有一条 quote 腿,quote 是稳定/可靠定价侧)。
|
|
457
|
+
// quote 是 USD 稳定币 → 直接按 $1;否则(SOL 等)→ 用 quote 市价。base(pump 币)有没有价都不影响。
|
|
458
|
+
const quoteIsToken0 = quoteToken.address === token0Address; // base58 原值比较(不 lowercase)
|
|
459
|
+
const quotePriceUsd = USD_STABLECOINS.has((quoteToken.symbol || '').toUpperCase())
|
|
460
|
+
? 1
|
|
461
|
+
: (quoteIsToken0 ? (token0PriceUsd || 0) : (token1PriceUsd || 0));
|
|
462
|
+
const quoteLegAmount = isBuy ? inputAmountUi : outputAmountUi; // 买:input=quote;卖:output=quote
|
|
463
|
+
const swapUsd = quotePriceUsd > 0 ? quoteLegAmount * quotePriceUsd : 0;
|
|
464
|
+
|
|
463
465
|
if (refPrice <= 0 || execPriceNum <= 0) return;
|
|
464
466
|
|
|
465
467
|
// sign 约定:(ref - exec) / ref —— "我们报价 vs 实际成交"
|
|
@@ -470,7 +472,7 @@ export class QuotePriceVerify {
|
|
|
470
472
|
const absDiffBps = Math.abs(diffBps);
|
|
471
473
|
// 阈值统一:<5 ✅ / 5-10 ⚠️ / ≥10 ❌(前端颜色阈值同步)
|
|
472
474
|
const status = absDiffBps < 5 ? '✅' : absDiffBps < 10 ? '⚠️' : '❌';
|
|
473
|
-
const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(
|
|
475
|
+
const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(2)}` : '$?';
|
|
474
476
|
|
|
475
477
|
// USD 范围标注(不过滤,全部打印)
|
|
476
478
|
// 规则:仅排除"过大的 swap"(超过 2× 询价金额),偏差大可能是滑点而非报价不准;
|
|
@@ -509,7 +511,7 @@ export class QuotePriceVerify {
|
|
|
509
511
|
ref_price: refPrice,
|
|
510
512
|
exec_price: execPriceNum,
|
|
511
513
|
diff_bps: parseFloat(diffBps.toFixed(1)),
|
|
512
|
-
swap_usd: parseFloat(swapUsd.toFixed(
|
|
514
|
+
swap_usd: parseFloat(swapUsd.toFixed(2)), // 显示到分:pump 币多是 <$1 的小单,取整会全成 $0
|
|
513
515
|
in_range: inRange,
|
|
514
516
|
input_amount: inputAmountUi,
|
|
515
517
|
output_amount: outputAmountUi,
|
package/src/trade/tx_builder.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CHAIN_ID, TradeContext } from "@clonegod/ttd-core/dist";
|
|
1
|
+
import { CHAIN_ID, log_debug, TradeContext } from "@clonegod/ttd-core/dist";
|
|
2
2
|
import { ComputeBudgetProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js";
|
|
3
3
|
import { LAMPORTS_PER_MICRO_LAMPORTS } from "../common";
|
|
4
4
|
import { SolanaBlockMetaUpdateEvent } from "../types";
|
|
@@ -55,9 +55,13 @@ const jitodontfrontAccounts = [
|
|
|
55
55
|
'jitodontfront111111111111234565432123456RX5',
|
|
56
56
|
]
|
|
57
57
|
|
|
58
|
+
// 固定地址预构 PublicKey(避免每笔交易 base58 解码;模块加载时一次性)
|
|
59
|
+
const jitodontfrontPubkeys = jitodontfrontAccounts.map(s => new PublicKey(s));
|
|
60
|
+
const heliusTipPubkeys = HELIUS_TIP_ACCOUNTS.map(s => new PublicKey(s));
|
|
61
|
+
|
|
58
62
|
function getJitoDontFrontAccount(): PublicKey {
|
|
59
|
-
const randomIndex = Math.floor(Math.random() *
|
|
60
|
-
return
|
|
63
|
+
const randomIndex = Math.floor(Math.random() * jitodontfrontPubkeys.length);
|
|
64
|
+
return jitodontfrontPubkeys[randomIndex];
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
|
|
@@ -79,6 +83,29 @@ export class SolTransactionBuilder {
|
|
|
79
83
|
public async init(): Promise<void> {
|
|
80
84
|
// 订阅区块更新事件
|
|
81
85
|
this.appConfig.arb_event_subscriber.subscribe_new_block(CHAIN_ID.SOLANA, this.handleBlockUpdateEvent.bind(this));
|
|
86
|
+
// 预热签名路径:web3.js 1.98 用 @noble/curves/ed25519,首次 sign 要懒构建 base-point wNAF 表
|
|
87
|
+
// + V8 JIT 整条 compileMessage/serialize 路径,冷启动 ~34ms。在 init 跑几笔 dummy sign
|
|
88
|
+
// 把这 ~30ms 从「第一笔真实交易的关键路径」挪到进程启动期(实测首笔 34ms→~5ms)。
|
|
89
|
+
this.warmupSigner()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 进程启动期预热 ed25519 签名 + 序列化热路径,消除第一笔交易的冷启动尖刺 */
|
|
93
|
+
private warmupSigner(): void {
|
|
94
|
+
try {
|
|
95
|
+
for (let i = 0; i < 2; i++) {
|
|
96
|
+
const tx = new Transaction()
|
|
97
|
+
tx.add(SystemProgram.transfer({
|
|
98
|
+
fromPubkey: this.keypair.publicKey,
|
|
99
|
+
toPubkey: this.keypair.publicKey,
|
|
100
|
+
lamports: 0,
|
|
101
|
+
}))
|
|
102
|
+
tx.recentBlockhash = '11111111111111111111111111111111' // 仅预热用,不广播
|
|
103
|
+
tx.feePayer = this.keypair.publicKey
|
|
104
|
+
tx.sign(this.keypair)
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// 预热失败不影响主流程(首笔退化为冷签名)
|
|
108
|
+
}
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
private async handleBlockUpdateEvent(eventData: string): Promise<void> {
|
|
@@ -157,6 +184,7 @@ export class SolTransactionBuilder {
|
|
|
157
184
|
* @returns
|
|
158
185
|
*/
|
|
159
186
|
buildTransactionForSwap(context: TradeContext, swapInstructions: TransactionInstruction[]): Transaction {
|
|
187
|
+
const _t0 = Date.now()
|
|
160
188
|
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset
|
|
161
189
|
// console.log(`buildTransactionForSwap, max_block_offset: ${max_block_offset}`)
|
|
162
190
|
|
|
@@ -185,12 +213,16 @@ export class SolTransactionBuilder {
|
|
|
185
213
|
}
|
|
186
214
|
let tip_instruction = SystemProgram.transfer({
|
|
187
215
|
fromPubkey: this.keypair.publicKey,
|
|
188
|
-
toPubkey:
|
|
216
|
+
toPubkey: heliusTipPubkeys[Math.floor(Math.random() * heliusTipPubkeys.length)],
|
|
189
217
|
lamports: tip_lamports,
|
|
190
218
|
})
|
|
191
219
|
swapTx.instructions.push(tip_instruction)
|
|
192
220
|
|
|
193
|
-
|
|
221
|
+
const _t1 = Date.now()
|
|
222
|
+
swapTx.sign(this.keypair) // = compileMessage(serialize) + @noble/curves ed25519 签名(已在 init 预热)
|
|
223
|
+
const _t2 = Date.now()
|
|
224
|
+
// 耗时拆分(debug 级,不刷屏):assemble=组装指令+设字段;sign=编译消息+ed25519 签名
|
|
225
|
+
log_debug(`[txBuilder] build breakdown: assemble=${_t1 - _t0}ms sign=${_t2 - _t1}ms total=${_t2 - _t0}ms`)
|
|
194
226
|
|
|
195
227
|
return swapTx
|
|
196
228
|
}
|