@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
@@ -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.TransactionResultChecker = void 0;
13
4
  const dist_1 = require("@clonegod/ttd-core/dist");
@@ -21,50 +12,51 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
21
12
  this.leadgerService = ledgerService;
22
13
  }
23
14
  isValidTransactionResult(txReceipt) {
24
- var _a, _b, _c, _d;
25
- const success = (_c = (_b = (_a = txReceipt.transaction) === null || _a === void 0 ? void 0 : _a.effects) === null || _b === void 0 ? void 0 : _b.status) === null || _c === void 0 ? void 0 : _c.success;
26
- const balance_changes = (_d = txReceipt.transaction) === null || _d === void 0 ? void 0 : _d.balance_changes;
27
- return !success || (success && balance_changes && balance_changes.length > 0);
15
+ const success = txReceipt.transaction?.effects?.status?.success;
16
+ const balance_changes = txReceipt.transaction?.balance_changes;
17
+ if (success === true)
18
+ return !!(balance_changes && balance_changes.length > 0);
19
+ if (success === false)
20
+ return true;
21
+ return false;
28
22
  }
29
- check_tx_result_interval() {
30
- return __awaiter(this, void 0, void 0, function* () {
31
- const check_start_time = Date.now();
32
- const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
33
- const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '30000');
34
- if (check_interval >= check_timeout) {
35
- return;
36
- }
37
- const intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
38
- this.check_count += 1;
39
- (0, dist_1.log_info)(`check transaction start: seq=[${this.check_count}], txhash= ${this.txid}, trace_id=${this.trace_id}`);
40
- try {
41
- if (Date.now() - check_start_time < check_timeout) {
42
- let txReceipt = yield this.leadgerService.getTransaction(this.txid, ['*']);
43
- if (!txReceipt || (0, dist_1.isEmpty)(txReceipt)) {
44
- return;
45
- }
46
- txReceipt['transaction']['signatures'] = [];
47
- txReceipt = (0, index_1.decodeBytes)(txReceipt);
48
- (0, dist_1.log_info)(`Received transaction result via polling: ${this.txid}`);
49
- if (this.isValidTransactionResult(txReceipt)) {
50
- clearInterval(intervalId);
51
- yield this.processTransactionResult(txReceipt, 'interval');
52
- }
23
+ async check_tx_result_interval() {
24
+ const check_start_time = Date.now();
25
+ const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
26
+ const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '30000');
27
+ if (check_interval >= check_timeout) {
28
+ return;
29
+ }
30
+ const intervalId = setInterval(async () => {
31
+ this.check_count += 1;
32
+ (0, dist_1.log_info)(`check transaction start: seq=[${this.check_count}], txhash= ${this.txid}, trace_id=${this.trace_id}`);
33
+ try {
34
+ if (Date.now() - check_start_time < check_timeout) {
35
+ let txReceipt = await this.leadgerService.getTransaction(this.txid, ['*']);
36
+ if (!txReceipt || (0, dist_1.isEmpty)(txReceipt)) {
37
+ return;
53
38
  }
54
- else {
39
+ txReceipt['transaction']['signatures'] = [];
40
+ txReceipt = (0, index_1.decodeBytes)(txReceipt);
41
+ (0, dist_1.log_info)(`Received transaction result via polling: ${this.txid}`);
42
+ if (this.isValidTransactionResult(txReceipt)) {
55
43
  clearInterval(intervalId);
44
+ await this.processTransactionResult(txReceipt, 'interval');
56
45
  }
57
46
  }
58
- catch (err) {
47
+ else {
59
48
  clearInterval(intervalId);
60
- (0, dist_1.log_error)('parse transaction error!', err);
61
49
  }
62
- }), check_interval);
63
- this.intervalId = intervalId;
64
- });
50
+ }
51
+ catch (err) {
52
+ clearInterval(intervalId);
53
+ (0, dist_1.log_error)('parse transaction error!', err);
54
+ }
55
+ }, check_interval);
56
+ this.intervalId = intervalId;
65
57
  }
