@tonappchain/sdk 0.6.6-mainnet-alpha → 0.7.0-rc10
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/LICENSE +20 -20
- package/README.md +191 -191
- package/dist/adapters/contractOpener.d.ts +6 -5
- package/dist/adapters/contractOpener.js +6 -6
- package/dist/adapters/retryableContractOpener.d.ts +9 -5
- package/dist/adapters/retryableContractOpener.js +2 -2
- package/dist/assets/AssetCache.d.ts +23 -0
- package/dist/assets/AssetCache.js +36 -0
- package/dist/assets/AssetFactory.d.ts +7 -0
- package/dist/assets/AssetFactory.js +46 -0
- package/dist/assets/FT.d.ts +58 -0
- package/dist/assets/FT.js +231 -0
- package/dist/assets/NFT.d.ts +65 -0
- package/dist/assets/NFT.js +210 -0
- package/dist/assets/TON.d.ts +38 -0
- package/dist/assets/TON.js +91 -0
- package/dist/assets/index.d.ts +4 -0
- package/dist/assets/index.js +11 -0
- package/dist/errors/errors.d.ts +6 -0
- package/dist/errors/errors.js +15 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +26 -22
- package/dist/errors/instances.d.ts +4 -1
- package/dist/errors/instances.js +11 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -1
- package/dist/interfaces/IAsset.d.ts +73 -0
- package/dist/interfaces/IAsset.js +2 -0
- package/dist/interfaces/IConfiguration.d.ts +35 -0
- package/dist/interfaces/IConfiguration.js +2 -0
- package/dist/interfaces/IContractOpener.d.ts +21 -0
- package/dist/interfaces/IContractOpener.js +2 -0
- package/dist/interfaces/IHttpClient.d.ts +16 -0
- package/dist/interfaces/IHttpClient.js +2 -0
- package/dist/interfaces/ILiteSequencerClient.d.ts +30 -0
- package/dist/interfaces/ILiteSequencerClient.js +2 -0
- package/dist/interfaces/ILiteSequencerClientFactory.d.ts +9 -0
- package/dist/interfaces/ILiteSequencerClientFactory.js +2 -0
- package/dist/interfaces/ILogger.d.ts +10 -0
- package/dist/interfaces/ILogger.js +2 -0
- package/dist/interfaces/IOperationTracker.d.ts +66 -0
- package/dist/interfaces/IOperationTracker.js +2 -0
- package/dist/interfaces/ISender.d.ts +35 -0
- package/dist/interfaces/ISender.js +2 -0
- package/dist/interfaces/ISimulator.d.ts +47 -0
- package/dist/interfaces/ISimulator.js +2 -0
- package/dist/interfaces/ITacSDK.d.ts +147 -0
- package/dist/interfaces/ITacSDK.js +2 -0
- package/dist/interfaces/ITransactionManager.d.ts +35 -0
- package/dist/interfaces/ITransactionManager.js +2 -0
- package/dist/interfaces/IWallet.d.ts +20 -0
- package/dist/interfaces/IWallet.js +2 -0
- package/dist/interfaces/index.d.ts +13 -0
- package/dist/interfaces/index.js +29 -0
- package/dist/sdk/AxiosHttpClient.d.ts +12 -0
- package/dist/sdk/AxiosHttpClient.js +23 -0
- package/dist/sdk/Configuration.d.ts +21 -0
- package/dist/sdk/Configuration.js +90 -0
- package/dist/sdk/LiteSequencerClient.d.ts +6 -2
- package/dist/sdk/LiteSequencerClient.js +58 -14
- package/dist/sdk/Logger.d.ts +13 -0
- package/dist/sdk/Logger.js +25 -0
- package/dist/sdk/OperationTracker.d.ts +10 -5
- package/dist/sdk/OperationTracker.js +87 -45
- package/dist/sdk/Simulator.d.ts +17 -0
- package/dist/sdk/Simulator.js +163 -0
- package/dist/sdk/StartTracking.d.ts +6 -0
- package/dist/sdk/StartTracking.js +69 -32
- package/dist/sdk/TacSdk.d.ts +27 -44
- package/dist/sdk/TacSdk.js +121 -816
- package/dist/sdk/TransactionManager.d.ts +22 -0
- package/dist/sdk/TransactionManager.js +272 -0
- package/dist/sdk/TxFinalizer.d.ts +10 -0
- package/dist/sdk/TxFinalizer.js +104 -0
- package/dist/sdk/Utils.d.ts +7 -2
- package/dist/sdk/Utils.js +43 -24
- package/dist/sdk/Validator.d.ts +9 -0
- package/dist/sdk/Validator.js +43 -0
- package/dist/sender/BatchSender.d.ts +7 -5
- package/dist/sender/BatchSender.js +18 -6
- package/dist/sender/RawSender.d.ts +11 -6
- package/dist/sender/RawSender.js +46 -18
- package/dist/sender/SenderFactory.d.ts +2 -2
- package/dist/sender/SenderFactory.js +5 -4
- package/dist/sender/TonConnectSender.d.ts +7 -5
- package/dist/sender/TonConnectSender.js +14 -10
- package/dist/sender/index.d.ts +2 -2
- package/dist/sender/index.js +2 -2
- package/dist/structs/InternalStruct.d.ts +52 -33
- package/dist/structs/Struct.d.ts +92 -94
- package/dist/structs/Struct.js +11 -1
- package/dist/wrappers/HighloadQueryId.js +0 -1
- package/dist/wrappers/HighloadWalletV3.d.ts +4 -3
- package/dist/wrappers/HighloadWalletV3.js +5 -2
- package/package.json +67 -67
- package/dist/sender/SenderAbstraction.d.ts +0 -20
- package/dist/sender/SenderAbstraction.js +0 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Cell } from '@ton/ton';
|
|
2
|
+
import { Wallet } from 'ethers';
|
|
3
|
+
import type { ISender } from '../sender';
|
|
4
|
+
import { IConfiguration, ILogger, IOperationTracker, ISimulator, ITransactionManager } from '../interfaces';
|
|
5
|
+
import { IAsset, CrossChainTransactionOptions, CrosschainTx, EvmProxyMsg, OperationIdsByShardsKey, TransactionLinker, TransactionLinkerWithOperationId, ValidExecutors, WaitOptions } from '../structs/Struct';
|
|
6
|
+
export declare class TransactionManager implements ITransactionManager {
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly simulator;
|
|
9
|
+
private readonly operationTracker;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
private readonly evmDataCellBuilder;
|
|
12
|
+
constructor(config: IConfiguration, simulator: ISimulator, operationTracker: IOperationTracker, logger?: ILogger, options?: {
|
|
13
|
+
evmDataCellBuilder?: (transactionLinker: TransactionLinker, evmProxyMsg: EvmProxyMsg, validExecutors: ValidExecutors) => Cell;
|
|
14
|
+
});
|
|
15
|
+
private prepareCrossChainTransaction;
|
|
16
|
+
private generateCrossChainMessages;
|
|
17
|
+
sendCrossChainTransaction(evmProxyMsg: EvmProxyMsg, sender: ISender, assets?: IAsset[], options?: CrossChainTransactionOptions, waitOptions?: WaitOptions<string>): Promise<TransactionLinkerWithOperationId>;
|
|
18
|
+
sendCrossChainTransactions(sender: ISender, txs: CrosschainTx[], waitOptions?: WaitOptions<OperationIdsByShardsKey>): Promise<TransactionLinkerWithOperationId[]>;
|
|
19
|
+
private prepareCrossChainTransactions;
|
|
20
|
+
private waitForOperationIds;
|
|
21
|
+
bridgeTokensToTON(signer: Wallet, value: bigint, tonTarget: string, assets?: IAsset[], tvmExecutorFee?: bigint, tvmValidExecutors?: string[]): Promise<string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransactionManager = void 0;
|
|
4
|
+
const assets_1 = require("../assets");
|
|
5
|
+
const Struct_1 = require("../structs/Struct");
|
|
6
|
+
const Consts_1 = require("./Consts");
|
|
7
|
+
const Logger_1 = require("./Logger");
|
|
8
|
+
const Utils_1 = require("./Utils");
|
|
9
|
+
const Validator_1 = require("./Validator");
|
|
10
|
+
class TransactionManager {
|
|
11
|
+
constructor(config, simulator, operationTracker, logger = new Logger_1.NoopLogger(), options) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.simulator = simulator;
|
|
14
|
+
this.operationTracker = operationTracker;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.evmDataCellBuilder = options?.evmDataCellBuilder ?? Utils_1.buildEvmDataCell;
|
|
17
|
+
}
|
|
18
|
+
async prepareCrossChainTransaction(evmProxyMsg, caller, assets, options) {
|
|
19
|
+
this.logger.debug('Preparing cross-chain transaction');
|
|
20
|
+
const { allowSimulationError = false, isRoundTrip = undefined, protocolFee = undefined, evmExecutorFee = undefined, tvmExecutorFee = undefined, calculateRollbackFee = true, } = options || {};
|
|
21
|
+
let { evmValidExecutors = [], tvmValidExecutors = [] } = options || {};
|
|
22
|
+
Validator_1.Validator.validateEVMAddress(evmProxyMsg.evmTargetAddress);
|
|
23
|
+
Validator_1.Validator.validateEVMAddresses(options?.evmValidExecutors);
|
|
24
|
+
Validator_1.Validator.validateTVMAddresses(options?.tvmValidExecutors);
|
|
25
|
+
const aggregatedData = await (0, Utils_1.aggregateTokens)(assets);
|
|
26
|
+
await Promise.all(aggregatedData.jettons.map((jetton) => jetton.checkCanBeTransferredBy(caller)));
|
|
27
|
+
await Promise.all(aggregatedData.nfts.map((nft) => nft.checkCanBeTransferredBy(caller)));
|
|
28
|
+
await aggregatedData.ton?.checkCanBeTransferredBy(caller);
|
|
29
|
+
const tokensLength = aggregatedData.jettons.length + aggregatedData.nfts.length;
|
|
30
|
+
this.logger.debug(`Tokens length: ${tokensLength}`);
|
|
31
|
+
const transactionLinkerShardCount = tokensLength == 0 ? 1 : tokensLength;
|
|
32
|
+
this.logger.debug(`Transaction linker shard count: ${transactionLinkerShardCount}`);
|
|
33
|
+
const transactionLinker = (0, Utils_1.generateTransactionLinker)(caller, transactionLinkerShardCount);
|
|
34
|
+
this.logger.debug(`Generated transaction linker: ${(0, Utils_1.formatObjectForLogging)(transactionLinker)}`);
|
|
35
|
+
if (evmValidExecutors.length == 0) {
|
|
36
|
+
evmValidExecutors = this.config.getTrustedTACExecutors;
|
|
37
|
+
}
|
|
38
|
+
if (tvmValidExecutors.length == 0) {
|
|
39
|
+
tvmValidExecutors = this.config.getTrustedTONExecutors;
|
|
40
|
+
}
|
|
41
|
+
const { feeParams } = await this.simulator.getSimulationInfoForTransaction(evmProxyMsg, transactionLinker, assets ?? [], allowSimulationError, isRoundTrip, evmValidExecutors, tvmValidExecutors, calculateRollbackFee);
|
|
42
|
+
if (evmProxyMsg.gasLimit == undefined) {
|
|
43
|
+
evmProxyMsg.gasLimit = feeParams.gasLimit;
|
|
44
|
+
}
|
|
45
|
+
if (evmExecutorFee != undefined) {
|
|
46
|
+
feeParams.evmExecutorFee = evmExecutorFee;
|
|
47
|
+
}
|
|
48
|
+
if (feeParams.isRoundTrip && tvmExecutorFee != undefined) {
|
|
49
|
+
feeParams.tvmExecutorFee = tvmExecutorFee;
|
|
50
|
+
}
|
|
51
|
+
if (protocolFee != undefined) {
|
|
52
|
+
feeParams.protocolFee = protocolFee;
|
|
53
|
+
}
|
|
54
|
+
this.logger.debug(`Resulting fee params: ${(0, Utils_1.formatObjectForLogging)(feeParams)}`);
|
|
55
|
+
const validExecutors = {
|
|
56
|
+
tac: evmValidExecutors,
|
|
57
|
+
ton: tvmValidExecutors,
|
|
58
|
+
};
|
|
59
|
+
this.logger.debug(`Valid executors: ${(0, Utils_1.formatObjectForLogging)(validExecutors)}`);
|
|
60
|
+
const evmData = this.evmDataCellBuilder(transactionLinker, evmProxyMsg, validExecutors);
|
|
61
|
+
const messages = await this.generateCrossChainMessages(caller, evmData, aggregatedData, feeParams);
|
|
62
|
+
const transaction = {
|
|
63
|
+
validUntil: +new Date() + 15 * 60 * 1000,
|
|
64
|
+
messages,
|
|
65
|
+
network: this.config.network,
|
|
66
|
+
};
|
|
67
|
+
this.logger.debug('Transaction prepared');
|
|
68
|
+
return { transaction, transactionLinker };
|
|
69
|
+
}
|
|
70
|
+
async generateCrossChainMessages(caller, evmData, aggregatedData, feeParams) {
|
|
71
|
+
this.logger.debug(`Generating cross-chain messages`);
|
|
72
|
+
const { jettons, nfts, ton = assets_1.TON.create(this.config) } = aggregatedData;
|
|
73
|
+
let crossChainTonAmount = ton.rawAmount;
|
|
74
|
+
let feeTonAmount = feeParams.protocolFee + feeParams.evmExecutorFee + feeParams.tvmExecutorFee;
|
|
75
|
+
this.logger.debug(`Crosschain ton amount: ${crossChainTonAmount}`);
|
|
76
|
+
this.logger.debug(`Fee ton amount: ${feeTonAmount}`);
|
|
77
|
+
if (jettons.length == 0 && nfts.length == 0) {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
address: this.config.TONParams.crossChainLayerAddress,
|
|
81
|
+
value: crossChainTonAmount + feeTonAmount + Consts_1.TRANSACTION_TON_AMOUNT,
|
|
82
|
+
payload: await ton.generatePayload({
|
|
83
|
+
excessReceiver: caller,
|
|
84
|
+
evmData,
|
|
85
|
+
feeParams,
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
const messages = [];
|
|
91
|
+
let currentFeeParams = feeParams;
|
|
92
|
+
for (const jetton of aggregatedData.jettons) {
|
|
93
|
+
const payload = await jetton.generatePayload({
|
|
94
|
+
excessReceiver: caller,
|
|
95
|
+
evmData,
|
|
96
|
+
crossChainTonAmount,
|
|
97
|
+
forwardFeeTonAmount: feeTonAmount,
|
|
98
|
+
feeParams: currentFeeParams,
|
|
99
|
+
});
|
|
100
|
+
const jettonWalletAddress = await jetton.getUserWalletAddress(caller);
|
|
101
|
+
messages.push({
|
|
102
|
+
address: jettonWalletAddress,
|
|
103
|
+
value: crossChainTonAmount + feeTonAmount + Consts_1.TRANSACTION_TON_AMOUNT,
|
|
104
|
+
payload,
|
|
105
|
+
});
|
|
106
|
+
crossChainTonAmount = 0n;
|
|
107
|
+
feeTonAmount = 0n;
|
|
108
|
+
currentFeeParams = undefined;
|
|
109
|
+
}
|
|
110
|
+
for (const nft of aggregatedData.nfts) {
|
|
111
|
+
const payload = await nft.generatePayload({
|
|
112
|
+
excessReceiver: caller,
|
|
113
|
+
evmData,
|
|
114
|
+
crossChainTonAmount,
|
|
115
|
+
forwardFeeTonAmount: feeTonAmount,
|
|
116
|
+
feeParams: currentFeeParams,
|
|
117
|
+
});
|
|
118
|
+
messages.push({
|
|
119
|
+
address: nft.address,
|
|
120
|
+
value: crossChainTonAmount + feeTonAmount + Consts_1.TRANSACTION_TON_AMOUNT,
|
|
121
|
+
payload,
|
|
122
|
+
});
|
|
123
|
+
crossChainTonAmount = 0n;
|
|
124
|
+
feeTonAmount = 0n;
|
|
125
|
+
currentFeeParams = undefined;
|
|
126
|
+
}
|
|
127
|
+
this.logger.debug('Generating cross-chain messages success');
|
|
128
|
+
return messages;
|
|
129
|
+
}
|
|
130
|
+
async sendCrossChainTransaction(evmProxyMsg, sender, assets, options, waitOptions) {
|
|
131
|
+
const caller = sender.getSenderAddress();
|
|
132
|
+
this.logger.debug(`Caller: ${caller}`);
|
|
133
|
+
const { transaction, transactionLinker } = await this.prepareCrossChainTransaction(evmProxyMsg, caller, assets, options);
|
|
134
|
+
await assets_1.TON.checkBalance(sender, this.config, [transaction]);
|
|
135
|
+
this.logger.debug(`*****Sending transaction: ${(0, Utils_1.formatObjectForLogging)(transaction)}`);
|
|
136
|
+
const sendTransactionResult = await sender.sendShardTransaction(transaction, this.config.network, this.config.TONParams.contractOpener);
|
|
137
|
+
return waitOptions
|
|
138
|
+
? {
|
|
139
|
+
sendTransactionResult,
|
|
140
|
+
operationId: await this.operationTracker
|
|
141
|
+
.getOperationId(transactionLinker, {
|
|
142
|
+
...waitOptions,
|
|
143
|
+
successCheck: (operationId) => !!operationId,
|
|
144
|
+
logger: this.logger,
|
|
145
|
+
})
|
|
146
|
+
.catch((error) => {
|
|
147
|
+
this.logger.error(`Error while waiting for operation ID: ${error}`);
|
|
148
|
+
return undefined;
|
|
149
|
+
}),
|
|
150
|
+
...transactionLinker,
|
|
151
|
+
}
|
|
152
|
+
: { sendTransactionResult, ...transactionLinker };
|
|
153
|
+
}
|
|
154
|
+
async sendCrossChainTransactions(sender, txs, waitOptions) {
|
|
155
|
+
const caller = sender.getSenderAddress();
|
|
156
|
+
this.logger.debug(`Caller: ${caller}`);
|
|
157
|
+
this.logger.debug('Preparing multiple cross-chain transactions');
|
|
158
|
+
const { transactions, transactionLinkers } = await this.prepareCrossChainTransactions(txs, caller);
|
|
159
|
+
await assets_1.TON.checkBalance(sender, this.config, transactions);
|
|
160
|
+
this.logger.debug(`*****Sending transactions: ${(0, Utils_1.formatObjectForLogging)(transactions)}`);
|
|
161
|
+
await sender.sendShardTransactions(transactions, this.config.network, this.config.TONParams.contractOpener);
|
|
162
|
+
if (!waitOptions) {
|
|
163
|
+
return transactionLinkers;
|
|
164
|
+
}
|
|
165
|
+
return await this.waitForOperationIds(transactionLinkers, caller, waitOptions);
|
|
166
|
+
}
|
|
167
|
+
async prepareCrossChainTransactions(txs, caller) {
|
|
168
|
+
const transactions = [];
|
|
169
|
+
const transactionLinkers = [];
|
|
170
|
+
for (const { options, assets, evmProxyMsg } of txs) {
|
|
171
|
+
const { transaction, transactionLinker } = await this.prepareCrossChainTransaction(evmProxyMsg, caller, assets, options);
|
|
172
|
+
transactions.push(transaction);
|
|
173
|
+
transactionLinkers.push(transactionLinker);
|
|
174
|
+
}
|
|
175
|
+
return { transactions, transactionLinkers };
|
|
176
|
+
}
|
|
177
|
+
async waitForOperationIds(transactionLinkers, caller, waitOptions) {
|
|
178
|
+
this.logger.debug(`Waiting for operation IDs`);
|
|
179
|
+
try {
|
|
180
|
+
const operationIds = await this.operationTracker.getOperationIdsByShardsKeys(transactionLinkers.map((linker) => linker.shardsKey), caller, {
|
|
181
|
+
...waitOptions,
|
|
182
|
+
logger: this.logger,
|
|
183
|
+
successCheck: (operationIds) => Object.keys(operationIds).length == transactionLinkers.length &&
|
|
184
|
+
Object.values(operationIds).every((ids) => ids.operationIds.length > 0),
|
|
185
|
+
});
|
|
186
|
+
this.logger.debug(`Operation IDs: ${(0, Utils_1.formatObjectForLogging)(operationIds)}`);
|
|
187
|
+
return transactionLinkers.map((linker) => ({
|
|
188
|
+
...linker,
|
|
189
|
+
operationId: operationIds[linker.shardsKey].operationIds.at(0),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
this.logger.error(`Error while waiting for operation IDs: ${error}`);
|
|
194
|
+
return transactionLinkers;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async bridgeTokensToTON(signer, value, tonTarget, assets, tvmExecutorFee, tvmValidExecutors) {
|
|
198
|
+
this.logger.debug('Bridging tokens to TON');
|
|
199
|
+
if (assets == undefined) {
|
|
200
|
+
assets = [];
|
|
201
|
+
}
|
|
202
|
+
const tonAssets = [...assets];
|
|
203
|
+
if (value > 0n) {
|
|
204
|
+
tonAssets.push(await (await assets_1.AssetFactory.from(this.config, {
|
|
205
|
+
address: await this.config.nativeTACAddress(),
|
|
206
|
+
tokenType: Struct_1.AssetType.FT,
|
|
207
|
+
})).withAmount({ rawAmount: value }));
|
|
208
|
+
}
|
|
209
|
+
Validator_1.Validator.validateTVMAddress(tonTarget);
|
|
210
|
+
if (tvmExecutorFee == undefined) {
|
|
211
|
+
const suggestedTONExecutorFee = await this.simulator.getTVMExecutorFeeInfo(tonAssets, Consts_1.TAC_SYMBOL, tvmValidExecutors);
|
|
212
|
+
this.logger.debug(`Suggested TON executor fee: ${(0, Utils_1.formatObjectForLogging)(suggestedTONExecutorFee)}`);
|
|
213
|
+
tvmExecutorFee = BigInt(suggestedTONExecutorFee.inTAC);
|
|
214
|
+
}
|
|
215
|
+
const crossChainLayerAddress = await this.config.TACParams.crossChainLayer.getAddress();
|
|
216
|
+
for (const asset of assets) {
|
|
217
|
+
const evmAddress = await asset.getEVMAddress();
|
|
218
|
+
if (asset.type == Struct_1.AssetType.FT) {
|
|
219
|
+
this.logger.debug(`Approving token ${evmAddress} for ${crossChainLayerAddress}`);
|
|
220
|
+
const tokenContract = this.config.artifacts.tac.wrappers.ERC20FactoryTAC.connect(evmAddress, this.config.TACParams.provider);
|
|
221
|
+
const tx = await tokenContract.connect(signer).approve(crossChainLayerAddress, asset.rawAmount);
|
|
222
|
+
await tx.wait();
|
|
223
|
+
this.logger.debug(`Approved ${evmAddress} for ${crossChainLayerAddress}`);
|
|
224
|
+
}
|
|
225
|
+
if (asset.type == Struct_1.AssetType.NFT) {
|
|
226
|
+
this.logger.debug(`Approving collection ${evmAddress} for ${crossChainLayerAddress}`);
|
|
227
|
+
const tokenContract = this.config.artifacts.tac.wrappers.ERC721FactoryTAC.connect(evmAddress, this.config.TACParams.provider);
|
|
228
|
+
const tx = await tokenContract
|
|
229
|
+
.connect(signer)
|
|
230
|
+
.approve(crossChainLayerAddress, asset.addresses.index);
|
|
231
|
+
await tx.wait();
|
|
232
|
+
this.logger.debug(`Approved ${asset.address} for ${crossChainLayerAddress}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const shardsKey = BigInt(Math.round(Math.random() * 1e18));
|
|
236
|
+
this.logger.debug(`Shards key: ${shardsKey}`);
|
|
237
|
+
const protocolFee = await this.config.TACParams.crossChainLayer.getProtocolFee();
|
|
238
|
+
this.logger.debug(`Protocol fee: ${protocolFee}`);
|
|
239
|
+
const outMessage = {
|
|
240
|
+
shardsKey: shardsKey,
|
|
241
|
+
tvmTarget: tonTarget,
|
|
242
|
+
tvmPayload: '',
|
|
243
|
+
tvmProtocolFee: protocolFee,
|
|
244
|
+
tvmExecutorFee: tvmExecutorFee,
|
|
245
|
+
tvmValidExecutors: this.config.getTrustedTONExecutors,
|
|
246
|
+
toBridge: await Promise.all(assets
|
|
247
|
+
.filter((asset) => asset.type === Struct_1.AssetType.FT)
|
|
248
|
+
.map(async (asset) => ({
|
|
249
|
+
evmAddress: await asset.getEVMAddress(),
|
|
250
|
+
amount: asset.rawAmount,
|
|
251
|
+
}))),
|
|
252
|
+
toBridgeNFT: await Promise.all(assets
|
|
253
|
+
.filter((asset) => asset.type === Struct_1.AssetType.NFT)
|
|
254
|
+
.map(async (asset) => ({
|
|
255
|
+
evmAddress: await asset.getEVMAddress(),
|
|
256
|
+
amount: 1n,
|
|
257
|
+
tokenId: asset.addresses.index,
|
|
258
|
+
}))),
|
|
259
|
+
};
|
|
260
|
+
const encodedOutMessage = this.config.artifacts.tac.utils.encodeOutMessageV1(outMessage);
|
|
261
|
+
const outMsgVersion = 1n;
|
|
262
|
+
const totalValue = value + BigInt(outMessage.tvmProtocolFee) + BigInt(outMessage.tvmExecutorFee);
|
|
263
|
+
this.logger.debug(`Total value: ${totalValue}`);
|
|
264
|
+
const tx = await this.config.TACParams.crossChainLayer
|
|
265
|
+
.connect(signer)
|
|
266
|
+
.sendMessage(outMsgVersion, encodedOutMessage, { value: totalValue });
|
|
267
|
+
await tx.wait();
|
|
268
|
+
this.logger.debug(`Transaction hash: ${tx.hash}`);
|
|
269
|
+
return tx.hash;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
exports.TransactionManager = TransactionManager;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TxFinalizerConfig } from '../structs/InternalStruct';
|
|
2
|
+
import { ILogger } from '../interfaces';
|
|
3
|
+
export declare class TonTxFinalizer {
|
|
4
|
+
private logger;
|
|
5
|
+
private apiConfig;
|
|
6
|
+
constructor(apiConfig: TxFinalizerConfig, logger?: ILogger);
|
|
7
|
+
private logHashFormats;
|
|
8
|
+
private fetchAdjacentTransactions;
|
|
9
|
+
trackTransactionTree(hash: string, maxDepth?: number): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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.TonTxFinalizer = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const Logger_1 = require("./Logger");
|
|
9
|
+
const Utils_1 = require("./Utils");
|
|
10
|
+
const IGNORE_OPCODE = [
|
|
11
|
+
'0xd53276db', // Excess
|
|
12
|
+
'0x7362d09c', // Jetton Notify
|
|
13
|
+
];
|
|
14
|
+
class TonTxFinalizer {
|
|
15
|
+
constructor(apiConfig, logger = new Logger_1.NoopLogger()) {
|
|
16
|
+
this.apiConfig = apiConfig;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
logHashFormats(hash) {
|
|
20
|
+
let hex, base64;
|
|
21
|
+
if (hash.startsWith('0x')) {
|
|
22
|
+
hex = hash;
|
|
23
|
+
const cleanHex = hex.slice(2);
|
|
24
|
+
const buffer = Buffer.from(cleanHex, 'hex');
|
|
25
|
+
base64 = buffer.toString('base64');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
base64 = hash;
|
|
29
|
+
const buffer = Buffer.from(base64, 'base64');
|
|
30
|
+
hex = '0x' + buffer.toString('hex');
|
|
31
|
+
}
|
|
32
|
+
return { hex: hex, base64: base64 };
|
|
33
|
+
}
|
|
34
|
+
// Fetches adjacent transactions from toncenter
|
|
35
|
+
async fetchAdjacentTransactions(hash, retries = 5, delay = 1000) {
|
|
36
|
+
for (let i = retries; i >= 0; i--) {
|
|
37
|
+
await (0, Utils_1.sleep)(delay);
|
|
38
|
+
try {
|
|
39
|
+
const url = this.apiConfig.urlBuilder(hash);
|
|
40
|
+
const response = await axios_1.default.get(url, {
|
|
41
|
+
headers: {
|
|
42
|
+
[this.apiConfig.authorization.header]: this.apiConfig.authorization.value,
|
|
43
|
+
},
|
|
44
|
+
transformResponse: [Utils_1.toCamelCaseTransformer],
|
|
45
|
+
});
|
|
46
|
+
return response.data.transactions || [];
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const errorMessage = error.message;
|
|
50
|
+
// Rate limit error (429) - retry
|
|
51
|
+
if (errorMessage.includes('429')) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Log all errors except 404 Not Found
|
|
55
|
+
if (!errorMessage.includes('404')) {
|
|
56
|
+
const logMessage = error instanceof Error ? error.message : error;
|
|
57
|
+
console.warn(`Failed to fetch adjacent transactions for ${hash}:`, logMessage);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
// Checks if all transactions in the tree are successful
|
|
64
|
+
async trackTransactionTree(hash, maxDepth = 10) {
|
|
65
|
+
const visitedHashes = new Set();
|
|
66
|
+
const queue = [{ hash, depth: 0 }];
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
const { hash: currentHash, depth: currentDepth } = queue.shift();
|
|
69
|
+
if (visitedHashes.has(currentHash)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
visitedHashes.add(currentHash);
|
|
73
|
+
this.logger.debug(`Checking hash (depth ${currentDepth}):\nhex: ${this.logHashFormats(currentHash).hex}\nbase64: ${this.logHashFormats(currentHash).base64}`);
|
|
74
|
+
const transactions = await this.fetchAdjacentTransactions(currentHash);
|
|
75
|
+
if (transactions.length === 0)
|
|
76
|
+
continue;
|
|
77
|
+
for (const tx of transactions) {
|
|
78
|
+
if (!IGNORE_OPCODE.includes(tx.inMsg.opcode) && tx.inMsg.opcode !== null) {
|
|
79
|
+
const { aborted, computePh: compute_ph, action } = tx.description;
|
|
80
|
+
if (aborted ||
|
|
81
|
+
!compute_ph.success ||
|
|
82
|
+
!action.success ||
|
|
83
|
+
compute_ph.exitCode !== 0 ||
|
|
84
|
+
action.resultCode !== 0) {
|
|
85
|
+
throw new Error(`Transaction failed:\n` +
|
|
86
|
+
`hash = ${currentHash}, ` +
|
|
87
|
+
`aborted = ${aborted}, ` +
|
|
88
|
+
`compute_ph.success = ${compute_ph.success}, ` +
|
|
89
|
+
`compute_ph.exit_code = ${compute_ph.exitCode}, ` +
|
|
90
|
+
`action.success = ${action.success}, ` +
|
|
91
|
+
`action.result_code = ${action.resultCode}`);
|
|
92
|
+
}
|
|
93
|
+
if (currentDepth + 1 < maxDepth) {
|
|
94
|
+
queue.push({ hash: tx.hash, depth: currentDepth + 1 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.logger.debug(`Skipping hash (depth ${currentDepth}):\nhex: ${this.logHashFormats(tx.hash).hex}\nbase64: ${this.logHashFormats(tx.hash).base64}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.TonTxFinalizer = TonTxFinalizer;
|
package/dist/sdk/Utils.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Address, Cell } from '@ton/ton';
|
|
2
2
|
import { AbiCoder } from 'ethers';
|
|
3
|
+
import { FT, NFT, TON } from '../assets';
|
|
3
4
|
import { RandomNumberByTimestamp } from '../structs/InternalStruct';
|
|
5
|
+
import { IAsset } from '../interfaces';
|
|
4
6
|
import { EvmProxyMsg, FeeParams, TransactionLinker, ValidExecutors, WaitOptions } from '../structs/Struct';
|
|
5
7
|
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
6
8
|
export declare function generateRandomNumber(interval: number): number;
|
|
@@ -9,8 +11,6 @@ export declare function calculateContractAddress(code: Cell, data: Cell): Promis
|
|
|
9
11
|
export declare function buildEvmDataCell(transactionLinker: TransactionLinker, evmProxyMsg: EvmProxyMsg, validExecutors: ValidExecutors): Cell;
|
|
10
12
|
export declare function formatSolidityMethodName(methodName?: string): string;
|
|
11
13
|
export declare function generateTransactionLinker(caller: string, shardCount: number): TransactionLinker;
|
|
12
|
-
export declare function validateTVMAddress(address: string): void;
|
|
13
|
-
export declare function validateEVMAddress(address: string): void;
|
|
14
14
|
export declare function calculateEVMTokenAddress(abiCoder: AbiCoder, tokenUtilsAddress: string, crossChainLayerTokenBytecode: string, crossChainLayerAddress: string, tvmAddress: string): string;
|
|
15
15
|
export declare const convertKeysToCamelCase: <T>(data: T) => T;
|
|
16
16
|
export declare const calculateRawAmount: (amount: number, decimals: number) => bigint;
|
|
@@ -19,3 +19,8 @@ export declare const toCamelCaseTransformer: (data: string) => any;
|
|
|
19
19
|
export declare const generateFeeData: (feeParams?: FeeParams) => Cell | undefined;
|
|
20
20
|
export declare function waitUntilSuccess<T, A extends unknown[]>(options: WaitOptions<T> | undefined, operation: (...args: A) => Promise<T>, ...args: A): Promise<T>;
|
|
21
21
|
export declare function formatObjectForLogging(obj: unknown): string;
|
|
22
|
+
export declare function aggregateTokens(assets?: IAsset[]): Promise<{
|
|
23
|
+
jettons: FT[];
|
|
24
|
+
nfts: NFT[];
|
|
25
|
+
ton?: TON;
|
|
26
|
+
}>;
|
package/dist/sdk/Utils.js
CHANGED
|
@@ -7,14 +7,14 @@ exports.calculateContractAddress = calculateContractAddress;
|
|
|
7
7
|
exports.buildEvmDataCell = buildEvmDataCell;
|
|
8
8
|
exports.formatSolidityMethodName = formatSolidityMethodName;
|
|
9
9
|
exports.generateTransactionLinker = generateTransactionLinker;
|
|
10
|
-
exports.validateTVMAddress = validateTVMAddress;
|
|
11
|
-
exports.validateEVMAddress = validateEVMAddress;
|
|
12
10
|
exports.calculateEVMTokenAddress = calculateEVMTokenAddress;
|
|
13
11
|
exports.waitUntilSuccess = waitUntilSuccess;
|
|
14
12
|
exports.formatObjectForLogging = formatObjectForLogging;
|
|
13
|
+
exports.aggregateTokens = aggregateTokens;
|
|
15
14
|
const ton_1 = require("@ton/ton");
|
|
16
15
|
const ethers_1 = require("ethers");
|
|
17
16
|
const errors_1 = require("../errors");
|
|
17
|
+
const Struct_1 = require("../structs/Struct");
|
|
18
18
|
const Consts_1 = require("./Consts");
|
|
19
19
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
20
|
exports.sleep = sleep;
|
|
@@ -67,19 +67,6 @@ function generateTransactionLinker(caller, shardCount) {
|
|
|
67
67
|
timestamp: random.timestamp,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
function validateTVMAddress(address) {
|
|
71
|
-
try {
|
|
72
|
-
ton_1.Address.parse(address); // will throw on error address
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
throw (0, errors_1.tvmAddressError)(address);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function validateEVMAddress(address) {
|
|
79
|
-
if (!(0, ethers_1.isAddress)(address)) {
|
|
80
|
-
throw (0, errors_1.evmAddressError)(address);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
70
|
function calculateEVMTokenAddress(abiCoder, tokenUtilsAddress, crossChainLayerTokenBytecode, crossChainLayerAddress, tvmAddress) {
|
|
84
71
|
const salt = ethers_1.ethers.keccak256(ethers_1.ethers.solidityPacked(['string'], [tvmAddress]));
|
|
85
72
|
const initCode = ethers_1.ethers.solidityPacked(['bytes', 'bytes'], [crossChainLayerTokenBytecode, abiCoder.encode(['address'], [crossChainLayerAddress])]);
|
|
@@ -149,8 +136,7 @@ async function waitUntilSuccess(options = {}, operation, ...args) {
|
|
|
149
136
|
const maxAttempts = options.maxAttempts ?? 30;
|
|
150
137
|
const delay = options.delay ?? 10000;
|
|
151
138
|
const successCheck = options.successCheck;
|
|
152
|
-
|
|
153
|
-
log(`Starting wait for success with timeout=${timeout}ms, maxAttempts=${maxAttempts}, delay=${delay}ms`);
|
|
139
|
+
options.logger?.debug(`Starting wait for success with timeout=${timeout}ms, maxAttempts=${maxAttempts}, delay=${delay}ms`);
|
|
154
140
|
const startTime = Date.now();
|
|
155
141
|
let attempt = 1;
|
|
156
142
|
while (true) {
|
|
@@ -161,29 +147,62 @@ async function waitUntilSuccess(options = {}, operation, ...args) {
|
|
|
161
147
|
if (!result) {
|
|
162
148
|
throw new Error(`Empty result`);
|
|
163
149
|
}
|
|
164
|
-
|
|
150
|
+
options.logger?.debug(`Result: ${formatObjectForLogging(result)}`);
|
|
165
151
|
if (successCheck && !successCheck(result)) {
|
|
166
152
|
throw new Error(`Result is not successful`);
|
|
167
153
|
}
|
|
168
|
-
|
|
154
|
+
options.logger?.debug(`Attempt ${attempt} successful`);
|
|
169
155
|
return result;
|
|
170
156
|
}
|
|
171
157
|
catch (error) {
|
|
172
158
|
if (elapsedTime >= timeout) {
|
|
173
|
-
|
|
159
|
+
options.logger?.debug(`Timeout after ${elapsedTime}ms`);
|
|
174
160
|
throw error;
|
|
175
161
|
}
|
|
176
162
|
if (attempt >= maxAttempts) {
|
|
177
|
-
|
|
163
|
+
options.logger?.debug(`Max attempts (${maxAttempts}) reached`);
|
|
178
164
|
throw error;
|
|
179
165
|
}
|
|
180
|
-
|
|
181
|
-
|
|
166
|
+
options.logger?.debug(`Error on attempt ${attempt}: ${error}`);
|
|
167
|
+
options.logger?.debug(`Waiting ${delay}ms before next attempt`);
|
|
182
168
|
await (0, exports.sleep)(delay);
|
|
183
169
|
attempt++;
|
|
184
170
|
}
|
|
185
171
|
}
|
|
186
172
|
}
|
|
187
173
|
function formatObjectForLogging(obj) {
|
|
188
|
-
return JSON.stringify(obj, (key, value) => typeof value === 'bigint' ? value.toString() : value);
|
|
174
|
+
return JSON.stringify(obj, (key, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
175
|
+
}
|
|
176
|
+
async function aggregateTokens(assets) {
|
|
177
|
+
const uniqueAssetsMap = new Map();
|
|
178
|
+
let ton;
|
|
179
|
+
for await (const asset of assets ?? []) {
|
|
180
|
+
if (asset.type !== Struct_1.AssetType.FT)
|
|
181
|
+
continue;
|
|
182
|
+
if (!asset.address) {
|
|
183
|
+
ton = ton ? await ton.addAmount({ rawAmount: asset.rawAmount }) : asset.clone;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let jetton = uniqueAssetsMap.get(asset.address);
|
|
187
|
+
if (!jetton) {
|
|
188
|
+
jetton = asset.clone;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
jetton = await jetton.addAmount({ rawAmount: asset.rawAmount });
|
|
192
|
+
}
|
|
193
|
+
uniqueAssetsMap.set(asset.address, jetton);
|
|
194
|
+
}
|
|
195
|
+
const jettons = Array.from(uniqueAssetsMap.values());
|
|
196
|
+
uniqueAssetsMap.clear();
|
|
197
|
+
for await (const asset of assets ?? []) {
|
|
198
|
+
if (asset.type !== Struct_1.AssetType.NFT)
|
|
199
|
+
continue;
|
|
200
|
+
uniqueAssetsMap.set(asset.address, asset.clone);
|
|
201
|
+
}
|
|
202
|
+
const nfts = Array.from(uniqueAssetsMap.values());
|
|
203
|
+
return {
|
|
204
|
+
jettons,
|
|
205
|
+
nfts,
|
|
206
|
+
ton,
|
|
207
|
+
};
|
|
189
208
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { EvmProxyMsg, TACSimulationRequest } from '../structs/Struct';
|
|
2
|
+
export declare class Validator {
|
|
3
|
+
static validateTACSimulationRequest(req: TACSimulationRequest): void;
|
|
4
|
+
static validateEVMProxyMsg(evmProxyMsg: EvmProxyMsg): void;
|
|
5
|
+
static validateTVMAddresses(addresses?: string[]): void;
|
|
6
|
+
static validateEVMAddresses(addresses?: string[]): void;
|
|
7
|
+
static validateTVMAddress(address: string): void;
|
|
8
|
+
static validateEVMAddress(address: string): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Validator = void 0;
|
|
4
|
+
const ton_1 = require("@ton/ton");
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
class Validator {
|
|
8
|
+
static validateTACSimulationRequest(req) {
|
|
9
|
+
this.validateEVMAddress(req.tacCallParams.target);
|
|
10
|
+
req.evmValidExecutors?.forEach(this.validateEVMAddress);
|
|
11
|
+
req.tvmValidExecutors?.forEach(this.validateTVMAddress);
|
|
12
|
+
req.tonAssets.forEach((asset) => {
|
|
13
|
+
// if empty then it's native TON
|
|
14
|
+
if (asset.tokenAddress !== '') {
|
|
15
|
+
this.validateTVMAddress(asset.tokenAddress);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
this.validateTVMAddress(req.tonCaller);
|
|
19
|
+
}
|
|
20
|
+
static validateEVMProxyMsg(evmProxyMsg) {
|
|
21
|
+
this.validateEVMAddress(evmProxyMsg.evmTargetAddress);
|
|
22
|
+
}
|
|
23
|
+
static validateTVMAddresses(addresses) {
|
|
24
|
+
addresses?.forEach(this.validateTVMAddress);
|
|
25
|
+
}
|
|
26
|
+
static validateEVMAddresses(addresses) {
|
|
27
|
+
addresses?.forEach(this.validateEVMAddress);
|
|
28
|
+
}
|
|
29
|
+
static validateTVMAddress(address) {
|
|
30
|
+
try {
|
|
31
|
+
ton_1.Address.parse(address); // will throw on error address
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw (0, errors_1.tvmAddressError)(address);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
static validateEVMAddress(address) {
|
|
38
|
+
if (!address.startsWith('0x') || !(0, ethers_1.isAddress)(address)) {
|
|
39
|
+
throw (0, errors_1.evmAddressError)(address);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.Validator = Validator;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type { ContractOpener } from '../structs/Struct';
|
|
2
1
|
import type { SendResult, ShardTransaction } from '../structs/InternalStruct';
|
|
2
|
+
import type { IAsset, IContractOpener, ISender } from '../interfaces';
|
|
3
3
|
import { Network } from '../structs/Struct';
|
|
4
|
-
import { SenderAbstraction } from './SenderAbstraction';
|
|
5
4
|
import { HighloadWalletV3 } from '../wrappers/HighloadWalletV3';
|
|
6
|
-
export declare class BatchSender implements
|
|
5
|
+
export declare class BatchSender implements ISender {
|
|
7
6
|
private wallet;
|
|
8
7
|
private secretKey;
|
|
8
|
+
private lastCreatedAt;
|
|
9
9
|
constructor(wallet: HighloadWalletV3, secretKey: Buffer);
|
|
10
|
-
|
|
10
|
+
getBalanceOf(asset: IAsset): Promise<bigint>;
|
|
11
|
+
getBalance(contractOpener: IContractOpener): Promise<bigint>;
|
|
12
|
+
sendShardTransactions(shardTransactions: ShardTransaction[], _chain: Network, contractOpener: IContractOpener): Promise<SendResult[]>;
|
|
11
13
|
private prepareGroups;
|
|
12
14
|
private sendGroup;
|
|
13
15
|
getSenderAddress(): string;
|
|
14
|
-
sendShardTransaction(shardTransaction: ShardTransaction,
|
|
16
|
+
sendShardTransaction(shardTransaction: ShardTransaction, _chain: Network, contractOpener: IContractOpener): Promise<SendResult>;
|
|
15
17
|
}
|