@clonegod/ttd-sol-common 2.0.67 → 2.0.69

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.
Files changed (94) hide show
  1. package/dist/appconfig/SolanaQuoteAppConfig.d.ts +9 -0
  2. package/dist/appconfig/SolanaQuoteAppConfig.js +29 -0
  3. package/dist/appconfig/SolanaTradeAppConfig.d.ts +13 -0
  4. package/dist/{config → appconfig}/SolanaTradeAppConfig.js +25 -0
  5. package/dist/appconfig/ensure_core_env.d.ts +1 -0
  6. package/dist/appconfig/ensure_core_env.js +18 -0
  7. package/dist/appconfig/index.d.ts +5 -0
  8. package/dist/appconfig/index.js +21 -0
  9. package/dist/appconfig/sol_dex_env_args.d.ts +5 -0
  10. package/dist/appconfig/sol_dex_env_args.js +29 -0
  11. package/dist/appconfig/sol_env_args.d.ts +17 -0
  12. package/dist/appconfig/sol_env_args.js +37 -0
  13. package/dist/common/get_wallet_token_account.js +13 -16
  14. package/dist/grpc/grpc_provider_registry.d.ts +14 -0
  15. package/dist/grpc/grpc_provider_registry.js +70 -0
  16. package/dist/grpc/index.d.ts +1 -0
  17. package/dist/grpc/index.js +17 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +3 -0
  20. package/dist/quote/abstract_dex_quote.d.ts +68 -0
  21. package/dist/quote/abstract_dex_quote.js +208 -0
  22. package/dist/quote/chain_ops.d.ts +18 -0
  23. package/dist/quote/chain_ops.js +66 -0
  24. package/dist/quote/depth/amm_depth_calculator.d.ts +19 -0
  25. package/dist/quote/depth/amm_depth_calculator.js +55 -0
  26. package/dist/quote/depth/clmm_depth_calculator.d.ts +42 -0
  27. package/dist/quote/depth/clmm_depth_calculator.js +173 -0
  28. package/dist/quote/depth/index.d.ts +37 -0
  29. package/dist/quote/depth/index.js +164 -0
  30. package/dist/quote/index.d.ts +9 -0
  31. package/dist/quote/index.js +9 -0
  32. package/dist/quote/pool_event.d.ts +20 -0
  33. package/dist/quote/pool_event.js +22 -0
  34. package/dist/quote/pool_subscription_registry.d.ts +4 -0
  35. package/dist/quote/pool_subscription_registry.js +62 -0
  36. package/dist/quote/quote_amount.d.ts +4 -0
  37. package/dist/quote/quote_amount.js +24 -0
  38. package/dist/quote/quote_trace.d.ts +16 -0
  39. package/dist/quote/quote_trace.js +40 -0
  40. package/dist/quote/tick/clmm_tick_math.d.ts +5 -0
  41. package/dist/quote/tick/clmm_tick_math.js +61 -0
  42. package/dist/quote/tick/index.d.ts +1 -0
  43. package/dist/{config → quote/tick}/index.js +1 -1
  44. package/dist/quote/verify/index.d.ts +1 -0
  45. package/dist/quote/verify/index.js +17 -0
  46. package/dist/quote/verify/quote_price_verify.d.ts +30 -0
  47. package/dist/quote/verify/quote_price_verify.js +247 -0
  48. package/dist/trade/index.d.ts +0 -1
  49. package/dist/trade/index.js +0 -1
  50. package/dist/trade/tx_builder.d.ts +1 -1
  51. package/dist/trade/tx_result_parse.js +1 -0
  52. package/dist/types/index.d.ts +9 -1
  53. package/dist/types/index.js +2 -0
  54. package/dist/utils/index.d.ts +1 -0
  55. package/dist/utils/index.js +17 -0
  56. package/dist/utils/trade_direction.d.ts +14 -0
  57. package/dist/utils/trade_direction.js +23 -0
  58. package/package.json +4 -4
  59. package/src/appconfig/SolanaQuoteAppConfig.ts +55 -0
  60. package/src/appconfig/SolanaTradeAppConfig.ts +117 -0
  61. package/src/appconfig/ensure_core_env.ts +28 -0
  62. package/src/appconfig/index.ts +5 -0
  63. package/src/appconfig/sol_dex_env_args.ts +52 -0
  64. package/src/appconfig/sol_env_args.ts +79 -0
  65. package/src/common/get_wallet_token_account.ts +27 -33
  66. package/src/grpc/grpc_provider_registry.ts +103 -0
  67. package/src/grpc/index.ts +1 -0
  68. package/src/index.ts +3 -0
  69. package/src/quote/abstract_dex_quote.ts +337 -0
  70. package/src/quote/chain_ops.ts +91 -0
  71. package/src/quote/depth/amm_depth_calculator.ts +143 -0
  72. package/src/quote/depth/clmm_depth_calculator.ts +321 -0
  73. package/src/quote/depth/index.ts +272 -0
  74. package/src/quote/index.ts +9 -0
  75. package/src/quote/pool_event.ts +82 -0
  76. package/src/quote/pool_subscription_registry.ts +81 -0
  77. package/src/quote/quote_amount.ts +37 -0
  78. package/src/quote/quote_trace.ts +56 -0
  79. package/src/quote/tick/clmm_tick_math.ts +77 -0
  80. package/src/quote/tick/index.ts +1 -0
  81. package/src/quote/verify/index.ts +1 -0
  82. package/src/quote/verify/quote_price_verify.ts +508 -0
  83. package/src/trade/index.ts +0 -1
  84. package/src/trade/tx_builder.ts +1 -1
  85. package/src/trade/tx_result_parse.ts +1 -0
  86. package/src/types/index.ts +20 -2
  87. package/src/utils/index.ts +1 -0
  88. package/src/utils/trade_direction.ts +68 -0
  89. package/dist/config/SolanaTradeAppConfig.d.ts +0 -10
  90. package/dist/config/index.d.ts +0 -1
  91. package/dist/trade/SolanaTradeAppConfig.d.ts +0 -8
  92. package/dist/trade/SolanaTradeAppConfig.js +0 -26
  93. package/src/config/SolanaTradeAppConfig.ts +0 -70
  94. package/src/config/index.ts +0 -2
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QuotePriceVerify = void 0;
4
+ const trade_direction_1 = require("../../utils/trade_direction");
5
+ const ttd_core_1 = require("@clonegod/ttd-core");
6
+ function pickMatchingTier(tiers, actualAmountIn) {
7
+ if (!tiers || tiers.length === 0)
8
+ return null;
9
+ if (tiers.length === 1)
10
+ return { tier: tiers[0], mode: 'single_tier', lo_pct: tiers[0].pct, hi_pct: tiers[0].pct };
11
+ const sorted = [...tiers].sort((a, b) => a.amount_in - b.amount_in);
12
+ const top = sorted[sorted.length - 1];
13
+ if (actualAmountIn < sorted[0].amount_in) {
14
+ return { tier: sorted[0], mode: 'extrapolated_low', lo_pct: null, hi_pct: sorted[0].pct };
15
+ }
16
+ if (actualAmountIn > top.amount_in) {
17
+ return { tier: top, mode: 'extrapolated_high', lo_pct: top.pct, hi_pct: null };
18
+ }
19
+ for (let i = 0; i < sorted.length - 1; i++) {
20
+ const lower = sorted[i];
21
+ const upper = sorted[i + 1];
22
+ if (actualAmountIn >= lower.amount_in && actualAmountIn <= upper.amount_in) {
23
+ if (actualAmountIn === lower.amount_in)
24
+ return { tier: lower, mode: 'exact', lo_pct: lower.pct, hi_pct: lower.pct };
25
+ if (actualAmountIn === upper.amount_in)
26
+ return { tier: upper, mode: 'exact', lo_pct: upper.pct, hi_pct: upper.pct };
27
+ const span = upper.amount_in - lower.amount_in;
28
+ if (span <= 0)
29
+ return { tier: lower, mode: 'exact', lo_pct: lower.pct, hi_pct: lower.pct };
30
+ const ratio = (actualAmountIn - lower.amount_in) / span;
31
+ const lerp = (a, b) => a + ratio * (b - a);
32
+ return {
33
+ tier: {
34
+ pct: lerp(lower.pct, upper.pct),
35
+ price: lerp(lower.price, upper.price),
36
+ amount: lerp(lower.amount, upper.amount),
37
+ amount_in: actualAmountIn,
38
+ amount_in_usd: lerp(lower.amount_in_usd, upper.amount_in_usd),
39
+ fee: lerp(lower.fee, upper.fee),
40
+ fee_usd: lerp(lower.fee_usd, upper.fee_usd),
41
+ },
42
+ mode: 'interpolated',
43
+ lo_pct: lower.pct,
44
+ hi_pct: upper.pct,
45
+ };
46
+ }
47
+ }
48
+ return { tier: top, mode: 'extrapolated_high', lo_pct: top.pct, hi_pct: null };
49
+ }
50
+ class QuotePriceVerify {
51
+ constructor() {
52
+ this.quoteCache = new Map();
53
+ }
54
+ get enabled() {
55
+ return process.env.VERIFY_QUOTE_PRICE === 'true';
56
+ }
57
+ cacheQuote(poolAddress, priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd, token0PriceUsd = 0, token1PriceUsd = 0, tiers) {
58
+ if (!this.enabled)
59
+ return;
60
+ if (!source)
61
+ return;
62
+ let sourceMap = this.quoteCache.get(poolAddress);
63
+ if (!sourceMap) {
64
+ sourceMap = new Map();
65
+ this.quoteCache.set(poolAddress, sourceMap);
66
+ }
67
+ const existing = sourceMap.get(source);
68
+ const verifiedReferenceBlock = (existing && existing.referenceBlock === blockNumber)
69
+ ? existing.verifiedReferenceBlock
70
+ : 0;
71
+ sourceMap.set(source, {
72
+ priceId, source, askPrice, bidPrice,
73
+ askTiers: tiers?.askTiers,
74
+ bidTiers: tiers?.bidTiers,
75
+ referenceBlock: blockNumber,
76
+ verifiedReferenceBlock,
77
+ quoteAmountUsd,
78
+ token0PriceUsd, token1PriceUsd,
79
+ });
80
+ }
81
+ checkSwap(params) {
82
+ if (!this.enabled)
83
+ return;
84
+ const { poolAddress, blockNumber, txHash, poolInfo, token0Address } = params;
85
+ const sourceMap = this.quoteCache.get(poolAddress);
86
+ if (!sourceMap || sourceMap.size === 0)
87
+ return;
88
+ const swapData = this.deriveSwapDirection(params);
89
+ if (!swapData)
90
+ return;
91
+ const direction = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
92
+ const inputIsQuoteToken = swapData.inputTokenAddress.toLowerCase() === direction.quoteToken.address.toLowerCase();
93
+ const isBuy = inputIsQuoteToken;
94
+ const execPriceStr = (0, trade_direction_1.calculateStandardPrice)(swapData.inputAmountUi, swapData.outputAmountUi, isBuy);
95
+ const execPrice = Number(execPriceStr);
96
+ if (execPrice <= 0)
97
+ return;
98
+ const verifiable = [];
99
+ for (const [source, cached] of sourceMap) {
100
+ if (blockNumber <= cached.referenceBlock)
101
+ continue;
102
+ if (cached.verifiedReferenceBlock === cached.referenceBlock)
103
+ continue;
104
+ const tiers = isBuy ? cached.askTiers : cached.bidTiers;
105
+ let refPrice;
106
+ let tierInfo;
107
+ if (tiers && tiers.length > 0) {
108
+ const match = pickMatchingTier(tiers, swapData.inputAmountUi);
109
+ if (!match)
110
+ continue;
111
+ refPrice = match.tier.price;
112
+ tierInfo = {
113
+ matched_pct: parseFloat(match.tier.pct.toFixed(4)),
114
+ mode: match.mode,
115
+ tier_amount_in: match.tier.amount_in,
116
+ lo_pct: match.lo_pct,
117
+ hi_pct: match.hi_pct,
118
+ };
119
+ }
120
+ else {
121
+ refPrice = isBuy ? cached.askPrice : cached.bidPrice;
122
+ }
123
+ if (refPrice <= 0)
124
+ continue;
125
+ cached.verifiedReferenceBlock = cached.referenceBlock;
126
+ const diff_bps = (refPrice - execPrice) / refPrice * 10000;
127
+ verifiable.push({ source, cached, refPrice, diff_bps, tierInfo });
128
+ }
129
+ if (verifiable.length === 0)
130
+ return;
131
+ verifiable.sort((a, b) => b.cached.referenceBlock - a.cached.referenceBlock);
132
+ const primary = verifiable[0];
133
+ const sources = {};
134
+ for (const r of verifiable) {
135
+ sources[r.source] = {
136
+ ask: r.cached.askPrice,
137
+ bid: r.cached.bidPrice,
138
+ matched_ref_price: r.refPrice,
139
+ diff_bps: parseFloat(r.diff_bps.toFixed(1)),
140
+ quote_block: r.cached.referenceBlock,
141
+ ...(r.tierInfo && {
142
+ matched_pct: r.tierInfo.matched_pct,
143
+ tier_mode: r.tierInfo.mode,
144
+ tier_amount_in: r.tierInfo.tier_amount_in,
145
+ }),
146
+ };
147
+ }
148
+ return this.compareAndLog(poolAddress, params.poolName, poolInfo, primary.cached, swapData.inputTokenAddress, swapData.outputTokenAddress, swapData.inputAmountUi, swapData.outputAmountUi, primary.refPrice, execPrice, primary.cached.token0PriceUsd, primary.cached.token1PriceUsd, token0Address, blockNumber, txHash, sources, primary.tierInfo);
149
+ }
150
+ deriveSwapDirection(params) {
151
+ const { amount0, amount1, token0Address, token1Address, token0Decimals, token1Decimals } = params;
152
+ const sign = params.swapperDeltaConvention ? -1n : 1n;
153
+ const amt0 = BigInt(amount0) * sign;
154
+ const amt1 = BigInt(amount1) * sign;
155
+ if (amt0 === 0n && amt1 === 0n)
156
+ return null;
157
+ if (amt0 > 0n && amt1 < 0n) {
158
+ return {
159
+ inputTokenAddress: token0Address,
160
+ outputTokenAddress: token1Address,
161
+ inputAmountUi: Number(amt0) / Math.pow(10, token0Decimals),
162
+ outputAmountUi: Number(-amt1) / Math.pow(10, token1Decimals),
163
+ };
164
+ }
165
+ else if (amt0 < 0n && amt1 > 0n) {
166
+ return {
167
+ inputTokenAddress: token1Address,
168
+ outputTokenAddress: token0Address,
169
+ inputAmountUi: Number(amt1) / Math.pow(10, token1Decimals),
170
+ outputAmountUi: Number(-amt0) / Math.pow(10, token0Decimals),
171
+ };
172
+ }
173
+ return null;
174
+ }
175
+ compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, refPrice, execPriceNum, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash, sources, primaryTierInfo) {
176
+ if (inputAmountUi <= 0 || outputAmountUi <= 0)
177
+ return;
178
+ let swapUsd = 0;
179
+ if (token0PriceUsd && token1PriceUsd && token0Address) {
180
+ const inputIsToken0 = inputTokenAddress.toLowerCase() === token0Address.toLowerCase();
181
+ swapUsd = inputIsToken0
182
+ ? inputAmountUi * token0PriceUsd
183
+ : inputAmountUi * token1PriceUsd;
184
+ }
185
+ const direction = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
186
+ const baseToken = direction.baseToken;
187
+ const quoteToken = direction.quoteToken;
188
+ const inputIsQuoteToken = inputTokenAddress.toLowerCase() === quoteToken.address.toLowerCase();
189
+ const isBuy = inputIsQuoteToken;
190
+ const side = isBuy ? 'BUY' : 'SELL';
191
+ if (refPrice <= 0 || execPriceNum <= 0)
192
+ return;
193
+ const diffBps = (refPrice - execPriceNum) / refPrice * 10000;
194
+ const absDiffBps = Math.abs(diffBps);
195
+ const status = absDiffBps < 5 ? '✅' : absDiffBps < 10 ? '⚠️' : '❌';
196
+ const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(0)}` : '$?';
197
+ const maxUsd = cached.quoteAmountUsd * 2;
198
+ const inRange = swapUsd <= 0 || swapUsd <= maxUsd;
199
+ const rangeTag = inRange ? '' : ' [out]';
200
+ const tradeFlow = isBuy
201
+ ? `${inputAmountUi.toFixed(4)} ${quoteToken.symbol} -> ${outputAmountUi.toFixed(4)} ${baseToken.symbol}`
202
+ : `${inputAmountUi.toFixed(4)} ${baseToken.symbol} -> ${outputAmountUi.toFixed(4)} ${quoteToken.symbol}`;
203
+ const quoteSrc = cached.source || '?';
204
+ const quoteTag = `quote[${quoteSrc} blk:${cached.referenceBlock}]`;
205
+ const swapBlk = swapBlockNumber || '?';
206
+ const swapTx = txHash ? ` ${txHash.slice(0, 10)}` : '';
207
+ const swapTag = `swap[blk:${swapBlk}${swapTx}]`;
208
+ const priceLabel = isBuy ? 'ask' : 'bid';
209
+ const msg = ` ↳ [Verify] ${side} ${usdStr} (${tradeFlow}) ${quoteTag} ${priceLabel}=${refPrice.toFixed(12)} vs ${swapTag} exec=${execPriceNum.toFixed(12)} diff=${diffBps > 0 ? '+' : ''}${diffBps.toFixed(1)}bps ${status}${rangeTag}`;
210
+ try {
211
+ (0, ttd_core_1.report_data_to_analyze)('QuoteVerify', {
212
+ pool_address: poolAddress,
213
+ pool_name: poolName,
214
+ price_id: cached.priceId,
215
+ source: cached.source,
216
+ quote_amount_usd: cached.quoteAmountUsd,
217
+ quote_block: cached.referenceBlock,
218
+ swap_block: swapBlockNumber,
219
+ tx_hash: txHash || '',
220
+ side,
221
+ ref_price: refPrice,
222
+ exec_price: execPriceNum,
223
+ diff_bps: parseFloat(diffBps.toFixed(1)),
224
+ swap_usd: parseFloat(swapUsd.toFixed(0)),
225
+ in_range: inRange,
226
+ input_amount: inputAmountUi,
227
+ output_amount: outputAmountUi,
228
+ input_symbol: isBuy ? quoteToken.symbol : baseToken.symbol,
229
+ output_symbol: isBuy ? baseToken.symbol : quoteToken.symbol,
230
+ status,
231
+ time: Date.now(),
232
+ sources: sources || undefined,
233
+ ...(primaryTierInfo && {
234
+ matched_tier_pct: primaryTierInfo.matched_pct,
235
+ matched_tier_mode: primaryTierInfo.mode,
236
+ matched_tier_amount_in: primaryTierInfo.tier_amount_in,
237
+ matched_tier_lo_pct: primaryTierInfo.lo_pct,
238
+ matched_tier_hi_pct: primaryTierInfo.hi_pct,
239
+ }),
240
+ });
241
+ }
242
+ catch (_) {
243
+ }
244
+ return msg;
245
+ }
246
+ }
247
+ exports.QuotePriceVerify = QuotePriceVerify;
@@ -1,6 +1,5 @@
1
1
  export * from './tx_result_check';
2
2
  export * from './tx_result_parse';
3
- export * from '../config/SolanaTradeAppConfig';
4
3
  export * from './tx_builder';
5
4
  export * from './send';
6
5
  export * from './jito_tip_wallets';
@@ -16,7 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./tx_result_check"), exports);
18
18
  __exportStar(require("./tx_result_parse"), exports);
19
- __exportStar(require("../config/SolanaTradeAppConfig"), exports);
20
19
  __exportStar(require("./tx_builder"), exports);
21
20
  __exportStar(require("./send"), exports);
22
21
  __exportStar(require("./jito_tip_wallets"), exports);
@@ -1,6 +1,6 @@
1
1
  import { TradeContext } from "@clonegod/ttd-core/dist";
2
2
  import { Connection, Keypair, Transaction, TransactionInstruction } from "@solana/web3.js";
3
- import { SolanaTradeAppConfig } from "../config/SolanaTradeAppConfig";
3
+ import { SolanaTradeAppConfig } from "../appconfig/SolanaTradeAppConfig";
4
4
  import { JitoTipWalletManager } from "./jito_tip_wallets";
5
5
  export declare class SolTransactionBuilder {
6
6
  appConfig: SolanaTradeAppConfig;
@@ -64,6 +64,7 @@ class TransactionResultParser {
64
64
  wallet: owner,
65
65
  block_time: blockTime,
66
66
  block_number: slot,
67
+ tx_index: 0,
67
68
  txid,
68
69
  pool_address,
69
70
  tokenA: {
@@ -1,7 +1,9 @@
1
1
  export declare enum SolanaPoolAccountType {
2
2
  POOL = "pool",
3
3
  VAULT_A = "vaultA",
4
- VAULT_B = "vaultB"
4
+ VAULT_B = "vaultB",
5
+ TICK_ARRAY = "tickArray",
6
+ BIN_ARRAY = "binArray"
5
7
  }
6
8
  export interface SolanaAccountUpdateEvent {
7
9
  recvTimeMs: number;
@@ -35,6 +37,7 @@ export interface SolanaPoolAccountUpdateEventData {
35
37
  pool_name: string;
36
38
  data: {
37
39
  slot: number;
40
+ write_version?: number;
38
41
  tx_hash: string;
39
42
  pool_account: string;
40
43
  pool_account_data: string;
@@ -42,5 +45,10 @@ export interface SolanaPoolAccountUpdateEventData {
42
45
  vaultA_account_data?: string;
43
46
  vaultB_account?: string;
44
47
  vaultB_account_data?: string;
48
+ sub_account?: {
49
+ role: string;
50
+ account: string;
51
+ account_data: string;
52
+ };
45
53
  };
46
54
  }
@@ -6,4 +6,6 @@ var SolanaPoolAccountType;
6
6
  SolanaPoolAccountType["POOL"] = "pool";
7
7
  SolanaPoolAccountType["VAULT_A"] = "vaultA";
8
8
  SolanaPoolAccountType["VAULT_B"] = "vaultB";
9
+ SolanaPoolAccountType["TICK_ARRAY"] = "tickArray";
10
+ SolanaPoolAccountType["BIN_ARRAY"] = "binArray";
9
11
  })(SolanaPoolAccountType || (exports.SolanaPoolAccountType = SolanaPoolAccountType = {}));
@@ -0,0 +1 @@
1
+ export * from './trade_direction';
@@ -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("./trade_direction"), exports);
@@ -0,0 +1,14 @@
1
+ export interface TradeDirectionResult<T = any> {
2
+ isBuy: boolean;
3
+ inputToken: T;
4
+ outputToken: T;
5
+ baseToken: T;
6
+ quoteToken: T;
7
+ }
8
+ export declare function resolveTradeDirection<T = any>(poolInfo: {
9
+ tokenA: T;
10
+ tokenB: T;
11
+ quote_token: string;
12
+ [key: string]: any;
13
+ }, isBuy: boolean): TradeDirectionResult<T>;
14
+ export declare function calculateStandardPrice(inputAmount: number, outputAmount: number, isBuy: boolean): string;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTradeDirection = resolveTradeDirection;
4
+ exports.calculateStandardPrice = calculateStandardPrice;
5
+ function resolveTradeDirection(poolInfo, isBuy) {
6
+ const { tokenA, tokenB, quote_token } = poolInfo;
7
+ const quoteToken = [tokenA, tokenB].find(t => t.symbol === quote_token);
8
+ const baseToken = [tokenA, tokenB].find(t => t.symbol !== quote_token);
9
+ if (!quoteToken || !baseToken) {
10
+ throw new Error(`Cannot resolve tokens: tokenA=${tokenA?.symbol}, tokenB=${tokenB?.symbol}, quote_token=${quote_token}`);
11
+ }
12
+ const inputToken = isBuy ? quoteToken : baseToken;
13
+ const outputToken = isBuy ? baseToken : quoteToken;
14
+ return { isBuy, inputToken, outputToken, baseToken, quoteToken };
15
+ }
16
+ function calculateStandardPrice(inputAmount, outputAmount, isBuy) {
17
+ if (isBuy) {
18
+ return (inputAmount / outputAmount).toFixed(18);
19
+ }
20
+ else {
21
+ return (outputAmount / inputAmount).toFixed(18);
22
+ }
23
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sol-common",
3
- "version": "2.0.67",
3
+ "version": "2.0.69",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
- "types": "types/index.d.ts",
6
+ "types": "dist/index.d.ts",
7
7
  "keywords": [],
8
8
  "author": "",
9
9
  "license": "ISC",
@@ -13,13 +13,13 @@
13
13
  "push": "npm run build && npm publish"
14
14
  },
15
15
  "dependencies": {
16
- "@clonegod/ttd-core": "2.1.33",
16
+ "@clonegod/ttd-core": "3.1.84",
17
17
  "@solana/web3.js": "1.91.6",
18
18
  "rpc-websockets": "7.10.0",
19
19
  "axios": "^1.2.3",
20
20
  "bn.js": "^4.12.1",
21
21
  "bs58": "^6.0.0",
22
- "helius-laserstream": "^0.2.7"
22
+ "helius-laserstream": "^0.4.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^22.7.9",
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Solana 链通用 Quote 配置基类(对标 BSC 的 BscQuoteAppConfig)。
3
+ *
4
+ * 统一所有 DEX Quote 进程的配置初始化逻辑:
5
+ * - ensureCoreEnv(即便入口忘记 import './appconfig' 也兜底)
6
+ * - 初始化 arb_cache(super.init)+ arb_event_subscriber
7
+ * - 建立 Solana Connection(所有 DEX 共用,commitment 可由 env 覆盖)
8
+ * - env_args 收窄为 SolanaDexEnvArgs
9
+ *
10
+ * 池子加载 / 过滤不在这里做 —— 与 BSC 一致,放到各 DEX 的 entry(quote/index.ts),
11
+ * 直接 arb_cache.get_pair_dex_pool_list([pair],[dex_id]) + filterPools。
12
+ */
13
+
14
+ import { AppConfig, getArbEventSubscriber, getCoreEnv, log_info } from '@clonegod/ttd-core'
15
+ import { Commitment, Connection } from '@solana/web3.js'
16
+ import { COMMITMENT_LEVEL } from '../common/constants'
17
+ import { SolanaDexEnvArgs } from './sol_dex_env_args'
18
+ import { ensureCoreEnv } from './ensure_core_env'
19
+
20
+ export class SolanaQuoteAppConfig extends AppConfig {
21
+ public env_args: SolanaDexEnvArgs
22
+ public connection: Connection
23
+
24
+ constructor() {
25
+ // super 前 ensure coreEnv(AppConfig.constructor 内部读 getCoreEnv())
26
+ ensureCoreEnv()
27
+ super()
28
+ // AppConfig.constructor 已 this.env_args = getCoreEnv();这里只做类型收窄
29
+ this.env_args = getCoreEnv() as SolanaDexEnvArgs
30
+ }
31
+
32
+ async init() {
33
+ // super.init() 初始化 arb_cache
34
+ await super.init()
35
+
36
+ // 确保 arb_event_subscriber 已就绪(池事件订阅依赖)
37
+ if (!this.arb_event_subscriber) {
38
+ this.arb_event_subscriber = getArbEventSubscriber(this.arb_cache)
39
+ }
40
+
41
+ // Solana 连接(所有 DEX quote 共用;commitment 默认 PROCESSED,可由 env 覆盖)
42
+ const commitment =
43
+ (process.env.CONNECTION_COMMITMENT_LEVEL as Commitment) || COMMITMENT_LEVEL.PROCESSED
44
+ this.connection = new Connection(this.env_args.rpc_endpoint, { commitment })
45
+
46
+ log_info('SolanaQuoteAppConfig initialized', {
47
+ chain_id: this.env_args.chain_id,
48
+ dex_id: this.env_args.dex_id,
49
+ pair: this.env_args.pair,
50
+ rpc_endpoint: this.env_args.rpc_endpoint,
51
+ })
52
+ }
53
+
54
+ // subscribe_config_change 不覆盖 —— 继承 AppConfig 基类版本
55
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Solana 链通用 Trade 配置基类(对标 BSC 的 BscTradeAppConfig,与 SolanaQuoteAppConfig 对称)。
3
+ *
4
+ * 统一所有 DEX Trade 进程:
5
+ * - ensureCoreEnv + env_args 收窄为 SolanaDexEnvArgs
6
+ * - 建立 Solana Connection(commitment 可由 env 覆盖)
7
+ * - init_trade_runtime:create_trade_runtime + 从 wallet.private_key 还原 keypair(原本每个 DEX 各抄一份,现上提)
8
+ * - subscribe_wallet_raw_txn_event:订阅 stream-trade 推送的链上 tx 结果
9
+ *
10
+ * 各 DEX 的 *TradeAppConfig 直接 `extends SolanaTradeAppConfig` 即可,通常无需再覆盖任何方法。
11
+ */
12
+
13
+ import {
14
+ AbstractTradeAppConfig,
15
+ getCoreEnv,
16
+ LOCAL_EVENT_NAME,
17
+ log_error,
18
+ log_info,
19
+ sleep,
20
+ WebSocketClient,
21
+ } from '@clonegod/ttd-core/dist'
22
+ import { Commitment, Connection, Keypair } from '@solana/web3.js'
23
+ import bs58 from 'bs58'
24
+ import { COMMITMENT_LEVEL } from '../common/constants'
25
+ import { SolanaDexEnvArgs } from './sol_dex_env_args'
26
+ import { ensureCoreEnv } from './ensure_core_env'
27
+
28
+ export class SolanaTradeAppConfig extends AbstractTradeAppConfig {
29
+ public env_args: SolanaDexEnvArgs
30
+ public connection: Connection
31
+ public keypair: Keypair
32
+
33
+ // WebSocket 客户端实例,用于订阅交易结果
34
+ private ws_client: WebSocketClient | null = null
35
+
36
+ constructor() {
37
+ ensureCoreEnv()
38
+ super()
39
+ this.env_args = getCoreEnv() as SolanaDexEnvArgs
40
+ }
41
+
42
+ async init() {
43
+ await super.init()
44
+
45
+ // Solana 连接(所有 DEX trade 共用;commitment 默认 PROCESSED)
46
+ const commitment =
47
+ (process.env.CONNECTION_COMMITMENT_LEVEL as Commitment) || COMMITMENT_LEVEL.PROCESSED
48
+ this.connection = new Connection(this.env_args.rpc_endpoint, { commitment })
49
+
50
+ this.subscribe_wallet_raw_txn_event()
51
+ log_info('SolanaTradeAppConfig init ...')
52
+ }
53
+
54
+ /**
55
+ * 初始化交易上下文 + 从 wallet.private_key 还原 keypair。
56
+ * (原本 byreal / meteora / orca / pumpswap / raydium / jupiter 各抄一份,现统一上提)
57
+ */
58
+ async init_trade_runtime() {
59
+ try {
60
+ const { chain_id, dex_id, group_id, pair } = this.env_args
61
+
62
+ this.trade_runtime = await this.arb_cache.create_trade_runtime(
63
+ chain_id,
64
+ group_id,
65
+ dex_id,
66
+ pair,
67
+ )
68
+ this.trade_runtime.wallet_token_accounts = new Map()
69
+
70
+ // 从 trade_runtime.wallet.private_key 还原 Solana keypair
71
+ const secret_key = new Uint8Array(bs58.decode(this.trade_runtime.wallet.private_key))
72
+ this.keypair = Keypair.fromSecretKey(secret_key)
73
+ } catch (err) {
74
+ log_error(`SolanaTradeAppConfig create_trade_runtime error!`, err)
75
+ await sleep(1000)
76
+ process.exit(0)
77
+ }
78
+ }
79
+
80
+ // 订阅 Wallet 在链上发生的 tx data(stream-trade 经 WS 推送)
81
+ subscribe_wallet_raw_txn_event(): void {
82
+ // 检查是否已经订阅
83
+ if (this.is_already_subscribe_wallet_raw_txn) {
84
+ if (this.ws_client && this.ws_client.isConnected()) {
85
+ return
86
+ } else {
87
+ // 连接已断开,需要重新连接
88
+ log_info('subscribe_wallet_raw_txn_event was subscribed but connection lost, reconnecting...')
89
+ if (this.ws_client) {
90
+ this.ws_client.disconnect()
91
+ this.ws_client = null
92
+ }
93
+ }
94
+ }
95
+ this.is_already_subscribe_wallet_raw_txn = true
96
+
97
+ const transactionHandler = async (messageStr: any) => {
98
+ let messageObj = messageStr
99
+ if (typeof messageStr === 'string') {
100
+ messageObj = JSON.parse(messageStr)
101
+ }
102
+
103
+ const tx_id = messageObj.transaction.signature
104
+ log_info(`Received transaction result via WebSocket: ${tx_id}`)
105
+
106
+ this.emit(LOCAL_EVENT_NAME.EVENT_WALLET_TRANSACTION + '#' + tx_id, messageObj)
107
+ }
108
+
109
+ const ws_port = process.env.STREAM_WS_TRADE_PORT || 10002
110
+ const ws_url = `ws://127.0.0.1:${ws_port}`
111
+ this.ws_client = new WebSocketClient(ws_url)
112
+ this.ws_client.onMessage(transactionHandler)
113
+ this.ws_client.connect()
114
+
115
+ log_info(`subscribe_wallet_raw_txn_event, WebSocket connecting to ${ws_url}`)
116
+ }
117
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 通用 ensureCoreEnv:Solana AppConfig 子类构造器在 super() 之前调用,
3
+ * 确保 ttd-core 的 coreEnv 已被 setCoreEnv —— 即便入口忘记 import './appconfig'。
4
+ *
5
+ * 设计理由(同 BSC ensure_core_env):
6
+ * - AppConfig.constructor() 内部读 getCoreEnv(),没设置就抛错
7
+ * - 入口必须 import './appconfig' 触发 setCoreEnv 是隐式约定,容易漏
8
+ * - 此 helper 在 super 前补一刀;即便已设置(入口正常 import)也无副作用(短路返回)
9
+ *
10
+ * 限制:super() 之前不能访问 this,本 helper 只做无 this 的纯函数调用。
11
+ */
12
+
13
+ import { getCoreEnv, setCoreEnv } from '@clonegod/ttd-core'
14
+ import { SolanaDexEnvArgs } from './sol_dex_env_args'
15
+
16
+ let _ensured = false
17
+
18
+ export function ensureCoreEnv(): void {
19
+ if (_ensured) return
20
+ try {
21
+ getCoreEnv() // 已设置则直接通过
22
+ _ensured = true
23
+ } catch {
24
+ // 未设置:兜底创建一个 Solana DEX 子类的 EnvArgs
25
+ setCoreEnv(new SolanaDexEnvArgs())
26
+ _ensured = true
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export * from './sol_env_args'
2
+ export * from './sol_dex_env_args'
3
+ export * from './SolanaQuoteAppConfig'
4
+ export * from './SolanaTradeAppConfig'
5
+ export * from './ensure_core_env'