@clonegod/ttd-sol-common 2.0.68 → 2.0.70

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,19 @@
1
+ export interface AmmDepthInput {
2
+ reserve0: string;
3
+ reserve1: string;
4
+ targetBps: number;
5
+ isBuy: boolean;
6
+ baseIsToken0: boolean;
7
+ token0Decimals: number;
8
+ token1Decimals: number;
9
+ }
10
+ export interface AmmDepthResult {
11
+ amountOutWei: bigint;
12
+ amountOut: number;
13
+ outputDecimals: number;
14
+ amountInWei: bigint;
15
+ amountIn: number;
16
+ inputDecimals: number;
17
+ targetPrice: number;
18
+ }
19
+ export declare function calculateAmmDepth(input: AmmDepthInput): AmmDepthResult;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateAmmDepth = calculateAmmDepth;
4
+ const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
5
+ function calculateAmmDepth(input) {
6
+ const { targetBps, isBuy, baseIsToken0, token0Decimals, token1Decimals } = input;
7
+ const R0 = BigInt(input.reserve0);
8
+ const R1 = BigInt(input.reserve1);
9
+ if (R0 === 0n || R1 === 0n) {
10
+ return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0, amountInWei: 0n, amountIn: 0, inputDecimals: 0, targetPrice: 0 };
11
+ }
12
+ const k = R0 * R1;
13
+ const bps = BigInt(targetBps);
14
+ const PRECISION = 10n ** 18n;
15
+ let amountOutWei;
16
+ let amountInWei;
17
+ let outputDecimals;
18
+ let inputDecimals;
19
+ if (isBuy) {
20
+ const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
21
+ outputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
22
+ inputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
23
+ const baseReserveNew = baseReserve * (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)((10000n + bps) * PRECISION);
24
+ const quoteReserveNew = quoteReserve * (0, clmm_depth_calculator_1.bigIntSqrt)((10000n + bps) * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION);
25
+ amountOutWei = baseReserve - baseReserveNew;
26
+ amountInWei = quoteReserveNew - quoteReserve;
27
+ }
28
+ else {
29
+ const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
30
+ outputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
31
+ inputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
32
+ if (bps >= 10000n) {
33
+ amountOutWei = quoteReserve;
34
+ amountInWei = baseReserve;
35
+ }
36
+ else {
37
+ const quoteReserveNew = quoteReserve * (0, clmm_depth_calculator_1.bigIntSqrt)((10000n - bps) * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION);
38
+ const baseReserveNew = baseReserve * (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)((10000n - bps) * PRECISION);
39
+ amountOutWei = quoteReserve - quoteReserveNew;
40
+ amountInWei = baseReserveNew - baseReserve;
41
+ }
42
+ }
43
+ const amountOut = Number(amountOutWei) / Math.pow(10, outputDecimals);
44
+ const amountIn = Number(amountInWei) / Math.pow(10, inputDecimals);
45
+ const baseDec = baseIsToken0 ? token0Decimals : token1Decimals;
46
+ const quoteDec = baseIsToken0 ? token1Decimals : token0Decimals;
47
+ const baseReserveUi = Number(baseIsToken0 ? R0 : R1) / Math.pow(10, baseDec);
48
+ const quoteReserveUi = Number(baseIsToken0 ? R1 : R0) / Math.pow(10, quoteDec);
49
+ const midPrice = quoteReserveUi / baseReserveUi;
50
+ const bpsNum = Number(targetBps);
51
+ const targetPrice = isBuy
52
+ ? midPrice * (10000 + bpsNum) / 10000
53
+ : midPrice * (10000 - bpsNum) / 10000;
54
+ return { amountOutWei, amountOut, outputDecimals, amountInWei, amountIn, inputDecimals, targetPrice };
55
+ }
@@ -1,8 +1,15 @@
1
1
  import { StandardPoolInfoType } from '@clonegod/ttd-core/dist';
2
- import { QuoteDepthOutput } from '@clonegod/ttd-core/dist';
2
+ import { QuoteTier, QuoteEntry, QuoteDepthOutput } from '@clonegod/ttd-core/dist';
3
3
  import { DepthTick } from './clmm_depth_calculator';
4
4
  export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt, swapExactInClmm } from './clmm_depth_calculator';
5
5
  export type { DepthTick, ClmmDepthInput, DepthResult, SwapExactInInput, SwapExactInResult } from './clmm_depth_calculator';
