@clonegod/ttd-sol-common 2.0.80 → 2.0.82
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 +9 -0
- package/dist/trade/abstract_dex_trade.js +47 -0
- package/dist/trade/index.d.ts +2 -0
- package/dist/trade/index.js +2 -0
- package/dist/trade/trade_trace.d.ts +17 -0
- package/dist/trade/trade_trace.js +65 -0
- package/dist/trade/tx_result_check.js +1 -0
- package/package.json +1 -1
- package/src/trade/abstract_dex_trade.ts +73 -0
- package/src/trade/index.ts +2 -0
- package/src/trade/trade_trace.ts +98 -0
- package/src/trade/tx_result_check.ts +5 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AbastrcatTrade, QuoteResultType, TradeContext } from '@clonegod/ttd-core/dist';
|
|
2
|
+
import { TransactionInstruction } from '@solana/web3.js';
|
|
3
|
+
import { SolTransactionBuilder } from './tx_builder';
|
|
4
|
+
export declare abstract class AbstractSolDexTrade<Q extends QuoteResultType = QuoteResultType> extends AbastrcatTrade {
|
|
5
|
+
abstract txBuilder: SolTransactionBuilder;
|
|
6
|
+
protected abstract prepareSwapParams(context: TradeContext): Promise<Q> | Q;
|
|
7
|
+
protected abstract createSwapInstructions(context: TradeContext, quote_result: Q): Promise<TransactionInstruction[]>;
|
|
8
|
+
execute(context: TradeContext): Promise<string>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AbstractSolDexTrade = void 0;
|
|
7
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
8
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
9
|
+
const trade_trace_1 = require("./trade_trace");
|
|
10
|
+
const send_1 = require("./send");
|
|
11
|
+
class AbstractSolDexTrade extends dist_1.AbastrcatTrade {
|
|
12
|
+
async execute(context) {
|
|
13
|
+
const pair = context.price_msg.pair;
|
|
14
|
+
const { order_msg, slippage_bps, order_trace_id } = context;
|
|
15
|
+
const direction = order_msg.aToB ? 'SELL' : 'BUY';
|
|
16
|
+
const trace = new trade_trace_1.TradeTrace(order_trace_id, pair, direction);
|
|
17
|
+
trace.set('order_id', order_msg.unique_order_msg_id);
|
|
18
|
+
trace.set('amount', order_msg.amount);
|
|
19
|
+
trace.set('slippage', `${slippage_bps / 100}%`);
|
|
20
|
+
let stage = 'prepare';
|
|
21
|
+
try {
|
|
22
|
+
const quote_result = await this.prepareSwapParams(context);
|
|
23
|
+
trace.mark('prepare');
|
|
24
|
+
trace.set('exec_price', quote_result.price);
|
|
25
|
+
stage = 'encode';
|
|
26
|
+
const swapIxs = await this.createSwapInstructions(context, quote_result);
|
|
27
|
+
trace.mark('encode');
|
|
28
|
+
stage = 'build';
|
|
29
|
+
const { sol_swqos_only } = context.trade_runtime.settings.strategy;
|
|
30
|
+
const swapTx = this.txBuilder.buildTransactionForSwap(context, swapIxs);
|
|
31
|
+
trace.mark('build');
|
|
32
|
+
const txid = bs58_1.default.encode(swapTx.signature);
|
|
33
|
+
trace.set('txid', txid);
|
|
34
|
+
stage = 'send';
|
|
35
|
+
new send_1.TransactionSender().sendTransaction(context, swapTx, sol_swqos_only);
|
|
36
|
+
trace.mark('sent');
|
|
37
|
+
trace.flush();
|
|
38
|
+
return txid;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
trace.markError(stage, err instanceof Error ? err.message : String(err));
|
|
42
|
+
trace.flush();
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.AbstractSolDexTrade = AbstractSolDexTrade;
|
package/dist/trade/index.d.ts
CHANGED
package/dist/trade/index.js
CHANGED
|
@@ -14,6 +14,8 @@ 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("./trade_trace"), exports);
|
|
18
|
+
__exportStar(require("./abstract_dex_trade"), exports);
|
|
17
19
|
__exportStar(require("./tx_result_check"), exports);
|
|
18
20
|
__exportStar(require("./tx_result_parse"), exports);
|
|
19
21
|
__exportStar(require("./tx_builder"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class TradeTrace {
|
|
2
|
+
readonly orderId: string;
|
|
3
|
+
readonly pair: string;
|
|
4
|
+
readonly direction: string;
|
|
5
|
+
private startTime;
|
|
6
|
+
private marks;
|
|
7
|
+
private data;
|
|
8
|
+
private error;
|
|
9
|
+
constructor(orderId: string, pair?: string, direction?: string);
|
|
10
|
+
mark(name: string): void;
|
|
11
|
+
set(key: string, value: any): void;
|
|
12
|
+
markError(stage: string, message: string): void;
|
|
13
|
+
private buildTimeline;
|
|
14
|
+
getAbsoluteMarks(): Record<string, number>;
|
|
15
|
+
getStartTime(): number;
|
|
16
|
+
flush(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TradeTrace = void 0;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
const logger = (0, ttd_core_1.createLogger)(__filename);
|
|
6
|
+
class TradeTrace {
|
|
7
|
+
constructor(orderId, pair = '', direction = '') {
|
|
8
|
+
this.orderId = orderId;
|
|
9
|
+
this.pair = pair;
|
|
10
|
+
this.direction = direction;
|
|
11
|
+
this.marks = [];
|
|
12
|
+
this.data = {};
|
|
13
|
+
this.error = null;
|
|
14
|
+
this.startTime = Date.now();
|
|
15
|
+
}
|
|
16
|
+
mark(name) {
|
|
17
|
+
this.marks.push({ name, elapsed: Date.now() - this.startTime });
|
|
18
|
+
}
|
|
19
|
+
set(key, value) {
|
|
20
|
+
this.data[key] = value;
|
|
21
|
+
}
|
|
22
|
+
markError(stage, message) {
|
|
23
|
+
this.error = { stage, message };
|
|
24
|
+
}
|
|
25
|
+
buildTimeline() {
|
|
26
|
+
if (this.marks.length === 0)
|
|
27
|
+
return 'recv';
|
|
28
|
+
let prev = 0;
|
|
29
|
+
const parts = this.marks.map(m => {
|
|
30
|
+
const delta = m.elapsed - prev;
|
|
31
|
+
prev = m.elapsed;
|
|
32
|
+
return `${m.name}(${delta}ms)`;
|
|
33
|
+
});
|
|
34
|
+
const total = this.marks[this.marks.length - 1].elapsed;
|
|
35
|
+
return `recv -> ${parts.join(' -> ')} = ${total}ms`;
|
|
36
|
+
}
|
|
37
|
+
getAbsoluteMarks() {
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const m of this.marks) {
|
|
40
|
+
result[m.name] = this.startTime + m.elapsed;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
getStartTime() {
|
|
45
|
+
return this.startTime;
|
|
46
|
+
}
|
|
47
|
+
flush() {
|
|
48
|
+
const timeline = this.buildTimeline();
|
|
49
|
+
if (this.error) {
|
|
50
|
+
logger.error(`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction} | stage=${this.error.stage} | ${this.error.message}`, new Error(this.error.message));
|
|
51
|
+
logger.info(`[TRADE FAILED detail] ${this.orderId}`, {
|
|
52
|
+
timeline,
|
|
53
|
+
error_stage: this.error.stage,
|
|
54
|
+
...this.data,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, {
|
|
59
|
+
timeline,
|
|
60
|
+
...this.data,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.TradeTrace = TradeTrace;
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AbastrcatTrade, QuoteResultType, TradeContext } from '@clonegod/ttd-core/dist'
|
|
2
|
+
import { TransactionInstruction } from '@solana/web3.js'
|
|
3
|
+
import bs58 from 'bs58'
|
|
4
|
+
import { TradeTrace } from './trade_trace'
|
|
5
|
+
import { SolTransactionBuilder } from './tx_builder'
|
|
6
|
+
import { TransactionSender } from './send'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AbstractSolDexTrade —— Solana 交易执行基类(对齐 BSC/BASE 的 abstract_dex_trade)。
|
|
10
|
+
*
|
|
11
|
+
* 共享一套交易热路径 + TradeTrace 打点(一处维护,8 个 DEX 共用),DEX 只实现两个差异点:
|
|
12
|
+
* - prepareSwapParams:按订单算 swap 参数(amountIn/amountOutMin 滑点下限/amountInMax)。**不是询价** ——
|
|
13
|
+
* 价格随订单 order_msg.price 而来(询价进程早算好),trade 信任它,纯算术、无 RPC。
|
|
14
|
+
* - createSwapInstructions:组装这笔 swap 的指令(内部自取 poolData / account / 0-RPC;CLMM 的真 tick 模拟在这)。
|
|
15
|
+
*
|
|
16
|
+
* 流程(SOL 模型,非 EVM):recv → prepare → encode → build(签名) → sent(Helius Sender 发出,fire-and-forget;
|
|
17
|
+
* 结果走 tx_result_check 异步回来)。无 nonce / caller。
|
|
18
|
+
*
|
|
19
|
+
* 输出对齐 BSC:成功 `[TRADE OK] <id> | pair dir { timeline: recv -> prepare(Xms) -> encode -> build -> sent = N ms, ... }`,
|
|
20
|
+
* 失败 `[TRADE FAILED] ... stage=<阶段> | <错误>`。
|
|
21
|
+
*/
|
|
22
|
+
export abstract class AbstractSolDexTrade<Q extends QuoteResultType = QuoteResultType> extends AbastrcatTrade {
|
|
23
|
+
/** DEX 子类持有(构造时 new SolTransactionBuilder(appConfig));基类只调用 buildTransactionForSwap。 */
|
|
24
|
+
abstract txBuilder: SolTransactionBuilder
|
|
25
|
+
|
|
26
|
+
/** DEX 实现:按订单算 swap 参数(amountIn/minOut/滑点边界)。**非询价**,纯算术。Q 允许 DEX 用自己的子类型(如 PumfunQuoteResultType)。 */
|
|
27
|
+
protected abstract prepareSwapParams(context: TradeContext): Promise<Q> | Q
|
|
28
|
+
/** DEX 实现:组装 swap 指令(内部自取 poolData / 0-RPC)。返回指令数组。 */
|
|
29
|
+
protected abstract createSwapInstructions(context: TradeContext, quote_result: Q): Promise<TransactionInstruction[]>
|
|
30
|
+
|
|
31
|
+
async execute(context: TradeContext): Promise<string> {
|
|
32
|
+
const pair = context.price_msg.pair
|
|
33
|
+
const { order_msg, slippage_bps, order_trace_id } = context
|
|
34
|
+
const direction = order_msg.aToB ? 'SELL' : 'BUY'
|
|
35
|
+
|
|
36
|
+
const trace = new TradeTrace(order_trace_id, pair, direction)
|
|
37
|
+
trace.set('order_id', order_msg.unique_order_msg_id)
|
|
38
|
+
trace.set('amount', order_msg.amount)
|
|
39
|
+
trace.set('slippage', `${slippage_bps / 100}%`)
|
|
40
|
+
|
|
41
|
+
let stage = 'prepare'
|
|
42
|
+
try {
|
|
43
|
+
const quote_result = await this.prepareSwapParams(context)
|
|
44
|
+
trace.mark('prepare')
|
|
45
|
+
trace.set('exec_price', quote_result.price)
|
|
46
|
+
|
|
47
|
+
stage = 'encode'
|
|
48
|
+
const swapIxs = await this.createSwapInstructions(context, quote_result)
|
|
49
|
+
trace.mark('encode')
|
|
50
|
+
|
|
51
|
+
// 构建交易(签名)
|
|
52
|
+
stage = 'build'
|
|
53
|
+
const { sol_swqos_only } = context.trade_runtime.settings.strategy
|
|
54
|
+
const swapTx = this.txBuilder.buildTransactionForSwap(context, swapIxs)
|
|
55
|
+
trace.mark('build')
|
|
56
|
+
|
|
57
|
+
const txid = bs58.encode(swapTx.signature)
|
|
58
|
+
trace.set('txid', txid)
|
|
59
|
+
|
|
60
|
+
// 发送:只走 Helius Sender(swqos + jito 路由由 Sender 负责),fire-and-forget
|
|
61
|
+
stage = 'send'
|
|
62
|
+
new TransactionSender().sendTransaction(context, swapTx, sol_swqos_only)
|
|
63
|
+
trace.mark('sent')
|
|
64
|
+
|
|
65
|
+
trace.flush()
|
|
66
|
+
return txid
|
|
67
|
+
} catch (err) {
|
|
68
|
+
trace.markError(stage, err instanceof Error ? err.message : String(err))
|
|
69
|
+
trace.flush()
|
|
70
|
+
throw err
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/trade/index.ts
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createLogger } from '@clonegod/ttd-core';
|
|
2
|
+
|
|
3
|
+
const logger = createLogger(__filename);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TradeTrace — 交易链路追踪(对齐 BSC/BASE ttd-{bsc,base}-common/trade/trade_trace.ts,逐字节一致)
|
|
7
|
+
*
|
|
8
|
+
* 在交易热路径中收集关键时间点和数据,交易完成后一次性输出结构化日志。
|
|
9
|
+
* 不改变现有 log_info 逻辑,只是额外收集数据。
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* const trace = new TradeTrace(order_trace_id, 'THREE/SOL', 'BUY');
|
|
13
|
+
* trace.mark('encode');
|
|
14
|
+
* trace.set('txid', '...');
|
|
15
|
+
* trace.flush(); // 一次性输出
|
|
16
|
+
*/
|
|
17
|
+
export class TradeTrace {
|
|
18
|
+
private startTime: number;
|
|
19
|
+
private marks: Array<{ name: string; elapsed: number }> = [];
|
|
20
|
+
private data: Record<string, any> = {};
|
|
21
|
+
private error: { stage: string; message: string } | null = null;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
public readonly orderId: string,
|
|
25
|
+
public readonly pair: string = '',
|
|
26
|
+
public readonly direction: string = '',
|
|
27
|
+
) {
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 记录一个时间点 */
|
|
32
|
+
mark(name: string): void {
|
|
33
|
+
this.marks.push({ name, elapsed: Date.now() - this.startTime });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 记录键值数据 */
|
|
37
|
+
set(key: string, value: any): void {
|
|
38
|
+
this.data[key] = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 记录错误 */
|
|
42
|
+
markError(stage: string, message: string): void {
|
|
43
|
+
this.error = { stage, message };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 构建可视化 timeline 字符串:
|
|
48
|
+
* recv -> quote(2ms) -> encode(2ms) -> build(11ms) -> sent(33ms) = 48ms
|
|
49
|
+
* 每段括号内是该阶段耗时(增量,非累计)
|
|
50
|
+
*/
|
|
51
|
+
private buildTimeline(): string {
|
|
52
|
+
if (this.marks.length === 0) return 'recv';
|
|
53
|
+
let prev = 0;
|
|
54
|
+
const parts = this.marks.map(m => {
|
|
55
|
+
const delta = m.elapsed - prev;
|
|
56
|
+
prev = m.elapsed;
|
|
57
|
+
return `${m.name}(${delta}ms)`;
|
|
58
|
+
});
|
|
59
|
+
const total = this.marks[this.marks.length - 1].elapsed;
|
|
60
|
+
return `recv -> ${parts.join(' -> ')} = ${total}ms`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 返回各 mark 的绝对时间戳 (ms) */
|
|
64
|
+
getAbsoluteMarks(): Record<string, number> {
|
|
65
|
+
const result: Record<string, number> = {};
|
|
66
|
+
for (const m of this.marks) {
|
|
67
|
+
result[m.name] = this.startTime + m.elapsed;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 返回 trace 起始时间 (ms) */
|
|
73
|
+
getStartTime(): number {
|
|
74
|
+
return this.startTime;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 一次性输出结构化日志 */
|
|
78
|
+
flush(): void {
|
|
79
|
+
const timeline = this.buildTimeline();
|
|
80
|
+
|
|
81
|
+
if (this.error) {
|
|
82
|
+
logger.error(
|
|
83
|
+
`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction} | stage=${this.error.stage} | ${this.error.message}`,
|
|
84
|
+
new Error(this.error.message),
|
|
85
|
+
);
|
|
86
|
+
logger.info(`[TRADE FAILED detail] ${this.orderId}`, {
|
|
87
|
+
timeline,
|
|
88
|
+
error_stage: this.error.stage,
|
|
89
|
+
...this.data,
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, {
|
|
93
|
+
timeline,
|
|
94
|
+
...this.data,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OrderMessageType, PriceMessageType, StandardPoolInfoType, StandardSwapDetailType, TradeBroadcastType, TradeResultBalanceChangeType, TradeResultType, TradeTimeFlowType } from "@clonegod/ttd-core"
|
|
2
|
-
import { AbstractTransactionResultCheck, getServerInfo, isEmpty, LOCAL_EVENT_NAME, log_error, log_info, log_warn, TradeContext, TRANSACTION_STATE_FAILED, TRANSACTION_STATE_PROCESSING, TRANSACTION_STATE_SUCCESS } from "@clonegod/ttd-core/dist"
|
|
2
|
+
import { AbstractTransactionResultCheck, getServerInfo, isEmpty, LOCAL_EVENT_NAME, log_error, log_info, log_warn, report_data_to_analyze, TradeContext, TRANSACTION_STATE_FAILED, TRANSACTION_STATE_PROCESSING, TRANSACTION_STATE_SUCCESS } from "@clonegod/ttd-core/dist"
|
|
3
3
|
import { Connection, ParsedTransactionWithMeta, PublicKey } from "@solana/web3.js"
|
|
4
4
|
import { EventEmitter } from 'events'
|
|
5
5
|
import { COMMITMENT_LEVEL } from "../common"
|
|
@@ -339,6 +339,10 @@ export class TransactionResultChecker extends AbstractTransactionResultCheck {
|
|
|
339
339
|
c_id
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// 对齐 core AbstractTransactionResultCheck.map_swap_result_to_tx_result:本方法是 core 的覆盖拷贝,
|
|
343
|
+
// 之前漏掉了 core 末尾这行上报 → analyze 交易列表一直空。analyze 按 txid 去重,重复上报幂等。
|
|
344
|
+
report_data_to_analyze('TradeResultType', trade_result)
|
|
345
|
+
|
|
342
346
|
return trade_result
|
|
343
347
|
}
|
|
344
348
|
|