@clonegod/ttd-sui-common 1.0.103 → 2.0.2
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/sui_dex_env_args.js +0 -1
- package/dist/appconfig/sui_env_args.js +2 -0
- package/dist/grpc/sui-grpc-client.js +6 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/test/test.js +3 -3
- package/dist/test/test_checkpoint.js +2 -2
- package/dist/test/test_grpc.js +2 -2
- package/dist/trade/abstract_sui_dex_trade.d.ts +43 -0
- package/dist/trade/abstract_sui_dex_trade.js +380 -0
- package/dist/trade/executor/core_channel.js +4 -3
- package/dist/trade/index.d.ts +1 -1
- package/dist/trade/index.js +1 -1
- package/dist/trade/test/test_parse_sui_tx_result.js +2 -2
- package/package.json +1 -1
|
@@ -10,7 +10,6 @@ class SuiDexEnvArgs extends sui_env_args_1.SuiEnvArgs {
|
|
|
10
10
|
this.dex_id = (cfg.dex_id || '').toUpperCase();
|
|
11
11
|
this.rpc_endpoint = cfg.rpc_endpoint;
|
|
12
12
|
this.ws_endpoint = cfg.ws_endpoint;
|
|
13
|
-
this.grpc_endpoint = cfg.grpc_endpoint;
|
|
14
13
|
this.pair = cfg.pair ?? '';
|
|
15
14
|
this.group_id = cfg.group_id ?? '';
|
|
16
15
|
this.quote_pool_address = cfg.quote_pool_address;
|
|
@@ -11,6 +11,8 @@ class SuiEnvArgs extends dist_1.EnvArgs {
|
|
|
11
11
|
this.server_id = cfg.server_id ?? '';
|
|
12
12
|
this.redis_host = cfg.redis_host;
|
|
13
13
|
this.redis_port = String(cfg.redis_port);
|
|
14
|
+
this.grpc_endpoint = cfg.grpc_endpoint;
|
|
15
|
+
this.grpc_token = cfg.grpc_token ?? '';
|
|
14
16
|
this.server_ip_list = cfg.server_ip_list ?? '';
|
|
15
17
|
this.ip_exclude_prefix = cfg.ip_exclude_prefix ?? '';
|
|
16
18
|
this.config_center_host = cfg.config_center_host;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SuiGrpcClient = void 0;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
4
5
|
const grpc_connection_1 = require("./grpc-connection");
|
|
5
6
|
const ledger_service_1 = require("./ledger-service");
|
|
6
7
|
const state_service_1 = require("./state-service");
|
|
@@ -13,7 +14,11 @@ class SuiGrpcClient {
|
|
|
13
14
|
this.init();
|
|
14
15
|
}
|
|
15
16
|
init() {
|
|
16
|
-
const
|
|
17
|
+
const { grpc_endpoint, grpc_token } = (0, dist_1.getCoreEnv)();
|
|
18
|
+
if (!grpc_endpoint || !grpc_token) {
|
|
19
|
+
throw new Error('GRPC_ENDPOINT / GRPC_TOKEN 未配置(SuiGrpcClient 需 gRPC 端点)');
|
|
20
|
+
}
|
|
21
|
+
const grpcClient = grpc_connection_1.GrpcConnection.getInstance(grpc_endpoint, grpc_token);
|
|
17
22
|
this.ledgerService = new ledger_service_1.LedgerService(grpcClient);
|
|
18
23
|
this.transactionService = new transaction_service_1.TransactionService(grpcClient);
|
|
19
24
|
this.stateService = new state_service_1.StateService(grpcClient);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/test/test.js
CHANGED
|
@@ -49,10 +49,10 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
|
49
49
|
});
|
|
50
50
|
const suiProto = grpc.loadPackageDefinition(packageDefinition);
|
|
51
51
|
const LedgerService = suiProto.sui.rpc.v2.LedgerService;
|
|
52
|
-
const endpoint = process.env.
|
|
53
|
-
const token = process.env.
|
|
52
|
+
const endpoint = process.env.GRPC_ENDPOINT;
|
|
53
|
+
const token = process.env.GRPC_TOKEN;
|
|
54
54
|
if (!endpoint || !token) {
|
|
55
|
-
throw new Error('
|
|
55
|
+
throw new Error('GRPC_ENDPOINT and GRPC_TOKEN must be set');
|
|
56
56
|
}
|
|
57
57
|
const client = new LedgerService(endpoint, grpc.credentials.createSsl(), {
|
|
58
58
|
'grpc.keepalive_time_ms': 30000,
|
|
@@ -5,8 +5,8 @@ const fs_1 = require("fs");
|
|
|
5
5
|
const grpc_connection_1 = require("../grpc/grpc-connection");
|
|
6
6
|
const subscription_service_1 = require("../grpc/subscription-service");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
|
-
const grpc_endpoint = process.env.
|
|
9
|
-
const grpc_token = process.env.
|
|
8
|
+
const grpc_endpoint = process.env.GRPC_ENDPOINT;
|
|
9
|
+
const grpc_token = process.env.GRPC_TOKEN;
|
|
10
10
|
const test_subscribe_checkpoints = async () => {
|
|
11
11
|
const connection = grpc_connection_1.GrpcConnection.getInstance(grpc_endpoint, grpc_token);
|
|
12
12
|
const subscriptionService = new subscription_service_1.SubscriptionService(connection);
|
package/dist/test/test_grpc.js
CHANGED
|
@@ -4,8 +4,8 @@ const dist_1 = require("@clonegod/ttd-core/dist");
|
|
|
4
4
|
const grpc_connection_1 = require("../grpc/grpc-connection");
|
|
5
5
|
const ledger_service_1 = require("../grpc/ledger-service");
|
|
6
6
|
const state_service_1 = require("../grpc/state-service");
|
|
7
|
-
const grpc_endpoint = process.env.
|
|
8
|
-
const grpc_token = process.env.
|
|
7
|
+
const grpc_endpoint = process.env.GRPC_ENDPOINT;
|
|
8
|
+
const grpc_token = process.env.GRPC_TOKEN;
|
|
9
9
|
const test_get_service_info = async () => {
|
|
10
10
|
const connection = grpc_connection_1.GrpcConnection.getInstance(grpc_endpoint, grpc_token);
|
|
11
11
|
const ledgerService = new ledger_service_1.LedgerService(connection);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { QuoteResultType } from "@clonegod/ttd-core";
|
|
2
|
+
import { AbastrcatTrade, AppConfig, CHAIN_ID, TradeContext } from "@clonegod/ttd-core/dist";
|
|
3
|
+
import { SuiClient } from "@mysten/sui/client";
|
|
4
|
+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
5
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
6
|
+
import { PoolObjectInfo, SuiGrpcClient, SuiTxSender } from '../index';
|
|
7
|
+
import { SimpleRedisClient } from '../redis';
|
|
8
|
+
import { ExecutorWsClient } from './executor';
|
|
9
|
+
export declare abstract class AbstractSuiDexTrade extends AbastrcatTrade {
|
|
10
|
+
protected appConfig: AppConfig;
|
|
11
|
+
chain_id: CHAIN_ID.SUI;
|
|
12
|
+
protected group_wallets: Ed25519Keypair[];
|
|
13
|
+
protected wallet: Ed25519Keypair;
|
|
14
|
+
protected walletMode: 'single' | 'multi';
|
|
15
|
+
sui_client: SuiClient;
|
|
16
|
+
grpcClient: SuiGrpcClient;
|
|
17
|
+
transactionSender: SuiTxSender;
|
|
18
|
+
protected redisClient: SimpleRedisClient;
|
|
19
|
+
protected executorClient: ExecutorWsClient;
|
|
20
|
+
walletAddresses: string[];
|
|
21
|
+
currentWalletAddress: string;
|
|
22
|
+
protected chainNameLower: string;
|
|
23
|
+
constructor(appConfig: AppConfig, grpcClient: SuiGrpcClient);
|
|
24
|
+
init(): Promise<void>;
|
|
25
|
+
private initSuiClient;
|
|
26
|
+
protected getWalletMode(): 'single' | 'multi';
|
|
27
|
+
protected getSingleWallet(): Ed25519Keypair;
|
|
28
|
+
protected getGroupWallets(): Ed25519Keypair[];
|
|
29
|
+
protected getWalletAddresses(): string[];
|
|
30
|
+
protected chooseWallet(context: TradeContext): Promise<Ed25519Keypair>;
|
|
31
|
+
protected determineInputOutputTokens(order_msg: any, pool_info: any): {
|
|
32
|
+
inputToken: any;
|
|
33
|
+
outputToken: any;
|
|
34
|
+
};
|
|
35
|
+
local_calculate_output_amt(context: TradeContext): QuoteResultType;
|
|
36
|
+
protected getWalletAssetsFromRedis(walletAddress: string): Promise<any>;
|
|
37
|
+
protected getObjectInfo(objectId: string): Promise<PoolObjectInfo | null>;
|
|
38
|
+
protected compareBigIntBalance(a: any, b: any): number;
|
|
39
|
+
protected mergeTokenObjects(tx: Transaction, primaryCoin: any, objectsToMerge: any[], tokenSymbol: string): void;
|
|
40
|
+
protected try_refresh_wallet_objects(wallet: string, txid: string, context: TradeContext, initial_delay_ms?: number, try_times?: number, try_delay_ms?: number): Promise<void>;
|
|
41
|
+
execute(context: TradeContext, _retryCount?: number): Promise<string>;
|
|
42
|
+
protected abstract initConfigs(): Promise<void>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AbstractSuiDexTrade = void 0;
|
|
7
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
8
|
+
const client_1 = require("@mysten/sui/client");
|
|
9
|
+
const ed25519_1 = require("@mysten/sui/keypairs/ed25519");
|
|
10
|
+
const transactions_1 = require("@mysten/sui/transactions");
|
|
11
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
12
|
+
const index_1 = require("../index");
|
|
13
|
+
const redis_1 = require("../redis");
|
|
14
|
+
const executor_1 = require("./executor");
|
|
15
|
+
const tx_result_channel_1 = require("./tx_result_channel");
|
|
16
|
+
class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
|
|
17
|
+
constructor(appConfig, grpcClient) {
|
|
18
|
+
super();
|
|
19
|
+
this.appConfig = appConfig;
|
|
20
|
+
this.walletMode = 'multi';
|
|
21
|
+
this.walletAddresses = [];
|
|
22
|
+
this.currentWalletAddress = '';
|
|
23
|
+
this.grpcClient = grpcClient;
|
|
24
|
+
this.chainNameLower = this.appConfig.env_args.chain_id.toLowerCase();
|
|
25
|
+
this.redisClient = new redis_1.SimpleRedisClient(`${this.chainNameLower}:tx`);
|
|
26
|
+
}
|
|
27
|
+
async init() {
|
|
28
|
+
this.initSuiClient();
|
|
29
|
+
await this.initConfigs();
|
|
30
|
+
const walletGroupIds = process.env.SUI_WALLET_GROUP_IDS || process.env.WALLET_GROUP_IDS || '';
|
|
31
|
+
this.walletMode = walletGroupIds && walletGroupIds.trim().split(',').length > 0 ? 'multi' : 'single';
|
|
32
|
+
if (this.walletMode === 'multi') {
|
|
33
|
+
let wallet_infos = (0, dist_1.load_wallet_multi)(walletGroupIds.split(','), false);
|
|
34
|
+
this.group_wallets = wallet_infos.map(info => ed25519_1.Ed25519Keypair.fromSecretKey(info.private_key));
|
|
35
|
+
this.walletAddresses = this.group_wallets.map(keypair => keypair.getPublicKey().toSuiAddress());
|
|
36
|
+
(0, dist_1.log_info)(`init wallet mode: multi, wallet count: ${this.group_wallets.length}, walletAddresses: ${this.walletAddresses}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const { private_key } = this.appConfig.trade_runtime.wallet;
|
|
40
|
+
this.wallet = ed25519_1.Ed25519Keypair.fromSecretKey(private_key);
|
|
41
|
+
this.currentWalletAddress = this.wallet.getPublicKey().toSuiAddress();
|
|
42
|
+
this.walletAddresses = [this.currentWalletAddress];
|
|
43
|
+
(0, dist_1.log_info)(`init wallet mode: single, walletAddress= ${this.currentWalletAddress}`);
|
|
44
|
+
}
|
|
45
|
+
this.transactionSender = new index_1.SuiTxSender(this.appConfig, this.sui_client, this.grpcClient);
|
|
46
|
+
this.executorClient = new executor_1.ExecutorWsClient();
|
|
47
|
+
const txResultChannel = (0, tx_result_channel_1.get_sui_tx_result_channel)();
|
|
48
|
+
this.appConfig.arb_cache.redis_cmd.subscribe(txResultChannel, (message) => {
|
|
49
|
+
try {
|
|
50
|
+
const msg = JSON.parse(message);
|
|
51
|
+
if (msg.digest)
|
|
52
|
+
this.appConfig.emit(`SUI_TX_RESULT_${msg.digest}`, msg.digest);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
(0, dist_1.log_warn)(`[trade] bad tx result msg`, message);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
initSuiClient() {
|
|
60
|
+
const sui_rpc_url = process.env.RPC_ENDPOINT || 'https://fullnode.mainnet.sui.io:443';
|
|
61
|
+
this.sui_client = new client_1.SuiClient({ url: sui_rpc_url });
|
|
62
|
+
(0, dist_1.log_info)(`init sui client, rpc_url: ${sui_rpc_url}`);
|
|
63
|
+
}
|
|
64
|
+
getWalletMode() {
|
|
65
|
+
return this.walletMode;
|
|
66
|
+
}
|
|
67
|
+
getSingleWallet() {
|
|
68
|
+
if (this.walletMode !== 'single' || !this.wallet) {
|
|
69
|
+
throw new Error('Single wallet not available in multi-wallet mode or not initialized');
|
|
70
|
+
}
|
|
71
|
+
return this.wallet;
|
|
72
|
+
}
|
|
73
|
+
getGroupWallets() {
|
|
74
|
+
if (this.walletMode !== 'multi' || !this.group_wallets) {
|
|
75
|
+
throw new Error('Group wallets not available in single-wallet mode or not initialized');
|
|
76
|
+
}
|
|
77
|
+
return this.group_wallets;
|
|
78
|
+
}
|
|
79
|
+
getWalletAddresses() {
|
|
80
|
+
return this.walletAddresses;
|
|
81
|
+
}
|
|
82
|
+
async chooseWallet(context) {
|
|
83
|
+
if (this.getWalletMode() === 'single') {
|
|
84
|
+
return this.getSingleWallet();
|
|
85
|
+
}
|
|
86
|
+
const lockIdentifier = `choose_wallet`;
|
|
87
|
+
const lockKey = `${this.chainNameLower}:lock:${lockIdentifier}`;
|
|
88
|
+
const lockValue = `${Math.random().toString(36).substring(2, 15)}`;
|
|
89
|
+
let lockAcquired = false;
|
|
90
|
+
const start_time = Date.now();
|
|
91
|
+
const maxRetryTime = 200;
|
|
92
|
+
const retryDelay = 20;
|
|
93
|
+
do {
|
|
94
|
+
lockAcquired = await this.redisClient.acquireLock(lockKey, lockValue, 2);
|
|
95
|
+
if (lockAcquired) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
await (0, dist_1.sleep)(retryDelay);
|
|
99
|
+
} while (!lockAcquired && Date.now() - start_time < maxRetryTime);
|
|
100
|
+
if (!lockAcquired) {
|
|
101
|
+
throw new Error(`choose wallet, acquire lock failed: ${lockKey}, took ${Date.now() - start_time}ms`);
|
|
102
|
+
}
|
|
103
|
+
(0, dist_1.log_info)(`choose wallet, acquire lock success: ${lockKey}, took ${Date.now() - start_time}ms`);
|
|
104
|
+
try {
|
|
105
|
+
const { inputToken } = this.determineInputOutputTokens(context.order_msg, context.pool_info);
|
|
106
|
+
console.log('inputToken', inputToken);
|
|
107
|
+
const requiredAmount = new decimal_js_1.default(context.order_msg.amount);
|
|
108
|
+
const wallet_assets = await this.redisClient.hgetall('sui:wallet:assets');
|
|
109
|
+
if (!wallet_assets || Object.keys(wallet_assets).length === 0) {
|
|
110
|
+
throw new Error('没有找到钱包资产信息,请确保钱包监控服务正在运行');
|
|
111
|
+
}
|
|
112
|
+
const wallet_assets_map = {};
|
|
113
|
+
for (const [walletAddress, assetsJson] of Object.entries(wallet_assets)) {
|
|
114
|
+
try {
|
|
115
|
+
wallet_assets_map[walletAddress] = JSON.parse(assetsJson);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.warn(`解析钱包资产数据失败: ${walletAddress}`, error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const availableWallets = [];
|
|
122
|
+
const allWallets = [];
|
|
123
|
+
for (const [walletAddress, assets] of Object.entries(wallet_assets_map)) {
|
|
124
|
+
try {
|
|
125
|
+
const wallet = this.group_wallets?.find(w => w.getPublicKey().toSuiAddress().toLowerCase() === walletAddress.toLowerCase());
|
|
126
|
+
if (!wallet) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const tokenAsset = assets.tokens?.find((token) => (0, index_1.normalizeSuiTokenAddress)(token.address) === (0, index_1.normalizeSuiTokenAddress)(inputToken.address));
|
|
130
|
+
let tokenBalance = tokenAsset?.balance || '0';
|
|
131
|
+
const balanceDecimal = new decimal_js_1.default(tokenBalance);
|
|
132
|
+
if (balanceDecimal.gte(requiredAmount)) {
|
|
133
|
+
availableWallets.push({
|
|
134
|
+
wallet,
|
|
135
|
+
balance: tokenBalance
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log(`钱包 ${walletAddress} 余额不足: ${tokenBalance} ${inputToken.symbol} < ${requiredAmount} ${inputToken.symbol}`);
|
|
140
|
+
}
|
|
141
|
+
allWallets.push({
|
|
142
|
+
wallet,
|
|
143
|
+
balance: tokenBalance
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.warn(`处理钱包资产时出错: ${walletAddress}`, error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (availableWallets.length === 0) {
|
|
151
|
+
let error_msg = `all wallets are insufficient, required: ${requiredAmount} ${inputToken.symbol}`;
|
|
152
|
+
(0, dist_1.log_warn)(error_msg);
|
|
153
|
+
allWallets.forEach((walletInfo, index) => {
|
|
154
|
+
const walletAddress = walletInfo.wallet.getPublicKey().toSuiAddress();
|
|
155
|
+
const shortAddress = walletAddress.substring(0, 6) + '...' + walletAddress.substring(walletAddress.length - 4);
|
|
156
|
+
console.log(`${index + 1}. ${shortAddress}: ${walletInfo.balance} ${inputToken.symbol}`);
|
|
157
|
+
});
|
|
158
|
+
throw new Error(error_msg);
|
|
159
|
+
}
|
|
160
|
+
console.log('availableWallets', availableWallets.map(w => w.wallet.getPublicKey().toSuiAddress()));
|
|
161
|
+
const wallet_last_used = await this.redisClient.hgetall('sui:wallet:last_used');
|
|
162
|
+
for (const availableWallet of availableWallets) {
|
|
163
|
+
const walletAddress = availableWallet.wallet.getPublicKey().toSuiAddress();
|
|
164
|
+
const lastUsedData = wallet_last_used?.[walletAddress];
|
|
165
|
+
if (lastUsedData) {
|
|
166
|
+
try {
|
|
167
|
+
const lastUsedInfo = JSON.parse(lastUsedData);
|
|
168
|
+
availableWallet.lastUsedAt = lastUsedInfo.lastUsedAt;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.warn(`解析钱包最后使用时间失败: ${walletAddress}`, error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
await this.redisClient.hsetValue('sui:wallet:last_used', walletAddress, JSON.stringify({
|
|
176
|
+
lastUsedAt: 0,
|
|
177
|
+
lastUsedTime: new Date(0).toISOString()
|
|
178
|
+
}), 24 * 60 * 60);
|
|
179
|
+
availableWallet.lastUsedAt = 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let selectedWallet;
|
|
183
|
+
let selectionStrategy;
|
|
184
|
+
if (availableWallets.length === 1) {
|
|
185
|
+
selectedWallet = availableWallets[0].wallet;
|
|
186
|
+
selectionStrategy = 'OnlyOne';
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const hasUsageHistory = wallet_last_used && Object.keys(wallet_last_used).length > 0;
|
|
190
|
+
if (hasUsageHistory) {
|
|
191
|
+
availableWallets.sort((a, b) => {
|
|
192
|
+
const aTime = a.lastUsedAt || 0;
|
|
193
|
+
const bTime = b.lastUsedAt || 0;
|
|
194
|
+
return aTime - bTime;
|
|
195
|
+
});
|
|
196
|
+
selectedWallet = availableWallets[0].wallet;
|
|
197
|
+
selectionStrategy = 'MaxLastUsed';
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
availableWallets.sort((a, b) => {
|
|
201
|
+
const aBalance = new decimal_js_1.default(a.balance);
|
|
202
|
+
const bBalance = new decimal_js_1.default(b.balance);
|
|
203
|
+
return bBalance.cmp(aBalance);
|
|
204
|
+
});
|
|
205
|
+
selectedWallet = availableWallets[0].wallet;
|
|
206
|
+
selectionStrategy = 'MaxBalance';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const selectedWalletAddress = selectedWallet.getPublicKey().toSuiAddress();
|
|
210
|
+
await this.redisClient.hsetValue('sui:wallet:last_used', selectedWalletAddress, JSON.stringify({
|
|
211
|
+
lastUsedAt: Date.now(),
|
|
212
|
+
lastUsedTime: new Date().toISOString()
|
|
213
|
+
}), 24 * 60 * 60);
|
|
214
|
+
const selectedWalletInfo = availableWallets.find(w => w.wallet.getPublicKey().toSuiAddress() === selectedWalletAddress);
|
|
215
|
+
(0, dist_1.log_info)(`Choose wallet: ${selectionStrategy} -> ${selectedWalletAddress}, balance: ${selectedWalletInfo?.balance} ${inputToken.symbol}, required: ${requiredAmount} ${inputToken.symbol}`);
|
|
216
|
+
return selectedWallet;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
(0, dist_1.log_error)(`choose wallet failed, error: ${error.message}`, error);
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
await this.redisClient.releaseLock(lockKey, lockValue);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
determineInputOutputTokens(order_msg, pool_info) {
|
|
227
|
+
const { aToB } = order_msg;
|
|
228
|
+
const { tokenA, tokenB, quote_token } = pool_info;
|
|
229
|
+
const quoteToken = [tokenA, tokenB].find(token => token.symbol === quote_token);
|
|
230
|
+
const nonQuoteToken = [tokenA, tokenB].find(token => token.symbol !== quote_token);
|
|
231
|
+
const inputToken = aToB ? nonQuoteToken : quoteToken;
|
|
232
|
+
const outputToken = aToB ? quoteToken : nonQuoteToken;
|
|
233
|
+
if (!inputToken || !outputToken) {
|
|
234
|
+
throw new Error(`无法确定输入/输出代币: ${JSON.stringify({
|
|
235
|
+
tokenA: tokenA.symbol,
|
|
236
|
+
tokenB: tokenB.symbol,
|
|
237
|
+
quote: quote_token,
|
|
238
|
+
aToB
|
|
239
|
+
})}`);
|
|
240
|
+
}
|
|
241
|
+
return { inputToken, outputToken };
|
|
242
|
+
}
|
|
243
|
+
local_calculate_output_amt(context) {
|
|
244
|
+
const { price_msg, order_msg, pool_info, trade_runtime, slippage_bps } = context;
|
|
245
|
+
let { ask, bid } = price_msg;
|
|
246
|
+
const { aToB, amount: uiAmount, price: cex_order_price } = order_msg;
|
|
247
|
+
let { inputToken, outputToken } = (0, dist_1.get_input_out_token)(pool_info, aToB);
|
|
248
|
+
let amountIn = new decimal_js_1.default(uiAmount).mul(10 ** inputToken.decimals);
|
|
249
|
+
let slippage = slippage_bps / 10000;
|
|
250
|
+
let price;
|
|
251
|
+
let amountOut;
|
|
252
|
+
let amountOutMin;
|
|
253
|
+
if (aToB) {
|
|
254
|
+
price = cex_order_price ?? bid.price;
|
|
255
|
+
amountOut = new decimal_js_1.default(uiAmount).mul(price).mul(10 ** outputToken.decimals);
|
|
256
|
+
amountOutMin = amountOut.mul(1 - slippage);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
price = cex_order_price ?? ask.price;
|
|
260
|
+
amountOut = new decimal_js_1.default(uiAmount).div(price).mul(10 ** outputToken.decimals);
|
|
261
|
+
amountOutMin = amountOut.mul(1 - slippage);
|
|
262
|
+
}
|
|
263
|
+
let quote_result = {
|
|
264
|
+
inputToken,
|
|
265
|
+
outputToken,
|
|
266
|
+
amountIn: Number(amountIn.toFixed(0)),
|
|
267
|
+
amountOut: Number(amountOut.toFixed(0)),
|
|
268
|
+
amountOutMin: Number(amountOutMin.toFixed(0)),
|
|
269
|
+
slippageBps: slippage_bps,
|
|
270
|
+
priceImpact: 0,
|
|
271
|
+
price: price.toString()
|
|
272
|
+
};
|
|
273
|
+
return quote_result;
|
|
274
|
+
}
|
|
275
|
+
async getWalletAssetsFromRedis(walletAddress) {
|
|
276
|
+
try {
|
|
277
|
+
const redisClient = this.appConfig.arb_cache.redis_cmd.redis_client;
|
|
278
|
+
const walletAssetsJson = await redisClient.HGET('sui:wallet:assets', walletAddress);
|
|
279
|
+
if (!walletAssetsJson) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return JSON.parse(walletAssetsJson);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
(0, dist_1.log_error)(`从Redis获取钱包资产信息失败`, error);
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async getObjectInfo(objectId) {
|
|
290
|
+
try {
|
|
291
|
+
let startTime = Date.now();
|
|
292
|
+
const objectResponse = await this.grpcClient.ledgerService.getObject(objectId, [
|
|
293
|
+
'object_id',
|
|
294
|
+
'version',
|
|
295
|
+
'digest',
|
|
296
|
+
'owner',
|
|
297
|
+
]);
|
|
298
|
+
let objectInfo = null;
|
|
299
|
+
if (objectResponse.object) {
|
|
300
|
+
objectInfo = {
|
|
301
|
+
objectId: objectId,
|
|
302
|
+
version: objectResponse.object.version,
|
|
303
|
+
digest: objectResponse.object.digest,
|
|
304
|
+
initialSharedVersion: objectResponse.object.owner.version
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
(0, dist_1.log_info)(`get object info success, cost: ${Date.now() - startTime} ms`, objectInfo);
|
|
308
|
+
return objectInfo;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
(0, dist_1.log_error)(`get object info failed, object_id=${objectId}`, error);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
compareBigIntBalance(a, b) {
|
|
316
|
+
const balanceA = BigInt(a?.balance || '0');
|
|
317
|
+
const balanceB = BigInt(b?.balance || '0');
|
|
318
|
+
if (balanceA > balanceB)
|
|
319
|
+
return -1;
|
|
320
|
+
if (balanceA < balanceB)
|
|
321
|
+
return 1;
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
mergeTokenObjects(tx, primaryCoin, objectsToMerge, tokenSymbol) {
|
|
325
|
+
if (objectsToMerge.length === 0)
|
|
326
|
+
return;
|
|
327
|
+
const other_objects = objectsToMerge.map((obj) => {
|
|
328
|
+
if (obj.version && obj.digest) {
|
|
329
|
+
return tx.object(transactions_1.Inputs.ObjectRef({
|
|
330
|
+
objectId: obj.objectId,
|
|
331
|
+
version: obj.version,
|
|
332
|
+
digest: obj.digest
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
throw new Error(`${tokenSymbol} 对象 ${obj.objectId} 数据不完整`);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
tx.mergeCoins(primaryCoin, other_objects);
|
|
340
|
+
(0, dist_1.log_info)(`✅ ${tokenSymbol} 进行对象合并,涉及 ${other_objects.length} 个对象`);
|
|
341
|
+
}
|
|
342
|
+
async try_refresh_wallet_objects(wallet, txid, context, initial_delay_ms = 0, try_times = 5, try_delay_ms = 300) {
|
|
343
|
+
for (let i = 0; i < try_times; i++) {
|
|
344
|
+
let delay_ms = initial_delay_ms + i * try_delay_ms;
|
|
345
|
+
setTimeout(() => {
|
|
346
|
+
let group_id = this.appConfig.trade_runtime.group.id;
|
|
347
|
+
let coin_types = [context.pool_info.tokenA.address, context.pool_info.tokenB.address];
|
|
348
|
+
this.appConfig.arb_cache.redis_event_publisher.publish_wallet_raw_tx_event(group_id, wallet, { group_id, wallet, txid, coin_types, remark: `PRE_REFRESH_OBJECTS_${i}` });
|
|
349
|
+
}, delay_ms);
|
|
350
|
+
(0, dist_1.log_info)(`try_refresh_wallet_objects, txid=${txid}, i=${i}, delay_ms=${delay_ms}ms`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async execute(context, _retryCount) {
|
|
354
|
+
const q = this.local_calculate_output_amt(context);
|
|
355
|
+
const { pool_info } = context;
|
|
356
|
+
const coinTypeA = (0, index_1.normalizeSuiTokenAddress)(pool_info.tokenA.address);
|
|
357
|
+
const coinTypeB = (0, index_1.normalizeSuiTokenAddress)(pool_info.tokenB.address);
|
|
358
|
+
const inAddr = (0, index_1.normalizeSuiTokenAddress)(q.inputToken.address);
|
|
359
|
+
const a2b = inAddr === coinTypeA;
|
|
360
|
+
const req = {
|
|
361
|
+
dexId: (pool_info.dex_id || this.appConfig.trade_runtime.dex_id),
|
|
362
|
+
poolId: pool_info.pool_address,
|
|
363
|
+
coinTypeA,
|
|
364
|
+
coinTypeB,
|
|
365
|
+
a2b,
|
|
366
|
+
amountIn: BigInt(q.amountIn),
|
|
367
|
+
minOut: BigInt(q.amountOutMin),
|
|
368
|
+
sqrtPriceLimit: 0n,
|
|
369
|
+
};
|
|
370
|
+
(0, dist_1.log_info)(`[trade] 转发 swap 给集中执行器`, {
|
|
371
|
+
dex_id: req.dexId, pair: pool_info.pair, a2b, amountIn: req.amountIn.toString(), minOut: req.minOut.toString(),
|
|
372
|
+
});
|
|
373
|
+
const res = await this.executorClient.requestSwap(req);
|
|
374
|
+
if (!res.submitted) {
|
|
375
|
+
throw new Error(`集中执行器提交失败: ${res.error}`);
|
|
376
|
+
}
|
|
377
|
+
return res.digest;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
exports.AbstractSuiDexTrade = AbstractSuiDexTrade;
|
|
@@ -23,10 +23,11 @@ const GRPC_CLIENT_OPTIONS = {
|
|
|
23
23
|
'grpc.max_reconnect_backoff_ms': 30000,
|
|
24
24
|
};
|
|
25
25
|
function buildGrpcCore(opts = {}) {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
26
|
+
const coreEnv = (0, dist_1.getCoreEnv)();
|
|
27
|
+
const endpoint = opts.endpoint ?? coreEnv.grpc_endpoint;
|
|
28
|
+
const token = opts.token ?? coreEnv.grpc_token;
|
|
28
29
|
if (!endpoint)
|
|
29
|
-
throw new Error('
|
|
30
|
+
throw new Error('GRPC_ENDPOINT 未配置(集中执行器需 gRPC 端点)');
|
|
30
31
|
const transport = new grpc_transport_1.GrpcTransport({
|
|
31
32
|
host: endpoint,
|
|
32
33
|
channelCredentials: grpc_js_1.ChannelCredentials.createSsl(),
|
package/dist/trade/index.d.ts
CHANGED
package/dist/trade/index.js
CHANGED
|
@@ -21,4 +21,4 @@ __exportStar(require("./coin"), exports);
|
|
|
21
21
|
__exportStar(require("./swap"), exports);
|
|
22
22
|
__exportStar(require("./executor"), exports);
|
|
23
23
|
__exportStar(require("./tx_result_channel"), exports);
|
|
24
|
-
__exportStar(require("./
|
|
24
|
+
__exportStar(require("./abstract_sui_dex_trade"), exports);
|
|
@@ -9,8 +9,8 @@ const sui_tx_parser_1 = require("../parse/sui_tx_parser");
|
|
|
9
9
|
const index_1 = require("../../index");
|
|
10
10
|
const index_2 = require("../../index");
|
|
11
11
|
async function test_get_tx_result() {
|
|
12
|
-
const grpc_endpoint = process.env.
|
|
13
|
-
const grpc_token = process.env.
|
|
12
|
+
const grpc_endpoint = process.env.GRPC_ENDPOINT || '';
|
|
13
|
+
const grpc_token = process.env.GRPC_TOKEN || '';
|
|
14
14
|
const grpcConnection = index_1.GrpcConnection.getInstance(grpc_endpoint, grpc_token);
|
|
15
15
|
const ledgerService = new index_1.LedgerService(grpcConnection);
|
|
16
16
|
let wallet_address = '0x6367c8755b8c39cab7305bfa75cb17d050508d2e55f6862a7682377ad6d46ee7';
|