6
+ export { calculateAmmDepth } from './amm_depth_calculator';
7
+ export type { AmmDepthInput, AmmDepthResult } from './amm_depth_calculator';
8
+ export declare function grossUpFee(amountInNet: number, feeRateBps: number): {
9
+ amountInGross: number;
10
+ feeAmount: number;
11
+ };
12
+ export declare function assembleEntry(tiers: QuoteTier[]): QuoteEntry;
6
13
  export interface BuildClmmDepthInput {
7
14
  poolInfo: StandardPoolInfoType;
8
15
  poolAddress: string;
@@ -18,3 +25,13 @@ export interface BuildClmmDepthInput {
18
25
  feeRateBps: number;
19
26
  }
20
27
  export declare function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | undefined;
28
+ export interface BuildAmmDepthInput {
29
+ poolInfo: StandardPoolInfoType;
30
+ reserve0: string;
31
+ reserve1: string;
32
+ baseIsToken0: boolean;
33
+ basePriceUsd: number;
34
+ quotePriceUsd: number;
35
+ feeRateBps: number;
36
+ }
37
+ export declare function buildAmmDepth(input: BuildAmmDepthInput): QuoteDepthOutput | undefined;
@@ -1,17 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.swapExactInClmm = exports.bigIntSqrt = exports.computeTargetSqrtX64 = exports.priceFromSqrtX64 = exports.calculateClmmDepth = void 0;
3
+ exports.calculateAmmDepth = exports.swapExactInClmm = exports.bigIntSqrt = exports.computeTargetSqrtX64 = exports.priceFromSqrtX64 = exports.calculateClmmDepth = void 0;
4
+ exports.grossUpFee = grossUpFee;
5
+ exports.assembleEntry = assembleEntry;
4
6
  exports.buildClmmDepth = buildClmmDepth;
7
+ exports.buildAmmDepth = buildAmmDepth;
5
8
  const dist_1 = require("@clonegod/ttd-core/dist");
6
9
  const dist_2 = require("@clonegod/ttd-core/dist");
7
10
  const trade_direction_1 = require("../../utils/trade_direction");
8
11
  const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
12
+ const amm_depth_calculator_1 = require("./amm_depth_calculator");
9
13
  var clmm_depth_calculator_2 = require("./clmm_depth_calculator");
10
14
  Object.defineProperty(exports, "calculateClmmDepth", { enumerable: true, get: function () { return clmm_depth_calculator_2.calculateClmmDepth; } });
11
15
  Object.defineProperty(exports, "priceFromSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.priceFromSqrtX64; } });
12
16
  Object.defineProperty(exports, "computeTargetSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.computeTargetSqrtX64; } });
13
17
  Object.defineProperty(exports, "bigIntSqrt", { enumerable: true, get: function () { return clmm_depth_calculator_2.bigIntSqrt; } });
14
18
  Object.defineProperty(exports, "swapExactInClmm", { enumerable: true, get: function () { return clmm_depth_calculator_2.swapExactInClmm; } });
19
+ var amm_depth_calculator_2 = require("./amm_depth_calculator");
20
+ Object.defineProperty(exports, "calculateAmmDepth", { enumerable: true, get: function () { return amm_depth_calculator_2.calculateAmmDepth; } });
15
21
  let _depthPctLogged = false;
16
22
  function logDepthLevelsOnce() {
17
23
  if (_depthPctLogged)
@@ -98,3 +104,61 @@ function buildClmmDepth(input) {
98
104
  return undefined;
99
105
  }
100
106
  }
107
+ function buildAmmDepth(input) {
108
+ logDepthLevelsOnce();
109
+ const pctLevels = (0, dist_2.getDepthPricePctLevels)();
110
+ if (pctLevels.length === 0)
111
+ return undefined;
112
+ const { poolInfo, reserve0, reserve1, baseIsToken0, basePriceUsd, quotePriceUsd, feeRateBps } = input;
113
+ if (feeRateBps == null || feeRateBps < 0) {
114
+ (0, dist_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM: invalid feeRateBps=${feeRateBps}, skip`, '');
115
+ return undefined;
116
+ }
117
+ try {
118
+ const dir = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
119
+ const baseToken = dir.baseToken;
120
+ const quoteToken = dir.quoteToken;
121
+ const baseReserveUi = Number(BigInt(baseIsToken0 ? reserve0 : reserve1)) / Math.pow(10, baseToken.decimals);
122
+ const quoteReserveUi = Number(BigInt(baseIsToken0 ? reserve1 : reserve0)) / Math.pow(10, quoteToken.decimals);
123
+ const midPrice = quoteReserveUi / baseReserveUi;
124
+ const askTiers = [];
125
+ const bidTiers = [];
126
+ for (const pct of pctLevels) {
127
+ const bps = Math.round(pct * 100);
128
+ const askResult = (0, amm_depth_calculator_1.calculateAmmDepth)({
129
+ reserve0, reserve1, targetBps: bps, isBuy: true, baseIsToken0,
130
+ token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
131
+ token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
132
+ });
133
+ const bidResult = (0, amm_depth_calculator_1.calculateAmmDepth)({
134
+ reserve0, reserve1, targetBps: bps, isBuy: false, baseIsToken0,
135
+ token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
136
+ token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
137
+ });
138
+ const askGross = grossUpFee(askResult.amountIn, feeRateBps);
139
+ const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
140
+ const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askResult.targetPrice;
141
+ const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidResult.targetPrice;
142
+ askTiers.push({
143
+ pct, price: askEffPrice, amount: askResult.amountOut,
144
+ amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
145
+ fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd,
146
+ });
147
+ bidTiers.push({
148
+ pct, price: bidEffPrice, amount: bidResult.amountOut,
149
+ amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
150
+ fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd,
151
+ });
152
+ }
153
+ return {
154
+ mid_price: midPrice,
155
+ fee_rate_bps: feeRateBps,
156
+ ask: assembleEntry(askTiers),
157
+ bid: assembleEntry(bidTiers),
158
+ };
159
+ }
160
+ catch (error) {
161
+ (0, dist_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM depth failed: ${error.message}`, '');
162
+ return undefined;
163
+ }
164
+ }
@@ -2,5 +2,4 @@ export * from './tx_result_check';
2
2
  export * from './tx_result_parse';
3
3
  export * from './tx_builder';
4
4
  export * from './send';
5
- export * from './jito_tip_wallets';
6
5
  export * from './sol_gas_cache';
@@ -18,5 +18,4 @@ __exportStar(require("./tx_result_check"), exports);
18
18
  __exportStar(require("./tx_result_parse"), exports);
19
19
  __exportStar(require("./tx_builder"), exports);
20
20
  __exportStar(require("./send"), exports);
21
- __exportStar(require("./jito_tip_wallets"), exports);
22
21
  __exportStar(require("./sol_gas_cache"), exports);
@@ -3,5 +3,4 @@ import { TradeContext } from '@clonegod/ttd-core/dist';
3
3
  export declare class TransactionSender {
4
4
  constructor();
5
5
  sendTransaction(context: TradeContext, mainTx: Transaction | VersionedTransaction, swqos_only?: boolean): Promise<string>;
6
- sendBundle(context: TradeContext, mainTx: Transaction | VersionedTransaction, tipTx: Transaction | VersionedTransaction, use_multi_ips?: boolean): Promise<string>;
7
6
  }
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TransactionSender = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
5
  const helius_1 = require("./helius");
6
- const jito_1 = require("./jito");
7
6
  const common_1 = require("../../common");
8
7
  const sol_gas_cache_1 = require("../sol_gas_cache");
9
8
  class TransactionSender {
@@ -15,18 +14,6 @@ class TransactionSender {
15
14
  const singedTxBase64 = serializeTransactionBase64(mainTx);
16
15
  return await (0, helius_1.sendTxWithHelius)(singedTxBase64, swqos_only);
17
16
  }
18
- async sendBundle(context, mainTx, tipTx, use_multi_ips = false) {
19
- const mainTxHash = (0, common_1.getSignature)(mainTx);
20
- set_transaciton_gas_cost(mainTxHash, context);
21
- const mainTxBase64 = serializeTransactionBase64(mainTx);
22
- const tipTxBase64 = serializeTransactionBase64(tipTx);
23
- if (use_multi_ips) {
24
- return await (0, jito_1.sendBundleWithJitoMultiIps)(mainTxHash, mainTxBase64, tipTxBase64);
25
- }
26
- else {
27
- return await (0, jito_1.sendBundleWithJito)(mainTxBase64, tipTxBase64);
28
- }
29
- }
30
17
  }
31
18
  exports.TransactionSender = TransactionSender;
32
19
  function serializeTransactionBase64(tx) {
@@ -1,14 +1,12 @@
1
1
  import { TradeContext } from "@clonegod/ttd-core/dist";
2
2
  import { Connection, Keypair, Transaction, TransactionInstruction } from "@solana/web3.js";
3
3
  import { SolanaTradeAppConfig } from "../appconfig/SolanaTradeAppConfig";
4
- import { JitoTipWalletManager } from "./jito_tip_wallets";
5
4
  export declare class SolTransactionBuilder {
6
5
  appConfig: SolanaTradeAppConfig;
7
6
  connection: Connection;
8
7
  keypair: Keypair;
9
8
  recentBlockhash: string;
10
9
  recentBlockheight: number;
11
- jitoTipWalletManager: JitoTipWalletManager;
12
10
  constructor(appConfig: SolanaTradeAppConfig);
13
11
  init(): Promise<void>;
14
12
  private handleBlockUpdateEvent;
@@ -16,5 +14,4 @@ export declare class SolTransactionBuilder {
16
14
  private createComputeUnitLimitWithJitoDontFront;
17
15
  private getPostInstructions;
18
16
  buildTransactionForSwap(context: TradeContext, swapInstructions: TransactionInstruction[]): Transaction;
19
- buildTransactionForTipJito(context: TradeContext): Transaction;
20
17
  }
@@ -5,8 +5,6 @@ const dist_1 = require("@clonegod/ttd-core/dist");
5
5
  const web3_js_1 = require("@solana/web3.js");
6
6
  const common_1 = require("../common");
7
7
  const helius_1 = require("./send/helius");
8
- const jito_1 = require("./send/jito");
9
- const jito_tip_wallets_1 = require("./jito_tip_wallets");
10
8
  const jitodontfrontAccounts = [
11
9
  'jitodontfront111111111111111111111111111111',
12
10
  'jitodontfront111111111111111111111111111123',
@@ -52,7 +50,6 @@ class SolTransactionBuilder {
52
50
  this.appConfig = appConfig;
53
51
  this.connection = appConfig.connection;
54
52
  this.keypair = appConfig.keypair;
55
- this.jitoTipWalletManager = new jito_tip_wallets_1.JitoTipWalletManager();
56
53
  this.init();
57
54
  }
58
55
  async init() {
@@ -99,51 +96,22 @@ class SolTransactionBuilder {
99
96
  swapTx.recentBlockhash = this.recentBlockhash;
100
97
  swapTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset;
101
98
  swapTx.feePayer = this.keypair.publicKey;
102
- const { sol_bundle_only, sol_tip_fee } = context.trade_runtime.settings.strategy;
103
- if (!sol_bundle_only) {
104
- let tip_lamports = sol_tip_fee;
105
- if (tip_lamports < 5000) {
106
- tip_lamports = 5000;
107
- }
108
- if (tip_lamports > 300000) {
109
- tip_lamports = 300000;
110
- }
111
- let tip_instruction = web3_js_1.SystemProgram.transfer({
112
- fromPubkey: this.keypair.publicKey,
113
- toPubkey: new web3_js_1.PublicKey(helius_1.HELIUS_TIP_ACCOUNTS[Math.floor(Math.random() * helius_1.HELIUS_TIP_ACCOUNTS.length)]),
114
- lamports: tip_lamports,
115
- });
116
- swapTx.instructions.push(tip_instruction);
117
- }
118
- swapTx.sign(this.keypair);
119
- return swapTx;
120
- }
121
- buildTransactionForTipJito(context) {
122
- const groupId = context.trade_runtime.group.id;
123
- const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset;
124
- let tipPayer = this.jitoTipWalletManager.getNextWallet(groupId);
125
- if (!tipPayer) {
126
- tipPayer = this.keypair;
127
- }
128
- let tipAccount = (0, jito_1.getJitoTipAccount)();
129
- let tip_lamports = context.trade_runtime.settings.strategy.sol_tip_fee;
99
+ const { sol_tip_fee } = context.trade_runtime.settings.strategy;
100
+ let tip_lamports = sol_tip_fee;
130
101
  if (tip_lamports < 5000) {
131
102
  tip_lamports = 5000;
132
103
  }
133
104
  if (tip_lamports > 300000) {
134
105
  tip_lamports = 300000;
135
106
  }
136
- const jitoTipTx = new web3_js_1.Transaction();
137
- jitoTipTx.add(web3_js_1.SystemProgram.transfer({
138
- fromPubkey: tipPayer.publicKey,
139
- toPubkey: new web3_js_1.PublicKey(tipAccount),
107
+ let tip_instruction = web3_js_1.SystemProgram.transfer({
108
+ fromPubkey: this.keypair.publicKey,
109
+ toPubkey: new web3_js_1.PublicKey(helius_1.HELIUS_TIP_ACCOUNTS[Math.floor(Math.random() * helius_1.HELIUS_TIP_ACCOUNTS.length)]),
140
110
  lamports: tip_lamports,
141
- }));
142
- jitoTipTx.recentBlockhash = this.recentBlockhash;
143
- jitoTipTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset;
144
- jitoTipTx.feePayer = tipPayer.publicKey;
145
- jitoTipTx.sign(tipPayer);
146
- return jitoTipTx;
111
+ });
112
+ swapTx.instructions.push(tip_instruction);
113
+ swapTx.sign(this.keypair);
114
+ return swapTx;
147
115
  }
148
116
  }
149
117
  exports.SolTransactionBuilder = SolTransactionBuilder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sol-common",
3
- "version": "2.0.68",
3
+ "version": "2.0.70",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,143 @@
1
+ /**
2
+ * AMM 深度计算器
3
+ *
4
+ * 给定 reserves + 价格偏移(bps),用 x*y=k 闭合公式计算可交易量。
5
+ * 一次乘除,零遍历。
6
+ *
7
+ * 与 CLMM 一样,给定价格范围的输出量与 fee 无关(fee 只影响需要多少 input)。
8
+ */
9
+
10
+ import { bigIntSqrt } from './clmm_depth_calculator';
11
+
12
+ export interface AmmDepthInput {
13
+ reserve0: string; // token0 reserve(wei string)
14
+ reserve1: string; // token1 reserve(wei string)
15
+ targetBps: number; // 价格偏移(如 15, 30)
16
+ isBuy: boolean; // true=买入 base token(token0), false=卖出 base token
17
+ /** base token 是 pool 的 token0 还是 token1 */
18
+ baseIsToken0: boolean;
19
+ token0Decimals: number;
20
+ token1Decimals: number;
21
+ }
22
+
23
+ export interface AmmDepthResult {
24
+ /** 输出 token 数量(wei) */
25
+ amountOutWei: bigint;
26
+ /** 输出 token 数量(UI 单位) */
27
+ amountOut: number;
28
+ /** 输出的 decimals */
29
+ outputDecimals: number;
30
+ /** 输入 token 数量(wei,不含 fee;池子曲线纯输入) */
31
+ amountInWei: bigint;
32
+ /** 输入 token 数量(UI 单位,不含 fee) */
33
+ amountIn: number;
34
+ /** 输入的 decimals */
35
+ inputDecimals: number;
36
+ /** 推到的目标价(quote per base,UI 单位) */
37
+ targetPrice: number;
38
+ }
39
+
40
+ /**
41
+ * 计算 AMM 池子在给定价格偏移范围内的深度
42
+ *
43
+ * 公式推导(以标准 token0/token1 为例):
44
+ * k = R0 * R1
45
+ * P = R1 / R0(token0 相对 token1 的价格)
46
+ *
47
+ * Ask(买入 token0,价格上涨):
48
+ * P_target = P * (1 + bps/10000)
49
+ * R0_new = sqrt(k / P_target)
50
+ * depth_token0 = R0 - R0_new
51
+ *
52
+ * Bid(卖出 token0,价格下跌):
53
+ * P_target = P * (1 - bps/10000)
54
+ * R1_new = sqrt(k * P_target)
55
+ * depth_token1 = R1 - R1_new
56
+ *
57
+ * 注意:pair 的 base/quote 可能与 pool 的 token0/token1 顺序不同,
58
+ * 通过 baseIsToken0 参数处理。
59
+ */
60
+ export function calculateAmmDepth(input: AmmDepthInput): AmmDepthResult {
61
+ const { targetBps, isBuy, baseIsToken0, token0Decimals, token1Decimals } = input;
62
+ const R0 = BigInt(input.reserve0);
63
+ const R1 = BigInt(input.reserve1);
64
+
65
+ if (R0 === 0n || R1 === 0n) {
66
+ return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0, amountInWei: 0n, amountIn: 0, inputDecimals: 0, targetPrice: 0 };
67
+ }
68
+
69
+ const k = R0 * R1;
70
+ const bps = BigInt(targetBps);
71
+ // 精度放大因子
72
+ const PRECISION = 10n ** 18n;
73
+
74
+ let amountOutWei: bigint;
75
+ let amountInWei: bigint;
76
+ let outputDecimals: number;
77
+ let inputDecimals: number;
78
+
79
+ if (isBuy) {
80
+ // 买入 base token → 价格上涨 → 输出 base token
81
+ // base token 从池子中减少
82
+
83
+ // P = quoteReserve / baseReserve
84
+ // P_target = P * (1 + bps/10000) = P * (10000 + bps) / 10000
85
+ // baseReserve_new = sqrt(k / P_target) = sqrt(k * baseReserve / quoteReserve * 10000 / (10000 + bps))
86
+ //
87
+ // 简化:baseReserve_new = sqrt(k * 10000 / ((10000 + bps) * P))
88
+ // = sqrt(k * baseReserve * 10000 / (quoteReserve * (10000 + bps)))
89
+
90
+ const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
91
+ outputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
92
+ inputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
93
+
94
+ // baseReserve_new = baseReserve * sqrt(10000 / (10000+bps))
95
+ // quoteReserve_new = k / baseReserve_new = quoteReserve * sqrt((10000+bps) / 10000)
96
+ // amountOut(base) = baseReserve - baseReserve_new
97
+ // amountIn(quote, not fee-adjusted) = quoteReserve_new - quoteReserve
98
+ const baseReserveNew = baseReserve * bigIntSqrt(10000n * PRECISION) / bigIntSqrt((10000n + bps) * PRECISION);
99
+ const quoteReserveNew = quoteReserve * bigIntSqrt((10000n + bps) * PRECISION) / bigIntSqrt(10000n * PRECISION);
100
+ amountOutWei = baseReserve - baseReserveNew;
101
+ amountInWei = quoteReserveNew - quoteReserve;
102
+
103
+ } else {
104
+ // 卖出 base token → 价格下跌 → 输出 quote token
105
+ // quote token 从池子中减少
106
+
107
+ const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
108
+ outputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
109
+ inputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
110
+
111
+ // quoteReserve_new = quoteReserve * sqrt((10000-bps) / 10000)
112
+ // baseReserve_new = k / quoteReserve_new = baseReserve * sqrt(10000 / (10000-bps))
113
+ // amountOut(quote) = quoteReserve - quoteReserve_new
114
+ // amountIn(base, not fee-adjusted) = baseReserve_new - baseReserve
115
+ if (bps >= 10000n) {
116
+ // 100% 偏移 → 所有 quote token;amountIn 取一个有限上界(baseReserve)
117
+ amountOutWei = quoteReserve;
118
+ amountInWei = baseReserve; // 极端情况下池子 base 耗尽
119
+ } else {
120
+ const quoteReserveNew = quoteReserve * bigIntSqrt((10000n - bps) * PRECISION) / bigIntSqrt(10000n * PRECISION);
121
+ const baseReserveNew = baseReserve * bigIntSqrt(10000n * PRECISION) / bigIntSqrt((10000n - bps) * PRECISION);
122
+ amountOutWei = quoteReserve - quoteReserveNew;
123
+ amountInWei = baseReserveNew - baseReserve;
124
+ }
125
+ }
126
+
127
+ const amountOut = Number(amountOutWei) / Math.pow(10, outputDecimals);
128
+ const amountIn = Number(amountInWei) / Math.pow(10, inputDecimals);
129
+
130
+ // 计算目标价(quote per base,UI 单位)
131
+ // 推到目标价后的池子状态:基于 P_target = P * (1 ± bps/10000)
132
+ const baseDec = baseIsToken0 ? token0Decimals : token1Decimals;
133
+ const quoteDec = baseIsToken0 ? token1Decimals : token0Decimals;
134
+ const baseReserveUi = Number(baseIsToken0 ? R0 : R1) / Math.pow(10, baseDec);
135
+ const quoteReserveUi = Number(baseIsToken0 ? R1 : R0) / Math.pow(10, quoteDec);
136
+ const midPrice = quoteReserveUi / baseReserveUi;
137
+ const bpsNum = Number(targetBps);
138
+ const targetPrice = isBuy
139
+ ? midPrice * (10000 + bpsNum) / 10000
140
+ : midPrice * (10000 - bpsNum) / 10000;
141
+
142
+ return { amountOutWei, amountOut, outputDecimals, amountInWei, amountIn, inputDecimals, targetPrice };
143
+ }
@@ -2,8 +2,9 @@
2
2
  * 深度计算模块(Solana,tier 版)
3
3
  *
4
4
  * port 自 SUI `ttd-sui-common/src/quote/depth/index.ts`(同源 BSC),差异(有理由的偏离):
5
- * · **仅 CLMM**:Solana 三个 CLMM(Raydium/Orca/ByReal)全是 Uniswap V3 风格集中流动性 X64;
6
- * AMM/DAMM 走各自路径,无统一深度引擎暂只点亮 buildClmmDepth。
5
+ * · **CLMM + AMM**:三个 CLMM(Raydium/Orca/ByReal)全是 Uniswap V3 风格集中流动性 X64 → buildClmmDepth
6
+ * 两个 AMM(Raydium AMM/CPMM)是 x·y=k 恒定乘积 buildAmmDepth(reserve 上闭式);
7
+ * DAMMv2(CP-AMM 单 sqrtPrice 区间)复用 buildClmmDepth(ticks=[]);DLMM bin 深度待做。
7
8
  * · **X64**:调用 X64 版 clmm_depth_calculator(见同目录,逐字照搬 SUI)。
8
9
  * · **tick + sqrtPriceX64 由调用方物化**:各 DEX 把本地 tickArray cache 的已初始化 tick 配
9
10
  * **共享 TickMath**(`../tick/clmm_tick_math` getSqrtPriceX64FromTick)算的 sqrtPriceX64,
@@ -24,9 +25,12 @@ import {
24
25
  } from '@clonegod/ttd-core/dist';
25
26
  import { resolveTradeDirection } from '../../utils/trade_direction';
26
27
  import { DepthTick, calculateClmmDepth, priceFromSqrtX64 } from './clmm_depth_calculator';
28
+ import { calculateAmmDepth } from './amm_depth_calculator';
27
29
 
28
30
  export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt, swapExactInClmm } from './clmm_depth_calculator';
29
31
  export type { DepthTick, ClmmDepthInput, DepthResult, SwapExactInInput, SwapExactInResult } from './clmm_depth_calculator';
32
+ export { calculateAmmDepth } from './amm_depth_calculator';
33
+ export type { AmmDepthInput, AmmDepthResult } from './amm_depth_calculator';
30
34
 
31
35
  // ========== 启动日志一次性输出 ==========
32
36
  let _depthPctLogged = false;
@@ -40,8 +44,10 @@ function logDepthLevelsOnce(): void {
40
44
  /**
41
45
  * 池子曲线纯输入 → 用户实付(含费):
42
46
  * amountInGross = amountInNet / (1 - feeRate);feeAmount = amountInGross - amountInNet
47
+ *
48
+ * 导出供 DEX 专有 depth 构建复用(如 DLMM 离散 bin 深度,在各自包内但 tier 语义统一)。
43
49
  */
44
- function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: number; feeAmount: number } {
50
+ export function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: number; feeAmount: number } {
45
51
  if (feeRateBps <= 0 || amountInNet <= 0) return { amountInGross: amountInNet, feeAmount: 0 };
46
52
  const feeRatio = feeRateBps / 10000;
47
53
  const amountInGross = amountInNet / (1 - feeRatio);
@@ -49,7 +55,8 @@ function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: n
49
55
  }
50
56
 
51
57
  // ========== 把多档 tier 装配成 QuoteEntry(主档镜像顶层)==========
52
- function assembleEntry(tiers: QuoteTier[]): QuoteEntry {
58
+ /** 导出供 DEX 专有 depth 构建复用(tier 语义统一)。 */
59
+ export function assembleEntry(tiers: QuoteTier[]): QuoteEntry {
53
60
  const def = tiers.find(t => t.pct === DEFAULT_TIER_PCT) ?? tiers[0];
54
61
  return {
55
62
  price: def.price, amount: def.amount, amount_in: def.amount_in, amount_in_usd: def.amount_in_usd,
@@ -165,3 +172,101 @@ export function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | u
165
172
  return undefined;
166
173
  }
167
174
  }
175
+
176
+ // ========== AMM 通用深度构建 ==========
177
+ //
178
+ // port 自 BSC `arb-chain-bsc/.../quote/depth/index.ts` buildAmmDepth;差异(有理由的偏离):
179
+ // · **baseIsToken0 由调用方传**(用链上 mintA/mintB 判定),不在此用地址 lowercase 比较——
180
+ // Solana 地址 base58 大小写敏感,不可 lowercase(与 buildClmmDepth 一致)。
181
+ // · reserve0/reserve1 = 链上 token0(mintA)/token1(mintB) 的 reserve(wei string)。
182
+
183
+ export interface BuildAmmDepthInput {
184
+ poolInfo: StandardPoolInfoType;
185
+ /** token0(=链上 mintA) 的 reserve(wei string) */
186
+ reserve0: string;
187
+ /** token1(=链上 mintB) 的 reserve(wei string) */
188
+ reserve1: string;
189
+ /** base token 是否为链上 token0(=mintA);调用方用链上 mint 判定(base58 大小写敏感) */
190
+ baseIsToken0: boolean;
191
+ basePriceUsd: number;
192
+ quotePriceUsd: number;
193
+ /**
194
+ * 池子 effective fee rate (bps),必填。各 DEX 从配置 fee_rate(market-data 强制整数 ≥1)传入。
195
+ * 错则 verify 跨源 diff 系统性偏移会暴露。
196
+ */
197
+ feeRateBps: number;
198
+ }
199
+
200
+ /**
201
+ * 通用 AMM 深度构建:多档(默认 5 档)ask/bid 深度,从内存 reserve 零额外 RPC 计算(x·y=k 闭式)。
202
+ */
203
+ export function buildAmmDepth(input: BuildAmmDepthInput): QuoteDepthOutput | undefined {
204
+ logDepthLevelsOnce();
205
+ const pctLevels = getDepthPricePctLevels();
206
+ if (pctLevels.length === 0) return undefined;
207
+
208
+ const { poolInfo, reserve0, reserve1, baseIsToken0, basePriceUsd, quotePriceUsd, feeRateBps } = input;
209
+ if (feeRateBps == null || feeRateBps < 0) {
210
+ log_debug(`[Depth] ${poolInfo.pool_name} AMM: invalid feeRateBps=${feeRateBps}, skip`, '');
211
+ return undefined;
212
+ }
213
+
214
+ try {
215
+ const dir = resolveTradeDirection(poolInfo, true);
216
+ const baseToken = dir.baseToken;
217
+ const quoteToken = dir.quoteToken;
218
+
219
+ // 中间价:从 reserve 比值算(UI 单位)
220
+ const baseReserveUi = Number(BigInt(baseIsToken0 ? reserve0 : reserve1)) / Math.pow(10, baseToken.decimals);
221
+ const quoteReserveUi = Number(BigInt(baseIsToken0 ? reserve1 : reserve0)) / Math.pow(10, quoteToken.decimals);
222
+ const midPrice = quoteReserveUi / baseReserveUi;
223
+
224
+ const askTiers: QuoteTier[] = [];
225
+ const bidTiers: QuoteTier[] = [];
226
+
227
+ for (const pct of pctLevels) {
228
+ const bps = Math.round(pct * 100);
229
+
230
+ const askResult = calculateAmmDepth({
231
+ reserve0, reserve1, targetBps: bps, isBuy: true, baseIsToken0,
232
+ token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
233
+ token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
234
+ });
235
+ const bidResult = calculateAmmDepth({
236
+ reserve0, reserve1, targetBps: bps, isBuy: false, baseIsToken0,
237
+ token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
238
+ token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
239
+ });
240
+
241
+ // Ask: amount_in 是 quote token;Bid: amount_in 是 base token
242
+ const askGross = grossUpFee(askResult.amountIn, feeRateBps);
243
+ const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
244
+
245
+ // 【强制·原则2】tier.price = effective avg price including fee = amount_in_gross / amount_out
246
+ // 跟 chain swap exec_price 严格同语义;禁止改回 marginal / 不含 fee 形态(G4 事故根因)。
247
+ const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askResult.targetPrice;
248
+ const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidResult.targetPrice;
249
+
250
+ askTiers.push({
251
+ pct, price: askEffPrice, amount: askResult.amountOut,
252
+ amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
253
+ fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd,
254
+ });
255
+ bidTiers.push({
256
+ pct, price: bidEffPrice, amount: bidResult.amountOut,
257
+ amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
258
+ fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd,
259
+ });
260
+ }
261
+
262
+ return {
263
+ mid_price: midPrice,
264
+ fee_rate_bps: feeRateBps,
265
+ ask: assembleEntry(askTiers),
266
+ bid: assembleEntry(bidTiers),
267
+ };
268
+ } catch (error) {
269
+ log_debug(`[Depth] ${poolInfo.pool_name} AMM depth failed: ${(error as Error).message}`, '');
270
+ return undefined;
271
+ }
272
+ }
@@ -2,6 +2,5 @@ export * from './tx_result_check'
2
2
  export * from './tx_result_parse'
3
3
  export * from './tx_builder'
4
4
  export * from './send'
5
- export * from './jito_tip_wallets'
6
5
  export * from './sol_gas_cache'
7
6
 
@@ -1,6 +1,5 @@
1
1
  import { Transaction, VersionedTransaction } from '@solana/web3.js';
2
2
  import { sendTxWithHelius } from './helius';
3
- import { sendBundleWithJito, sendBundleWithJitoMultiIps } from './jito';
4
3
  import { getSignature } from '../../common';
5
4
  import { TradeContext } from '@clonegod/ttd-core/dist';
6
5
  import { set_sol_gas_fee } from '../sol_gas_cache';
@@ -31,30 +30,6 @@ export class TransactionSender {
31
30
  return await sendTxWithHelius(singedTxBase64, swqos_only)
32
31
  }
33
32
 
34
- /**
35
- * 使用Jito Bundle 发送交易
36
- * - use_multi_ips=true, 使用多台服务器向jito提交交易(缓解jito的限流)
37
- * - use_multi_ips=false, 本地直接向jito提交交易
38
- *
39
- * @param mainTx 主交易
40
- * @param tipTx 小费交易
41
- * @param use_multi_ips true:多节点;false: 单节点
42
- * @returns bundleId
43
- */
44
- async sendBundle(context:TradeContext, mainTx: Transaction | VersionedTransaction, tipTx: Transaction | VersionedTransaction, use_multi_ips: boolean=false): Promise<string> {
45
- const mainTxHash = getSignature(mainTx)
46
- set_transaciton_gas_cost(mainTxHash, context)
47
-
48
- const mainTxBase64 = serializeTransactionBase64(mainTx)
49
- const tipTxBase64 = serializeTransactionBase64(tipTx)
50
-
51
- if(use_multi_ips) {
52
- return await sendBundleWithJitoMultiIps(mainTxHash, mainTxBase64, tipTxBase64)
53
- } else {
54
- return await sendBundleWithJito(mainTxBase64, tipTxBase64)
55
- }
56
- }
57
-
58
33
  }
59
34
 
60
35
 
@@ -4,8 +4,6 @@ import { LAMPORTS_PER_MICRO_LAMPORTS } from "../common";
4
4
  import { SolanaBlockMetaUpdateEvent } from "../types";
5
5
  import { SolanaTradeAppConfig } from "../appconfig/SolanaTradeAppConfig";
6
6
  import { HELIUS_TIP_ACCOUNTS } from "./send/helius";
7
- import { getJitoTipAccount } from "./send/jito";
8
- import { JitoTipWalletManager } from "./jito_tip_wallets";
9
7
 
10
8
  /**
11
9
  * Jito Sandwich Mitigation: jitodontfront 账户
@@ -71,13 +69,10 @@ export class SolTransactionBuilder {
71
69
  recentBlockhash: string
72
70
  recentBlockheight: number
73
71
 
74
- jitoTipWalletManager: JitoTipWalletManager
75
-
76
72
  constructor(appConfig: SolanaTradeAppConfig) {
77
73
  this.appConfig = appConfig
78
74
  this.connection = appConfig.connection
79
75
  this.keypair = appConfig.keypair
80
- this.jitoTipWalletManager = new JitoTipWalletManager()
81
76
  this.init()
82
77
  }
83
78
 
@@ -175,74 +170,29 @@ export class SolTransactionBuilder {
175
170
  swapTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset
176
171
  swapTx.feePayer = this.keypair.publicKey
177
172
 
178
- const { sol_bundle_only, sol_tip_fee } = context.trade_runtime.settings.strategy
179
-
180
- // sol_bundle_only = false, 使用 Helius Sender
181
- // sol_bundle_only = true, 使用 Jito Bundle - 不需要给Helius小费
182
- if (!sol_bundle_only) {
183
- // 使用 Helius Sender:
184
- // 1、swqos_only: 至少 5000 lamports 小费
185
- // -> transaction must send a tip of at least 5000 lamports to one of the following Helius wallets: [xxx, ...]
186
- // 2、swqos + jito: 至少 0.0002 SOL 小费 -> lamports = 0.0002 * LAMPORTS_PER_SOL
187
- // -> Requires minimum 0.0002 SOL tip.
188
- let tip_lamports = sol_tip_fee
189
- if (tip_lamports < 5000) {
190
- tip_lamports = 5000
191
- }
192
- if (tip_lamports > 300000) {
193
- tip_lamports = 300000
194
- }
195
- let tip_instruction = SystemProgram.transfer({
196
- fromPubkey: this.keypair.publicKey,
197
- toPubkey: new PublicKey(HELIUS_TIP_ACCOUNTS[Math.floor(Math.random() * HELIUS_TIP_ACCOUNTS.length)]),
198
- lamports: tip_lamports,
199
- })
200
- swapTx.instructions.push(tip_instruction)
201
- }
202
-
203
- swapTx.sign(this.keypair)
204
-
205
- return swapTx
206
- }
207
-
173
+ const { sol_tip_fee } = context.trade_runtime.settings.strategy
208
174
 
209
-
210
- /**
211
- * build transaction for tip jito
212
- */
213
- buildTransactionForTipJito(context: TradeContext): Transaction {
214
- const groupId = context.trade_runtime.group.id
215
- const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset
216
-
217
- let tipPayer = this.jitoTipWalletManager.getNextWallet(groupId)
218
- if (!tipPayer) {
219
- tipPayer = this.keypair
220
- }
221
-
222
- let tipAccount = getJitoTipAccount()
223
- let tip_lamports = context.trade_runtime.settings.strategy.sol_tip_fee
175
+ // 发送只走 Helius Sender,必须附带最低小费:
176
+ // 1、swqos_only: 至少 5000 lamports 小费
177
+ // -> transaction must send a tip of at least 5000 lamports to one of the Helius tip wallets
178
+ // 2、swqos + jito: 至少 0.0002 SOL 小费 -> lamports = 0.0002 * LAMPORTS_PER_SOL
179
+ let tip_lamports = sol_tip_fee
224
180
  if (tip_lamports < 5000) {
225
181
  tip_lamports = 5000
226
182
  }
227
183
  if (tip_lamports > 300000) {
228
184
  tip_lamports = 300000
229
185
  }
186
+ let tip_instruction = SystemProgram.transfer({
187
+ fromPubkey: this.keypair.publicKey,
188
+ toPubkey: new PublicKey(HELIUS_TIP_ACCOUNTS[Math.floor(Math.random() * HELIUS_TIP_ACCOUNTS.length)]),
189
+ lamports: tip_lamports,
190
+ })
191
+ swapTx.instructions.push(tip_instruction)
192
+
193
+ swapTx.sign(this.keypair)
230
194
 
231
- const jitoTipTx = new Transaction();
232
- jitoTipTx.add(
233
- SystemProgram.transfer({
234
- fromPubkey: tipPayer.publicKey,
235
- toPubkey: new PublicKey(tipAccount),
236
- lamports: tip_lamports,
237
- }),
238
- );
239
- jitoTipTx.recentBlockhash = this.recentBlockhash
240
- jitoTipTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset
241
- jitoTipTx.feePayer = tipPayer.publicKey;
242
-
243
- jitoTipTx.sign(tipPayer)
244
-
245
- return jitoTipTx
195
+ return swapTx
246
196
  }
247
197
 
248
198
  }
@@ -1,143 +0,0 @@
1
- import { Keypair } from '@solana/web3.js';
2
- import { log_info, log_warn, log_error } from '@clonegod/ttd-core/dist';
3
- import bs58 from 'bs58';
4
- import * as fs from 'fs';
5
- import os from 'os';
6
- import path from 'path';
7
-
8
- /**
9
- * 钱包配置接口
10
- */
11
- export interface WalletConfig {
12
- public_key: string;
13
- private_key: string;
14
- type: string; // main | sub
15
- keypair: Keypair; // 添加 Keypair 字段
16
- }
17
-
18
- /**
19
- * 分组配置接口
20
- */
21
- export interface GroupConfig {
22
- group_id: string;
23
- tip_wallets: WalletConfig[];
24
- }
25
-
26
- /**
27
- * Jito Tip 钱包管理器
28
- *
29
- * 功能:
30
- * 1. 从文件加载 tip wallets
31
- * 2. 返回下一个钱包(轮询)
32
- */
33
- export class JitoTipWalletManager {
34
- private readonly jito_tip_wallet_filename: string = 'jito_tip_wallet.json';
35
- private walletConfigs: GroupConfig[] = [];
36
- private walletIndexMap: Map<string, number> = new Map();
37
-
38
- constructor() {
39
- this.loadWalletConfigs();
40
- }
41
-
42
- /**
43
- * 从私钥创建 Keypair
44
- */
45
- private createKeypair(privateKeyBase58: string): Keypair {
46
- const privateKeyBytes = bs58.decode(privateKeyBase58);
47
- return Keypair.fromSecretKey(privateKeyBytes);
48
- }
49
-
50
- /**
51
- * 从文件加载钱包配置
52
- */
53
- private loadWalletConfigs(): void {
54
- try {
55
- // 从环境变量获取配置文件路径
56
- let jito_tip_wallet_path = process.env.JITO_TIP_WALLET_PATH || '';
57
- if (!jito_tip_wallet_path.startsWith('/')) {
58
- jito_tip_wallet_path = path.join(os.homedir(), 'data', 'keypairs', this.jito_tip_wallet_filename);
59
- }
60
-
61
- if (!fs.existsSync(jito_tip_wallet_path)) {
62
- log_warn(`[JitoTipWalletManager] 配置文件${this.jito_tip_wallet_filename}不存在!路径: ${jito_tip_wallet_path}`);
63
- this.walletConfigs = [];
64
- return;
65
- }
66
-
67
- const configData = fs.readFileSync(jito_tip_wallet_path, 'utf-8');
68
- this.walletConfigs = JSON.parse(configData);
69
-
70
- // 为每个钱包生成 KeyPair
71
- this.walletConfigs.forEach(group => {
72
- group.tip_wallets.forEach(wallet => {
73
- try {
74
- wallet.keypair = this.createKeypair(wallet.private_key);
75
- } catch (error) {
76
- log_error(`[JitoTipWalletManager] 生成钱包 KeyPair 失败 - public_key: ${wallet.public_key}`, error);
77
- }
78
- });
79
- });
80
-
81
- log_info(`[JitoTipWalletManager] 成功加载配置文件: ${jito_tip_wallet_path}`);
82
-
83
- // 加载完成后打印钱包信息
84
- this.printWalletInfo();
85
- } catch (error) {
86
- log_error('[JitoTipWalletManager] 加载钱包配置时出错:', error);
87
- this.walletConfigs = [];
88
- }
89
- }
90
-
91
- /**
92
- * 打印钱包信息(用于调试)
93
- */
94
- private printWalletInfo(): void {
95
- log_info('\n=== Tip钱包配置信息 ===');
96
- if (this.walletConfigs.length === 0) {
97
- log_info('没有找到任何钱包配置');
98
- return;
99
- }
100
-
101
- this.walletConfigs.forEach(group => {
102
- log_info(`\n分组: ${group.group_id}`);
103
- log_info(`钱包数量: ${group.tip_wallets.length}`);
104
- group.tip_wallets.forEach((wallet, index) => {
105
- const keypairStatus = wallet.keypair ? '✓' : '✗';
106
- log_info(` ${index + 1}. ${wallet.public_key}${wallet.type ? ` (${wallet.type})` : ''} [KeyPair: ${keypairStatus}]`);
107
- });
108
- });
109
- log_info('\n===================\n');
110
- }
111
-
112
- /**
113
- * 获取下一个钱包(轮询)
114
- * @param groupId 分组ID
115
- * @returns 钱包配置,如果未找到则返回 null
116
- */
117
- public getNextWallet(groupId: string): Keypair | null {
118
- const group = this.walletConfigs.find(g => g.group_id === groupId);
119
- if (!group) {
120
- log_warn(`[JitoTipWalletManager] 未找到group_id对应的钱包组: ${groupId}`);
121
- return null;
122
- }
123
-
124
- if (group.tip_wallets.length === 0) {
125
- log_warn(`[JitoTipWalletManager] group_id: ${groupId} 没有配置钱包`);
126
- return null;
127
- }
128
-
129
- // 获取当前索引,如果不存在则初始化为0
130
- let currentIndex = this.walletIndexMap.get(groupId) || 0;
131
-
132
- // 获取下一个钱包
133
- const wallet = group.tip_wallets[currentIndex];
134
-
135
- // 更新索引,如果达到列表末尾则重置为0
136
- currentIndex = (currentIndex + 1) % group.tip_wallets.length;
137
- this.walletIndexMap.set(groupId, currentIndex);
138
-
139
- return wallet.keypair;
140
- }
141
- }
142
-
143
-
@@ -1,132 +0,0 @@
1
- import { log_warn } from "@clonegod/ttd-core/dist";
2
- import axios, { AxiosError } from "axios";
3
- import { getHttpClient } from "./http_client";
4
-
5
- export const JITO_TIP_ACCOUNTS = [
6
- "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
7
- "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
8
- "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
9
- "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
10
- "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
11
- "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
12
- "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
13
- "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe"
14
- ]
15
-
16
- export class JitoUtils {
17
- static jitoTipAccounts: string[] = []
18
- static jitoTipAccountUrl = 'https://mainnet.block-engine.jito.wtf/api/v1/getTipAccounts'
19
-
20
- static async init(): Promise<void> {
21
- return JitoUtils.fetchJitoTipAccounts().then((accounts) => {
22
- JitoUtils.jitoTipAccounts = accounts;
23
- console.log(`fetchJitoTipAccounts success: ${accounts.length} accounts`, accounts)
24
- }).catch((error) => {
25
- log_warn(`fetchJitoTipAccounts failed!`, {
26
- url: JitoUtils.jitoTipAccountUrl,
27
- error: error.message
28
- });
29
- });
30
- }
31
-
32
- static async fetchJitoTipAccounts(): Promise<string[]> {
33
- const response = await axios.post<{ result: string[] }>(JitoUtils.jitoTipAccountUrl, {
34
- jsonrpc: '2.0',
35
- id: 1,
36
- method: 'getTipAccounts',
37
- params: []
38
- })
39
- return response.data.result;
40
- }
41
- }
42
-
43
-
44
- export const getJitoTipAccount = (): string => {
45
- let jito_tip_accounts = JitoUtils.jitoTipAccounts
46
- if (jito_tip_accounts.length === 0) {
47
- jito_tip_accounts = JITO_TIP_ACCOUNTS
48
- }
49
- return jito_tip_accounts[Math.floor(Math.random() * jito_tip_accounts.length)];
50
- }
51
-
52
- JitoUtils.init()
53
-
54
- // -----------------------------------------------------------
55
-
56
- export const sendBundleWithJito = async (mainTxBase64: string, tipTxBase64: string): Promise<string> => {
57
- let url = process.env.JITO_SEND_BUNDLE_URL || 'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles'
58
-
59
- // 使用共享的 axios 实例,复用连接池
60
- const client = getHttpClient(url)
61
-
62
- // Bundle 顺序: [mainTx, tipTx]
63
- // 根据 Jito Sandwich Mitigation 规则 (https://docs.jito.wtf/lowlatencytxnsend/#sandwich-mitigation):
64
- // - 如果主交易包含 jitodontfront 账户,它必须在 bundle 的第一个位置(索引 0)
65
- // - 允许的模式: [tx_with_dont_front, tip]
66
- // - 不允许的模式: [tip, tx_with_dont_front]
67
- const requestData = {
68
- jsonrpc: '2.0',
69
- id: 1,
70
- method: 'sendBundle',
71
- params: [
72
- [mainTxBase64, tipTxBase64],
73
- {
74
- "encoding": "base64"
75
- }
76
- ]
77
- };
78
-
79
- try {
80
- const response = await client.post<{ result: string }>(url, requestData)
81
- // {
82
- // jsonrpc: '2.0',
83
- // result: 'e62062b4af6963bb0f67fea429f97e4f4fee59851a4ef65154b9f49b5995b5be', // bundle id
84
- // id: 1
85
- // }
86
- // console.dir(response.data, { depth: null })
87
- const bundleId = response.data.result;
88
- console.log(`[sendBundleWithJito] bundleId: ${bundleId}`);
89
-
90
- return bundleId;
91
- } catch (error) {
92
- if(error instanceof AxiosError) {
93
- // 提取关键错误信息
94
- const status = error.response?.status;
95
- const statusText = error.response?.statusText;
96
-
97
- log_warn(`[sendBundleWithJito] Request failed: ${status} - ${statusText}`);
98
- } else {
99
- log_warn(`[sendBundleWithJito] Unexpected error: ${url} - ${error.message}`);
100
- }
101
- }
102
- }
103
-
104
-
105
- export const sendBundleWithJitoMultiIps = async (mainTxHash: string, mainTxBase64: string, tipTxBase64: string): Promise<string> => {
106
- let ips = (process.env.JITO_SEND_BUNDLE_SERVER_IPS || '127.0.0.1').split(',')
107
- let urls = ips.map(ip => `http://${ip}:10429/solana/send_tx`)
108
-
109
- // Bundle 顺序: [mainTx, tipTx]
110
- // 与 sendBundleWithJito 保持一致,符合 Jito Sandwich Mitigation 规则
111
- const body = {
112
- trace_id: '',
113
- txid: mainTxHash,
114
- encoding: 'base64', // base64
115
- encoded_tx: [mainTxBase64, tipTxBase64].join(','), // bundle: mainTx, tipTx
116
- max_retry: 3
117
- }
118
-
119
- Promise.all(urls.map(async (url) => {
120
- try {
121
- const client = getHttpClient(url)
122
- const response = await client.post<{ result: string }>(url, body)
123
- return response.data.result;
124
- } catch (error) {
125
- console.error(`sendBundleWithJitoByMultiIps error: ${url}`, error)
126
- }
127
- }))
128
-
129
- return 'success';
130
- }
131
-
132
-