@clonegod/ttd-sui-common 1.0.101 → 2.0.1

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 (126) hide show
  1. package/dist/appconfig/SuiQuoteAppConfig.d.ts +10 -0
  2. package/dist/appconfig/SuiQuoteAppConfig.js +35 -0
  3. package/dist/appconfig/SuiTradeAppConfig.d.ts +7 -0
  4. package/dist/appconfig/SuiTradeAppConfig.js +13 -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/sui_dex_env_args.d.ts +5 -0
  10. package/dist/appconfig/sui_dex_env_args.js +28 -0
  11. package/dist/appconfig/sui_env_args.d.ts +4 -0
  12. package/dist/appconfig/sui_env_args.js +20 -0
  13. package/dist/grpc/gas-price-cache.js +19 -32
  14. package/dist/grpc/grpc-connection.js +5 -3
  15. package/dist/grpc/grpc_provider_registry.d.ts +14 -0
  16. package/dist/grpc/grpc_provider_registry.js +60 -0
  17. package/dist/grpc/index.d.ts +3 -0
  18. package/dist/grpc/index.js +13 -1
  19. package/dist/grpc/ledger-service.js +107 -128
  20. package/dist/grpc/proto_value.d.ts +4 -0
  21. package/dist/grpc/proto_value.js +59 -0
  22. package/dist/grpc/state-service.d.ts +1 -0
  23. package/dist/grpc/state-service.js +99 -102
  24. package/dist/grpc/sui-grpc-client.js +2 -13
  25. package/dist/grpc/sui_object_reader.d.ts +15 -0
  26. package/dist/grpc/sui_object_reader.js +60 -0
  27. package/dist/grpc/transaction-service.js +26 -37
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/quote/abstract_dex_quote.d.ts +60 -0
  31. package/dist/quote/abstract_dex_quote.js +186 -0
  32. package/dist/quote/chain_ops.d.ts +17 -0
  33. package/dist/quote/chain_ops.js +52 -0
  34. package/dist/quote/index.d.ts +7 -0
  35. package/dist/quote/index.js +7 -0
  36. package/dist/quote/pool_event.d.ts +21 -0
  37. package/dist/quote/pool_event.js +6 -0
  38. package/dist/quote/pricing/token_price_cache.js +18 -29
  39. package/dist/quote/quote_amount.d.ts +4 -0
  40. package/dist/quote/quote_amount.js +24 -0
  41. package/dist/quote/quote_trace.d.ts +16 -0
  42. package/dist/quote/quote_trace.js +40 -0
  43. package/dist/quote/tick/clmm_v3_engine.d.ts +32 -0
  44. package/dist/quote/tick/clmm_v3_engine.js +48 -0
  45. package/dist/quote/tick/index.d.ts +4 -0
  46. package/dist/quote/tick/index.js +20 -0
  47. package/dist/quote/tick/local_clmm_state.d.ts +17 -0
  48. package/dist/quote/tick/local_clmm_state.js +22 -0
  49. package/dist/quote/tick/sui_clmm_tick_cache.d.ts +42 -0
  50. package/dist/quote/tick/sui_clmm_tick_cache.js +163 -0
  51. package/dist/quote/tick/sui_tick_data_provider.d.ts +2 -0
  52. package/dist/quote/tick/sui_tick_data_provider.js +6 -0
  53. package/dist/quote/verify/index.d.ts +1 -0
  54. package/dist/quote/verify/index.js +17 -0
  55. package/dist/quote/verify/quote_price_verify.d.ts +30 -0
  56. package/dist/quote/verify/quote_price_verify.js +247 -0
  57. package/dist/redis/redis_client.d.ts +1 -0
  58. package/dist/redis/redis_client.js +88 -117
  59. package/dist/rpc/index.js +59 -75
  60. package/dist/test/test.js +1 -1
  61. package/dist/test/test_checkpoint.js +4 -13
  62. package/dist/test/test_grpc.js +32 -41
  63. package/dist/trade/abstract_sui_dex_trade.d.ts +43 -0
  64. package/dist/trade/abstract_sui_dex_trade.js +380 -0
  65. package/dist/trade/abstract_sui_dex_trade_plus.d.ts +3 -1
  66. package/dist/trade/abstract_sui_dex_trade_plus.js +232 -212
  67. package/dist/trade/check/tx_result_checker.js +65 -75
  68. package/dist/trade/coin/index.d.ts +1 -0
  69. package/dist/trade/coin/index.js +17 -0
  70. package/dist/trade/coin/lua_scripts.d.ts +5 -0
  71. package/dist/trade/coin/lua_scripts.js +130 -0
  72. package/dist/trade/coin/types.d.ts +30 -0
  73. package/dist/trade/coin/types.js +2 -0
  74. package/dist/trade/coin/wallet_coin_ledger.d.ts +22 -0
  75. package/dist/trade/coin/wallet_coin_ledger.js +85 -0
  76. package/dist/trade/executor/central_executor.d.ts +72 -0
  77. package/dist/trade/executor/central_executor.js +240 -0
  78. package/dist/trade/executor/coin_cache.d.ts +21 -0
  79. package/dist/trade/executor/coin_cache.js +143 -0
  80. package/dist/trade/executor/coin_maintainer.d.ts +32 -0
  81. package/dist/trade/executor/coin_maintainer.js +123 -0
  82. package/dist/trade/executor/core_channel.d.ts +38 -0
  83. package/dist/trade/executor/core_channel.js +131 -0
  84. package/dist/trade/executor/data_channel.d.ts +27 -0
  85. package/dist/trade/executor/data_channel.js +2 -0
  86. package/dist/trade/executor/effects.d.ts +16 -0
  87. package/dist/trade/executor/effects.js +63 -0
  88. package/dist/trade/executor/executor_client.d.ts +13 -0
  89. package/dist/trade/executor/executor_client.js +55 -0
  90. package/dist/trade/executor/executor_protocol.d.ts +26 -0
  91. package/dist/trade/executor/executor_protocol.js +32 -0
  92. package/dist/trade/executor/executor_server.d.ts +8 -0
  93. package/dist/trade/executor/executor_server.js +33 -0
  94. package/dist/trade/executor/executor_ws_client.d.ts +13 -0
  95. package/dist/trade/executor/executor_ws_client.js +58 -0
  96. package/dist/trade/executor/grpc_channel.d.ts +14 -0
  97. package/dist/trade/executor/grpc_channel.js +73 -0
  98. package/dist/trade/executor/index.d.ts +7 -0
  99. package/dist/trade/executor/index.js +23 -0
  100. package/dist/trade/executor/json_rpc_channel.d.ts +14 -0
  101. package/dist/trade/executor/json_rpc_channel.js +77 -0
  102. package/dist/trade/index.d.ts +5 -1
  103. package/dist/trade/index.js +5 -1
  104. package/dist/trade/parse/sui_tx_parser.js +98 -81
  105. package/dist/trade/send_tx/index.js +34 -47
  106. package/dist/trade/swap/builders/bluefin.d.ts +9 -0
  107. package/dist/trade/swap/builders/bluefin.js +60 -0
  108. package/dist/trade/swap/builders/cetus_magma.d.ts +13 -0
  109. package/dist/trade/swap/builders/cetus_magma.js +52 -0
  110. package/dist/trade/swap/builders/momentum.d.ts +9 -0
  111. package/dist/trade/swap/builders/momentum.js +80 -0
  112. package/dist/trade/swap/dex_swap_config.d.ts +28 -0
  113. package/dist/trade/swap/dex_swap_config.js +40 -0
  114. package/dist/trade/swap/index.d.ts +7 -0
  115. package/dist/trade/swap/index.js +36 -0
  116. package/dist/trade/swap/types.d.ts +20 -0
  117. package/dist/trade/swap/types.js +2 -0
  118. package/dist/trade/test/test_parse_sui_tx_result.js +33 -44
  119. package/dist/trade/tx_result_channel.d.ts +7 -0
  120. package/dist/trade/tx_result_channel.js +7 -0
  121. package/dist/utils/decode.js +1 -2
  122. package/dist/utils/index.d.ts +1 -0
  123. package/dist/utils/index.js +1 -0
  124. package/dist/utils/trade_direction.d.ts +14 -0
  125. package/dist/utils/trade_direction.js +23 -0
  126. package/package.json +3 -2
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuiObjectReader = void 0;
4
+ const ledger_service_1 = require("./ledger-service");
5
+ const state_service_1 = require("./state-service");
6
+ const proto_value_1 = require("./proto_value");
7
+ const DF_PAGE_SIZE = 1000;
8
+ const DF_MAX_PAGES = 5000;
9
+ const BATCH_CHUNK = 50;
10
+ class SuiObjectReader {
11
+ constructor(connection) {
12
+ this.ledger = new ledger_service_1.LedgerService(connection);
13
+ this.state = new state_service_1.StateService(connection);
14
+ }
15
+ async getCoinDecimals(coinType) {
16
+ const r = await this.state.getCoinInfo(coinType);
17
+ const d = r?.metadata?.decimals;
18
+ if (d == null || !Number.isInteger(Number(d))) {
19
+ throw new Error(`[SuiObjectReader] 链上 decimals 读取失败 coinType=${coinType}(不兜底,须停下排查)`);
20
+ }
21
+ return Number(d);
22
+ }
23
+ async getObjectJson(objectId) {
24
+ const r = await this.ledger.getObject(objectId, ['object_id', 'object_type', 'json']);
25
+ const obj = r?.object;
26
+ if (!obj)
27
+ throw new Error(`[SuiObjectReader] object not found: ${objectId}`);
28
+ return { objectId: obj.object_id, objectType: obj.object_type, json: (0, proto_value_1.unwrapProtoValue)(obj.json) };
29
+ }
30
+ async batchGetObjectJsons(objectIds) {
31
+ const out = [];
32
+ for (let i = 0; i < objectIds.length; i += BATCH_CHUNK) {
33
+ const chunk = objectIds.slice(i, i + BATCH_CHUNK);
34
+ const r = await this.ledger.batchGetObjects(chunk, ['object_id', 'object_type', 'json']);
35
+ for (const res of (r?.objects || [])) {
36
+ const obj = res?.object;
37
+ if (obj)
38
+ out.push({ objectId: obj.object_id, objectType: obj.object_type, json: (0, proto_value_1.unwrapProtoValue)(obj.json) });
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ async listAllDynamicFieldObjectJsons(parent) {
44
+ const fieldIds = [];
45
+ let pageTokenHex;
46
+ for (let page = 0; page < DF_MAX_PAGES; page++) {
47
+ const resp = await this.state.listDynamicFields(parent, DF_PAGE_SIZE, pageTokenHex, ['field_id']);
48
+ for (const df of (resp?.dynamic_fields || [])) {
49
+ if (df.field_id)
50
+ fieldIds.push(df.field_id);
51
+ }
52
+ const next = resp?.next_page_token ?? resp?.nextPageToken;
53
+ if (!next || (typeof next === 'string' && next.length === 0))
54
+ break;
55
+ pageTokenHex = typeof next === 'string' ? next : Buffer.from(next).toString('hex');
56
+ }
57
+ return this.batchGetObjectJsons(fieldIds);
58
+ }
59
+ }
60
+ exports.SuiObjectReader = SuiObjectReader;
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.TransactionService = void 0;
13
4
  class TransactionService {
@@ -15,38 +6,36 @@ class TransactionService {
15
6
  this.connection = connection;
16
7
  this.transactionClient = connection.createServiceClient('transaction_execution_service.proto', 'TransactionExecutionService');
17
8
  }
18
- executeTransaction(transactionBytes, signatureBytes, fields) {
19
- return __awaiter(this, void 0, void 0, function* () {
20
- if (!fields || fields.length === 0) {
21
- fields = ['transaction', 'finality'];
22
- }
23
- return new Promise((resolve, reject) => {
24
- const request = {
25
- transaction: {
9
+ async executeTransaction(transactionBytes, signatureBytes, fields) {
10
+ if (!fields || fields.length === 0) {
11
+ fields = ['transaction', 'finality'];
12
+ }
13
+ return new Promise((resolve, reject) => {
14
+ const request = {
15
+ transaction: {
16
+ bcs: {
17
+ value: transactionBytes
18
+ }
19
+ },
20
+ signatures: [
21
+ {
22
+ scheme: 0,
26
23
  bcs: {
27
- value: transactionBytes
28
- }
29
- },
30
- signatures: [
31
- {
32
- scheme: 0,
33
- bcs: {
34
- value: signatureBytes
35
- }
24
+ value: signatureBytes
36
25
  }
37
- ],
38
- read_mask: {
39
- paths: fields
40
26
  }
41
- };
42
- this.transactionClient.ExecuteTransaction(request, this.connection.getMetadata(), (error, response) => {
43
- if (error)
44
- reject(error);
45
- else
46
- resolve(response);
47
- });
48
- console.log('executeTransaction, read_mask=', request.read_mask);
27
+ ],
28
+ read_mask: {
29
+ paths: fields
30
+ }
31
+ };
32
+ this.transactionClient.ExecuteTransaction(request, this.connection.getMetadata(), (error, response) => {
33
+ if (error)
34
+ reject(error);
35
+ else
36
+ resolve(response);
49
37
  });
38
+ console.log('executeTransaction, read_mask=', request.read_mask);
50
39
  });
51
40
  }
52
41
  }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,6 @@ export * from './type';
5
5
  export * from './grpc';
6
6
  export * from './utils';
7
7
  export * from './constants';
8
+ export * from './appconfig';
8
9
  export * from './trade';
9
10
  export * from './redis';
10
- export * from './rpc';
package/dist/index.js CHANGED
@@ -21,6 +21,6 @@ __exportStar(require("./type"), exports);
21
21
  __exportStar(require("./grpc"), exports);
22
22
  __exportStar(require("./utils"), exports);
23
23
  __exportStar(require("./constants"), exports);
24
+ __exportStar(require("./appconfig"), exports);
24
25
  __exportStar(require("./trade"), exports);
25
26
  __exportStar(require("./redis"), exports);
26
- __exportStar(require("./rpc"), exports);
@@ -0,0 +1,60 @@
1
+ import { AppConfig, QuoteDepthOutput, QuoteResultType, StandardPoolInfoType } from "@clonegod/ttd-core/dist";
2
+ import { SuiChainOps } from "./chain_ops";
3
+ import { SuiPoolEvent } from "./pool_event";
4
+ import { QuotePriceVerify, CheckSwapParams } from "./verify/quote_price_verify";
5
+ import { QuoteTrace } from "./quote_trace";
6
+ export interface PoolInitSummary {
7
+ protocol_type: string;
8
+ fee_bps: number;
9
+ tick_spacing?: number;
10
+ token0: string;
11
+ token1: string;
12
+ verifyContract?: string;
13
+ }
14
+ export interface QuoteBundle {
15
+ poolInfo: StandardPoolInfoType;
16
+ quote_amount_usd: number;
17
+ streamTimestamp: number;
18
+ quoteStartTime: number;
19
+ blockNumber: number;
20
+ txIndex?: number;
21
+ askQuote: QuoteResultType;
22
+ bidQuote: QuoteResultType;
23
+ txid: string;
24
+ source?: string;
25
+ depth?: QuoteDepthOutput;
26
+ priceMap?: Map<string, {
27
+ price: string;
28
+ } | undefined>;
29
+ trace?: QuoteTrace;
30
+ }
31
+ export declare abstract class AbstractDexQuote<C extends SuiChainOps = SuiChainOps> {
32
+ protected appConfig: AppConfig;
33
+ protected chain: C;
34
+ protected poolInfoMap: Map<string, StandardPoolInfoType>;
35
+ private poolLastQuoteTimeMap;
36
+ private lastPublished;
37
+ protected quotePriceVerify: QuotePriceVerify;
38
+ private readonly MIN_QUOTE_INTERVAL_MS;
39
+ constructor(appConfig: AppConfig, chain: C);
40
+ protected abstract readonly dexId: string;
41
+ protected abstract readonly debouncedEventTypes: string[];
42
+ protected abstract loadPool(poolInfo: StandardPoolInfoType): Promise<PoolInitSummary>;
43
+ protected abstract quoteV1(poolInfo: StandardPoolInfoType, isBuy: boolean): Promise<QuoteResultType>;
44
+ protected abstract quoteV2(poolInfo: StandardPoolInfoType, isBuy: boolean): Promise<QuoteResultType>;
45
+ protected refreshStateFromEvent(_poolInfo: StandardPoolInfoType, _evt: SuiPoolEvent): Promise<void>;
46
+ protected calculateDepth(_poolInfo: StandardPoolInfoType, _poolAddress: string, _priceMap: Map<string, {
47
+ price: string;
48
+ } | undefined>): Promise<QuoteDepthOutput | undefined>;
49
+ protected buildSwapVerify(_poolInfo: StandardPoolInfoType, _evt: SuiPoolEvent): CheckSwapParams | null;
50
+ init(poolList: StandardPoolInfoType[]): Promise<void>;
51
+ protected beforeLoadPools(): Promise<void>;
52
+ protected getQuoteAmountUsd(poolInfo: StandardPoolInfoType): number;
53
+ private registerEventHandlers;
54
+ private assertValidBlockNumber;
55
+ private isStaleUpdate;
56
+ private handleBlockUpdateEvent;
57
+ private calculateQuote;
58
+ private calculateQuoteForPool;
59
+ protected publishQuote(result: QuoteBundle): void;
60
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractDexQuote = void 0;
4
+ const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const quote_price_verify_1 = require("./verify/quote_price_verify");
6
+ const quote_amount_1 = require("./quote_amount");
7
+ const quote_trace_1 = require("./quote_trace");
8
+ class AbstractDexQuote {
9
+ constructor(appConfig, chain) {
10
+ this.poolInfoMap = new Map();
11
+ this.poolLastQuoteTimeMap = new Map();
12
+ this.lastPublished = new Map();
13
+ this.quotePriceVerify = new quote_price_verify_1.QuotePriceVerify();
14
+ this.MIN_QUOTE_INTERVAL_MS = Math.max(3000, parseInt(process.env.MIN_QUOTE_INTERVAL_MS || '10000', 10) || 10000);
15
+ this.appConfig = appConfig;
16
+ this.chain = chain;
17
+ }
18
+ async refreshStateFromEvent(_poolInfo, _evt) { }
19
+ async calculateDepth(_poolInfo, _poolAddress, _priceMap) { return undefined; }
20
+ buildSwapVerify(_poolInfo, _evt) { return null; }
21
+ async init(poolList) {
22
+ (0, dist_1.log_info)(`初始化 ${this.dexId} Quote,共 ${poolList.length} 个池...`);
23
+ for (const poolInfo of poolList) {
24
+ if (!this.chain.isValidPoolAddress(poolInfo.pool_address)) {
25
+ (0, dist_1.log_warn)(`跳过无效的池地址: ${poolInfo.pool_address}`, '');
26
+ continue;
27
+ }
28
+ this.poolInfoMap.set(poolInfo.pool_address, poolInfo);
29
+ }
30
+ await this.beforeLoadPools();
31
+ await Promise.all(Array.from(this.poolInfoMap.values()).map(async (p) => {
32
+ const summary = await this.loadPool(p);
33
+ if (summary.verifyContract)
34
+ await this.chain.assertContractDeployed(summary.verifyContract);
35
+ (0, dist_1.log_info)(`[QUOTE INIT] ${p.pool_name} (${p.pool_address}) dex=${this.dexId}`, { ...summary });
36
+ }));
37
+ this.registerEventHandlers();
38
+ (0, dist_1.log_info)(`${this.dexId} Quote 初始化完成,${this.poolInfoMap.size} 个有效池`);
39
+ }
40
+ async beforeLoadPools() { }
41
+ getQuoteAmountUsd(poolInfo) { return (0, quote_amount_1.getQuoteAmountUsd)(poolInfo); }
42
+ registerEventHandlers() {
43
+ this.chain.subscribeNewBlock((raw) => this.handleBlockUpdateEvent(raw));
44
+ this.chain.subscribePoolEvents(this.dexId, Array.from(this.poolInfoMap.values()), async (evt) => {
45
+ const { bundle, verifyLog } = await this.calculateQuote(evt);
46
+ if (bundle)
47
+ this.publishQuote(bundle);
48
+ if (verifyLog)
49
+ (0, dist_1.log_info)(verifyLog);
50
+ });
51
+ }
52
+ assertValidBlockNumber(blockNumber, ctx) {
53
+ if (!Number.isInteger(blockNumber) || blockNumber <= 0) {
54
+ throw new Error(`[block-number] ${ctx}: 非法 blockNumber=${blockNumber},每条数据源必须携带真实区块号`);
55
+ }
56
+ }
57
+ isStaleUpdate(poolAddr, block, txIndex) {
58
+ const last = this.lastPublished.get(poolAddr);
59
+ return !!last && (block < last.block || (block === last.block && txIndex <= last.txIndex));
60
+ }
61
+ async handleBlockUpdateEvent(eventData) {
62
+ const { blockNumber, blockTime } = JSON.parse(eventData);
63
+ this.assertValidBlockNumber(blockNumber, `${this.dexId} block-event`);
64
+ for (const poolInfo of this.poolInfoMap.values()) {
65
+ const poolAddress = poolInfo.pool_address;
66
+ const now = Date.now();
67
+ const last = this.poolLastQuoteTimeMap.get(poolAddress) || 0;
68
+ if (now - last >= this.MIN_QUOTE_INTERVAL_MS) {
69
+ this.poolLastQuoteTimeMap.set(poolAddress, now);
70
+ const result = await this.calculateQuoteForPool(poolInfo, blockTime, blockNumber);
71
+ if (result)
72
+ this.publishQuote(result);
73
+ }
74
+ }
75
+ }
76
+ async calculateQuote(evt) {
77
+ const poolInfo = this.poolInfoMap.get(evt.pool_address);
78
+ if (!poolInfo)
79
+ return { bundle: null };
80
+ const eventType = evt.type?.toLowerCase();
81
+ let verifyLog;
82
+ if (eventType === 'swap') {
83
+ const v = this.buildSwapVerify(poolInfo, evt);
84
+ if (v)
85
+ verifyLog = this.quotePriceVerify.checkSwap(v) || undefined;
86
+ if (!this.debouncedEventTypes.includes('swap'))
87
+ return { bundle: null, verifyLog };
88
+ }
89
+ this.assertValidBlockNumber(evt.blockNumber, `${this.dexId} ${poolInfo.pool_name} ${eventType}`);
90
+ await this.refreshStateFromEvent(poolInfo, evt);
91
+ if (Number.isInteger(evt.txIndex) && this.isStaleUpdate(poolInfo.pool_address, evt.blockNumber, evt.txIndex))
92
+ return { bundle: null, verifyLog };
93
+ const bundle = await this.calculateQuoteForPool(poolInfo, evt.blockTime, evt.blockNumber, evt);
94
+ return { bundle, verifyLog };
95
+ }
96
+ async calculateQuoteForPool(poolInfo, streamTimestamp, blockNumber, evt) {
97
+ const { pool_address } = poolInfo;
98
+ const eventDriven = !!evt && this.debouncedEventTypes.includes(evt.type?.toLowerCase());
99
+ const source = eventDriven ? 'local:v2' : 'rpc:v1';
100
+ const trace = new quote_trace_1.QuoteTrace(poolInfo.pool_name, pool_address, blockNumber, source);
101
+ trace.mark('trigger');
102
+ let stage = 'compute';
103
+ try {
104
+ const quote_amount_usd = (0, quote_amount_1.getQuoteAmountUsd)(poolInfo);
105
+ const quoteStartTime = Date.now();
106
+ const txid = evt ? (evt.txHash || 'block') : 'block';
107
+ const txIndex = evt?.txIndex;
108
+ let askQuote, bidQuote;
109
+ if (eventDriven) {
110
+ ;
111
+ [askQuote, bidQuote] = await Promise.all([this.quoteV2(poolInfo, true), this.quoteV2(poolInfo, false)]);
112
+ }
113
+ else {
114
+ ;
115
+ [askQuote, bidQuote] = await Promise.all([this.quoteV1(poolInfo, true), this.quoteV1(poolInfo, false)]);
116
+ }
117
+ trace.mark('compute');
118
+ const askN = Number(askQuote.price), bidN = Number(bidQuote.price);
119
+ if (askN > 0 && bidN > 0 && askN < bidN) {
120
+ throw new Error(`ask(${askN}) < bid(${bidN}) — 异常价差,丢弃该报价`);
121
+ }
122
+ this.poolLastQuoteTimeMap.set(pool_address, Date.now());
123
+ stage = 'depth';
124
+ const t0a = poolInfo.tokenA?.address, t1a = poolInfo.tokenB?.address;
125
+ const priceMap = (t0a && t1a) ? await this.chain.loadTokenPrices([t0a, t1a]) : new Map();
126
+ const depth = await this.calculateDepth(poolInfo, pool_address, priceMap);
127
+ trace.mark('depth');
128
+ trace.set('ask', askQuote.price);
129
+ trace.set('bid', bidQuote.price);
130
+ trace.set('tiers', depth?.ask?.tiers?.length ?? 0);
131
+ trace.set('quote_amount_usd', quote_amount_usd);
132
+ trace.set('txid', txid);
133
+ return { poolInfo, quote_amount_usd, streamTimestamp, quoteStartTime, blockNumber, txIndex, askQuote, bidQuote, txid, source, depth, priceMap, trace };
134
+ }
135
+ catch (error) {
136
+ trace.markError(stage, error instanceof Error ? error.message : String(error));
137
+ trace.flush();
138
+ return null;
139
+ }
140
+ }
141
+ publishQuote(result) {
142
+ const poolAddr = result.poolInfo.pool_address;
143
+ const isV2 = result.source?.endsWith(':v2');
144
+ let shouldPush = true;
145
+ if (result.source?.endsWith(':v1')) {
146
+ shouldPush = true;
147
+ }
148
+ else {
149
+ const incomingTx = result.txIndex;
150
+ if (!Number.isInteger(incomingTx)) {
151
+ throw new Error(`[publish] ${result.source} ${poolAddr}: 缺/非法 txIndex=${incomingTx},事件源必须提供块内序号`);
152
+ }
153
+ const tx = incomingTx;
154
+ shouldPush = !this.isStaleUpdate(poolAddr, result.blockNumber, tx);
155
+ if (shouldPush)
156
+ this.lastPublished.set(poolAddr, { block: result.blockNumber, txIndex: tx });
157
+ }
158
+ if (shouldPush) {
159
+ (0, dist_1.on_quote_response)({
160
+ appConfig: this.appConfig, poolInfo: result.poolInfo, quoteAmountUsd: result.quote_amount_usd,
161
+ streamTime: result.streamTimestamp, quoteStartTime: result.quoteStartTime, blockNumber: result.blockNumber,
162
+ quotes: [result.askQuote, result.bidQuote], txid: result.txid, source: result.source, depth: result.depth,
163
+ });
164
+ }
165
+ result.trace?.set('published', shouldPush);
166
+ result.trace?.mark('publish');
167
+ const candidateTiers = isV2 && result.depth ? {
168
+ ask_tiers: result.depth.ask.tiers.map(t => ({ pct: t.pct, price: t.price, amount: t.amount, amount_in: t.amount_in })),
169
+ bid_tiers: result.depth.bid.tiers.map(t => ({ pct: t.pct, price: t.price, amount: t.amount, amount_in: t.amount_in })),
170
+ } : {};
171
+ (0, dist_1.report_quote_candidate)({
172
+ pool_address: poolAddr, pair: result.poolInfo.pair, dex_id: result.poolInfo.dex_id,
173
+ source: result.source || '', block_number: result.blockNumber,
174
+ ask_price: result.askQuote.price, bid_price: result.bidQuote.price, ...candidateTiers,
175
+ });
176
+ const quoteId = result.txid?.slice(0, 10) || `blk:${result.blockNumber}`;
177
+ const tiersArg = isV2 ? { askTiers: result.depth?.ask?.tiers, bidTiers: result.depth?.bid?.tiers } : undefined;
178
+ const t0a = result.poolInfo.tokenA?.address, t1a = result.poolInfo.tokenB?.address;
179
+ const p0 = t0a ? Number(result.priceMap?.get(t0a.toLowerCase())?.price) || 0 : 0;
180
+ const p1 = t1a ? Number(result.priceMap?.get(t1a.toLowerCase())?.price) || 0 : 0;
181
+ this.quotePriceVerify.cacheQuote(poolAddr, quoteId, result.source || '', Number(result.askQuote.price), Number(result.bidQuote.price), result.blockNumber, result.quote_amount_usd, p0, p1, tiersArg);
182
+ result.trace?.mark('verify');
183
+ result.trace?.flush();
184
+ }
185
+ }
186
+ exports.AbstractDexQuote = AbstractDexQuote;
@@ -0,0 +1,17 @@
1
+ import { AppConfig, CHAIN_ID, StandardPoolInfoType } from "@clonegod/ttd-core/dist";
2
+ import { TokenPriceCache } from "./pricing/token_price_cache";
3
+ import { SuiPoolEvent } from "./pool_event";
4
+ export declare class SuiChainOps {
5
+ private readonly appConfig;
6
+ private readonly tokenPriceCache;
7
+ readonly chainId = CHAIN_ID.SUI;
8
+ private readonly poolEventChannel;
9
+ constructor(appConfig: AppConfig, tokenPriceCache: TokenPriceCache);
10
+ isValidPoolAddress(addr: string): boolean;
11
+ assertContractDeployed(_contractAddress: string): Promise<void>;
12
+ loadTokenPrices(addresses: string[]): Promise<Map<string, {
13
+ price: string;
14
+ } | undefined>>;
15
+ subscribeNewBlock(handler: (raw: string) => void): void;
16
+ subscribePoolEvents(dexId: string, pools: StandardPoolInfoType[], handler: (evt: SuiPoolEvent) => void): void;
17
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuiChainOps = void 0;
4
+ const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const pool_event_1 = require("./pool_event");
6
+ class SuiChainOps {
7
+ constructor(appConfig, tokenPriceCache) {
8
+ this.appConfig = appConfig;
9
+ this.tokenPriceCache = tokenPriceCache;
10
+ this.chainId = dist_1.CHAIN_ID.SUI;
11
+ this.poolEventChannel = (0, pool_event_1.get_sui_pool_event_channel)(dist_1.CHAIN_ID.SUI);
12
+ }
13
+ isValidPoolAddress(addr) {
14
+ return /^0x[0-9a-fA-F]{64}$/.test(addr);
15
+ }
16
+ async assertContractDeployed(_contractAddress) {
17
+ }
18
+ async loadTokenPrices(addresses) {
19
+ const out = new Map();
20
+ await Promise.all(addresses.map(async (addr) => {
21
+ try {
22
+ const p = await this.tokenPriceCache.getTokenPrice(addr);
23
+ out.set(addr.toLowerCase(), { price: p.price });
24
+ }
25
+ catch {
26
+ out.set(addr.toLowerCase(), undefined);
27
+ }
28
+ }));
29
+ return out;
30
+ }
31
+ subscribeNewBlock(handler) {
32
+ this.appConfig.arb_event_subscriber.subscribe_new_block(this.chainId, handler);
33
+ }
34
+ subscribePoolEvents(dexId, pools, handler) {
35
+ const dex = dexId.toUpperCase();
36
+ const poolSet = new Set(pools.map(p => p.pool_address.toLowerCase()));
37
+ this.appConfig.arb_cache.redis_cmd.subscribe(this.poolEventChannel, (message) => {
38
+ try {
39
+ const evt = JSON.parse(message);
40
+ if (evt.dex_id?.toUpperCase() !== dex)
41
+ return;
42
+ if (!poolSet.has(evt.pool_address.toLowerCase()))
43
+ return;
44
+ handler(evt);
45
+ }
46
+ catch (e) {
47
+ (0, dist_1.log_warn)(`[SuiChainOps] bad pool event message`, message);
48
+ }
49
+ });
50
+ }
51
+ }
52
+ exports.SuiChainOps = SuiChainOps;
@@ -1 +1,8 @@
1
1
  export * from './pricing';
2
+ export * from './pool_event';
3
+ export * from './chain_ops';
4
+ export * from './quote_trace';
5
+ export * from './quote_amount';
6
+ export * from './verify';
7
+ export * from './tick';
8
+ export * from './abstract_dex_quote';
@@ -15,3 +15,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./pricing"), exports);
18
+ __exportStar(require("./pool_event"), exports);
19
+ __exportStar(require("./chain_ops"), exports);
20
+ __exportStar(require("./quote_trace"), exports);
21
+ __exportStar(require("./quote_amount"), exports);
22
+ __exportStar(require("./verify"), exports);
23
+ __exportStar(require("./tick"), exports);
24
+ __exportStar(require("./abstract_dex_quote"), exports);
@@ -0,0 +1,21 @@
1
+ export type SuiEventKind = 'swap' | 'add' | 'remove';
2
+ export interface SuiPoolEvent {
3
+ pool_address: string;
4
+ dex_id: string;
5
+ pair: string;
6
+ type: string;
7
+ blockNumber: number;
8
+ txIndex: number;
9
+ txHash: string;
10
+ blockTime: number;
11
+ eventKind?: SuiEventKind;
12
+ afterSqrtPrice?: string;
13
+ tick?: number;
14
+ liquidity?: string;
15
+ tickLower?: number;
16
+ tickUpper?: number;
17
+ liquidityDelta?: string;
18
+ amount0?: string;
19
+ amount1?: string;
20
+ }
21
+ export declare function get_sui_pool_event_channel(chain_id: string): string;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get_sui_pool_event_channel = get_sui_pool_event_channel;
4
+ function get_sui_pool_event_channel(chain_id) {
5
+ return `${chain_id}:pool:events`;
6
+ }
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.TokenPriceCache = void 0;
13
4
  const dist_1 = require("@clonegod/ttd-core/dist");
@@ -17,26 +8,24 @@ class TokenPriceCache {
17
8
  this.PRICE_CACHE_TIMEOUT_MILLS = parseInt(process.env.PRICE_CACHE_TIMEOUT_MILLS || Number(1000 * 60 * 60 * 1).toString());
18
9
  (0, dist_1.log_info)(`代币价格缓存超时时间: ${this.PRICE_CACHE_TIMEOUT_MILLS} ms`);
19
10
  }
20
- getTokenPrice(tokenAddress) {
21
- return __awaiter(this, void 0, void 0, function* () {
22
- let tokenParts = tokenAddress.split('::');
23
- tokenParts[0] = tokenParts[0].toLowerCase();
24
- tokenAddress = tokenParts.join('::');
25
- const now = Date.now();
26
- const cachedData = this.tokenPriceCache.get(tokenAddress);
27
- if (cachedData && (now - cachedData.timestamp) < this.PRICE_CACHE_TIMEOUT_MILLS) {
28
- (0, dist_1.log_debug)(`use cached token price: ${tokenAddress}, price: ${cachedData.price}`, '');
29
- return cachedData;
30
- }
31
- const priceMap = yield (0, dist_1.get_sui_token_price_info)([tokenAddress]);
32
- const tokenPrice = priceMap.get(tokenAddress);
33
- if (!tokenPrice || !tokenPrice.price || Number(tokenPrice.price) <= 0) {
34
- throw new Error(`无法获取代币 ${tokenAddress} 的有效USD价格`);
35
- }
36
- const newPrice = { price: tokenPrice.price, timestamp: now };
37
- this.tokenPriceCache.set(tokenAddress, newPrice);
38
- return newPrice;
39
- });
11
+ async getTokenPrice(tokenAddress) {
12
+ let tokenParts = tokenAddress.split('::');
13
+ tokenParts[0] = tokenParts[0].toLowerCase();
14
+ tokenAddress = tokenParts.join('::');
15
+ const now = Date.now();
16
+ const cachedData = this.tokenPriceCache.get(tokenAddress);
17
+ if (cachedData && (now - cachedData.timestamp) < this.PRICE_CACHE_TIMEOUT_MILLS) {
18
+ (0, dist_1.log_debug)(`use cached token price: ${tokenAddress}, price: ${cachedData.price}`, '');
19
+ return cachedData;
20
+ }
21
+ const priceMap = await (0, dist_1.get_sui_token_price_info)([tokenAddress]);
22
+ const tokenPrice = priceMap.get(tokenAddress);
23
+ if (!tokenPrice || !tokenPrice.price || Number(tokenPrice.price) <= 0) {
24
+ throw new Error(`无法获取代币 ${tokenAddress} 的有效USD价格`);
25
+ }
26
+ const newPrice = { price: tokenPrice.price, timestamp: now };
27
+ this.tokenPriceCache.set(tokenAddress, newPrice);
28
+ return newPrice;
40
29
  }
41
30
  }
42
31
  exports.TokenPriceCache = TokenPriceCache;
@@ -0,0 +1,4 @@
1
+ import { StandardPoolInfoType } from '@clonegod/ttd-core';
2
+ import Decimal from 'decimal.js';
3
+ export declare function getQuoteAmountUsd(poolInfo: StandardPoolInfoType): number;
4
+ export declare function usdToTokenUiAmount(amountInUsd: number, tokenAddress: string): Promise<Decimal>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getQuoteAmountUsd = getQuoteAmountUsd;
7
+ exports.usdToTokenUiAmount = usdToTokenUiAmount;
8
+ const ttd_core_1 = require("@clonegod/ttd-core");
9
+ const decimal_js_1 = __importDefault(require("decimal.js"));
10
+ function getQuoteAmountUsd(poolInfo) {
11
+ const envValue = Number(process.env.QUOTE_AMOUNT_USD);
12
+ if (envValue > 0) {
13
+ return envValue;
14
+ }
15
+ return poolInfo.quote_amount_usd;
16
+ }
17
+ async function usdToTokenUiAmount(amountInUsd, tokenAddress) {
18
+ const priceMap = await (0, ttd_core_1.get_sui_token_price_info)([tokenAddress]);
19
+ const price = priceMap.get(tokenAddress)?.price;
20
+ if (!price || price === '0') {
21
+ throw new Error(`price not available for ${tokenAddress}`);
22
+ }
23
+ return new decimal_js_1.default(amountInUsd).div(new decimal_js_1.default(price));
24
+ }
@@ -0,0 +1,16 @@
1
+ export declare class QuoteTrace {
2
+ readonly poolName: string;
3
+ readonly poolAddress: string;
4
+ blockNumber: number;
5
+ source: string;
6
+ private startTime;
7
+ private marks;
8
+ private data;
9
+ private error;
10
+ constructor(poolName: string, poolAddress: string, blockNumber?: number, source?: string);
11
+ mark(name: string): void;
12
+ set(key: string, value: any): void;
13
+ markError(stage: string, message: string): void;
14
+ private buildTimeline;
15
+ flush(): void;
16
+ }