@tonappchain/sdk 0.7.2 → 0.7.3-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +203 -199
  3. package/dist/src/adapters/BaseContractOpener.js +8 -5
  4. package/dist/src/assets/AssetFactory.d.ts +4 -1
  5. package/dist/src/assets/AssetFactory.js +4 -0
  6. package/dist/src/assets/FT.d.ts +3 -3
  7. package/dist/src/assets/FT.js +28 -15
  8. package/dist/src/assets/NFT.d.ts +3 -3
  9. package/dist/src/assets/NFT.js +10 -4
  10. package/dist/src/assets/TAC.d.ts +24 -0
  11. package/dist/src/assets/TAC.js +74 -0
  12. package/dist/src/assets/TON.d.ts +3 -3
  13. package/dist/src/assets/TON.js +12 -5
  14. package/dist/src/assets/index.d.ts +1 -0
  15. package/dist/src/assets/index.js +3 -1
  16. package/dist/src/errors/index.d.ts +1 -1
  17. package/dist/src/errors/index.js +6 -1
  18. package/dist/src/errors/instances.d.ts +4 -0
  19. package/dist/src/errors/instances.js +7 -1
  20. package/dist/src/interfaces/Asset.d.ts +11 -6
  21. package/dist/src/interfaces/IConfiguration.d.ts +1 -3
  22. package/dist/src/interfaces/ILiteSequencerClient.d.ts +9 -1
  23. package/dist/src/interfaces/IOperationTracker.d.ts +20 -1
  24. package/dist/src/interfaces/ITACTransactionManager.d.ts +19 -1
  25. package/dist/src/interfaces/ITacSDK.d.ts +29 -8
  26. package/dist/src/sdk/Consts.d.ts +4 -0
  27. package/dist/src/sdk/Consts.js +5 -1
  28. package/dist/src/sdk/LiteSequencerClient.d.ts +3 -1
  29. package/dist/src/sdk/LiteSequencerClient.js +37 -0
  30. package/dist/src/sdk/OperationTracker.d.ts +4 -1
  31. package/dist/src/sdk/OperationTracker.js +58 -0
  32. package/dist/src/sdk/TACTransactionManager.d.ts +3 -0
  33. package/dist/src/sdk/TACTransactionManager.js +170 -34
  34. package/dist/src/sdk/TONTransactionManager.js +28 -6
  35. package/dist/src/sdk/TacSdk.d.ts +4 -3
  36. package/dist/src/sdk/TacSdk.js +4 -0
  37. package/dist/src/sdk/Utils.d.ts +2 -1
  38. package/dist/src/sdk/Utils.js +4 -0
  39. package/dist/src/structs/InternalStruct.d.ts +35 -1
  40. package/dist/src/structs/Struct.d.ts +155 -5
  41. package/dist/src/structs/Struct.js +12 -1
  42. package/dist/src/wrappers/ContentUtils.d.ts +36 -13
  43. package/dist/src/wrappers/ContentUtils.js +197 -98
  44. package/package.json +121 -121
@@ -37,6 +37,11 @@ export declare enum OperationType {
37
37
  TAC_TON = "TAC-TON",
38
38
  UNKNOWN = "UNKNOWN"
39
39
  }
