@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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResilientCore = void 0;
|
|
4
|
+
exports.buildGrpcCore = buildGrpcCore;
|
|
5
|
+
exports.buildGraphqlCore = buildGraphqlCore;
|
|
6
|
+
exports.buildDefaultCore = buildDefaultCore;
|
|
7
|
+
exports.buildResilientCore = buildResilientCore;
|
|
8
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
9
|
+
const grpc_transport_1 = require("@protobuf-ts/grpc-transport");
|
|
10
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
11
|
+
const grpc_1 = require("@mysten/sui/grpc");
|
|
12
|
+
const graphql_1 = require("@mysten/sui/graphql");
|
|
13
|
+
const GRPC_CLIENT_OPTIONS = {
|
|
14
|
+
'grpc.keepalive_time_ms': 60000,
|
|
15
|
+
'grpc.keepalive_timeout_ms': 10000,
|
|
16
|
+
'grpc.keepalive_permit_without_calls': 0,
|
|
17
|
+
'grpc.http2.max_pings_without_data': 0,
|
|
18
|
+
'grpc.http2.min_time_between_pings_ms': 60000,
|
|
19
|
+
'grpc.http2.min_ping_interval_without_data_ms': 60000,
|
|
20
|
+
'grpc.max_receive_message_length': 8 * 1024 * 1024,
|
|
21
|
+
'grpc.max_send_message_length': 8 * 1024 * 1024,
|
|
22
|
+
'grpc.initial_reconnect_backoff_ms': 1000,
|
|
23
|
+
'grpc.max_reconnect_backoff_ms': 30000,
|
|
24
|
+
};
|
|
25
|
+
function buildGrpcCore(opts = {}) {
|
|
26
|
+
const endpoint = opts.endpoint ?? process.env.SUI_GRPC_ENDPOINT;
|
|
27
|
+
const token = opts.token ?? process.env.SUI_GRPC_TOKEN;
|
|
28
|
+
if (!endpoint)
|
|
29
|
+
throw new Error('SUI_GRPC_ENDPOINT 未配置(集中执行器需 gRPC 端点)');
|
|
30
|
+
const transport = new grpc_transport_1.GrpcTransport({
|
|
31
|
+
host: endpoint,
|
|
32
|
+
channelCredentials: grpc_js_1.ChannelCredentials.createSsl(),
|
|
33
|
+
clientOptions: GRPC_CLIENT_OPTIONS,
|
|
34
|
+
meta: token ? { 'x-token': token } : undefined,
|
|
35
|
+
});
|
|
36
|
+
const core = new grpc_1.SuiGrpcClient({ network: opts.network ?? 'mainnet', transport }).core;
|
|
37
|
+
(0, dist_1.log_info)(`[core-channel] gRPC(native) core ready, endpoint=${endpoint}`);
|
|
38
|
+
return core;
|
|
39
|
+
}
|
|
40
|
+
function buildGraphqlCore(opts = {}) {
|
|
41
|
+
const url = opts.url ?? process.env.SUI_GRAPHQL_URL;
|
|
42
|
+
if (!url)
|
|
43
|
+
throw new Error('SUI_GRAPHQL_URL 未配置(GraphQL fallback 通道需端点)');
|
|
44
|
+
const core = new graphql_1.SuiGraphQLClient({ url }).core;
|
|
45
|
+
(0, dist_1.log_info)(`[core-channel] GraphQL core ready, url=${url}`);
|
|
46
|
+
return core;
|
|
47
|
+
}
|
|
48
|
+
function buildDefaultCore() {
|
|
49
|
+
return buildGrpcCore();
|
|
50
|
+
}
|
|
51
|
+
function withTimeout(p, ms, label) {
|
|
52
|
+
return Promise.race([
|
|
53
|
+
p,
|
|
54
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error(`[core] ${label} 超时 ${ms}ms`)), ms)),
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
async function withRetry(fn, opts) {
|
|
58
|
+
let lastErr;
|
|
59
|
+
for (let i = 0; i <= opts.retries; i++) {
|
|
60
|
+
try {
|
|
61
|
+
return await withTimeout(fn(), opts.deadlineMs, opts.label);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
lastErr = e;
|
|
65
|
+
if (i < opts.retries)
|
|
66
|
+
await (0, dist_1.sleep)(Math.min(100 * 2 ** i, 1000));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw lastErr;
|
|
70
|
+
}
|
|
71
|
+
class ResilientCore {
|
|
72
|
+
constructor(primary, fallback, opts = {}) {
|
|
73
|
+
this.primary = primary;
|
|
74
|
+
this.fallback = fallback;
|
|
75
|
+
this.retries = opts.retries ?? Number(process.env.SUI_CORE_RETRIES || 2);
|
|
76
|
+
this.readDeadlineMs = opts.readDeadlineMs ?? Number(process.env.SUI_CORE_READ_DEADLINE_MS || 5000);
|
|
77
|
+
this.execDeadlineMs = opts.execDeadlineMs ?? Number(process.env.SUI_CORE_EXEC_DEADLINE_MS || 30000);
|
|
78
|
+
this.gasPriceTtlMs = opts.gasPriceTtlMs ?? Number(process.env.SUI_GAS_PRICE_TTL_MS || 3600000);
|
|
79
|
+
}
|
|
80
|
+
async read(label, call) {
|
|
81
|
+
try {
|
|
82
|
+
return await withRetry(() => call(this.primary), { retries: this.retries, deadlineMs: this.readDeadlineMs, label });
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (!this.fallback)
|
|
86
|
+
throw e;
|
|
87
|
+
(0, dist_1.log_warn)(`[core] ${label} 主通道失败,转 GraphQL fallback: ${e.message}`);
|
|
88
|
+
return await withRetry(() => call(this.fallback), { retries: this.retries, deadlineMs: this.readDeadlineMs, label: label + ':fallback' });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
getCoins(o) {
|
|
92
|
+
return this.read('getCoins', c => c.getCoins(o));
|
|
93
|
+
}
|
|
94
|
+
getObjects(o) {
|
|
95
|
+
return this.read('getObjects', c => c.getObjects(o));
|
|
96
|
+
}
|
|
97
|
+
dryRunTransaction(o) {
|
|
98
|
+
return this.read('dryRunTransaction', c => c.dryRunTransaction(o));
|
|
99
|
+
}
|
|
100
|
+
async getReferenceGasPrice() {
|
|
101
|
+
const fresh = this.gasPriceCache && (Date.now() - this.gasPriceCache.ts) < this.gasPriceTtlMs;
|
|
102
|
+
if (fresh)
|
|
103
|
+
return { referenceGasPrice: this.gasPriceCache.value };
|
|
104
|
+
const r = await this.read('getReferenceGasPrice', c => {
|
|
105
|
+
if (!c.getReferenceGasPrice)
|
|
106
|
+
throw new Error('core 不支持 getReferenceGasPrice');
|
|
107
|
+
return c.getReferenceGasPrice();
|
|
108
|
+
});
|
|
109
|
+
this.gasPriceCache = { value: r.referenceGasPrice, ts: Date.now() };
|
|
110
|
+
return r;
|
|
111
|
+
}
|
|
112
|
+
async executeTransaction(o) {
|
|
113
|
+
return withTimeout(this.primary.executeTransaction(o), this.execDeadlineMs, 'executeTransaction');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.ResilientCore = ResilientCore;
|
|
117
|
+
function buildResilientCore(opts = {}) {
|
|
118
|
+
const primary = buildGrpcCore();
|
|
119
|
+
let fallback;
|
|
120
|
+
const gqlUrl = process.env.SUI_GRAPHQL_URL;
|
|
121
|
+
if (gqlUrl) {
|
|
122
|
+
try {
|
|
123
|
+
fallback = buildGraphqlCore({ url: gqlUrl });
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
(0, dist_1.log_warn)(`[core] GraphQL fallback 构建失败: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
(0, dist_1.log_info)(`[core] ResilientCore ready (failover=${fallback ? 'graphql' : 'none'})`);
|
|
130
|
+
return new ResilientCore(primary, fallback, opts);
|
|
131
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CoinRef } from '../coin/types';
|
|
2
|
+
import { RpcObjectChange } from './effects';
|
|
3
|
+
export interface SharedRef {
|
|
4
|
+
objectId: string;
|
|
5
|
+
initialSharedVersion: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NormalizedBalanceChange {
|
|
8
|
+
owner?: string;
|
|
9
|
+
coinType: string;
|
|
10
|
+
amount: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NormalizedTxResult {
|
|
13
|
+
digest: string;
|
|
14
|
+
success: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
objectChanges: RpcObjectChange[];
|
|
17
|
+
balanceChanges?: NormalizedBalanceChange[];
|
|
18
|
+
gasUsed?: bigint;
|
|
19
|
+
}
|
|
20
|
+
export interface DataChannel {
|
|
21
|
+
readonly kind: 'jsonrpc' | 'grpc';
|
|
22
|
+
listCoins(owner: string, coinType: string): Promise<CoinRef[]>;
|
|
23
|
+
getSharedRef(objectId: string): Promise<SharedRef>;
|
|
24
|
+
getGasPrice(): Promise<bigint>;
|
|
25
|
+
simulate(txBytes: Uint8Array): Promise<NormalizedTxResult>;
|
|
26
|
+
execute(txBytes: Uint8Array, signatures: string[]): Promise<NormalizedTxResult>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Experimental_SuiClientTypes } from '@mysten/sui/experimental';
|
|
2
|
+
import { CoinObjectChange } from '../coin/types';
|
|
3
|
+
type TransactionEffects = Experimental_SuiClientTypes.TransactionEffects;
|
|
4
|
+
export declare function parseCoinType(type: string | undefined): string | null;
|
|
5
|
+
export declare function coreTxStatus(effects: TransactionEffects): {
|
|
6
|
+
success: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function coreGasUsed(effects: TransactionEffects): bigint;
|
|
10
|
+
export interface ExtractCoinChangesOptions {
|
|
11
|
+
owners?: Set<string>;
|
|
12
|
+
balanceById?: Map<string, string>;
|
|
13
|
+
coinTypeById?: Map<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function extractCoinChangesFromCore(effects: TransactionEffects, objectTypes: Record<string, string>, opts?: ExtractCoinChangesOptions): CoinObjectChange[];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCoinType = parseCoinType;
|
|
4
|
+
exports.coreTxStatus = coreTxStatus;
|
|
5
|
+
exports.coreGasUsed = coreGasUsed;
|
|
6
|
+
exports.extractCoinChangesFromCore = extractCoinChangesFromCore;
|
|
7
|
+
function parseCoinType(type) {
|
|
8
|
+
if (!type)
|
|
9
|
+
return null;
|
|
10
|
+
const m = type.match(/^0x2::coin::Coin<(.+)>$/);
|
|
11
|
+
return m ? m[1] : null;
|
|
12
|
+
}
|
|
13
|
+
function ownerAddress(owner) {
|
|
14
|
+
if (!owner)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (owner.$kind === 'AddressOwner')
|
|
17
|
+
return owner.AddressOwner;
|
|
18
|
+
if (owner.$kind === 'ConsensusAddressOwner')
|
|
19
|
+
return owner.ConsensusAddressOwner.owner;
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function coreTxStatus(effects) {
|
|
23
|
+
const s = effects.status;
|
|
24
|
+
return { success: s.success, error: s.success ? undefined : s.error };
|
|
25
|
+
}
|
|
26
|
+
function coreGasUsed(effects) {
|
|
27
|
+
const g = effects.gasUsed;
|
|
28
|
+
return BigInt(g.computationCost) + BigInt(g.storageCost) - BigInt(g.storageRebate);
|
|
29
|
+
}
|
|
30
|
+
function extractCoinChangesFromCore(effects, objectTypes, opts = {}) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const ch of effects.changedObjects) {
|
|
33
|
+
const kind = changedKind(ch);
|
|
34
|
+
if (!kind)
|
|
35
|
+
continue;
|
|
36
|
+
const coinType = parseCoinType(objectTypes[ch.id]) ?? opts.coinTypeById?.get(ch.id);
|
|
37
|
+
if (!coinType)
|
|
38
|
+
continue;
|
|
39
|
+
if (opts.owners?.size) {
|
|
40
|
+
const addr = ownerAddress(ch.outputOwner);
|
|
41
|
+
if (!addr || !opts.owners.has(addr))
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
out.push({
|
|
45
|
+
kind,
|
|
46
|
+
objectId: ch.id,
|
|
47
|
+
coinType,
|
|
48
|
+
version: ch.outputVersion ?? undefined,
|
|
49
|
+
digest: ch.outputDigest ?? undefined,
|
|
50
|
+
balance: opts.balanceById?.get(ch.id),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function changedKind(ch) {
|
|
56
|
+
if (ch.idOperation === 'Created')
|
|
57
|
+
return 'created';
|
|
58
|
+
if (ch.idOperation === 'Deleted')
|
|
59
|
+
return 'deleted';
|
|
60
|
+
if (ch.outputState === 'ObjectWrite')
|
|
61
|
+
return 'mutated';
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SwapExecRequest, SwapExecResult } from './central_executor';
|
|
2
|
+
import { RedisPubSub } from './executor_protocol';
|
|
3
|
+
export declare class ExecutorClient {
|
|
4
|
+
private readonly redis;
|
|
5
|
+
private readonly defaultTimeoutMs;
|
|
6
|
+
private pending;
|
|
7
|
+
private subscribed;
|
|
8
|
+
private seq;
|
|
9
|
+
constructor(redis: RedisPubSub, defaultTimeoutMs?: number);
|
|
10
|
+
private ensureSubscribed;
|
|
11
|
+
private nextReqId;
|
|
12
|
+
requestSwap(req: SwapExecRequest, timeoutMs?: number): Promise<SwapExecResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExecutorClient = void 0;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
const executor_protocol_1 = require("./executor_protocol");
|
|
6
|
+
class ExecutorClient {
|
|
7
|
+
constructor(redis, defaultTimeoutMs = 30000) {
|
|
8
|
+
this.redis = redis;
|
|
9
|
+
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
10
|
+
this.pending = new Map();
|
|
11
|
+
this.subscribed = false;
|
|
12
|
+
this.seq = 0;
|
|
13
|
+
}
|
|
14
|
+
async ensureSubscribed() {
|
|
15
|
+
if (this.subscribed)
|
|
16
|
+
return;
|
|
17
|
+
this.subscribed = true;
|
|
18
|
+
await this.redis.subscribe(executor_protocol_1.EXEC_RESPONSE_CHANNEL, (message) => {
|
|
19
|
+
try {
|
|
20
|
+
const env = JSON.parse(message);
|
|
21
|
+
const p = this.pending.get(env.reqId);
|
|
22
|
+
if (!p)
|
|
23
|
+
return;
|
|
24
|
+
clearTimeout(p.timer);
|
|
25
|
+
this.pending.delete(env.reqId);
|
|
26
|
+
p.resolve(env.result);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
(0, dist_1.log_warn)('[executor-client] bad response message', message);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
nextReqId() {
|
|
34
|
+
return `${Date.now()}-${++this.seq}-${Math.random().toString(36).slice(2, 8)}`;
|
|
35
|
+
}
|
|
36
|
+
async requestSwap(req, timeoutMs = this.defaultTimeoutMs) {
|
|
37
|
+
await this.ensureSubscribed();
|
|
38
|
+
const reqId = this.nextReqId();
|
|
39
|
+
const envelope = { reqId, req: (0, executor_protocol_1.toWire)(req) };
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const timer = setTimeout(() => {
|
|
42
|
+
this.pending.delete(reqId);
|
|
43
|
+
(0, dist_1.log_error)(`[executor-client] 请求超时 reqId=${reqId} dex=${req.dexId}`, new Error('executor request timeout'));
|
|
44
|
+
resolve({ digest: '', success: false, error: `executor request timeout (${timeoutMs}ms)` });
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
this.pending.set(reqId, { resolve, timer });
|
|
47
|
+
Promise.resolve(this.redis.publish(executor_protocol_1.EXEC_REQUEST_CHANNEL, JSON.stringify(envelope))).catch(e => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
this.pending.delete(reqId);
|
|
50
|
+
resolve({ digest: '', success: false, error: `publish failed: ${e.message}` });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.ExecutorClient = ExecutorClient;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SwapExecRequest } from './central_executor';
|
|
2
|
+
export declare const EXECUTOR_WS_PATH = "/sui/executor";
|
|
3
|
+
export interface SwapExecRequestWire {
|
|
4
|
+
dexId: string;
|
|
5
|
+
poolId: string;
|
|
6
|
+
coinTypeA: string;
|
|
7
|
+
coinTypeB: string;
|
|
8
|
+
a2b: boolean;
|
|
9
|
+
amountIn: string;
|
|
10
|
+
minOut: string;
|
|
11
|
+
sqrtPriceLimit: string;
|
|
12
|
+
walletAddress?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ExecRequestEnvelope {
|
|
15
|
+
type: 'swap';
|
|
16
|
+
reqId: string;
|
|
17
|
+
req: SwapExecRequestWire;
|
|
18
|
+
}
|
|
19
|
+
export interface ExecResponseEnvelope {
|
|
20
|
+
reqId: string;
|
|
21
|
+
digest: string;
|
|
22
|
+
submitted: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function toWire(req: SwapExecRequest): SwapExecRequestWire;
|
|
26
|
+
export declare function fromWire(w: SwapExecRequestWire): SwapExecRequest;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXECUTOR_WS_PATH = void 0;
|
|
4
|
+
exports.toWire = toWire;
|
|
5
|
+
exports.fromWire = fromWire;
|
|
6
|
+
exports.EXECUTOR_WS_PATH = '/sui/executor';
|
|
7
|
+
function toWire(req) {
|
|
8
|
+
return {
|
|
9
|
+
dexId: req.dexId,
|
|
10
|
+
poolId: req.poolId,
|
|
11
|
+
coinTypeA: req.coinTypeA,
|
|
12
|
+
coinTypeB: req.coinTypeB,
|
|
13
|
+
a2b: req.a2b,
|
|
14
|
+
amountIn: req.amountIn.toString(),
|
|
15
|
+
minOut: req.minOut.toString(),
|
|
16
|
+
sqrtPriceLimit: req.sqrtPriceLimit.toString(),
|
|
17
|
+
walletAddress: req.walletAddress,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function fromWire(w) {
|
|
21
|
+
return {
|
|
22
|
+
dexId: w.dexId,
|
|
23
|
+
poolId: w.poolId,
|
|
24
|
+
coinTypeA: w.coinTypeA,
|
|
25
|
+
coinTypeB: w.coinTypeB,
|
|
26
|
+
a2b: w.a2b,
|
|
27
|
+
amountIn: BigInt(w.amountIn),
|
|
28
|
+
minOut: BigInt(w.minOut),
|
|
29
|
+
sqrtPriceLimit: BigInt(w.sqrtPriceLimit),
|
|
30
|
+
walletAddress: w.walletAddress,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CentralExecutor } from './central_executor';
|
|
2
|
+
import { RedisPubSub } from './executor_protocol';
|
|
3
|
+
export declare class ExecutorServer {
|
|
4
|
+
private readonly executor;
|
|
5
|
+
private readonly redis;
|
|
6
|
+
constructor(executor: CentralExecutor, redis: RedisPubSub);
|
|
7
|
+
start(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExecutorServer = void 0;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
const executor_protocol_1 = require("./executor_protocol");
|
|
6
|
+
class ExecutorServer {
|
|
7
|
+
constructor(executor, redis) {
|
|
8
|
+
this.executor = executor;
|
|
9
|
+
this.redis = redis;
|
|
10
|
+
}
|
|
11
|
+
async start() {
|
|
12
|
+
await this.redis.subscribe(executor_protocol_1.EXEC_REQUEST_CHANNEL, async (message) => {
|
|
13
|
+
let reqId = '';
|
|
14
|
+
try {
|
|
15
|
+
const env = JSON.parse(message);
|
|
16
|
+
reqId = env.reqId;
|
|
17
|
+
const req = (0, executor_protocol_1.fromWire)(env.req);
|
|
18
|
+
const result = await this.executor.executeSwap(req);
|
|
19
|
+
const resp = { reqId, result };
|
|
20
|
+
await this.redis.publish(executor_protocol_1.EXEC_RESPONSE_CHANNEL, JSON.stringify(resp));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
(0, dist_1.log_error)(`[executor-server] 处理请求失败 reqId=${reqId}`, e);
|
|
24
|
+
if (reqId) {
|
|
25
|
+
const resp = { reqId, result: { digest: '', success: false, error: e.message } };
|
|
26
|
+
await Promise.resolve(this.redis.publish(executor_protocol_1.EXEC_RESPONSE_CHANNEL, JSON.stringify(resp))).catch(() => { });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
(0, dist_1.log_info)('[executor-server] started, listening ' + executor_protocol_1.EXEC_REQUEST_CHANNEL);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ExecutorServer = ExecutorServer;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SwapExecRequest } from './central_executor';
|
|
2
|
+
import { ExecResponseEnvelope } from './executor_protocol';
|
|
3
|
+
export declare class ExecutorWsClient {
|
|
4
|
+
private readonly defaultTimeoutMs;
|
|
5
|
+
private ws;
|
|
6
|
+
private connected;
|
|
7
|
+
private pending;
|
|
8
|
+
private seq;
|
|
9
|
+
private clientName;
|
|
10
|
+
constructor(host?: string, port?: number, defaultTimeoutMs?: number);
|
|
11
|
+
private nextReqId;
|
|
12
|
+
requestSwap(req: SwapExecRequest, timeoutMs?: number): Promise<ExecResponseEnvelope>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExecutorWsClient = void 0;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
const executor_protocol_1 = require("./executor_protocol");
|
|
6
|
+
class ExecutorWsClient {
|
|
7
|
+
constructor(host, port = ttd_core_1.SERVICE_PORT.SEND_TX_WS, defaultTimeoutMs = 30000) {
|
|
8
|
+
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
9
|
+
this.connected = false;
|
|
10
|
+
this.pending = new Map();
|
|
11
|
+
this.seq = 0;
|
|
12
|
+
const h = host || process.env.SUI_EXECUTOR_WS_HOST || process.env.SEND_TX_WS_HOST || '127.0.0.1';
|
|
13
|
+
const url = `ws://${h}:${port}${executor_protocol_1.EXECUTOR_WS_PATH}`;
|
|
14
|
+
this.clientName = process.env.APP_NAME || (process.env.pm_id ? `pm2-${process.env.pm_id}` : `pid-${process.pid}`);
|
|
15
|
+
this.ws = new ttd_core_1.WebSocketClient(url);
|
|
16
|
+
this.ws.onOpen(() => {
|
|
17
|
+
this.connected = true;
|
|
18
|
+
this.ws.send(JSON.stringify({ type: 'hello', clientName: this.clientName }));
|
|
19
|
+
});
|
|
20
|
+
this.ws.onMessage((data) => {
|
|
21
|
+
try {
|
|
22
|
+
const env = JSON.parse(typeof data === 'string' ? data : data.toString());
|
|
23
|
+
const p = this.pending.get(env.reqId);
|
|
24
|
+
if (!p)
|
|
25
|
+
return;
|
|
26
|
+
clearTimeout(p.timer);
|
|
27
|
+
this.pending.delete(env.reqId);
|
|
28
|
+
p.resolve(env);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
(0, ttd_core_1.log_warn)('[executor-client] bad response', String(data));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
this.ws.connect();
|
|
35
|
+
}
|
|
36
|
+
nextReqId() {
|
|
37
|
+
return `${Date.now()}-${++this.seq}-${Math.random().toString(36).slice(2, 8)}`;
|
|
38
|
+
}
|
|
39
|
+
async requestSwap(req, timeoutMs = this.defaultTimeoutMs) {
|
|
40
|
+
const reqId = this.nextReqId();
|
|
41
|
+
const envelope = { type: 'swap', reqId, req: (0, executor_protocol_1.toWire)(req) };
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
this.pending.delete(reqId);
|
|
45
|
+
(0, ttd_core_1.log_error)(`[executor-client] 请求超时 reqId=${reqId} dex=${req.dexId}`, new Error('executor request timeout'));
|
|
46
|
+
resolve({ reqId, digest: '', submitted: false, error: `executor request timeout (${timeoutMs}ms)` });
|
|
47
|
+
}, timeoutMs);
|
|
48
|
+
this.pending.set(reqId, { resolve, timer });
|
|
49
|
+
const ok = this.ws.send(JSON.stringify(envelope));
|
|
50
|
+
if (!ok && !this.connected) {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
this.pending.delete(reqId);
|
|
53
|
+
resolve({ reqId, digest: '', submitted: false, error: 'executor ws not connected' });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ExecutorWsClient = ExecutorWsClient;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SuiGrpcClient } from '../../grpc';
|
|
2
|
+
import { CoinRef } from '../coin/types';
|
|
3
|
+
import { DataChannel, NormalizedTxResult, SharedRef } from './data_channel';
|
|
4
|
+
export declare class GrpcDataChannel implements DataChannel {
|
|
5
|
+
private readonly grpc;
|
|
6
|
+
readonly kind: "grpc";
|
|
7
|
+
constructor(grpc: SuiGrpcClient);
|
|
8
|
+
listCoins(owner: string, coinType: string): Promise<CoinRef[]>;
|
|
9
|
+
getSharedRef(objectId: string): Promise<SharedRef>;
|
|
10
|
+
getGasPrice(): Promise<bigint>;
|
|
11
|
+
simulate(_txBytes: Uint8Array): Promise<NormalizedTxResult>;
|
|
12
|
+
execute(txBytes: Uint8Array, signatures: string[]): Promise<NormalizedTxResult>;
|
|
13
|
+
private normalize;
|
|
14
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GrpcDataChannel = void 0;
|
|
4
|
+
const utils_1 = require("@mysten/sui/utils");
|
|
5
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
6
|
+
class GrpcDataChannel {
|
|
7
|
+
constructor(grpc) {
|
|
8
|
+
this.grpc = grpc;
|
|
9
|
+
this.kind = 'grpc';
|
|
10
|
+
}
|
|
11
|
+
async listCoins(owner, coinType) {
|
|
12
|
+
const objs = await this.grpc.stateService.listAllOwnedObjects(owner, coinType);
|
|
13
|
+
return objs.map(o => ({
|
|
14
|
+
objectId: o.object_id ?? o.objectId,
|
|
15
|
+
version: String(o.version),
|
|
16
|
+
digest: o.digest,
|
|
17
|
+
balance: String(o.balance ?? '0'),
|
|
18
|
+
})).filter(c => c.objectId);
|
|
19
|
+
}
|
|
20
|
+
async getSharedRef(objectId) {
|
|
21
|
+
const res = await this.grpc.ledgerService.getObject(objectId, ['owner']);
|
|
22
|
+
const owner = res?.object?.owner;
|
|
23
|
+
const isv = owner?.initial_shared_version ?? owner?.version;
|
|
24
|
+
if (isv == null)
|
|
25
|
+
throw new Error(`[grpc] ${objectId} 无 initial_shared_version(非共享对象?)`);
|
|
26
|
+
return { objectId, initialSharedVersion: String(isv) };
|
|
27
|
+
}
|
|
28
|
+
async getGasPrice() {
|
|
29
|
+
return BigInt(await this.grpc.gasPriceCache.getGasPrice());
|
|
30
|
+
}
|
|
31
|
+
async simulate(_txBytes) {
|
|
32
|
+
throw new Error('[grpc] simulate 待接入 live_data_service SimulateTransaction wrapper;dry-run 期请用 JsonRpcDataChannel');
|
|
33
|
+
}
|
|
34
|
+
async execute(txBytes, signatures) {
|
|
35
|
+
const sigBytes = signatures.map(s => (0, utils_1.fromB64)(s));
|
|
36
|
+
const resp = await this.grpc.transactionService.executeTransactionMultiSig(txBytes, sigBytes, ['transaction', 'effects', 'finality']);
|
|
37
|
+
return this.normalize(resp);
|
|
38
|
+
}
|
|
39
|
+
normalize(resp) {
|
|
40
|
+
const tx = resp?.transaction ?? resp;
|
|
41
|
+
const digest = tx?.digest ?? resp?.digest ?? '';
|
|
42
|
+
const effects = tx?.effects ?? resp?.effects;
|
|
43
|
+
const success = effects?.status?.success === true || (!!effects && !effects?.status?.error && effects?.status?.success !== false);
|
|
44
|
+
const error = success ? undefined : (effects?.status?.error || effects?.status?.description || 'unknown');
|
|
45
|
+
const changed = effects?.changed_objects ?? effects?.changedObjects ?? [];
|
|
46
|
+
const changes = changed.map((c) => {
|
|
47
|
+
const idOp = c.id_operation ?? c.idOperation;
|
|
48
|
+
const kind = idOp === 'CREATED' ? 'created' : idOp === 'DELETED' ? 'deleted' : 'mutated';
|
|
49
|
+
return {
|
|
50
|
+
type: kind,
|
|
51
|
+
objectId: c.object_id ?? c.objectId,
|
|
52
|
+
objectType: undefined,
|
|
53
|
+
version: (c.output_version ?? c.outputVersion) != null ? String(c.output_version ?? c.outputVersion) : undefined,
|
|
54
|
+
digest: c.output_digest ?? c.outputDigest,
|
|
55
|
+
owner: (c.output_owner ?? c.outputOwner)?.address ?? c.output_owner ?? c.outputOwner,
|
|
56
|
+
};
|
|
57
|
+
}).filter((c) => c.objectId);
|
|
58
|
+
let gasUsed;
|
|
59
|
+
const g = effects?.gas_used ?? effects?.gasUsed;
|
|
60
|
+
if (g) {
|
|
61
|
+
try {
|
|
62
|
+
gasUsed = BigInt(g.computation_cost ?? g.computationCost ?? 0)
|
|
63
|
+
+ BigInt(g.storage_cost ?? g.storageCost ?? 0)
|
|
64
|
+
- BigInt(g.storage_rebate ?? g.storageRebate ?? 0);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
(0, dist_1.log_warn)('[grpc] gasUsed 解析失败');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { digest, success, error, objectChanges: changes, gasUsed };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.GrpcDataChannel = GrpcDataChannel;
|
|
@@ -0,0 +1,23 @@
|
|
|
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("./coin_cache"), exports);
|
|
18
|
+
__exportStar(require("./effects"), exports);
|
|
19
|
+
__exportStar(require("./core_channel"), exports);
|
|
20
|
+
__exportStar(require("./central_executor"), exports);
|
|
21
|
+
__exportStar(require("./coin_maintainer"), exports);
|
|
22
|
+
__exportStar(require("./executor_protocol"), exports);
|
|
23
|
+
__exportStar(require("./executor_ws_client"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
2
|
+
import { CoinRef } from '../coin/types';
|
|
3
|
+
import { DataChannel, NormalizedTxResult, SharedRef } from './data_channel';
|
|
4
|
+
export declare class JsonRpcDataChannel implements DataChannel {
|
|
5
|
+
private readonly client;
|
|
6
|
+
readonly kind: "jsonrpc";
|
|
7
|
+
constructor(client: SuiClient);
|
|
8
|
+
listCoins(owner: string, coinType: string): Promise<CoinRef[]>;
|
|
9
|
+
getSharedRef(objectId: string): Promise<SharedRef>;
|
|
10
|
+
getGasPrice(): Promise<bigint>;
|
|
11
|
+
simulate(txBytes: Uint8Array): Promise<NormalizedTxResult>;
|
|
12
|
+
execute(txBytes: Uint8Array, signatures: string[]): Promise<NormalizedTxResult>;
|
|
13
|
+
private normalize;
|
|
14
|
+
}
|