@clonegod/ttd-sol-common 2.0.6 → 2.0.8

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.
@@ -0,0 +1,5 @@
1
+ export declare enum COMMITMENT_LEVEL {
2
+ PROCESSED = "processed",
3
+ CONFIRMED = "confirmed",
4
+ _FINALIZED = "finalized"
5
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMMITMENT_LEVEL = void 0;
4
+ var COMMITMENT_LEVEL;
5
+ (function (COMMITMENT_LEVEL) {
6
+ COMMITMENT_LEVEL["PROCESSED"] = "processed";
7
+ COMMITMENT_LEVEL["CONFIRMED"] = "confirmed";
8
+ COMMITMENT_LEVEL["_FINALIZED"] = "finalized";
9
+ })(COMMITMENT_LEVEL || (exports.COMMITMENT_LEVEL = COMMITMENT_LEVEL = {}));
@@ -0,0 +1 @@
1
+ export * from './constants';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./constants"), exports);
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export * from './quote';
2
1
  export * from './types';
2
+ export * from './common';
3
+ export * from './quote';
4
+ export * from './trade';
package/dist/index.js CHANGED
@@ -14,5 +14,7 @@ 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("./quote"), exports);
18
17
  __exportStar(require("./types"), exports);
18
+ __exportStar(require("./common"), exports);
19
+ __exportStar(require("./quote"), exports);
20
+ __exportStar(require("./trade"), exports);
@@ -0,0 +1,2 @@
1
+ export * from './tx_result_check';
2
+ export * from './tx_result_parse';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./tx_result_check"), exports);
18
+ __exportStar(require("./tx_result_parse"), exports);
@@ -0,0 +1,28 @@
1
+ import { OrderMessageType, PriceMessageType, StandardPoolInfoType, StandardSwapDetailType, TradeResultType } from "@clonegod/ttd-core";
2
+ import { AbstractTransactionResultCheck, TradeContext } from "@clonegod/ttd-core/dist";
3
+ import { Connection } from "@solana/web3.js";
4
+ import { EventEmitter } from 'events';
5
+ import { TransactionResultParser } from "./tx_result_parse";
6
+ export declare class TransactionResultChecker extends AbstractTransactionResultCheck {
7
+ env_args: any;
8
+ event_emitter: EventEmitter;
9
+ connection: Connection;
10
+ wallet: string;
11
+ context: TradeContext;
12
+ group_id: string;
13
+ txid: string;
14
+ trace_id: string;
15
+ price_msg: PriceMessageType;
16
+ order_msg: OrderMessageType;
17
+ pool_info: StandardPoolInfoType;
18
+ intervalId: any;
19
+ check_count: number;
20
+ transactionParser: TransactionResultParser;
21
+ trade_result_already_processed: boolean;
22
+ constructor(env_args: any, event_emitter: EventEmitter, context: TradeContext, txid: string);
23
+ init_tx_status(): this;
24
+ check(): Promise<void>;
25
+ on_subscibe_transaction(): void;
26
+ check_tx_result_interval(): Promise<void>;
27
+ map_swap_result_to_tx_result(swap_result: StandardSwapDetailType): TradeResultType;
28
+ }
@@ -0,0 +1,235 @@
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.TransactionResultChecker = void 0;
13
+ const dist_1 = require("@clonegod/ttd-core/dist");
14
+ const web3_js_1 = require("@solana/web3.js");
15
+ const tx_result_parse_1 = require("./tx_result_parse");
16
+ const common_1 = require("../common");
17
+ class TransactionResultChecker extends dist_1.AbstractTransactionResultCheck {
18
+ constructor(env_args, event_emitter, context, txid) {
19
+ super(env_args, event_emitter);
20
+ this.env_args = env_args;
21
+ this.event_emitter = event_emitter;
22
+ this.context = context;
23
+ this.group_id = context.group_id;
24
+ this.connection = new web3_js_1.Connection(env_args.rpc_endpoint, {
25
+ commitment: common_1.COMMITMENT_LEVEL.CONFIRMED,
26
+ });
27
+ this.wallet = context.trade_runtime.wallet.public_key;
28
+ this.txid = txid;
29
+ this.price_msg = context.price_msg;
30
+ this.order_msg = context.order_msg;
31
+ this.pool_info = context.pool_info;
32
+ this.trace_id = context.order_msg.order_trace_id;
33
+ this.check_count = 0;
34
+ this.transactionParser = new tx_result_parse_1.TransactionResultParser(this.env_args, new web3_js_1.PublicKey(this.wallet), this.event_emitter);
35
+ this.trade_result_already_processed = false;
36
+ }
37
+ init_tx_status() {
38
+ let empty_trade_result = JSON.parse(JSON.stringify({
39
+ group_id: this.context.group_id,
40
+ txid: this.txid
41
+ }));
42
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_PROCESSING, empty_trade_result);
43
+ return this;
44
+ }
45
+ check() {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ this.check_tx_result_interval();
48
+ this.on_subscibe_transaction();
49
+ });
50
+ }
51
+ on_subscibe_transaction() {
52
+ this.event_emitter.once(dist_1.LOCAL_EVENT_NAME.EVENT_WALLET_TRANSACTION + '#' + this.txid, (messageStr) => {
53
+ try {
54
+ this.trade_result_already_processed = true;
55
+ (0, dist_1.log_debug)('on events: EVENT_WALLET_TRANSACTION', messageStr);
56
+ let messageObj = messageStr;
57
+ if (typeof messageStr === 'string') {
58
+ messageObj = JSON.parse(messageStr);
59
+ }
60
+ const result = messageObj.params.result;
61
+ const _txid = result.signature;
62
+ const slot = result.slot;
63
+ (0, dist_1.log_info)('check txid:', { _txid, txid: this.txid, slot });
64
+ result.transaction.transaction.signatures = [_txid];
65
+ let tx_result = {
66
+ slot,
67
+ transaction: result.transaction.transaction,
68
+ meta: result.transaction.meta,
69
+ blockTime: Date.now() / 1000,
70
+ version: result.transaction.version
71
+ };
72
+ let swap_result = this.transactionParser.parse_transaction_data(tx_result, this.pool_info);
73
+ let trade_result = this.map_swap_result_to_tx_result(swap_result);
74
+ if (trade_result.success) {
75
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
76
+ }
77
+ else {
78
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
79
+ }
80
+ console.log('<----------------------------------------------------');
81
+ console.dir(trade_result, { depth: 8 });
82
+ console.log('---------------------------------------------------->');
83
+ }
84
+ catch (err) {
85
+ (0, dist_1.log_error)('parse geyser stream message error!', err);
86
+ return;
87
+ }
88
+ });
89
+ }
90
+ check_tx_result_interval() {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ const check_start_time = Date.now();
93
+ const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
94
+ const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '10000');
95
+ (0, dist_1.log_info)(`check transaction start: check_interval=${check_interval}, check_timeout=${check_timeout}`);
96
+ if (check_interval >= check_timeout) {
97
+ (0, dist_1.log_warn)(`check_interval=${check_interval} >= check_timeout=${check_timeout}, check_tx_result_interval failed!`);
98
+ return;
99
+ }
100
+ const intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
101
+ this.check_count += 1;
102
+ (0, dist_1.log_info)(`check transaction: seq=[${this.check_count}], txhash= ${this.txid}, trace_id=${this.trace_id}`);
103
+ try {
104
+ if (Date.now() - check_start_time < check_timeout) {
105
+ let tx_result = yield this.connection.getParsedTransaction(this.txid, {
106
+ commitment: common_1.COMMITMENT_LEVEL.CONFIRMED,
107
+ maxSupportedTransactionVersion: 0,
108
+ });
109
+ if ((0, dist_1.isEmpty)(tx_result)) {
110
+ return;
111
+ }
112
+ clearInterval(intervalId);
113
+ let swap_result = this.transactionParser.parse_transaction_data(tx_result, this.pool_info);
114
+ let trade_result = this.map_swap_result_to_tx_result(swap_result);
115
+ if (this.trade_result_already_processed) {
116
+ (0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by interval check!`);
117
+ }
118
+ else {
119
+ if (trade_result.success) {
120
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
121
+ }
122
+ else {
123
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
124
+ }
125
+ console.log('<====================================================');
126
+ console.dir(trade_result, { depth: 8 });
127
+ console.log('====================================================>');
128
+ }
129
+ }
130
+ else {
131
+ clearInterval(intervalId);
132
+ }
133
+ }
134
+ catch (err) {
135
+ clearInterval(intervalId);
136
+ (0, dist_1.log_error)('parse transaction error!', err);
137
+ }
138
+ }), check_interval);
139
+ this.intervalId = intervalId;
140
+ });
141
+ }
142
+ map_swap_result_to_tx_result(swap_result) {
143
+ let { success, error_code, wallet, block_number, block_time: order_block_time, txid, tx_price, tokenA, tokenB, gas_fee } = swap_result;
144
+ let { pool_name, is_reverse_token } = this.pool_info;
145
+ let { chain_id, dex_id, unique_orderbook_id, pair, price_id, time: quote_time, ask, bid } = this.price_msg;
146
+ let { group_id, unique_order_msg_id, order_send_time, order_recv_time, order_submit_time, aToB } = this.order_msg;
147
+ let order_price = aToB ? bid.price : ask.price;
148
+ let server_info = (0, dist_1.getServerInfo)();
149
+ let order_end_time = Date.now();
150
+ let start_time = order_recv_time;
151
+ let end_time = order_end_time;
152
+ let total_time = end_time - start_time;
153
+ let total_order_time = total_time;
154
+ let time = {
155
+ block_time: quote_time.block_time,
156
+ stream_time: quote_time.stream_time,
157
+ quote_start_time: quote_time.quote_start_time,
158
+ quote_end_time: quote_time.quote_end_time,
159
+ price_time: quote_time.price_time,
160
+ total_quote_time: quote_time.total_quote_time,
161
+ order_send_time,
162
+ order_recv_time,
163
+ order_submit_time,
164
+ order_block_time,
165
+ order_end_time,
166
+ total_order_time,
167
+ };
168
+ let broadcast = [];
169
+ let trade_strategy = this.context.trade_runtime.settings.strategy;
170
+ let send_type = trade_strategy.broadcast_type;
171
+ if (send_type === 'rpc') {
172
+ send_type = trade_strategy.speed === 'fast' ? 'mainnet' : 'mainnet-premium';
173
+ }
174
+ broadcast.push({
175
+ rpc: {
176
+ read: 'qn',
177
+ write: 'qn'
178
+ },
179
+ type: send_type,
180
+ fee: gas_fee
181
+ });
182
+ let trade_balance_change = {
183
+ tokenA: {
184
+ symbol: tokenA.symbol,
185
+ address: tokenA.address,
186
+ pre_bal: tokenA.pre_bal,
187
+ post_bal: tokenA.post_bal,
188
+ change: Number(tokenA.change),
189
+ decimals: tokenA.decimals,
190
+ },
191
+ tokenB: {
192
+ symbol: tokenB.symbol,
193
+ address: tokenB.address,
194
+ pre_bal: tokenB.pre_bal,
195
+ post_bal: tokenB.post_bal,
196
+ change: Number(tokenB.change),
197
+ decimals: tokenB.decimals,
198
+ },
199
+ };
200
+ if (is_reverse_token) {
201
+ let { tokenA, tokenB } = trade_balance_change;
202
+ trade_balance_change.tokenA = tokenB;
203
+ trade_balance_change.tokenB = tokenA;
204
+ }
205
+ let trade_extra_info = {
206
+ time,
207
+ broadcast,
208
+ server_info,
209
+ };
210
+ const c_id = this.order_msg.c_id || '';
211
+ const trade_result = {
212
+ success,
213
+ error_code,
214
+ start_time,
215
+ end_time,
216
+ total_time,
217
+ group_id,
218
+ wallet,
219
+ chain_id,
220
+ pair,
221
+ dex_id,
222
+ txid,
223
+ unique_orderbook_id,
224
+ unique_order_msg_id,
225
+ order_price,
226
+ aToB,
227
+ tx_price,
228
+ balance: trade_balance_change,
229
+ execution: trade_extra_info,
230
+ c_id
231
+ };
232
+ return trade_result;
233
+ }
234
+ }
235
+ exports.TransactionResultChecker = TransactionResultChecker;
@@ -0,0 +1,12 @@
1
+ import { StandardPoolInfoType, StandardSwapDetailType } from '@clonegod/ttd-core';
2
+ import { ParsedTransactionWithMeta, PublicKey, TokenBalance } from '@solana/web3.js';
3
+ import { EventEmitter } from 'events';
4
+ export declare class TransactionResultParser {
5
+ env_args: any;
6
+ event_emitter: EventEmitter;
7
+ wallet_pubkey: PublicKey;
8
+ constructor(env_args: any, wallet_pubkey: PublicKey, event_emitter: EventEmitter);
9
+ parse_transaction_data(txData: ParsedTransactionWithMeta, pool_info: StandardPoolInfoType): StandardSwapDetailType;
10
+ getTokenBalance(owner: string, mint: string, tokenBalances: TokenBalance[]): number;
11
+ }
12
+ export declare const parse_tx_failed_error_code: (dex_id_list: string[], tx_failed_error_msg: string) => string;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parse_tx_failed_error_code = exports.TransactionResultParser = void 0;
4
+ const dist_1 = require("@clonegod/ttd-core/dist");
5
+ class TransactionResultParser {
6
+ constructor(env_args, wallet_pubkey, event_emitter) {
7
+ this.env_args = env_args;
8
+ this.wallet_pubkey = wallet_pubkey;
9
+ this.event_emitter = event_emitter;
10
+ }
11
+ parse_transaction_data(txData, pool_info) {
12
+ let success = true;
13
+ let error_code = undefined;
14
+ let { blockTime, meta, transaction, slot } = txData;
15
+ if (meta.err) {
16
+ success = false;
17
+ error_code = (0, exports.parse_tx_failed_error_code)(this.env_args.dex_id_list, JSON.stringify({
18
+ txid: transaction.signatures,
19
+ err: meta.err,
20
+ logMessages: meta.logMessages
21
+ }));
22
+ }
23
+ const { pool_address, tokenA, tokenB, is_reverse_token } = pool_info;
24
+ let txid = transaction.signatures[0];
25
+ const owner = this.wallet_pubkey.toBase58();
26
+ const { preTokenBalances, postTokenBalances, preBalances, postBalances } = meta;
27
+ const tokenA_PreBalance = this.getTokenBalance(owner, tokenA.address, preTokenBalances);
28
+ const tokenA_PostBalance = this.getTokenBalance(owner, tokenA.address, postTokenBalances);
29
+ const tokenB_PreBalance = this.getTokenBalance(owner, tokenB.address, preTokenBalances);
30
+ const tokenB_PostBalance = this.getTokenBalance(owner, tokenB.address, postTokenBalances);
31
+ blockTime = blockTime * 1000 || 0;
32
+ const tokenAChange = Number((tokenA_PostBalance - tokenA_PreBalance).toFixed(tokenA.decimals));
33
+ const tokenBChange = Number((tokenB_PostBalance - tokenB_PreBalance).toFixed(tokenB.decimals));
34
+ let price = "";
35
+ if (success) {
36
+ if (is_reverse_token) {
37
+ price = Math.abs(tokenAChange / tokenBChange).toFixed(tokenA.decimals);
38
+ }
39
+ else {
40
+ price = Math.abs(tokenBChange / tokenAChange).toFixed(tokenB.decimals);
41
+ }
42
+ }
43
+ const base_fee = 5000;
44
+ let priority_fee = 0;
45
+ let total_fee = 0;
46
+ total_fee = preBalances[0] - postBalances[0];
47
+ priority_fee = Math.abs(total_fee - base_fee);
48
+ let gas_fee = {
49
+ base_fee,
50
+ priority_fee,
51
+ total_fee
52
+ };
53
+ const tradeResult = {
54
+ success,
55
+ error_code,
56
+ wallet: owner,
57
+ block_time: blockTime,
58
+ block_number: slot,
59
+ txid,
60
+ pool_address,
61
+ tokenA: Object.assign(Object.assign({}, tokenA), { pre_bal: tokenA_PreBalance, post_bal: tokenA_PostBalance, change: tokenAChange }),
62
+ tokenB: Object.assign(Object.assign({}, tokenB), { pre_bal: tokenB_PreBalance, post_bal: tokenB_PostBalance, change: tokenBChange }),
63
+ tx_price: price,
64
+ gas_fee
65
+ };
66
+ return tradeResult;
67
+ }
68
+ getTokenBalance(owner, mint, tokenBalances) {
69
+ var _a, _b;
70
+ const balance = (_b = (_a = tokenBalances === null || tokenBalances === void 0 ? void 0 : tokenBalances.find((e) => e.owner === owner && e.mint == mint)) === null || _a === void 0 ? void 0 : _a.uiTokenAmount) === null || _b === void 0 ? void 0 : _b.uiAmount;
71
+ if (typeof balance !== 'undefined' && balance !== null) {
72
+ return balance;
73
+ }
74
+ return 0;
75
+ }
76
+ }
77
+ exports.TransactionResultParser = TransactionResultParser;
78
+ const parse_tx_failed_error_code = (dex_id_list, tx_failed_error_msg) => {
79
+ (0, dist_1.log_info)(`parse_tx_failed_error_code`, {
80
+ dex_id_list,
81
+ tx_failed_error_msg
82
+ });
83
+ let err_code = "";
84
+ if (dex_id_list.includes(dist_1.DEX_ID.RAYDIUM_AMM)) {
85
+ err_code = parse_raydium_amm_failure(tx_failed_error_msg);
86
+ }
87
+ else if (dex_id_list.includes(dist_1.DEX_ID.RAYDIUM_CLMM)) {
88
+ err_code = parse_raydium_clmm_failure(tx_failed_error_msg);
89
+ }
90
+ else if (dex_id_list.includes(dist_1.DEX_ID.ORCA_CLMM)) {
91
+ err_code = parse_orca_failure(tx_failed_error_msg);
92
+ }
93
+ if (!err_code) {
94
+ if (tx_failed_error_msg.includes('exceeded CUs meter at BPF instruction')) {
95
+ err_code = dist_1.TradeErrorCodeType.ExceededCUsLimit;
96
+ }
97
+ if (tx_failed_error_msg.includes('insufficient funds')) {
98
+ return dist_1.TradeErrorCodeType.InsufficientFunds;
99
+ }
100
+ }
101
+ if (!err_code) {
102
+ err_code = dist_1.TradeErrorCodeType.UNKOWN;
103
+ }
104
+ return err_code;
105
+ };
106
+ exports.parse_tx_failed_error_code = parse_tx_failed_error_code;
107
+ function parse_raydium_amm_failure(error_msg) {
108
+ if (error_msg.includes('0x28')) {
109
+ return dist_1.TradeErrorCodeType.InsufficientFunds;
110
+ }
111
+ if (error_msg.includes('0x1e') || error_msg.includes('exceeds desired slippage limit')) {
112
+ return dist_1.TradeErrorCodeType.AmountOutBelowMinimum;
113
+ }
114
+ return null;
115
+ }
116
+ function parse_raydium_clmm_failure(error_msg) {
117
+ if (error_msg.includes('Computational budget exceeded')) {
118
+ return dist_1.TradeErrorCodeType.ExceededCUsLimit;
119
+ }
120
+ if (error_msg.includes('0x1786') || error_msg.includes('Too little output received')) {
121
+ return dist_1.TradeErrorCodeType.AmountOutBelowMinimum;
122
+ }
123
+ if (error_msg.includes('0x178c') || error_msg.includes('Invaild first tick array account')) {
124
+ return dist_1.TradeErrorCodeType.InvalidTickArraySequence;
125
+ }
126
+ return null;
127
+ }
128
+ function parse_orca_failure(error_msg) {
129
+ if (error_msg.includes('0x1]') || error_msg.includes('insufficient funds')) {
130
+ return dist_1.TradeErrorCodeType.InsufficientFunds;
131
+ }
132
+ if (error_msg.includes('0x1794') || error_msg.includes('below minimum threshold')) {
133
+ return dist_1.TradeErrorCodeType.AmountOutBelowMinimum;
134
+ }
135
+ if (error_msg.includes('0xbc4') || error_msg.includes('expected this account to be already initialized')) {
136
+ return dist_1.TradeErrorCodeType.AccountNotInitialized;
137
+ }
138
+ if (error_msg.includes('0x1787') || error_msg.includes('Invalid tick array sequence')) {
139
+ return dist_1.TradeErrorCodeType.InvalidTickArraySequence;
140
+ }
141
+ return null;
142
+ }
@@ -12,7 +12,7 @@ export interface SolanaAccountUpdateEvent {
12
12
  }
13
13
  export interface SolanaBlockMetaUpdateEvent {
14
14
  slot: number;
15
- blockhash: string;
15
+ blockHash: string;
16
16
  blockTime: number;
17
17
  blockHeight: number;
18
18
  parentSlot: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sol-common",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Solana 常量定义
3
+ * 这些常量后续会移到 ttd-sol-common 包中
4
+ */
5
+
6
+ /**
7
+ * Solana 连接确认级别
8
+ */
9
+ export enum COMMITMENT_LEVEL {
10
+ PROCESSED = "processed",
11
+ CONFIRMED = "confirmed",
12
+ _FINALIZED = "finalized"
13
+ }
14
+
@@ -0,0 +1,2 @@
1
+ export * from './constants'
2
+
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
- export * from './quote'
2
1
  export * from './types'
2
+ export * from './common'
3
+ export * from './quote'
4
+ export * from './trade'
3
5
 
@@ -0,0 +1,2 @@
1
+ export * from './tx_result_check'
2
+ export * from './tx_result_parse'
@@ -0,0 +1,305 @@
1
+ import { OrderMessageType, PriceMessageType, StandardPoolInfoType, StandardSwapDetailType, TradeBroadcastType, TradeResultBalanceChangeType, TradeResultType, TradeTimeFlowType } from "@clonegod/ttd-core"
2
+ import { AbstractTransactionResultCheck, getServerInfo, isEmpty, LOCAL_EVENT_NAME, LOG, log_debug, log_error, log_info, log_warn, TradeContext, TRANSACTION_STATE_FAILED, TRANSACTION_STATE_PROCESSING, TRANSACTION_STATE_SUCCESS } from "@clonegod/ttd-core/dist"
3
+ import { Connection, ParsedTransactionWithMeta, PublicKey } from "@solana/web3.js"
4
+ import { EventEmitter } from 'events'
5
+ import { TransactionResultParser } from "./tx_result_parse"
6
+ import { COMMITMENT_LEVEL } from "../common"
7
+
8
+
9
+
10
+ /**
11
+ * 获取Transaction的交易结果
12
+ */
13
+ export class TransactionResultChecker extends AbstractTransactionResultCheck {
14
+ env_args: any
15
+ event_emitter: EventEmitter
16
+ connection: Connection
17
+ wallet: string
18
+
19
+ context: TradeContext
20
+ group_id: string
21
+ txid: string
22
+
23
+ trace_id: string
24
+ price_msg: PriceMessageType
25
+ order_msg: OrderMessageType
26
+ pool_info: StandardPoolInfoType
27
+
28
+ intervalId: any
29
+ check_count: number
30
+ transactionParser: TransactionResultParser
31
+
32
+ trade_result_already_processed: boolean
33
+
34
+ constructor(env_args: any, event_emitter: EventEmitter, context: TradeContext, txid: string) {
35
+ super(env_args, event_emitter)
36
+ this.env_args = env_args
37
+ this.event_emitter = event_emitter
38
+
39
+ this.context = context
40
+ this.group_id = context.group_id
41
+ this.connection = new Connection(env_args.rpc_endpoint, {
42
+ commitment: COMMITMENT_LEVEL.CONFIRMED,
43
+ })
44
+ this.wallet = context.trade_runtime.wallet.public_key
45
+
46
+ this.txid = txid
47
+ this.price_msg = context.price_msg
48
+ this.order_msg = context.order_msg
49
+ this.pool_info = context.pool_info
50
+ this.trace_id = context.order_msg.order_trace_id
51
+
52
+ this.check_count = 0
53
+ this.transactionParser = new TransactionResultParser(this.env_args, new PublicKey(this.wallet), this.event_emitter)
54
+ this.trade_result_already_processed = false
55
+ }
56
+
57
+ /**
58
+ * set trade initial status: PROCESSING
59
+ */
60
+ init_tx_status() {
61
+ let empty_trade_result = JSON.parse(JSON.stringify({
62
+ group_id: this.context.group_id,
63
+ txid: this.txid
64
+ }))
65
+ this.event_emitter.emit(TRANSACTION_STATE_PROCESSING, empty_trade_result)
66
+ return this
67
+ }
68
+
69
+ async check() {
70
+ this.check_tx_result_interval()
71
+ this.on_subscibe_transaction()
72
+ }
73
+
74
+ /**
75
+ * 从第三方服务(helius geyser ws)订阅Transaction结果
76
+ */
77
+ on_subscibe_transaction() {
78
+ this.event_emitter.once(LOCAL_EVENT_NAME.EVENT_WALLET_TRANSACTION + '#' + this.txid, (messageStr) => {
79
+ try {
80
+ this.trade_result_already_processed = true
81
+ log_debug('on events: EVENT_WALLET_TRANSACTION', messageStr)
82
+
83
+ let messageObj = messageStr
84
+ if(typeof messageStr === 'string') {
85
+ messageObj = JSON.parse(messageStr)
86
+ }
87
+
88
+ const result = messageObj.params.result
89
+ const _txid = result.signature // Extract the signature
90
+ const slot = result.slot
91
+ log_info('check txid:', { _txid, txid: this.txid, slot})
92
+
93
+ // set txid for parse
94
+ result.transaction.transaction.signatures = [_txid]
95
+
96
+ let tx_result:ParsedTransactionWithMeta = {
97
+ slot,
98
+ transaction: result.transaction.transaction,
99
+ meta: result.transaction.meta,
100
+ blockTime: Date.now() / 1000,
101
+ version: result.transaction.version
102
+ }
103
+
104
+ let swap_result = this.transactionParser.parse_transaction_data(tx_result, this.pool_info)
105
+
106
+ let trade_result = this.map_swap_result_to_tx_result(swap_result)
107
+ if (trade_result.success) {
108
+ this.event_emitter.emit(TRANSACTION_STATE_SUCCESS, trade_result)
109
+ } else {
110
+ this.event_emitter.emit(TRANSACTION_STATE_FAILED, trade_result)
111
+ }
112
+
113
+ console.log('<----------------------------------------------------')
114
+ console.dir(trade_result, { depth: 8 })
115
+ console.log('---------------------------------------------------->')
116
+ } catch (err) {
117
+ log_error('parse geyser stream message error!', err)
118
+ return
119
+ }
120
+ })
121
+ }
122
+
123
+
124
+ /**
125
+ * 主动确认交易结果
126
+ */
127
+ async check_tx_result_interval() {
128
+ const check_start_time = Date.now()
129
+ const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000')
130
+ const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '10000')
131
+
132
+ log_info(`check transaction start: check_interval=${check_interval}, check_timeout=${check_timeout}`)
133
+
134
+ if(check_interval >= check_timeout) {
135
+ log_warn(`check_interval=${check_interval} >= check_timeout=${check_timeout}, check_tx_result_interval failed!`)
136
+ return
137
+ }
138
+
139
+ const intervalId = setInterval(async () => {
140
+ this.check_count += 1
141
+ log_info(`check transaction: seq=[${this.check_count}], txhash= ${this.txid}, trace_id=${this.trace_id}`)
142
+ try {
143
+ if (Date.now() - check_start_time < check_timeout) {
144
+ let tx_result:ParsedTransactionWithMeta = await this.connection.getParsedTransaction(this.txid, {
145
+ commitment: COMMITMENT_LEVEL.CONFIRMED,
146
+ maxSupportedTransactionVersion: 0,
147
+ })
148
+ if (isEmpty(tx_result)) {
149
+ return
150
+ }
151
+ // 查询到结果
152
+ clearInterval(intervalId)
153
+
154
+ let swap_result = this.transactionParser.parse_transaction_data(tx_result, this.pool_info)
155
+ let trade_result = this.map_swap_result_to_tx_result(swap_result)
156
+
157
+ if(this.trade_result_already_processed) {
158
+ log_warn(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by interval check!`)
159
+ } else {
160
+ if (trade_result.success) {
161
+ this.event_emitter.emit(TRANSACTION_STATE_SUCCESS, trade_result)
162
+ } else {
163
+ this.event_emitter.emit(TRANSACTION_STATE_FAILED, trade_result)
164
+ }
165
+
166
+ console.log('<====================================================')
167
+ console.dir(trade_result, { depth: 8 })
168
+ console.log('====================================================>')
169
+ }
170
+ } else {
171
+ // 超时未查询到结果
172
+ clearInterval(intervalId)
173
+ }
174
+ } catch (err) {
175
+ // 解析结果异常
176
+ clearInterval(intervalId)
177
+ log_error('parse transaction error!', err)
178
+ }
179
+ }, check_interval)
180
+
181
+ this.intervalId = intervalId
182
+ }
183
+
184
+
185
+ map_swap_result_to_tx_result(swap_result: StandardSwapDetailType): TradeResultType {
186
+ let { success, error_code, wallet, block_number, block_time: order_block_time, txid, tx_price, tokenA, tokenB, gas_fee } = swap_result
187
+ let { pool_name, is_reverse_token } = this.pool_info
188
+
189
+ let { chain_id, dex_id, unique_orderbook_id, pair, price_id, time: quote_time, ask, bid } = this.price_msg
190
+ let { group_id, unique_order_msg_id, order_send_time, order_recv_time, order_submit_time, aToB } = this.order_msg
191
+
192
+ let order_price = aToB ? bid.price : ask.price
193
+
194
+ let server_info = getServerInfo()
195
+
196
+ let order_end_time = Date.now()
197
+
198
+ let start_time = order_recv_time
199
+ let end_time = order_end_time
200
+ let total_time = end_time - start_time
201
+
202
+ // block_time 只精确到秒,估算耗时+1000ms
203
+ // let total_order_time = (order_block_time + 1000) - order_recv_time
204
+ let total_order_time = total_time
205
+
206
+ let time: TradeTimeFlowType = {
207
+ // Quote Consumed Time
208
+ block_time: quote_time.block_time, // 触发本次quote询价的交易,链上的出块时间
209
+ stream_time: quote_time.stream_time, // 监听到链上发生新交易的时间(stream/webhook)
210
+ quote_start_time: quote_time.quote_start_time, // 询价开始时间
211
+ quote_end_time: quote_time.quote_end_time, // 询价结束时间
212
+ price_time: quote_time.price_time, // 生成价格的时间
213
+ total_quote_time: quote_time.total_quote_time, // 询价耗时:price_time - stream_time
214
+
215
+ // Trade Consumed Time
216
+ order_send_time, // cex 发送下单消息的时间
217
+ order_recv_time, // dex 接收到下单消息的时间
218
+ order_submit_time, // dex 提交交易时间
219
+ order_block_time, // 交易所属block的出块时间
220
+ order_end_time, // dex 解析得到交易结果的时间
221
+ total_order_time, // 交易总耗时: order_end_time - order_recv_time
222
+ }
223
+
224
+ let broadcast: TradeBroadcastType[] = []
225
+ let trade_strategy = this.context.trade_runtime.settings.strategy
226
+ let send_type = trade_strategy.broadcast_type as string
227
+ if (send_type === 'rpc') {
228
+ send_type = trade_strategy.speed === 'fast' ? 'mainnet' : 'mainnet-premium'
229
+ }
230
+ broadcast.push(
231
+ {
232
+ rpc: {
233
+ read: 'qn',
234
+ write: 'qn'
235
+ }, // quicknode | trongrid ...
236
+ type: send_type, // 主网,私单,自建节点
237
+ fee: gas_fee // gas
238
+ }
239
+ )
240
+
241
+ let trade_balance_change: TradeResultBalanceChangeType = {
242
+ tokenA: {
243
+ symbol: tokenA.symbol,
244
+ address: tokenA.address,
245
+ pre_bal: tokenA.pre_bal,
246
+ post_bal: tokenA.post_bal,
247
+ change: Number(tokenA.change),
248
+ decimals: tokenA.decimals,
249
+ },
250
+ tokenB: {
251
+ symbol: tokenB.symbol,
252
+ address: tokenB.address,
253
+ pre_bal: tokenB.pre_bal,
254
+ post_bal: tokenB.post_bal,
255
+ change: Number(tokenB.change),
256
+ decimals: tokenB.decimals,
257
+ },
258
+ }
259
+
260
+ if (is_reverse_token) {
261
+ let { tokenA, tokenB } = trade_balance_change
262
+ trade_balance_change.tokenA = tokenB
263
+ trade_balance_change.tokenB = tokenA
264
+ }
265
+
266
+ let trade_extra_info = {
267
+ time,
268
+ broadcast,
269
+ server_info,
270
+ }
271
+
272
+ // 返回c_id给cex
273
+ const c_id = this.order_msg.c_id || ''
274
+
275
+ const trade_result: TradeResultType = {
276
+ success,
277
+ error_code,
278
+
279
+ start_time,
280
+ end_time,
281
+ total_time,
282
+
283
+ group_id,
284
+ wallet,
285
+
286
+ chain_id,
287
+ pair,
288
+ dex_id,
289
+ txid,
290
+
291
+ unique_orderbook_id,
292
+ unique_order_msg_id,
293
+ order_price,
294
+ aToB,
295
+ tx_price,
296
+
297
+ balance: trade_balance_change,
298
+ execution: trade_extra_info,
299
+ c_id
300
+ }
301
+
302
+ return trade_result
303
+ }
304
+
305
+ }
@@ -0,0 +1,212 @@
1
+
2
+ import { StandardPoolInfoType, StandardSwapDetailType, TradeGasFeeType } from '@clonegod/ttd-core';
3
+ import { DEX_ID, log_info, TradeErrorCodeType } from '@clonegod/ttd-core/dist';
4
+ import { ParsedTransactionWithMeta, PublicKey, TokenBalance } from '@solana/web3.js';
5
+ import { EventEmitter } from 'events';
6
+
7
+ export class TransactionResultParser {
8
+
9
+ env_args: any
10
+ event_emitter: EventEmitter
11
+ wallet_pubkey: PublicKey
12
+
13
+ constructor(env_args: any, wallet_pubkey: PublicKey, event_emitter: EventEmitter) {
14
+ this.env_args = env_args
15
+ this.wallet_pubkey = wallet_pubkey
16
+ this.event_emitter = event_emitter
17
+ }
18
+
19
+ /**
20
+ * 解析 Transaction Data
21
+ */
22
+ parse_transaction_data(txData: ParsedTransactionWithMeta, pool_info: StandardPoolInfoType): StandardSwapDetailType {
23
+ let success: boolean = true
24
+ let error_code: string = undefined
25
+
26
+ let { blockTime, meta, transaction, slot } = txData
27
+
28
+ if (meta.err) {
29
+ // 交易失败,解析错误码!
30
+ success = false
31
+ error_code = parse_tx_failed_error_code(
32
+ this.env_args.dex_id_list,
33
+ JSON.stringify({
34
+ txid: transaction.signatures,
35
+ err: meta.err,
36
+ logMessages: meta.logMessages
37
+ }))
38
+ }
39
+
40
+ const { pool_address, tokenA, tokenB, is_reverse_token } = pool_info
41
+
42
+ let txid = transaction.signatures[0]
43
+ const owner = this.wallet_pubkey.toBase58()
44
+ const { preTokenBalances, postTokenBalances, preBalances, postBalances } = meta
45
+ const tokenA_PreBalance = this.getTokenBalance(owner, tokenA.address, preTokenBalances)
46
+ const tokenA_PostBalance = this.getTokenBalance(owner, tokenA.address, postTokenBalances)
47
+ const tokenB_PreBalance = this.getTokenBalance(owner, tokenB.address, preTokenBalances)
48
+ const tokenB_PostBalance = this.getTokenBalance(owner, tokenB.address, postTokenBalances)
49
+
50
+
51
+ blockTime = blockTime * 1000 || 0 // blockTime - Unix timestamp
52
+
53
+ const tokenAChange = Number((tokenA_PostBalance - tokenA_PreBalance).toFixed(tokenA.decimals))
54
+ const tokenBChange = Number((tokenB_PostBalance - tokenB_PreBalance).toFixed(tokenB.decimals))
55
+
56
+ let price = ""
57
+ if (success) {
58
+ if (is_reverse_token) {
59
+ // SOL/POPCAT
60
+ price = Math.abs(tokenAChange / tokenBChange).toFixed(tokenA.decimals)
61
+ } else {
62
+ // SOL/USDT
63
+ price = Math.abs(tokenBChange / tokenAChange).toFixed(tokenB.decimals)
64
+ }
65
+ }
66
+
67
+ const base_fee = 5000
68
+ let priority_fee = 0
69
+ let total_fee = 0
70
+
71
+ total_fee = preBalances[0] - postBalances[0]
72
+ priority_fee = Math.abs(total_fee - base_fee)
73
+
74
+
75
+ let gas_fee: TradeGasFeeType = {
76
+ base_fee,
77
+ priority_fee,
78
+ total_fee
79
+ }
80
+
81
+ const tradeResult: StandardSwapDetailType = {
82
+ success,
83
+ error_code,
84
+ wallet: owner,
85
+ block_time: blockTime,
86
+ block_number: slot,
87
+ txid,
88
+ pool_address,
89
+ tokenA: {
90
+ ...tokenA,
91
+ pre_bal: tokenA_PreBalance,
92
+ post_bal: tokenA_PostBalance,
93
+ change: tokenAChange
94
+ },
95
+ tokenB: {
96
+ ...tokenB,
97
+ pre_bal: tokenB_PreBalance,
98
+ post_bal: tokenB_PostBalance,
99
+ change: tokenBChange
100
+ },
101
+ tx_price: price,
102
+ gas_fee
103
+ }
104
+ return tradeResult
105
+ }
106
+
107
+ getTokenBalance(owner: string, mint: string, tokenBalances: TokenBalance[]) {
108
+ const balance = tokenBalances?.find((e) => e.owner === owner && e.mint == mint)?.uiTokenAmount?.uiAmount
109
+ if (typeof balance !== 'undefined' && balance !== null) {
110
+ return balance
111
+ }
112
+ return 0
113
+ }
114
+ }
115
+
116
+
117
+ /**
118
+ * 解析交易失败的原因
119
+ *
120
+ * @param failed_msg 交易失败的相关信息
121
+ * @returns
122
+ */
123
+ export const parse_tx_failed_error_code = (dex_id_list: string[], tx_failed_error_msg: string): string => {
124
+ log_info(`parse_tx_failed_error_code`, {
125
+ dex_id_list,
126
+ tx_failed_error_msg
127
+ })
128
+
129
+ let err_code = ""
130
+
131
+ if (dex_id_list.includes(DEX_ID.RAYDIUM_AMM)) {
132
+ err_code = parse_raydium_amm_failure(tx_failed_error_msg)
133
+ }
134
+ else if (dex_id_list.includes(DEX_ID.RAYDIUM_CLMM)) {
135
+ err_code = parse_raydium_clmm_failure(tx_failed_error_msg)
136
+ }
137
+ else if (dex_id_list.includes(DEX_ID.ORCA_CLMM)) {
138
+ err_code = parse_orca_failure(tx_failed_error_msg)
139
+ }
140
+
141
+ // common errors
142
+ if (!err_code) {
143
+ if (tx_failed_error_msg.includes('exceeded CUs meter at BPF instruction')) {
144
+ err_code = TradeErrorCodeType.ExceededCUsLimit
145
+ }
146
+
147
+ if (tx_failed_error_msg.includes('insufficient funds')) {
148
+ return TradeErrorCodeType.InsufficientFunds
149
+ }
150
+ }
151
+
152
+ if (!err_code) {
153
+ err_code = TradeErrorCodeType.UNKOWN
154
+ }
155
+
156
+ return err_code
157
+
158
+ }
159
+
160
+
161
+ /**
162
+ * Raydium AMM 错误码
163
+ * @param error_msg
164
+ */
165
+ function parse_raydium_amm_failure(error_msg: string) {
166
+ if (error_msg.includes('0x28')) {
167
+ return TradeErrorCodeType.InsufficientFunds
168
+ }
169
+ if (error_msg.includes('0x1e') || error_msg.includes('exceeds desired slippage limit')) {
170
+ return TradeErrorCodeType.AmountOutBelowMinimum
171
+ }
172
+ return null
173
+ }
174
+
175
+ /**
176
+ * Raydium CLMM 错误码
177
+ * @param error_msg
178
+ */
179
+ function parse_raydium_clmm_failure(error_msg: string) {
180
+ if (error_msg.includes('Computational budget exceeded')) {
181
+ return TradeErrorCodeType.ExceededCUsLimit
182
+ }
183
+ if (error_msg.includes('0x1786') || error_msg.includes('Too little output received')) {
184
+ return TradeErrorCodeType.AmountOutBelowMinimum
185
+ }
186
+ if (error_msg.includes('0x178c') || error_msg.includes('Invaild first tick array account')) {
187
+ return TradeErrorCodeType.InvalidTickArraySequence
188
+ }
189
+ return null
190
+ }
191
+
192
+ /**
193
+ * ORCA 错误码
194
+ *
195
+ * https://github.com/coral-xyz/anchor/blob/master/lang/src/error.rs#L185
196
+ */
197
+ function parse_orca_failure(error_msg: string) {
198
+ if (error_msg.includes('0x1]') || error_msg.includes('insufficient funds')) {
199
+ return TradeErrorCodeType.InsufficientFunds
200
+ }
201
+ if (error_msg.includes('0x1794') || error_msg.includes('below minimum threshold')) {
202
+ return TradeErrorCodeType.AmountOutBelowMinimum
203
+ }
204
+ if (error_msg.includes('0xbc4') || error_msg.includes('expected this account to be already initialized')) {
205
+ return TradeErrorCodeType.AccountNotInitialized
206
+ }
207
+ if (error_msg.includes('0x1787') || error_msg.includes('Invalid tick array sequence')) {
208
+ return TradeErrorCodeType.InvalidTickArraySequence
209
+ }
210
+ return null
211
+ }
212
+
@@ -27,7 +27,7 @@ export interface SolanaAccountUpdateEvent {
27
27
  */
28
28
  export interface SolanaBlockMetaUpdateEvent {
29
29
  slot: number;
30
- blockhash: string;
30
+ blockHash: string;
31
31
  blockTime: number;
32
32
  blockHeight: number;
33
33
  parentSlot: number;