@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.
- package/dist/appconfig/SolanaQuoteAppConfig.d.ts +9 -0
- package/dist/appconfig/SolanaQuoteAppConfig.js +29 -0
- package/dist/appconfig/SolanaTradeAppConfig.d.ts +13 -0
- package/dist/{config → appconfig}/SolanaTradeAppConfig.js +25 -0
- package/dist/appconfig/ensure_core_env.d.ts +1 -0
- package/dist/appconfig/ensure_core_env.js +18 -0
- package/dist/appconfig/index.d.ts +5 -0
- package/dist/appconfig/index.js +21 -0
- package/dist/appconfig/sol_dex_env_args.d.ts +5 -0
- package/dist/appconfig/sol_dex_env_args.js +29 -0
- package/dist/appconfig/sol_env_args.d.ts +17 -0
- package/dist/appconfig/sol_env_args.js +37 -0
- package/dist/common/get_wallet_token_account.js +13 -16
- package/dist/grpc/grpc_provider_registry.d.ts +14 -0
- package/dist/grpc/grpc_provider_registry.js +70 -0
- package/dist/grpc/index.d.ts +1 -0
- package/dist/grpc/index.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/quote/abstract_dex_quote.d.ts +68 -0
- package/dist/quote/abstract_dex_quote.js +208 -0
- package/dist/quote/chain_ops.d.ts +18 -0
- package/dist/quote/chain_ops.js +66 -0
- package/dist/quote/depth/amm_depth_calculator.d.ts +19 -0
- package/dist/quote/depth/amm_depth_calculator.js +55 -0
- package/dist/quote/depth/clmm_depth_calculator.d.ts +42 -0
- package/dist/quote/depth/clmm_depth_calculator.js +173 -0
- package/dist/quote/depth/index.d.ts +37 -0
- package/dist/quote/depth/index.js +164 -0
- package/dist/quote/index.d.ts +9 -0
- package/dist/quote/index.js +9 -0
- package/dist/quote/pool_event.d.ts +20 -0
- package/dist/quote/pool_event.js +22 -0
- package/dist/quote/pool_subscription_registry.d.ts +4 -0
- package/dist/quote/pool_subscription_registry.js +62 -0
- package/dist/quote/quote_amount.d.ts +4 -0
- package/dist/quote/quote_amount.js +24 -0
- package/dist/quote/quote_trace.d.ts +16 -0
- package/dist/quote/quote_trace.js +40 -0
- package/dist/quote/tick/clmm_tick_math.d.ts +5 -0
- package/dist/quote/tick/clmm_tick_math.js +61 -0
- package/dist/quote/tick/index.d.ts +1 -0
- package/dist/{config → quote/tick}/index.js +1 -1
- package/dist/quote/verify/index.d.ts +1 -0
- package/dist/quote/verify/index.js +17 -0
- package/dist/quote/verify/quote_price_verify.d.ts +30 -0
- package/dist/quote/verify/quote_price_verify.js +247 -0
- package/dist/trade/index.d.ts +0 -1
- package/dist/trade/index.js +0 -1
- package/dist/trade/tx_builder.d.ts +1 -1
- package/dist/trade/tx_result_parse.js +1 -0
- package/dist/types/index.d.ts +9 -1
- package/dist/types/index.js +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +17 -0
- package/dist/utils/trade_direction.d.ts +14 -0
- package/dist/utils/trade_direction.js +23 -0
- package/package.json +4 -4
- package/src/appconfig/SolanaQuoteAppConfig.ts +55 -0
- package/src/appconfig/SolanaTradeAppConfig.ts +117 -0
- package/src/appconfig/ensure_core_env.ts +28 -0
- package/src/appconfig/index.ts +5 -0
- package/src/appconfig/sol_dex_env_args.ts +52 -0
- package/src/appconfig/sol_env_args.ts +79 -0
- package/src/common/get_wallet_token_account.ts +27 -33
- package/src/grpc/grpc_provider_registry.ts +103 -0
- package/src/grpc/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/quote/abstract_dex_quote.ts +337 -0
- package/src/quote/chain_ops.ts +91 -0
- package/src/quote/depth/amm_depth_calculator.ts +143 -0
- package/src/quote/depth/clmm_depth_calculator.ts +321 -0
- package/src/quote/depth/index.ts +272 -0
- package/src/quote/index.ts +9 -0
- package/src/quote/pool_event.ts +82 -0
- package/src/quote/pool_subscription_registry.ts +81 -0
- package/src/quote/quote_amount.ts +37 -0
- package/src/quote/quote_trace.ts +56 -0
- package/src/quote/tick/clmm_tick_math.ts +77 -0
- package/src/quote/tick/index.ts +1 -0
- package/src/quote/verify/index.ts +1 -0
- package/src/quote/verify/quote_price_verify.ts +508 -0
- package/src/trade/index.ts +0 -1
- package/src/trade/tx_builder.ts +1 -1
- package/src/trade/tx_result_parse.ts +1 -0
- package/src/types/index.ts +20 -2
- package/src/utils/index.ts +1 -0
- package/src/utils/trade_direction.ts +68 -0
- package/dist/config/SolanaTradeAppConfig.d.ts +0 -10
- package/dist/config/index.d.ts +0 -1
- package/dist/trade/SolanaTradeAppConfig.d.ts +0 -8
- package/dist/trade/SolanaTradeAppConfig.js +0 -26
- package/src/config/SolanaTradeAppConfig.ts +0 -70
- 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;
|
package/dist/trade/index.d.ts
CHANGED
package/dist/trade/index.js
CHANGED
|
@@ -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 "../
|
|
3
|
+
import { SolanaTradeAppConfig } from "../appconfig/SolanaTradeAppConfig";
|
|
4
4
|
import { JitoTipWalletManager } from "./jito_tip_wallets";
|
|
5
5
|
export declare class SolTransactionBuilder {
|
|
6
6
|
appConfig: SolanaTradeAppConfig;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/types/index.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "2.0.69",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"types": "
|
|
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": "
|
|
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.
|
|
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
|
+
}
|