@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.
- package/dist/appconfig/SuiQuoteAppConfig.d.ts +10 -0
- package/dist/appconfig/SuiQuoteAppConfig.js +35 -0
- package/dist/appconfig/SuiTradeAppConfig.d.ts +7 -0
- package/dist/appconfig/SuiTradeAppConfig.js +13 -0
- package/dist/appconfig/ensure_core_env.d.ts +1 -0
- package/dist/appconfig/ensure_core_env.js +18 -0
- package/dist/appconfig/index.d.ts +5 -0
- package/dist/appconfig/index.js +21 -0
- package/dist/appconfig/sui_dex_env_args.d.ts +5 -0
- package/dist/appconfig/sui_dex_env_args.js +28 -0
- package/dist/appconfig/sui_env_args.d.ts +4 -0
- package/dist/appconfig/sui_env_args.js +20 -0
- package/dist/grpc/gas-price-cache.js +19 -32
- package/dist/grpc/grpc-connection.js +5 -3
- package/dist/grpc/grpc_provider_registry.d.ts +14 -0
- package/dist/grpc/grpc_provider_registry.js +60 -0
- package/dist/grpc/index.d.ts +3 -0
- package/dist/grpc/index.js +13 -1
- package/dist/grpc/ledger-service.js +107 -128
- package/dist/grpc/proto_value.d.ts +4 -0
- package/dist/grpc/proto_value.js +59 -0
- package/dist/grpc/state-service.d.ts +1 -0
- package/dist/grpc/state-service.js +99 -102
- package/dist/grpc/sui-grpc-client.js +2 -13
- package/dist/grpc/sui_object_reader.d.ts +15 -0
- package/dist/grpc/sui_object_reader.js +60 -0
- package/dist/grpc/transaction-service.js +26 -37
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/quote/abstract_dex_quote.d.ts +60 -0
- package/dist/quote/abstract_dex_quote.js +186 -0
- package/dist/quote/chain_ops.d.ts +17 -0
- package/dist/quote/chain_ops.js +52 -0
- package/dist/quote/index.d.ts +7 -0
- package/dist/quote/index.js +7 -0
- package/dist/quote/pool_event.d.ts +21 -0
- package/dist/quote/pool_event.js +6 -0
- package/dist/quote/pricing/token_price_cache.js +18 -29
- package/dist/quote/quote_amount.d.ts +4 -0
- package/dist/quote/quote_amount.js +24 -0
- package/dist/quote/quote_trace.d.ts +16 -0
- package/dist/quote/quote_trace.js +40 -0
- package/dist/quote/tick/clmm_v3_engine.d.ts +32 -0
- package/dist/quote/tick/clmm_v3_engine.js +48 -0
- package/dist/quote/tick/index.d.ts +4 -0
- package/dist/quote/tick/index.js +20 -0
- package/dist/quote/tick/local_clmm_state.d.ts +17 -0
- package/dist/quote/tick/local_clmm_state.js +22 -0
- package/dist/quote/tick/sui_clmm_tick_cache.d.ts +42 -0
- package/dist/quote/tick/sui_clmm_tick_cache.js +163 -0
- package/dist/quote/tick/sui_tick_data_provider.d.ts +2 -0
- package/dist/quote/tick/sui_tick_data_provider.js +6 -0
- package/dist/quote/verify/index.d.ts +1 -0
- package/dist/quote/verify/index.js +17 -0
- package/dist/quote/verify/quote_price_verify.d.ts +30 -0
- package/dist/quote/verify/quote_price_verify.js +247 -0
- package/dist/redis/redis_client.d.ts +1 -0
- package/dist/redis/redis_client.js +88 -117
- package/dist/rpc/index.js +59 -75
- package/dist/test/test.js +1 -1
- package/dist/test/test_checkpoint.js +4 -13
- package/dist/test/test_grpc.js +32 -41
- package/dist/trade/abstract_sui_dex_trade.d.ts +43 -0
- package/dist/trade/abstract_sui_dex_trade.js +380 -0
- package/dist/trade/abstract_sui_dex_trade_plus.d.ts +3 -1
- package/dist/trade/abstract_sui_dex_trade_plus.js +232 -212
- package/dist/trade/check/tx_result_checker.js +65 -75
- package/dist/trade/coin/index.d.ts +1 -0
- package/dist/trade/coin/index.js +17 -0
- package/dist/trade/coin/lua_scripts.d.ts +5 -0
- package/dist/trade/coin/lua_scripts.js +130 -0
- package/dist/trade/coin/types.d.ts +30 -0
- package/dist/trade/coin/types.js +2 -0
- package/dist/trade/coin/wallet_coin_ledger.d.ts +22 -0
- package/dist/trade/coin/wallet_coin_ledger.js +85 -0
- package/dist/trade/executor/central_executor.d.ts +72 -0
- package/dist/trade/executor/central_executor.js +240 -0
- package/dist/trade/executor/coin_cache.d.ts +21 -0
- package/dist/trade/executor/coin_cache.js +143 -0
- package/dist/trade/executor/coin_maintainer.d.ts +32 -0
- package/dist/trade/executor/coin_maintainer.js +123 -0
- package/dist/trade/executor/core_channel.d.ts +38 -0
- package/dist/trade/executor/core_channel.js +131 -0
- package/dist/trade/executor/data_channel.d.ts +27 -0
- package/dist/trade/executor/data_channel.js +2 -0
- package/dist/trade/executor/effects.d.ts +16 -0
- package/dist/trade/executor/effects.js +63 -0
- package/dist/trade/executor/executor_client.d.ts +13 -0
- package/dist/trade/executor/executor_client.js +55 -0
- package/dist/trade/executor/executor_protocol.d.ts +26 -0
- package/dist/trade/executor/executor_protocol.js +32 -0
- package/dist/trade/executor/executor_server.d.ts +8 -0
- package/dist/trade/executor/executor_server.js +33 -0
- package/dist/trade/executor/executor_ws_client.d.ts +13 -0
- package/dist/trade/executor/executor_ws_client.js +58 -0
- package/dist/trade/executor/grpc_channel.d.ts +14 -0
- package/dist/trade/executor/grpc_channel.js +73 -0
- package/dist/trade/executor/index.d.ts +7 -0
- package/dist/trade/executor/index.js +23 -0
- package/dist/trade/executor/json_rpc_channel.d.ts +14 -0
- package/dist/trade/executor/json_rpc_channel.js +77 -0
- package/dist/trade/index.d.ts +5 -1
- package/dist/trade/index.js +5 -1
- package/dist/trade/parse/sui_tx_parser.js +98 -81
- package/dist/trade/send_tx/index.js +34 -47
- package/dist/trade/swap/builders/bluefin.d.ts +9 -0
- package/dist/trade/swap/builders/bluefin.js +60 -0
- package/dist/trade/swap/builders/cetus_magma.d.ts +13 -0
- package/dist/trade/swap/builders/cetus_magma.js +52 -0
- package/dist/trade/swap/builders/momentum.d.ts +9 -0
- package/dist/trade/swap/builders/momentum.js +80 -0
- package/dist/trade/swap/dex_swap_config.d.ts +28 -0
- package/dist/trade/swap/dex_swap_config.js +40 -0
- package/dist/trade/swap/index.d.ts +7 -0
- package/dist/trade/swap/index.js +36 -0
- package/dist/trade/swap/types.d.ts +20 -0
- package/dist/trade/swap/types.js +2 -0
- package/dist/trade/test/test_parse_sui_tx_result.js +33 -44
- package/dist/trade/tx_result_channel.d.ts +7 -0
- package/dist/trade/tx_result_channel.js +7 -0
- package/dist/utils/decode.js +1 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/trade_direction.d.ts +14 -0
- package/dist/utils/trade_direction.js +23 -0
- 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
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
else {
|
|
59
48
|
clearInterval(intervalId);
|
|
60
|
-
(0, dist_1.log_error)('parse transaction error!', err);
|
|
61
49
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
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,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 {};
|