40
+ export type OperationTypeV2 = OperationType.TON_TAC_TON | OperationType.TON_TAC | OperationType.TAC_TON;
41
+ export declare enum OperationExecutionStatus {
42
+ SUCCESS = "success",
43
+ FAILED = "failed"
44
+ }
40
45
  export type TACParams = {
41
46
  /**
42
47
  * Provider for TAC side. Use your own provider for tests or to increase ratelimit
@@ -120,6 +125,16 @@ export type TransactionLinker = {
120
125
  export type TransactionLinkerWithOperationId = TransactionLinker & {
121
126
  operationId?: string;
122
127
  };
128
+ export type TACCrossChainTransactionResult = {
129
+ /**
130
+ * Hash of the TAC transaction
131
+ */
132
+ txHash: string;
133
+ /**
134
+ * Crosschain operation id
135
+ */
136
+ operationId?: string;
137
+ };
123
138
  export type TONAsset = {
124
139
  amount: string;
125
140
  tokenAddress: string;
@@ -225,7 +240,19 @@ export type ExecutionStages = {
225
240
  operationType: OperationType;
226
241
  metaInfo: MetaInfo;
227
242
  } & Record<StageName, ProfilingStageData>;
243
+ export type OperationTypeV2Info = {
244
+ operationType: OperationTypeV2;
245
+ finalized: boolean;
246
+ };
247
+ export type ExecutionStagesV2 = {
248
+ operationType: OperationTypeV2;
249
+ metaInfo: MetaInfo;
250
+ finalized: boolean;
251
+ status: OperationExecutionStatus;
252
+ rollback: boolean;
253
+ } & Record<StageName, ProfilingStageData>;
228
254
  export type ExecutionStagesByOperationId = Record<string, ExecutionStages>;
255
+ export type ExecutionStagesV2ByOperationId = Record<string, ExecutionStagesV2>;
229
256
  export type StatusInfosByOperationId = Record<string, StatusInfo>;
230
257
  export type OperationIds = {
231
258
  operationIds: string[];
@@ -288,35 +315,153 @@ export type FeeParams = {
288
315
  };
289
316
  export type evmDataBuilder = (transactionLinker: TransactionLinker, evmProxyMsg: EvmProxyMsg, validExecutors: ValidExecutors) => Cell;
290
317
  export type CrossChainTransactionOptions = {
318
+ /**
319
+ * Allows sending to continue even when TAC simulation returns an unsuccessful result.
320
+ * @default false
321
+ */
291
322
  allowSimulationError?: boolean;
292
323
  /**
293
- * If true, ensures TON transaction execution is validated before waiting for operation id.
324
+ * If true, validates the initial TON transaction tree right after sending the external message.
325
+ * This checks that the outbound TON transaction was actually executed before the SDK starts
326
+ * waiting for cross-chain tracking data.
294
327
  * @default true
295
328
  */
296
329
  ensureTxExecuted?: boolean;
297
330
  /**
298
331
  * If true, validates explicitly provided fee params against suggested values.
332
+ * Disable only when you intentionally want to send with custom fees.
299
333
  * @default true
300
334
  */
301
335
  shouldValidateFees?: boolean;
336
+ /**
337
+ * Forces the SDK to treat the operation as round-trip TON -> TAC -> TON.
338
+ * When omitted, the SDK derives this automatically from simulation results and attached assets.
339
+ */
302
340
  isRoundTrip?: boolean;
341
+ /**
342
+ * Explicit override for the protocol fee included in the transaction.
343
+ * Normally this is taken from simulation or on-chain configuration.
344
+ */
303
345
  protocolFee?: bigint;
346
+ /**
347
+ * Optional whitelist of TAC-side executor addresses allowed to execute the TAC part of the operation.
348
+ * When omitted, trusted executors from SDK configuration are used.
349
+ */
304
350
  evmValidExecutors?: string[];
351
+ /**
352
+ * Explicit fee paid to the TAC-side executor.
353
+ * Normally this is filled from simulation unless you provide custom manual fee params.
354
+ */
305
355
  evmExecutorFee?: bigint;
356
+ /**
357
+ * Optional whitelist of TON-side executor addresses allowed to execute the TON part of the operation.
358
+ * When omitted, trusted executors from SDK configuration are used.
359
+ */
306
360
  tvmValidExecutors?: string[];
361
+ /**
362
+ * Explicit fee paid to the TON-side executor for round-trip operations.
363
+ * Required in manual mode when `withoutSimulation` is true and `isRoundTrip` is true.
364
+ */
307
365
  tvmExecutorFee?: bigint;
366
+ /**
367
+ * Controls whether rollback fee should be included in TAC simulation.
368
+ * Keep enabled unless you intentionally want estimation without rollback costs.
369
+ * @default true
370
+ */
308
371
  calculateRollbackFee?: boolean;
372
+ /**
373
+ * Skips simulation entirely and uses only manually provided fee params.
374
+ * In this mode you must provide `protocolFee`, `evmExecutorFee`, and `evmProxyMsg.gasLimit`.
375
+ * If `isRoundTrip` is true, `tvmExecutorFee` is also required.
376
+ * @default false
377
+ */
309
378
  withoutSimulation?: boolean;
379
+ /**
380
+ * If true, validates that the sender currently owns or holds enough assets before building
381
+ * the cross-chain transaction payloads.
382
+ * Disable only if you already validated balances externally or need to skip this network call.
383
+ * @default true
384
+ */
310
385
  validateAssetsBalance?: boolean;
386
+ /**
387
+ * If true, waits until the operation ID becomes available after sending the TON transaction.
388
+ * Disable when you only need the raw send result and do not want any tracker polling.
389
+ * @default true
390
+ */
311
391
  waitOperationId?: boolean;
392
+ /**
393
+ * Retry policy used while resolving the operation ID.
394
+ * Use this to control timeout, attempts, delay, logger, callbacks, and success criteria.
395
+ */
312
396
  waitOptions?: WaitOptions<string>;
397
+ /**
398
+ * If true, waits for full cross-chain finalization after the operation ID is known:
399
+ * the SDK waits for a final operation type, fetches stage profiling, and validates
400
+ * TON transaction trees for `executedInTON` transactions when they exist.
401
+ * @default true
402
+ */
403
+ waitFinalization?: boolean;
404
+ /**
405
+ * Wait options used to finalize the cross-chain operation after operationId resolution.
406
+ * Use this to configure retries, delay, logger, successCheck, and onSuccess callback
407
+ * for stage profiling retrieval and post-processing.
408
+ */
409
+ finalizationWaitOptions?: WaitOptions<ExecutionStages>;
410
+ /**
411
+ * Custom builder for the EVM payload cell embedded into the TON message.
412
+ * Override only for advanced integrations that need non-standard TAC header encoding.
413
+ * By default the SDK uses `buildEvmDataCell`.
414
+ */
313
415
  evmDataBuilder?: evmDataBuilder;
314
416
  };
315
- export type BatchCrossChainTransactionOptions = Omit<CrossChainTransactionOptions, 'waitOperationId' | 'waitOptions' | 'ensureTxExecuted'>;
417
+ export type BatchCrossChainTransactionOptions = Omit<CrossChainTransactionOptions, 'waitOperationId' | 'waitOptions' | 'ensureTxExecuted' | 'waitFinalization' | 'finalizationWaitOptions'>;
316
418
  export type CrossChainTransactionsOptions = {
317
419
  waitOperationIds?: boolean;
318
420
  waitOptions?: WaitOptions<OperationIdsByShardsKey>;
319
421
  };
422
+ export type BridgeTokensToTONOptions = {
423
+ /**
424
+ * Optional explicit TON-side executor fee.
425
+ * When omitted, the SDK calls OperationTracker.getTVMExecutorFee().
426
+ */
427
+ tvmExecutorFee?: bigint;
428
+ /**
429
+ * Optional whitelist of allowed TVM executors.
430
+ * Used both for fee estimation and for the out message itself.
431
+ */
432
+ tvmValidExecutors?: string[];
433
+ /**
434
+ * If true, validates balances / ownership of non-native TAC assets before approvals and sending.
435
+ * Native TAC amount is still covered separately by the overall TAC balance sufficiency check.
436
+ * Disable only if you already validated balances externally or need to skip these RPC calls.
437
+ * @default true
438
+ */
439
+ validateAssetsBalance?: boolean;
440
+ /**
441
+ * If true, waits until the operation ID becomes available after sending the TON transaction.
442
+ * Disable when you only need the raw send result and do not want any tracker polling.
443
+ * @default true
444
+ */
445
+ waitOperationId?: boolean;
446
+ /**
447
+ * Retry policy used while resolving the operation ID.
448
+ * Use this to control timeout, attempts, delay, logger, callbacks, and success criteria.
449
+ */
450
+ waitOptions?: WaitOptions<string>;
451
+ /**
452
+ * Wait options used to finalize the cross-chain operation after operationId resolution.
453
+ * Use this to configure retries, delay, logger, successCheck, and onSuccess callback
454
+ * for stage profiling retrieval and post-processing.
455
+ */
456
+ waitFinalization?: boolean;
457
+ /**
458
+ * Wait options used to finalize the TAC->TON operation after operationId resolution.
459
+ * The SDK waits until the operation reaches a final operation type, fetches stage profiling,
460
+ * and, if profiling contains EXECUTED_IN_TON transactions, validates their TON transaction tree.
461
+ * This option is used only by sendCrossChainTransactionToTON and is ignored by bridgeTokensToTON.
462
+ */
463
+ finalizationWaitOptions?: WaitOptions<ExecutionStages>;
464
+ };
320
465
  export type ExecutionFeeEstimationResult = {
321
466
  feeParams: FeeParams;
322
467
  simulation?: TACSimulationResult;
@@ -461,6 +606,11 @@ export type TacGasPrice = {
461
606
  fast: number;
462
607
  slow: number;
463
608
  };
609
+ export declare enum TransactionTreeDirection {
610
+ FORWARD = "forward",
611
+ BACKWARD = "backward",
612
+ BOTH = "both"
613
+ }
464
614
  /**
465
615
  * Parameters for tracking and validating transaction trees
466
616
  */
@@ -491,10 +641,10 @@ export type TrackTransactionTreeParams = {
491
641
  * Direction to search the transaction tree:
492
642
  * - 'forward': only search children (outgoing messages)
493
643
  * - 'backward': only search parents (incoming messages)
494
- * - 'both': search in both directions (default)
495
- * @default 'both'
644
+ * - 'both': search in both directions
645
+ * @default 'forward'
496
646
  */
497
- direction?: 'forward' | 'backward' | 'both';
647
+ direction?: TransactionTreeDirection;
498
648
  /**
499
649
  * Retry transaction lookup when `not_found` error occurs.
500
650
  * Useful when transaction indexing has delays or when transactions appear gradually.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Origin = exports.defaultWaitOptions = exports.TokenSymbol = exports.StageName = exports.NFTAddressType = exports.AssetType = exports.OperationType = exports.CurrencyType = exports.BlockchainType = exports.Network = exports.SimplifiedStatuses = void 0;
3
+ exports.TransactionTreeDirection = exports.Origin = exports.defaultWaitOptions = exports.TokenSymbol = exports.StageName = exports.NFTAddressType = exports.AssetType = exports.OperationExecutionStatus = exports.OperationType = exports.CurrencyType = exports.BlockchainType = exports.Network = exports.SimplifiedStatuses = void 0;
4
4
  const Consts_1 = require("../sdk/Consts");
5
5
  var SimplifiedStatuses;
6
6
  (function (SimplifiedStatuses) {
@@ -34,6 +34,11 @@ var OperationType;
34
34
  OperationType["TAC_TON"] = "TAC-TON";
35
35
  OperationType["UNKNOWN"] = "UNKNOWN";
36
36
  })(OperationType || (exports.OperationType = OperationType = {}));
37
+ var OperationExecutionStatus;
38
+ (function (OperationExecutionStatus) {
39
+ OperationExecutionStatus["SUCCESS"] = "success";
40
+ OperationExecutionStatus["FAILED"] = "failed";
41
+ })(OperationExecutionStatus || (exports.OperationExecutionStatus = OperationExecutionStatus = {}));
37
42
  var AssetType;
38
43
  (function (AssetType) {
39
44
  AssetType["NFT"] = "NFT";
@@ -68,3 +73,9 @@ var Origin;
68
73
  Origin["TON"] = "TON";
69
74
  Origin["TAC"] = "TAC";
70
75
  })(Origin || (exports.Origin = Origin = {}));
76
+ var TransactionTreeDirection;
77
+ (function (TransactionTreeDirection) {
78
+ TransactionTreeDirection["FORWARD"] = "forward";
79
+ TransactionTreeDirection["BACKWARD"] = "backward";
80
+ TransactionTreeDirection["BOTH"] = "both";
81
+ })(TransactionTreeDirection || (exports.TransactionTreeDirection = TransactionTreeDirection = {}));
@@ -1,25 +1,48 @@
1
1
  import { Cell } from '@ton/ton';
2
+ import { MetadataPersistenceType } from '../structs/InternalStruct';
2
3
  export declare const ONCHAIN_CONTENT_PREFIX = 0;
3
4
  export declare const OFFCHAIN_CONTENT_PREFIX = 1;
4
- export interface JettonMetadata {
5
+ export type JsonMetadataValue = string | number | boolean | null | JsonMetadataValue[] | {
6
+ [key: string]: JsonMetadataValue;
7
+ };
8
+ export type MetadataRecord<TKey extends string = string> = Partial<Record<TKey, JsonMetadataValue>> & {
9
+ [key: string]: JsonMetadataValue | undefined;
10
+ };
11
+ export type CommonMetadataKeys = 'uri' | 'name' | 'description' | 'image' | 'image_data';
12
+ export type JettonMetaDataKeys = CommonMetadataKeys | 'symbol' | 'decimals' | 'amount_style' | 'render_type';
13
+ export type NFTMetadataKeys = CommonMetadataKeys;
14
+ export interface JettonMetadata extends MetadataRecord<JettonMetaDataKeys> {
5
15
  uri?: string;
6
- name: string;
7
- description: string;
16
+ name?: string;
17
+ description?: string;
8
18
  image?: string;
9
19
  image_data?: string;
10
- symbol: string;
11
- decimals?: string;
20
+ symbol?: string;
21
+ decimals?: string | number;
22
+ amount_style?: string;
23
+ render_type?: string;
12
24
  }
13
- export type JettonExtendedMetadata = {
14
- persistenceType: persistenceType;
15
- metadata: {
16
- [s in JettonMetaDataKeys]?: string;
17
- };
18
- isJettonDeployerFaultyOnChainData?: boolean;
25
+ export interface NFTMetadata extends MetadataRecord<NFTMetadataKeys> {
26
+ uri?: string;
27
+ name?: string;
28
+ description?: string;
29
+ image?: string;
30
+ image_data?: string;
31
+ }
32
+ type ExtendedMetadata<TKey extends string> = {
33
+ persistenceType: MetadataPersistenceType;
34
+ metadata: MetadataRecord<TKey> | null;
19
35
  contentUri?: string;
20
36
  };
37
+ export type JettonExtendedMetadata = ExtendedMetadata<JettonMetaDataKeys> & {
38
+ isJettonDeployerFaultyOnChainData?: boolean;
39
+ };
40
+ export type NFTExtendedMetadata = ExtendedMetadata<NFTMetadataKeys>;
21
41
  export declare function buildJettonOffChainMetadata(contentUri: string): Cell;
22
- export type JettonMetaDataKeys = 'uri' | 'name' | 'description' | 'image' | 'symbol' | 'image_data' | 'decimals';
42
+ export declare function buildNFTOffChainMetadata(contentUri: string): Cell;
23
43
  export declare function buildJettonOnchainMetadata(data: JettonMetadata): Cell;
24
- export type persistenceType = 'none' | 'onchain' | 'offchain_private_domain' | 'offchain_ipfs';
44
+ export declare function buildNFTOnchainMetadata(data: NFTMetadata): Cell;
45
+ export type persistenceType = MetadataPersistenceType;
25
46
  export declare function readJettonMetadata(contentCell: Cell): Promise<JettonExtendedMetadata>;
47
+ export declare function readNFTMetadata(contentCell: Cell): Promise<NFTExtendedMetadata>;
48
+ export {};
@@ -5,155 +5,254 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.OFFCHAIN_CONTENT_PREFIX = exports.ONCHAIN_CONTENT_PREFIX = void 0;
7
7
  exports.buildJettonOffChainMetadata = buildJettonOffChainMetadata;
8
+ exports.buildNFTOffChainMetadata = buildNFTOffChainMetadata;
8
9
  exports.buildJettonOnchainMetadata = buildJettonOnchainMetadata;
10
+ exports.buildNFTOnchainMetadata = buildNFTOnchainMetadata;
9
11
  exports.readJettonMetadata = readJettonMetadata;
12
+ exports.readNFTMetadata = readNFTMetadata;
10
13
  const sha256_js_1 = require("@aws-crypto/sha256-js");
11
14
  const ton_1 = require("@ton/ton");
12
15
  const axios_1 = __importDefault(require("axios"));
13
- const bn_js_1 = __importDefault(require("bn.js"));
14
16
  const errors_1 = require("../errors");
15
17
  exports.ONCHAIN_CONTENT_PREFIX = 0x00;
16
18
  exports.OFFCHAIN_CONTENT_PREFIX = 0x01;
17
- const SNAKE_PREFIX = 0x00;
18
- function buildJettonOffChainMetadata(contentUri) {
19
- return (0, ton_1.beginCell)().storeInt(exports.OFFCHAIN_CONTENT_PREFIX, 8).storeBuffer(Buffer.from(contentUri, 'ascii')).endCell();
20
- }
21
- const jettonOnChainMetadataSpec = {
19
+ const SNAKE_CONTENT_PREFIX = 0x00;
20
+ const CHUNKED_CONTENT_PREFIX = 0x01;
21
+ const CELL_MAX_BITS = 1023;
22
+ const commonMetadataSpec = {
22
23
  uri: 'ascii',
23
24
  name: 'utf8',
24
25
  description: 'utf8',
25
26
  image: 'ascii',
26
27
  image_data: 'ascii',
28
+ };
29
+ const jettonOnChainMetadataSpec = {
30
+ ...commonMetadataSpec,
27
31
  symbol: 'utf8',
28
32
  decimals: 'utf8',
33
+ amount_style: 'utf8',
34
+ render_type: 'utf8',
29
35
  };
30
- const sha256 = (str) => {
36
+ const nftOnChainMetadataSpec = {
37
+ ...commonMetadataSpec,
38
+ };
39
+ const sha256 = (value) => {
31
40
  const sha = new sha256_js_1.Sha256();
32
- sha.update(str);
41
+ sha.update(value);
33
42
  return Buffer.from(sha.digestSync());
34
43
  };
35
- function storeSnakeContent(content, isFirst) {
36
- const CELL_MAX_SIZE_BYTES = Math.floor((1023 - 8) / 8);
44
+ function getAvailableBytes(withPrefix) {
45
+ return Math.floor((CELL_MAX_BITS - (withPrefix ? 8 : 0)) / 8);
46
+ }
47
+ function buildSnakeCell(content, withPrefix) {
37
48
  const cell = new ton_1.Builder();
38
- if (isFirst) {
39
- cell.storeUint(SNAKE_PREFIX, 8);
49
+ if (withPrefix) {
50
+ cell.storeUint(SNAKE_CONTENT_PREFIX, 8);
40
51
  }
41
- cell.storeBuffer(content.subarray(0, CELL_MAX_SIZE_BYTES));
42
- const remainingContent = content.subarray(CELL_MAX_SIZE_BYTES);
52
+ const bytesToStore = getAvailableBytes(withPrefix);
53
+ cell.storeBuffer(content.subarray(0, bytesToStore));
54
+ const remainingContent = content.subarray(bytesToStore);
43
55
  if (remainingContent.length > 0) {
44
- cell.storeRef(storeSnakeContent(remainingContent, false));
56
+ cell.storeRef(buildSnakeCell(remainingContent, false));
45
57
  }
46
58
  return cell.endCell();
47
59
  }
48
- function buildJettonOnchainMetadata(data) {
49
- const dict = ton_1.Dictionary.empty();
50
- Object.entries(data).forEach(([k, v]) => {
51
- if (!jettonOnChainMetadataSpec[k]) {
52
- throw (0, errors_1.unsupportedKeyError)(k);
60
+ function buildOffchainContent(contentUri) {
61
+ const content = Buffer.from(contentUri, 'ascii');
62
+ const root = (0, ton_1.beginCell)().storeUint(exports.OFFCHAIN_CONTENT_PREFIX, 8);
63
+ const bytesToStore = getAvailableBytes(true);
64
+ root.storeBuffer(content.subarray(0, bytesToStore));
65
+ const remainingContent = content.subarray(bytesToStore);
66
+ if (remainingContent.length > 0) {
67
+ root.storeRef(buildSnakeCell(remainingContent, false));
68
+ }
69
+ return root.endCell();
70
+ }
71
+ function ensureByteAligned(slice) {
72
+ if (slice.remainingBits % 8 !== 0) {
73
+ throw errors_1.notMultiplyOf8Error;
74
+ }
75
+ }
76
+ function readSnakeData(slice, withPrefix) {
77
+ if (withPrefix) {
78
+ if (slice.loadUint(8) !== SNAKE_CONTENT_PREFIX) {
79
+ throw errors_1.unsupportedFormatError;
53
80
  }
54
- if (!v || v == '' || v == null) {
55
- return;
81
+ }
82
+ ensureByteAligned(slice);
83
+ const current = slice.remainingBits > 0 ? slice.loadBuffer(slice.remainingBits / 8) : Buffer.alloc(0);
84
+ if (slice.remainingRefs === 0) {
85
+ return current;
86
+ }
87
+ if (slice.remainingRefs > 1) {
88
+ throw errors_1.unsupportedFormatError;
89
+ }
90
+ return Buffer.concat([current, readSnakeData(slice.loadRef().beginParse(), false)]);
91
+ }
92
+ function readChunkedData(slice) {
93
+ const dict = slice.loadDict(ton_1.Dictionary.Keys.Uint(32), ton_1.Dictionary.Values.Cell());
94
+ const chunks = dict.keys().sort((a, b) => a - b);
95
+ return Buffer.concat(chunks.map((chunkIndex) => {
96
+ const chunk = dict.get(chunkIndex);
97
+ if (!chunk) {
98
+ return Buffer.alloc(0);
56
99
  }
57
- const bufferToStore = Buffer.from(v, jettonOnChainMetadataSpec[k]);
58
- dict.set(sha256(k), storeSnakeContent(bufferToStore, true));
59
- });
60
- return (0, ton_1.beginCell)()
61
- .storeInt(exports.ONCHAIN_CONTENT_PREFIX, 8)
62
- .storeDict(dict, ton_1.Dictionary.Keys.Buffer(32), ton_1.Dictionary.Values.Cell())
63
- .endCell();
100
+ return readSnakeData(chunk.beginParse(), true);
101
+ }));
64
102
  }
65
- function readSnakeContent(slice, isFirst) {
66
- if (isFirst && slice.loadUint(8) !== SNAKE_PREFIX) {
67
- throw errors_1.unsupportedFormatError;
103
+ function readContentData(cell) {
104
+ const slice = cell.beginParse();
105
+ const prefix = slice.loadUint(8);
106
+ if (prefix === SNAKE_CONTENT_PREFIX) {
107
+ return readSnakeData(slice, false);
68
108
  }
69
- if (slice.remainingBits % 8 !== 0) {
70
- throw errors_1.notMultiplyOf8Error;
109
+ if (prefix === CHUNKED_CONTENT_PREFIX) {
110
+ return readChunkedData(slice);
71
111
  }
72
- let remainingBytes = Buffer.from('');
73
- if (slice.remainingBits != 0) {
74
- remainingBytes = slice.loadBuffer(slice.remainingBits / 8);
75
- }
76
- if (slice.remainingRefs != 0) {
77
- const newCell = slice.loadRef();
78
- remainingBytes = Buffer.concat([remainingBytes, readSnakeContent(newCell.beginParse(), false)]);
79
- }
80
- return remainingBytes;
81
- }
82
- function parseJettonOnchainMetadata(contentSlice) {
83
- // Note that this relies on what is (perhaps) an internal implementation detail:
84
- // "sdk" library dict parser converts: key (provided as buffer) => BN(base10)
85
- // and upon parsing, it reads it back to a BN(base10)
86
- // tl;dr if we want to read the map back to a JSON with string keys, we have to convert BN(10) back to hex
87
- const toKey = (str) => BigInt(new bn_js_1.default(str, 'hex').toString(10));
88
- const isJettonDeployerFaultyOnChainData = false;
89
- const cellDict = contentSlice.loadDict(ton_1.Dictionary.Keys.BigUint(256), ton_1.Dictionary.Values.Cell());
90
- const dict = new Map();
91
- cellDict.values().forEach((item, index) => {
92
- dict.set(cellDict.keys()[index], readSnakeContent(item.beginParse(), true));
93
- });
94
- const res = {};
95
- Object.keys(jettonOnChainMetadataSpec).forEach((k) => {
96
- const val = dict
97
- .get(toKey(sha256(k).toString('hex')))
98
- ?.toString(jettonOnChainMetadataSpec[k]);
99
- if (val) {
100
- res[k] = val;
112
+ throw errors_1.unsupportedFormatError;
113
+ }
114
+ function parseOnchainMetadata(contentSlice, metadataSpec) {
115
+ const dict = contentSlice.loadDict(ton_1.Dictionary.Keys.Buffer(32), ton_1.Dictionary.Values.Cell());
116
+ const metadata = {};
117
+ for (const key of Object.keys(metadataSpec)) {
118
+ const encoding = metadataSpec[key];
119
+ if (!encoding) {
120
+ continue;
121
+ }
122
+ const cell = dict.get(sha256(key));
123
+ if (!cell) {
124
+ continue;
101
125
  }
102
- });
126
+ const value = readContentData(cell).toString(encoding);
127
+ if (value) {
128
+ metadata[key] = value;
129
+ }
130
+ }
103
131
  return {
104
- metadata: res,
105
- isJettonDeployerFaultyOnChainData,
132
+ metadata,
133
+ isJettonDeployerFaultyOnChainData: false,
106
134
  };
107
135
  }
108
- async function parseJettonOffchainMetadata(contentSlice) {
109
- const remainingBits = contentSlice.remainingBits;
110
- if (remainingBits % 8 !== 0) {
111
- throw errors_1.notMultiplyOf8Error;
136
+ function normalizeContentUri(contentUri) {
137
+ if (contentUri.startsWith('ipfs://')) {
138
+ const ipfsPath = contentUri.slice('ipfs://'.length).replace(/^ipfs\//, '');
139
+ return {
140
+ contentUri: `https://ipfs.io/ipfs/${ipfsPath}`,
141
+ isIpfs: true,
142
+ };
112
143
  }
113
- const jsonURI = contentSlice
114
- .loadBuffer(remainingBits / 8)
115
- .toString('ascii')
116
- .replace('ipfs://', 'https://ipfs.io/ipfs/');
117
- let metadata = null;
118
- let isIpfs = null;
144
+ return {
145
+ contentUri,
146
+ isIpfs: /(^|\/)ipfs[.:/]/i.test(contentUri),
147
+ };
148
+ }
149
+ async function fetchOffchainMetadata(contentUri) {
150
+ const normalized = normalizeContentUri(contentUri);
119
151
  try {
120
- metadata = (await axios_1.default.get(jsonURI)).data;
121
- isIpfs = /(^|\/)ipfs[.:]/.test(jsonURI);
152
+ const response = await axios_1.default.get(normalized.contentUri);
153
+ const metadata = response.data && typeof response.data === 'object' && !Array.isArray(response.data)
154
+ ? response.data
155
+ : null;
156
+ return {
157
+ metadata,
158
+ isIpfs: normalized.isIpfs,
159
+ contentUri: normalized.contentUri,
160
+ };
122
161
  }
123
162
  catch {
124
- // nothing
163
+ return {
164
+ metadata: null,
165
+ isIpfs: normalized.isIpfs,
166
+ contentUri: normalized.contentUri,
167
+ };
125
168
  }
126
- return {
127
- metadata,
128
- isIpfs,
129
- contentUri: jsonURI,
130
- };
131
169
  }
132
- async function readJettonMetadata(contentCell) {
170
+ async function parseOffchainMetadata(contentSlice) {
171
+ const contentUri = readSnakeData(contentSlice, false).toString('ascii');
172
+ return fetchOffchainMetadata(contentUri);
173
+ }
174
+ async function readMetadata(contentCell, metadataSpec) {
133
175
  if (contentCell.bits.length <= 0) {
134
176
  return {
135
177
  contentUri: undefined,
136
- isJettonDeployerFaultyOnChainData: false,
137
178
  metadata: {},
138
179
  persistenceType: 'none',
139
180
  };
140
181
  }
141
182
  const contentSlice = contentCell.beginParse();
142
- switch (contentSlice.loadUint(8)) {
143
- case exports.ONCHAIN_CONTENT_PREFIX:
183
+ const prefix = contentSlice.loadUint(8);
184
+ if (prefix === exports.ONCHAIN_CONTENT_PREFIX) {
185
+ const { metadata: onchainMetadata, isJettonDeployerFaultyOnChainData } = parseOnchainMetadata(contentSlice, metadataSpec);
186
+ const uri = onchainMetadata.uri;
187
+ if (typeof uri === 'string' && uri.length > 0) {
188
+ const { metadata: offchainMetadata, isIpfs, contentUri } = await fetchOffchainMetadata(uri);
144
189
  return {
145
- persistenceType: 'onchain',
146
- ...parseJettonOnchainMetadata(contentSlice),
147
- };
148
- case exports.OFFCHAIN_CONTENT_PREFIX: {
149
- const { metadata, isIpfs, contentUri } = await parseJettonOffchainMetadata(contentSlice);
150
- return {
151
- persistenceType: isIpfs ? 'offchain_ipfs' : 'offchain_private_domain',
152
190
  contentUri,
153
- metadata,
191
+ isJettonDeployerFaultyOnChainData,
192
+ metadata: {
193
+ ...(offchainMetadata ?? {}),
194
+ ...onchainMetadata,
195
+ },
196
+ persistenceType: isIpfs ? 'semichain_ipfs' : 'semichain_private_domain',
154
197
  };
155
198
  }
156
- default:
157
- throw errors_1.prefixError;
199
+ return {
200
+ isJettonDeployerFaultyOnChainData,
201
+ metadata: onchainMetadata,
202
+ persistenceType: 'onchain',
203
+ };
204
+ }
205
+ if (prefix === exports.OFFCHAIN_CONTENT_PREFIX) {
206
+ const { metadata, isIpfs, contentUri } = await parseOffchainMetadata(contentSlice);
207
+ return {
208
+ contentUri,
209
+ metadata,
210
+ persistenceType: isIpfs ? 'offchain_ipfs' : 'offchain_private_domain',
211
+ };
212
+ }
213
+ throw errors_1.prefixError;
214
+ }
215
+ function buildOnchainMetadata(data, metadataSpec) {
216
+ const dict = ton_1.Dictionary.empty(ton_1.Dictionary.Keys.Buffer(32), ton_1.Dictionary.Values.Cell());
217
+ for (const [key, value] of Object.entries(data)) {
218
+ const encoding = metadataSpec[key];
219
+ if (!encoding) {
220
+ throw (0, errors_1.unsupportedKeyError)(key);
221
+ }
222
+ if (value === '' || value == null) {
223
+ continue;
224
+ }
225
+ dict.set(sha256(key), buildSnakeCell(Buffer.from(String(value), encoding), true));
158
226
  }
227
+ return (0, ton_1.beginCell)()
228
+ .storeUint(exports.ONCHAIN_CONTENT_PREFIX, 8)
229
+ .storeDict(dict, ton_1.Dictionary.Keys.Buffer(32), ton_1.Dictionary.Values.Cell())
230
+ .endCell();
231
+ }
232
+ function buildJettonOffChainMetadata(contentUri) {
233
+ return buildOffchainContent(contentUri);
234
+ }
235
+ function buildNFTOffChainMetadata(contentUri) {
236
+ return buildOffchainContent(contentUri);
237
+ }
238
+ function buildJettonOnchainMetadata(data) {
239
+ return buildOnchainMetadata(data, jettonOnChainMetadataSpec);
240
+ }
241
+ function buildNFTOnchainMetadata(data) {
242
+ return buildOnchainMetadata(data, nftOnChainMetadataSpec);
243
+ }
244
+ async function readJettonMetadata(contentCell) {
245
+ const metadata = await readMetadata(contentCell, jettonOnChainMetadataSpec);
246
+ return {
247
+ ...metadata,
248
+ isJettonDeployerFaultyOnChainData: metadata.isJettonDeployerFaultyOnChainData ?? false,
249
+ };
250
+ }
251
+ async function readNFTMetadata(contentCell) {
252
+ const metadata = await readMetadata(contentCell, nftOnChainMetadataSpec);
253
+ return {
254
+ contentUri: metadata.contentUri,
255
+ metadata: metadata.metadata,
256
+ persistenceType: metadata.persistenceType,
257
+ };
159
258
  }