66
58
  on_subscibe_transaction() {
67
- this.event_emitter.once(`SUI_TX_RESULT_${this.txid}`, (response) => __awaiter(this, void 0, void 0, function* () {
59
+ this.event_emitter.once(`SUI_TX_RESULT_${this.txid}`, async (response) => {
68
60
  (0, dist_1.log_info)(`receive tx result notification, txid=${this.txid}`);
69
61
  if (response.transaction) {
70
62
  this.processTransactionResult(response, 'grpc');
@@ -73,7 +65,7 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
73
65
  for (let i = 1; i <= 6; i++) {
74
66
  (0, dist_1.log_info)(`re-fetch tx result by ledgerService start, i=${i}, txid=${this.txid}`);
75
67
  try {
76
- let txReceipt = yield this.leadgerService.getTransaction(this.txid, ['*']);
68
+ let txReceipt = await this.leadgerService.getTransaction(this.txid, ['*']);
77
69
  if (!txReceipt || (0, dist_1.isEmpty)(txReceipt)) {
78
70
  console.log('txReceipt is empty', txReceipt);
79
71
  continue;
@@ -90,38 +82,36 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
90
82
  catch (error) {
91
83
  (0, dist_1.log_warn)(`re-fetch tx result by ledgerService failed, i=${i}, txid=${this.txid}, error=${error.message}`);
92
84
  }
93
- yield (0, dist_1.sleep)(1000);
85
+ await (0, dist_1.sleep)(1000);
94
86
  }
95
- }));
96
- }
97
- processTransactionResult(txReceipt, source) {
98
- return __awaiter(this, void 0, void 0, function* () {
99
- if (this.trade_result_already_processed) {
100
- (0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by ${source} check!`);
101
- return;
102
- }
103
- const swap_detail = yield this.transactionParser.parseTransaction(txReceipt, this.pool_info);
104
- let trade_result = this.map_swap_result_to_tx_result(swap_detail);
105
- this.trade_result_already_processed = true;
106
- if (this.intervalId) {
107
- clearInterval(this.intervalId);
108
- this.intervalId = null;
109
- }
110
- if (trade_result.success) {
111
- this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
112
- }
113
- else {
114
- this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
115
- }
116
- if (source === 'interval') {
117
- console.log('--------------------- Transaction Result from Polling ---------------------');
118
- }
119
- else {
120
- console.log('--------------------- Transaction Result from GRPC ---------------------');
121
- }
122
- console.log(JSON.stringify(trade_result, null, 2));
123
- console.log('-----------------------------------------------------------------------------');
124
87
  });
125
88
  }
89
+ async processTransactionResult(txReceipt, source) {
90
+ if (this.trade_result_already_processed) {
91
+ (0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by ${source} check!`);
92
+ return;
93
+ }
94
+ const swap_detail = await this.transactionParser.parseTransaction(txReceipt, this.pool_info);
95
+ let trade_result = this.map_swap_result_to_tx_result(swap_detail);
96
+ this.trade_result_already_processed = true;
97
+ if (this.intervalId) {
98
+ clearInterval(this.intervalId);
99
+ this.intervalId = null;
100
+ }
101
+ if (trade_result.success) {
102
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
103
+ }
104
+ else {
105
+ this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
106
+ }
107
+ if (source === 'interval') {
108
+ console.log('--------------------- Transaction Result from Polling ---------------------');
109
+ }
110
+ else {
111
+ console.log('--------------------- Transaction Result from GRPC ---------------------');
112
+ }
113
+ console.log(JSON.stringify(trade_result, null, 2));
114
+ console.log('-----------------------------------------------------------------------------');
115
+ }
126
116
  }
127
117
  exports.TransactionResultChecker = TransactionResultChecker;
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -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("./types"), exports);
@@ -0,0 +1,5 @@
1
+ export declare const LUA_ACQUIRE = "\nlocal availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]\nlocal amount = tonumber(ARGV[1])\nlocal txTag, coinType, now = ARGV[2], ARGV[3], ARGV[4]\nlocal maxCoins = tonumber(ARGV[5])\nlocal ids = redis.call('ZRANGE', availKey, 0, -1)\nlocal picked, sum = {}, 0\nlocal i = #ids\nwhile i >= 1 do\n local id = ids[i]\n local m = redis.call('HGET', metaKey, id)\n if m then\n local meta = cjson.decode(m)\n picked[#picked+1] = { id = id, v = meta.v, d = meta.d, b = meta.b }\n sum = sum + tonumber(meta.b)\n if sum >= amount then break end\n if #picked >= maxCoins then break end\n end\n i = i - 1\nend\nif sum < amount then return redis.error_reply('INSUFFICIENT_COINS') end\nlocal out = {}\nfor _, p in ipairs(picked) do\n redis.call('ZREM', availKey, p.id)\n redis.call('HDEL', metaKey, p.id)\n redis.call('HSET', inflightKey, p.id, cjson.encode({ tag = txTag, ct = coinType, t = now, v = p.v, d = p.d, b = p.b }))\n out[#out+1] = cjson.encode({ objectId = p.id, version = p.v, digest = p.d, balance = p.b })\nend\nreturn out\n";
2
+ export declare const LUA_ACQUIRE_GAS = "\nlocal availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]\nlocal minGas = tonumber(ARGV[1])\nlocal txTag, now = ARGV[2], ARGV[3]\nlocal ids = redis.call('ZRANGEBYSCORE', availKey, minGas, '+inf', 'LIMIT', 0, 1)\nif #ids == 0 then return redis.error_reply('NO_GAS_COIN') end\nlocal id = ids[1]\nlocal m = redis.call('HGET', metaKey, id)\nif not m then return redis.error_reply('NO_GAS_META') end\nlocal meta = cjson.decode(m)\nredis.call('ZREM', availKey, id)\nredis.call('HDEL', metaKey, id)\nredis.call('HSET', inflightKey, id, cjson.encode({ tag = txTag, ct = 'gas', t = now, v = meta.v, d = meta.d, b = meta.b }))\nreturn cjson.encode({ objectId = id, version = meta.v, digest = meta.d, balance = meta.b })\n";
3
+ export declare const LUA_COMMIT = "\nlocal inflightKey = KEYS[1]\nlocal base, gasBase, wallet = ARGV[1], ARGV[2], ARGV[3]\nlocal changes = cjson.decode(ARGV[4])\nfor _, c in ipairs(changes) do\n local availKey, metaKey\n if c.gas then\n availKey = gasBase..':avail:{'..wallet..'}'\n metaKey = gasBase..':meta:{'..wallet..'}'\n else\n availKey = base..':avail:{'..wallet..'}:'..c.coinType\n metaKey = base..':meta:{'..wallet..'}:'..c.coinType\n end\n redis.call('HDEL', inflightKey, c.objectId)\n if c.kind == 'deleted' then\n redis.call('ZREM', availKey, c.objectId)\n redis.call('HDEL', metaKey, c.objectId)\n else\n redis.call('ZADD', availKey, tonumber(c.b), c.objectId)\n redis.call('HSET', metaKey, c.objectId, cjson.encode({ v = c.v, d = c.d, b = c.b }))\n end\nend\nreturn 'OK'\n";
4
+ export declare const LUA_ABORT = "\nlocal inflightKey = KEYS[1]\nlocal base, gasBase, wallet, txTag, consumed = ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]\nlocal all = redis.call('HGETALL', inflightKey)\nfor i = 1, #all, 2 do\n local objectId, info = all[i], cjson.decode(all[i+1])\n if info.tag == txTag then\n redis.call('HDEL', inflightKey, objectId)\n if consumed == '0' then\n local availKey, metaKey\n if info.ct == 'gas' then\n availKey = gasBase..':avail:{'..wallet..'}'\n metaKey = gasBase..':meta:{'..wallet..'}'\n else\n availKey = base..':avail:{'..wallet..'}:'..info.ct\n metaKey = base..':meta:{'..wallet..'}:'..info.ct\n end\n redis.call('ZADD', availKey, tonumber(info.b), objectId)\n redis.call('HSET', metaKey, objectId, cjson.encode({ v = info.v, d = info.d, b = info.b }))\n end\n end\nend\nreturn 'OK'\n";
5
+ export declare const LUA_RECONCILE = "\nlocal availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]\nlocal chain = cjson.decode(ARGV[1])\nlocal ttl, now = tonumber(ARGV[2]), tonumber(ARGV[3])\nlocal chainSet = {}\nfor _, o in ipairs(chain) do chainSet[o.objectId] = o end\n-- \u5904\u7406 inflight\uFF1A\u8D85\u65F6\u79FB\u51FA\uFF08\u89C6\u4F5C\u4E22\u5931\uFF0C\u94FE\u771F\u503C\u4F1A\u91CD\u52A0\uFF09\uFF1B\u5426\u5219\u8BB0\u5165 inflightSet \u4FDD\u62A4\nlocal inflight = redis.call('HGETALL', inflightKey)\nlocal inflightSet = {}\nfor i = 1, #inflight, 2 do\n local oid, info = inflight[i], cjson.decode(inflight[i+1])\n if (now - tonumber(info.t)) > ttl then\n redis.call('HDEL', inflightKey, oid)\n else\n inflightSet[oid] = true\n end\nend\n-- \u5220\u9664 avail \u4E2D\u94FE\u4E0A\u5DF2\u65E0\u4E14\u975E inflight \u7684\nlocal availIds = redis.call('ZRANGE', availKey, 0, -1)\nfor _, id in ipairs(availIds) do\n if not chainSet[id] then\n redis.call('ZREM', availKey, id)\n redis.call('HDEL', metaKey, id)\n end\nend\n-- \u52A0\u5165/\u66F4\u65B0\u94FE\u4E0A\u6709\u4E14\u975E inflight \u7684\nfor _, o in ipairs(chain) do\n if not inflightSet[o.objectId] then\n redis.call('ZADD', availKey, tonumber(o.b), o.objectId)\n redis.call('HSET', metaKey, o.objectId, cjson.encode({ v = o.v, d = o.d, b = o.b }))\n end\nend\nreturn 'OK'\n";
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LUA_RECONCILE = exports.LUA_ABORT = exports.LUA_COMMIT = exports.LUA_ACQUIRE_GAS = exports.LUA_ACQUIRE = void 0;
4
+ exports.LUA_ACQUIRE = `
5
+ local availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]
6
+ local amount = tonumber(ARGV[1])
7
+ local txTag, coinType, now = ARGV[2], ARGV[3], ARGV[4]
8
+ local maxCoins = tonumber(ARGV[5])
9
+ local ids = redis.call('ZRANGE', availKey, 0, -1)
10
+ local picked, sum = {}, 0
11
+ local i = #ids
12
+ while i >= 1 do
13
+ local id = ids[i]
14
+ local m = redis.call('HGET', metaKey, id)
15
+ if m then
16
+ local meta = cjson.decode(m)
17
+ picked[#picked+1] = { id = id, v = meta.v, d = meta.d, b = meta.b }
18
+ sum = sum + tonumber(meta.b)
19
+ if sum >= amount then break end
20
+ if #picked >= maxCoins then break end
21
+ end
22
+ i = i - 1
23
+ end
24
+ if sum < amount then return redis.error_reply('INSUFFICIENT_COINS') end
25
+ local out = {}
26
+ for _, p in ipairs(picked) do
27
+ redis.call('ZREM', availKey, p.id)
28
+ redis.call('HDEL', metaKey, p.id)
29
+ redis.call('HSET', inflightKey, p.id, cjson.encode({ tag = txTag, ct = coinType, t = now, v = p.v, d = p.d, b = p.b }))
30
+ out[#out+1] = cjson.encode({ objectId = p.id, version = p.v, digest = p.d, balance = p.b })
31
+ end
32
+ return out
33
+ `;
34
+ exports.LUA_ACQUIRE_GAS = `
35
+ local availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]
36
+ local minGas = tonumber(ARGV[1])
37
+ local txTag, now = ARGV[2], ARGV[3]
38
+ local ids = redis.call('ZRANGEBYSCORE', availKey, minGas, '+inf', 'LIMIT', 0, 1)
39
+ if #ids == 0 then return redis.error_reply('NO_GAS_COIN') end
40
+ local id = ids[1]
41
+ local m = redis.call('HGET', metaKey, id)
42
+ if not m then return redis.error_reply('NO_GAS_META') end
43
+ local meta = cjson.decode(m)
44
+ redis.call('ZREM', availKey, id)
45
+ redis.call('HDEL', metaKey, id)
46
+ redis.call('HSET', inflightKey, id, cjson.encode({ tag = txTag, ct = 'gas', t = now, v = meta.v, d = meta.d, b = meta.b }))
47
+ return cjson.encode({ objectId = id, version = meta.v, digest = meta.d, balance = meta.b })
48
+ `;
49
+ exports.LUA_COMMIT = `
50
+ local inflightKey = KEYS[1]
51
+ local base, gasBase, wallet = ARGV[1], ARGV[2], ARGV[3]
52
+ local changes = cjson.decode(ARGV[4])
53
+ for _, c in ipairs(changes) do
54
+ local availKey, metaKey
55
+ if c.gas then
56
+ availKey = gasBase..':avail:{'..wallet..'}'
57
+ metaKey = gasBase..':meta:{'..wallet..'}'
58
+ else
59
+ availKey = base..':avail:{'..wallet..'}:'..c.coinType
60
+ metaKey = base..':meta:{'..wallet..'}:'..c.coinType
61
+ end
62
+ redis.call('HDEL', inflightKey, c.objectId)
63
+ if c.kind == 'deleted' then
64
+ redis.call('ZREM', availKey, c.objectId)
65
+ redis.call('HDEL', metaKey, c.objectId)
66
+ else
67
+ redis.call('ZADD', availKey, tonumber(c.b), c.objectId)
68
+ redis.call('HSET', metaKey, c.objectId, cjson.encode({ v = c.v, d = c.d, b = c.b }))
69
+ end
70
+ end
71
+ return 'OK'
72
+ `;
73
+ exports.LUA_ABORT = `
74
+ local inflightKey = KEYS[1]
75
+ local base, gasBase, wallet, txTag, consumed = ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]
76
+ local all = redis.call('HGETALL', inflightKey)
77
+ for i = 1, #all, 2 do
78
+ local objectId, info = all[i], cjson.decode(all[i+1])
79
+ if info.tag == txTag then
80
+ redis.call('HDEL', inflightKey, objectId)
81
+ if consumed == '0' then
82
+ local availKey, metaKey
83
+ if info.ct == 'gas' then
84
+ availKey = gasBase..':avail:{'..wallet..'}'
85
+ metaKey = gasBase..':meta:{'..wallet..'}'
86
+ else
87
+ availKey = base..':avail:{'..wallet..'}:'..info.ct
88
+ metaKey = base..':meta:{'..wallet..'}:'..info.ct
89
+ end
90
+ redis.call('ZADD', availKey, tonumber(info.b), objectId)
91
+ redis.call('HSET', metaKey, objectId, cjson.encode({ v = info.v, d = info.d, b = info.b }))
92
+ end
93
+ end
94
+ end
95
+ return 'OK'
96
+ `;
97
+ exports.LUA_RECONCILE = `
98
+ local availKey, metaKey, inflightKey = KEYS[1], KEYS[2], KEYS[3]
99
+ local chain = cjson.decode(ARGV[1])
100
+ local ttl, now = tonumber(ARGV[2]), tonumber(ARGV[3])
101
+ local chainSet = {}
102
+ for _, o in ipairs(chain) do chainSet[o.objectId] = o end
103
+ -- 处理 inflight:超时移出(视作丢失,链真值会重加);否则记入 inflightSet 保护
104
+ local inflight = redis.call('HGETALL', inflightKey)
105
+ local inflightSet = {}
106
+ for i = 1, #inflight, 2 do
107
+ local oid, info = inflight[i], cjson.decode(inflight[i+1])
108
+ if (now - tonumber(info.t)) > ttl then
109
+ redis.call('HDEL', inflightKey, oid)
110
+ else
111
+ inflightSet[oid] = true
112
+ end
113
+ end
114
+ -- 删除 avail 中链上已无且非 inflight 的
115
+ local availIds = redis.call('ZRANGE', availKey, 0, -1)
116
+ for _, id in ipairs(availIds) do
117
+ if not chainSet[id] then
118
+ redis.call('ZREM', availKey, id)
119
+ redis.call('HDEL', metaKey, id)
120
+ end
121
+ end
122
+ -- 加入/更新链上有且非 inflight 的
123
+ for _, o in ipairs(chain) do
124
+ if not inflightSet[o.objectId] then
125
+ redis.call('ZADD', availKey, tonumber(o.b), o.objectId)
126
+ redis.call('HSET', metaKey, o.objectId, cjson.encode({ v = o.v, d = o.d, b = o.b }))
127
+ end
128
+ end
129
+ return 'OK'
130
+ `;
@@ -0,0 +1,30 @@
1
+ export interface CoinRef {
2
+ objectId: string;
3
+ version: string;
4
+ digest: string;
5
+ balance: string;
6
+ }
7
+ export interface CoinReservation {
8
+ txTag: string;
9
+ coinType: string;
10
+ coins: CoinRef[];
11
+ }
12
+ export interface GasReservation {
13
+ txTag: string;
14
+ coin: CoinRef;
15
+ }
16
+ export interface CoinObjectChange {
17
+ kind: 'mutated' | 'created' | 'deleted';
18
+ objectId: string;
19
+ coinType: string;
20
+ version?: string;
21
+ digest?: string;
22
+ balance?: string;
23
+ gas?: boolean;
24
+ }
25
+ export interface LedgerOptions {
26
+ keyBase?: string;
27
+ gasBase?: string;
28
+ inflightTtlMs?: number;
29
+ maxCoinsPerAcquire?: number;
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ import { RedisClientType } from 'redis';
2
+ import { CoinObjectChange, CoinRef, CoinReservation, GasReservation, LedgerOptions } from './types';
3
+ export declare class WalletCoinLedger {
4
+ private readonly getClient;
5
+ private readonly base;
6
+ private readonly gasBase;
7
+ private readonly inflightTtlMs;
8
+ private readonly maxCoins;
9
+ constructor(getClient: () => Promise<RedisClientType>, opts?: LedgerOptions);
10
+ private availKey;
11
+ private metaKey;
12
+ private inflightKey;
13
+ private gasAvailKey;
14
+ private gasMetaKey;
15
+ private newTxTag;
16
+ acquire(wallet: string, coinType: string, amount: bigint | string, txTag?: string): Promise<CoinReservation>;
17
+ acquireGas(wallet: string, minGas: bigint | string, txTag?: string): Promise<GasReservation>;
18
+ commit(wallet: string, changes: CoinObjectChange[]): Promise<void>;
19
+ abort(wallet: string, txTag: string, consumed: boolean): Promise<void>;
20
+ reconcile(wallet: string, coinType: string, chainObjs: CoinRef[], isGas?: boolean): Promise<void>;
21
+ availableCount(wallet: string, coinType: string, isGas?: boolean): Promise<number>;
22
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WalletCoinLedger = void 0;
4
+ const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const lua_scripts_1 = require("./lua_scripts");
6
+ class WalletCoinLedger {
7
+ constructor(getClient, opts = {}) {
8
+ this.getClient = getClient;
9
+ this.base = opts.keyBase ?? 'sui:coin';
10
+ this.gasBase = opts.gasBase ?? 'sui:gas';
11
+ this.inflightTtlMs = opts.inflightTtlMs ?? 30000;
12
+ this.maxCoins = opts.maxCoinsPerAcquire ?? 8;
13
+ }
14
+ availKey(wallet, coinType) { return `${this.base}:avail:{${wallet}}:${coinType}`; }
15
+ metaKey(wallet, coinType) { return `${this.base}:meta:{${wallet}}:${coinType}`; }
16
+ inflightKey(wallet) { return `${this.base}:inflight:{${wallet}}`; }
17
+ gasAvailKey(wallet) { return `${this.gasBase}:avail:{${wallet}}`; }
18
+ gasMetaKey(wallet) { return `${this.gasBase}:meta:{${wallet}}`; }
19
+ newTxTag(wallet) {
20
+ return `${wallet.slice(2, 8)}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
21
+ }
22
+ async acquire(wallet, coinType, amount, txTag) {
23
+ const client = await this.getClient();
24
+ const tag = txTag ?? this.newTxTag(wallet);
25
+ try {
26
+ const reply = await client.eval(lua_scripts_1.LUA_ACQUIRE, {
27
+ keys: [this.availKey(wallet, coinType), this.metaKey(wallet, coinType), this.inflightKey(wallet)],
28
+ arguments: [String(amount), tag, coinType, String(Date.now()), String(this.maxCoins)],
29
+ });
30
+ const coins = (reply || []).map(s => JSON.parse(s));
31
+ return { txTag: tag, coinType, coins };
32
+ }
33
+ catch (e) {
34
+ throw new Error(`[coin-ledger] acquire failed wallet=${wallet} coinType=${coinType} amount=${amount}: ${e?.message || e}`);
35
+ }
36
+ }
37
+ async acquireGas(wallet, minGas, txTag) {
38
+ const client = await this.getClient();
39
+ const tag = txTag ?? this.newTxTag(wallet);
40
+ try {
41
+ const reply = await client.eval(lua_scripts_1.LUA_ACQUIRE_GAS, {
42
+ keys: [this.gasAvailKey(wallet), this.gasMetaKey(wallet), this.inflightKey(wallet)],
43
+ arguments: [String(minGas), tag, String(Date.now())],
44
+ });
45
+ return { txTag: tag, coin: JSON.parse(reply) };
46
+ }
47
+ catch (e) {
48
+ throw new Error(`[coin-ledger] acquireGas failed wallet=${wallet} minGas=${minGas}: ${e?.message || e}`);
49
+ }
50
+ }
51
+ async commit(wallet, changes) {
52
+ if (!changes.length)
53
+ return;
54
+ const client = await this.getClient();
55
+ await client.eval(lua_scripts_1.LUA_COMMIT, {
56
+ keys: [this.inflightKey(wallet)],
57
+ arguments: [this.base, this.gasBase, wallet, JSON.stringify(changes)],
58
+ });
59
+ }
60
+ async abort(wallet, txTag, consumed) {
61
+ const client = await this.getClient();
62
+ await client.eval(lua_scripts_1.LUA_ABORT, {
63
+ keys: [this.inflightKey(wallet)],
64
+ arguments: [this.base, this.gasBase, wallet, txTag, consumed ? '1' : '0'],
65
+ });
66
+ }
67
+ async reconcile(wallet, coinType, chainObjs, isGas = false) {
68
+ const client = await this.getClient();
69
+ const keys = isGas
70
+ ? [this.gasAvailKey(wallet), this.gasMetaKey(wallet), this.inflightKey(wallet)]
71
+ : [this.availKey(wallet, coinType), this.metaKey(wallet, coinType), this.inflightKey(wallet)];
72
+ const payload = chainObjs.map(o => ({ objectId: o.objectId, v: o.version, d: o.digest, b: o.balance }));
73
+ await client.eval(lua_scripts_1.LUA_RECONCILE, {
74
+ keys,
75
+ arguments: [JSON.stringify(payload), String(this.inflightTtlMs), String(Date.now())],
76
+ });
77
+ (0, dist_1.log_info)(`[coin-ledger] reconciled wallet=${wallet} ${isGas ? 'gas' : coinType} chainCoins=${chainObjs.length}`);
78
+ }
79
+ async availableCount(wallet, coinType, isGas = false) {
80
+ const client = await this.getClient();
81
+ const key = isGas ? this.gasAvailKey(wallet) : this.availKey(wallet, coinType);
82
+ return await client.zCard(key);
83
+ }
84
+ }
85
+ exports.WalletCoinLedger = WalletCoinLedger;
@@ -0,0 +1,72 @@
1
+ import { DEX_ID } from '@clonegod/ttd-core/dist';
2
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
3
+ import type { Experimental_SuiClientTypes } from '@mysten/sui/experimental';
4
+ import { InProcessCoinCache } from './coin_cache';
5
+ import { ExecutorCore } from './core_channel';
6
+ export interface SwapExecRequest {
7
+ dexId: DEX_ID;
8
+ poolId: string;
9
+ coinTypeA: string;
10
+ coinTypeB: string;
11
+ a2b: boolean;
12
+ amountIn: bigint;
13
+ minOut: bigint;
14
+ sqrtPriceLimit: bigint;
15
+ walletAddress?: string;
16
+ }
17
+ export interface SwapExecResult {
18
+ digest: string;
19
+ success: boolean;
20
+ error?: string;
21
+ }
22
+ export interface SwapSubmitResult {
23
+ digest: string;
24
+ submitted: boolean;
25
+ error?: string;
26
+ }
27
+ export interface CentralExecutorOptions {
28
+ gasBudget?: bigint;
29
+ gasMinCoin?: bigint;
30
+ reconcileAfterTx?: boolean;
31
+ onBroadcastResult?: (r: {
32
+ digest: string;
33
+ success: boolean;
34
+ error?: string;
35
+ }) => void;
36
+ }
37
+ type TxResponse = Experimental_SuiClientTypes.TransactionResponse;
38
+ export declare class CentralExecutor {
39
+ private readonly core;
40
+ private readonly cache;
41
+ private readonly gasBudget;
42
+ private readonly gasMinCoin;
43
+ private readonly reconcileAfterTx;
44
+ private readonly onBroadcastResult?;
45
+ private gasKeypair;
46
+ private gasAddress;
47
+ private tradeWallets;
48
+ private sharedRefCache;
49
+ private seq;
50
+ constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
51
+ init(): Promise<void>;
52
+ reconcileCoins(wallet: string, coinType: string): Promise<void>;
53
+ private getSharedRefCached;
54
+ private getGasPrice;
55
+ private nextTag;
56
+ private inputCoinType;
57
+ private outputCoinType;
58
+ private chooseTradeWallet;
59
+ private registerShared;
60
+ submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
61
+ private broadcastAndCommit;
62
+ private reconcileAfterFailure;
63
+ simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
64
+ private buildSwapTx;
65
+ private onSuccess;
66
+ get coinCache(): InProcessCoinCache;
67
+ get gasWalletAddress(): string;
68
+ get gasSigner(): Ed25519Keypair;
69
+ get tradeWalletAddresses(): string[];
70
+ get coreClient(): ExecutorCore;
71
+ }
72
+ export {};