diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js index e331ffd24..d452feef4 100644 --- a/packages/core/.eslintrc.js +++ b/packages/core/.eslintrc.js @@ -1,3 +1,11 @@ module.exports = { - extends: ['../../.eslintrc.js'] + extends: ['../../.eslintrc.js'], + overrides: [ + { + files: ['**/__tests__/**/*.ts', '**/*.spec.ts', '**/*.test.ts'], + rules: { + 'import/no-extraneous-dependencies': ['error', { devDependencies: true }] + } + } + ] }; diff --git a/packages/core/package.json b/packages/core/package.json index 420520c79..7738f29c7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,13 +13,15 @@ "generate:swapsApi": "rm -r ./src/swapsApi || true && npx openapi-typescript-codegen --input 'https://raw.githubusercontent.com/tonkeeper/swaps-backend/master/swagger.yaml?token=GHSAT0AAAAAACJYQUODBKR67AB7WULZBFWEZSUUGFQ' --output ./src/swapsApi", "generate:batteryApi": "rm -fr ./src/batteryApi && docker build --no-cache --build-arg GITHUB_TOKEN=GHSAT0AAAAAACJYQUODBULKMUQJQTJ7E7ES2H3LZ7A -f resource/Dockerfile.batteryApi . -t batteryapi && docker run --rm --user=$(id -u):$(id -g) -v \"$PWD\":/local batteryapi", "generate:2faApi": "rm -fr src/2faApi && docker build -f resource/Dockerfile.2faApi . -t 2faapi && docker run --rm --user=$(id -u):$(id -g) -v \"$PWD\":/local 2faapi", + "test": "vitest", "build:pkg": "yarn build", "build:analytics": "ts-node --project ./tsconfig.task.json ./task/build-analytics.ts" }, "devDependencies": { "@types/aes-js": "^3.1.4", "@types/punycode": "^2", - "typescript": "^4.9.4" + "typescript": "^4.9.4", + "vitest": "^4.0.18" }, "dependencies": { "@keystonehq/keystone-sdk": "0.7.2", diff --git a/packages/core/src/service/ton-blockchain/fee/__tests__/fees.spec.ts b/packages/core/src/service/ton-blockchain/fee/__tests__/fees.spec.ts new file mode 100644 index 000000000..0b130bbbc --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/__tests__/fees.spec.ts @@ -0,0 +1,352 @@ +/* eslint-disable prettier/prettier */ +import { Cell } from '@ton/core'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { BLOCKCHAIN_CONFIG_2024_12 } from './fixtures/blockchain-config'; +import { fetchExpectedFees, shouldFetchRealFees } from './fixtures/tonapi-fetcher'; +import { FEE_TEST_CASES, EXT } from './fixtures/test-cases'; +import { WalletFeeTestCase, ExpectedFees, parseWalletOutMsgCells } from './fixtures/utils'; +import { + computeActionFee, + computeAddExtensionGas, + computeAddExtensionGasFromExtensions, + computeForwardFee, + computeGasFee, + computeImportFee, + computeRemoveExtensionGas, + computeRemoveExtensionGasFromExtensions, + computeRemoveLastExtensionGas, + computeStorageFee, + computeWalletGasUsed, + estimateWalletFee, + EstimateWalletFeeParams +} from '../fees'; +import { TonWalletVersion } from '../compat'; + +/** + * TON Fee Calculation Specification + * + * This file serves as executable documentation for TON fee estimation. + * Sections 1-7: unit tests for individual formulas. + * Section 8: integration tests against real blockchain transactions. + * + * Modes: + * yarn workspace @tonkeeper/core exec vitest run fees.spec.ts # compare with fixtures + * FETCH_REAL_FEES=1 yarn workspace @tonkeeper/core exec vitest run fees.spec.ts # fetch from blockchain + */ + +// Basechain config for unit tests +const baseConfig = BLOCKCHAIN_CONFIG_2024_12.basechain; + +// ============================================================================ +// 1. computeGasFee +// ============================================================================ + +describe('1. computeGasFee (formula: floor(gasUsed × gasPrice / 2^16))', () => { + it('returns 0 for gasUsed = 0', () => { + expect(computeGasFee(baseConfig, 0n)).toBe(0n); + }); + + it('calculates gas fee: gasUsed=4939 → 1_975_600', () => { + expect(computeGasFee(baseConfig, 4939n)).toBe(1_975_600n); + }); + + describe('floor rounding (gasPrice=1 → result = gasUsed / 2^16, truncated)', () => { + // Override gasPrice to 1 so gasFee = gasUsed >> 16, isolating rounding behavior + const roundingConfig = { ...baseConfig, gasPrice: 1n }; + + it('rounds 0.0000... down to 0', () => expect(computeGasFee(roundingConfig, 1n)).toBe(0n)); + it('rounds 0.9999... down to 0', () => expect(computeGasFee(roundingConfig, 65535n)).toBe(0n)); + it('keeps exact 1.0 ', () => expect(computeGasFee(roundingConfig, 65536n)).toBe(1n)); + it('rounds 1.0000... down to 1', () => expect(computeGasFee(roundingConfig, 65537n)).toBe(1n)); + }); +}); + +// ============================================================================ +// 2. computeStorageFee +// ============================================================================ + +describe('2. computeStorageFee (formula: ceil((bits×bitPrice + cells×cellPrice) × timeDelta / 2^16))', () => { + it('returns 0 for timeDelta = 0', () => { + expect(computeStorageFee(baseConfig, { bits: 100n, cells: 1n }, 0n)).toBe(0n); + }); + + it('returns 0 for negative timeDelta', () => { + expect(computeStorageFee(baseConfig, { bits: 100n, cells: 1n }, -100n)).toBe(0n); + }); + + it('calculates for V5R1 wallet (5012 bits, 22 cells, timeDelta=54358)', () => { + // used = 5012×1 + 22×500 = 16012 + // ceil(16012 × 54358 / 2^16) = ceil(870340696 / 65536) = 13281 + expect(computeStorageFee(baseConfig, { bits: 5012n, cells: 22n }, 54358n)).toBe(13281n); + }); + + // bitPrice=1, cellPrice=500 → used = 1×1 + 0×500 = 1, so result = timeDelta / 2^16 + describe('ceil rounding (used=1 → result = timeDelta / 2^16, rounded up)', () => { + const s = { bits: 1n, cells: 0n }; + + it('rounds 0.0000... up to 1', () => expect(computeStorageFee(baseConfig, s, 1n)).toBe(1n)); + it('keeps exact 1.0 ', () => expect(computeStorageFee(baseConfig, s, 65536n)).toBe(1n)); + it('rounds 1.0000... up to 2 ', () => expect(computeStorageFee(baseConfig, s, 65537n)).toBe(2n)); + }); +}); + +// ============================================================================ +// 3. computeForwardFee +// ============================================================================ + +describe('3. computeForwardFee (formula: lumpPrice + ceil((bitPrice×bits + cellPrice×cells) / 2^16))', () => { + // lumpPrice = 400000, bitPrice = 26214400, cellPrice = 2621440000 + + it('returns lumpPrice for bits=0, cells=0', () => { + expect(computeForwardFee(baseConfig.fwd, 0n, 0n)).toBe(400_000n); + }); + + it('calculates for bits > 0, cells = 0', () => { + // ceil(26214400 × 667 / 2^16) + 400000 = 266800 + 400000 = 666800 + expect(computeForwardFee(baseConfig.fwd, 667n, 0n)).toBe(666_800n); + }); + + it('calculates for bits = 0, cells > 0', () => { + // ceil(2621440000 × 1 / 2^16) + 400000 = 40000 + 400000 = 440000 + expect(computeForwardFee(baseConfig.fwd, 0n, 1n)).toBe(440_000n); + }); + + it('calculates for bits > 0, cells > 0', () => { + // ceil((26214400×667 + 2621440000×1) / 2^16) + 400000 = 306800 + 400000 = 706800 + expect(computeForwardFee(baseConfig.fwd, 667n, 1n)).toBe(706_800n); + }); + + // lumpPrice=0, bitPrice=1, cellPrice=0 → result = ceil(bits / 2^16) + describe('ceil rounding (bitPrice=1, lump=0, cell=0 → result = bits / 2^16, rounded up)', () => { + // Override prices so fwdFee = ceil(bits / 2^16), isolating rounding behavior + const roundingFwd = { ...baseConfig.fwd, bitPrice: 1n, cellPrice: 0n, lumpPrice: 0n }; + + it('rounds 0.0000... up to 1', () => expect(computeForwardFee(roundingFwd, 1n, 0n)).toBe(1n)); + it('keeps exact 1.0 ', () => expect(computeForwardFee(roundingFwd, 65536n, 0n)).toBe(1n)); + it('rounds 1.0000... up to 2 ', () => expect(computeForwardFee(roundingFwd, 65537n, 0n)).toBe(2n)); + }); +}); + +// ============================================================================ +// 4. computeImportFee +// ============================================================================ + +describe('4. computeImportFee', () => { + /** + * Same formula as computeForwardFee (alias). + * Used for external-in message import fee calculation. + */ + + it('calculates import fee: bits=667, cells=1 → 706800', () => { + expect(computeImportFee(baseConfig.fwd, 667n, 1n)).toBe(706800n); + }); +}); + +// ============================================================================ +// 5. computeActionFee +// ============================================================================ + +describe('5. computeActionFee (formula: floor(fwdFee × firstFrac / 2^16) ≈ 1/3)', () => { + // firstFrac = 21845, so multiplier ≈ 21845/65536 ≈ 0.33333 + + it('returns 0 for fwdFee = 0', () => { + expect(computeActionFee(baseConfig.fwd, 0n)).toBe(0n); + }); + + it('returns ~1/3 of forward fee', () => { + // 666672 × 21845 >> 16 = 222220 + expect(computeActionFee(baseConfig.fwd, 666672n)).toBe(222220n); + }); + + // firstFrac=1 → result = fwdFee / 2^16 + describe('floor rounding (firstFrac=1 → result = fwdFee / 2^16, truncated)', () => { + // Override firstFrac to 1 so actionFee = fwdFee >> 16, isolating rounding behavior + const roundingFwd = { ...baseConfig.fwd, firstFrac: 1n }; + + it('rounds 0.0000... down to 0', () => expect(computeActionFee(roundingFwd, 1n)).toBe(0n)); + it('rounds 0.9999... down to 0', () => expect(computeActionFee(roundingFwd, 65535n)).toBe(0n)); + it('keeps exact 1.0 ', () => expect(computeActionFee(roundingFwd, 65536n)).toBe(1n)); + it('rounds 1.0000... down to 1', () => expect(computeActionFee(roundingFwd, 65537n)).toBe(1n)); + }); +}); + +// ============================================================================ +// 6. computeWalletGasUsed +// ============================================================================ + +describe('6. computeWalletGasUsed (formula: baseGas + gasPerMsg × n)', () => { + /** + * | Version | baseGas | gasPerMsg | + * |---------|---------|-----------| + * | V5R1 | 4222 | 717 | + * | V4R2 | 2666 | 642 | + * | V3R2 | 2352 | 642 | + * | V3R1 | 2275 | 642 | + */ + const cases = [ + { version: TonWalletVersion.V5R1, label: 'V5R1', msgs: 1n, gas: 4939n }, // 4222 + 717×1 + { version: TonWalletVersion.V5R1, label: 'V5R1', msgs: 3n, gas: 6373n }, // 4222 + 717×3 + { version: TonWalletVersion.V4R2, label: 'V4R2', msgs: 1n, gas: 3308n }, // 2666 + 642×1 + { version: TonWalletVersion.V4R2, label: 'V4R2', msgs: 3n, gas: 4592n }, // 2666 + 642×3 + { version: TonWalletVersion.V3R2, label: 'V3R2', msgs: 1n, gas: 2994n }, // 2352 + 642×1 + { version: TonWalletVersion.V3R1, label: 'V3R1', msgs: 1n, gas: 2917n } // 2275 + 642×1 + ]; + + for (const c of cases) { + it(`${c.label}, ${c.msgs} msg → ${c.gas}`, () => { + expect(computeWalletGasUsed(c.version, c.msgs)).toBe(c.gas); + }); + } +}); + +// ============================================================================ +// 7. V5R1 Extension Gas +// ============================================================================ + +/** + * Extension gas calculation tests (blockchain-verified). + * + * ADD formula: 6610 + 600 × cellLoads (first: 6110) + * REMOVE formula: 5290 + 600 × cellLoads + (merge ? 75 : 0) + * + * Test data from wallet UQD3KlCnEgNeGs4blSjo03JGyS4Rn1QiWhO7H6hcxaZwpAH6 + * All extension operations verified against real blockchain transactions. + */ +describe('7. V5R1 Extension Gas', () => { + // ---- ADD Extension ---- + + // Full staircase 0→1→...→8 verified against real transactions. + // Gas is non-monotonic due to Patricia trie rebalancing (e.g. 4→5 < 3→4). + const addExtFromExtCases = [ + // https://tonviewer.com/transaction/3eb607af0ee02aa773c9e840c817e62e2addc0871a6d6bdcd30e95784840a95e + { existing: [], add: EXT.E1, gas: 6110n }, + // https://tonviewer.com/transaction/185a5fd6fe0a996786b7acd4b2a5ff3b69df8475be91118d4ba726d90c4bc8f3 + { existing: [EXT.E1], add: EXT.E2, gas: 7210n }, + // https://tonviewer.com/transaction/d505f6df24065a837fe0e3916b4dffdadf4de45f20b784a6862c58b7609c9828 + { existing: [EXT.E1, EXT.E2], add: EXT.E3, gas: 7810n }, + // https://tonviewer.com/transaction/e89c1640cd32335a123caa6737ac3767447f8818ff911bad03a3ba3555361565 + { existing: [EXT.E1, EXT.E2, EXT.E3], add: EXT.E4, gas: 8410n }, + // https://tonviewer.com/transaction/bdfdae4d4ddd87f0e45ee2249701e01538ec4d28a711e44b7debd2ba0c680f7b + { existing: [EXT.E1, EXT.E2, EXT.E3, EXT.E4], add: EXT.E5, gas: 7810n }, + // https://tonviewer.com/transaction/ca13fd5b2d0321128b265a7b4e1155ca142a08f8cc01523370385b05ab978e69 + { existing: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5], add: EXT.E6, gas: 8410n }, + // https://tonviewer.com/transaction/2e63cf4af8192d34f963656c632715ab66689a862d6f78e703360d3352adf07d + { existing: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5, EXT.E6], add: EXT.E7, gas: 9010n }, + // https://tonviewer.com/transaction/a24db9110975efc27875b5786240384e96e58b29bf5497fefc84b8914f20a8a0 + { existing: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5, EXT.E6, EXT.E7], add: EXT.E8, gas: 7810n } + ]; + + describe('computeAddExtensionGasFromExtensions', () => { + for (const c of addExtFromExtCases) { + it(`${c.existing.length}→${c.existing.length + 1}: gas=${c.gas}`, () => { + expect(computeAddExtensionGasFromExtensions(c.existing, c.add)).toBe(c.gas); + }); + } + }); + + // Formula: 6610 + 600 × cellLoads + const addExtCases = [ + { cellLoads: 1n, gas: 7210n }, + { cellLoads: 2n, gas: 7810n }, + { cellLoads: 3n, gas: 8410n }, + { cellLoads: 4n, gas: 9010n } + ]; + + describe('computeAddExtensionGas (6610 + 600×cellLoads)', () => { + for (const c of addExtCases) { + it(`cellLoads=${c.cellLoads} → ${c.gas}`, () => { + expect(computeAddExtensionGas(c.cellLoads)).toBe(c.gas); + }); + } + }); + + // ---- REMOVE Extension ---- + + describe('computeRemoveLastExtensionGas', () => { + it('1→0: 5290 + 600 - 25 = 5865', () => { + expect(computeRemoveLastExtensionGas()).toBe(5865n); + }); + }); + + // Formula: 5290 + 600 × cellLoads + (needsMerge ? 75 : 0) + const removeExtCases = [ + { cellLoads: 1n, merge: false, gas: 5890n }, + { cellLoads: 1n, merge: true, gas: 5965n }, + { cellLoads: 2n, merge: false, gas: 6490n }, + { cellLoads: 4n, merge: false, gas: 7690n } + ]; + + describe('computeRemoveExtensionGas (5290 + 600×cellLoads ± merge)', () => { + for (const c of removeExtCases) { + it(`cellLoads=${c.cellLoads}, merge=${c.merge} → ${c.gas}`, () => { + expect(computeRemoveExtensionGas(c.cellLoads, c.merge)).toBe(c.gas); + }); + } + }); + + const removeExtFromExtCases = [ + { existing: [EXT.E1], remove: EXT.E1, gas: 5865n, label: '1→0: last extension' }, + { existing: [EXT.E1, EXT.E2], remove: EXT.E2, gas: 6565n, label: '2→1: root collapse (+75)' } + ]; + + describe('computeRemoveExtensionGasFromExtensions', () => { + for (const c of removeExtFromExtCases) { + it(`${c.label} → ${c.gas}`, () => { + expect(computeRemoveExtensionGasFromExtensions(c.existing, c.remove)).toBe(c.gas); + }); + } + }); +}); + + +// ============================================================================ +// 8. Blockchain-verified Transactions +// ============================================================================ + +/** + * Integration tests with real blockchain transactions. + * Each fixture contains a real transaction hash and expected fee values. + */ +describe('8. Blockchain-verified Transactions', () => { + async function loadExpected(fixture: WalletFeeTestCase): Promise { + if (shouldFetchRealFees()) { + // eslint-disable-next-line no-console + console.log(`Fetching tx: ${fixture.txHash}`); + return fetchExpectedFees(fixture.txHash); + } + return fixture.expected; + } + + describe.each(FEE_TEST_CASES)('$name', (fixture) => { + const { input, blockchainConfig } = fixture; + const { walletVersion, storageUsed, timeDelta, existingExtensions } = input; + const inMsg = Cell.fromBase64(input.inMsgBoc); + const outMsgs = parseWalletOutMsgCells(inMsg, walletVersion); + + let expected: ExpectedFees; + beforeAll(async () => { + expected = await loadExpected(fixture); + }); + + it('walletFee', () => { + const params: EstimateWalletFeeParams = existingExtensions + ? { + walletVersion: walletVersion as TonWalletVersion.V5R1, + storageUsed, + inMsg, + timeDelta, + existingExtensions + } + : { + walletVersion, + storageUsed, + inMsg, + timeDelta, + outMsgs + }; + const estimation = estimateWalletFee(blockchainConfig, params); + expect(estimation.walletFee).toBe(expected.walletFee); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/blockchain-config.ts b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/blockchain-config.ts new file mode 100644 index 000000000..eba61e45e --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/blockchain-config.ts @@ -0,0 +1,37 @@ +/* eslint-disable prettier/prettier */ +/** + * TON blockchain configuration snapshot for testing (mainnet, December 2024). + * + * Values sourced from https://tonviewer.com/config + * TL-B definitions: https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb + * + * ConfigParam 18: StoragePrices (bit_price_ps / mc_bit_price_ps, cell_price_ps / mc_cell_price_ps) + * ConfigParam 20/21: GasLimitsPrices.gas_price (masterchain / basechain) + * ConfigParam 24/25: MsgForwardPrices (masterchain / basechain) + */ +import { FeeConfig } from '../../fees'; + +export const BLOCKCHAIN_CONFIG_2024_12: FeeConfig = { + basechain: { + gasPrice: 26_214_400n, // ConfigParam 21 + storageBitPrice: 1n, // ConfigParam 18: bit_price_ps + storageCellPrice: 500n, // ConfigParam 18: cell_price_ps + fwd: { // ConfigParam 25 + lumpPrice: 400_000n, + bitPrice: 26_214_400n, + cellPrice: 2_621_440_000n, + firstFrac: 21_845n + } + }, + masterchain: { + gasPrice: 655_360_000n, // ConfigParam 20 + storageBitPrice: 1_000n, // ConfigParam 18: mc_bit_price_ps + storageCellPrice: 500_000n, // ConfigParam 18: mc_cell_price_ps + fwd: { // ConfigParam 24 + lumpPrice: 10_000_000n, + bitPrice: 655_360_000n, + cellPrice: 65_536_000_000n, + firstFrac: 21_845n + } + } +}; diff --git a/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/test-cases.ts b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/test-cases.ts new file mode 100644 index 000000000..352df1d1e --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/test-cases.ts @@ -0,0 +1,780 @@ +/** + * Consolidated TON fee calculation test fixtures. + * + * All test cases are real blockchain transactions verified against TonAPI. + * Verify: yarn workspace @tonkeeper/core exec vitest run fees.spec.ts + */ + +import { BLOCKCHAIN_CONFIG_2024_12 } from './blockchain-config'; +import { WalletFeeTestCase } from './utils'; +import { TonWalletVersion } from '../../compat'; +import { UNINIT_ACCOUNT_STORAGE } from '../../fees'; + +// Extension hashes shared across fixtures (deduplication of 8+ fixture files) +export const EXT = { + E1: '613fbe5785a63a981b59e3255f8d94749deadba81d5658d806c6333a1e6a0526', + E2: 'ba6ede4924bdc9ecbd4582c10bfacf1dfdf3e4f1bde5796819e45ff6ea0f8522', + E3: '4758697a8b9cadbecfe0f102132435465768798a9bacbdcedff0011223344556', + E4: '5f708192a3b4c5d6e7f8091a2b3c4d5e6f8091a2b3c4d5e6f708192a3b4c5d6e', + E5: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01', + E6: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02', + E7: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03', + E8: '0000000000000000000000000000000000000000000000000000000000000004', + E9: '0000000000000000000000000000000000000000000000000000000000000005', + FIRST: 'e8af660480cb56a355e06cfcd22cf474252d721ecbcb32bd98fcc8522b6ebbca', + SECOND: '0000000000000000000000000000000000000000000000000000000000000001' +} as const; + +export const FEE_TEST_CASES: WalletFeeTestCase[] = [ + // ============================================================ + // V3R1 + // ============================================================ + + // https://tonviewer.com/transaction/9b431557cc90d4fee34fe8b3afa5cc68baf0afac76d8a603f04bc6eccb0328a3 + { + name: 'V3R1 - Simple TON Transfer', + tag: 'simple-transfer', + txHash: '9b431557cc90d4fee34fe8b3afa5cc68baf0afac76d8a603f04bc6eccb0328a3', + + input: { + inMsgBoc: + 'te6cckEBAgEAvAAB34gB4tRiYij2hXdgYiXa+8xTlV+MyEerBkhZwvQFX3bSTPYBb9MXN6TWOd2BFH0MHHC8e7AbH0XaKpn2ViX8n4vM4b6FN4c0n7CPo8ajbuNmDsu8CxTI7dNwXlW2Rq6V5GEwaU1NGLtKElyAAAAACBwBAI5iABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMSBzaW1wbGUgdGVzdOJYMOI=', + walletVersion: TonWalletVersion.V3R1, + storageUsed: { bits: 1195n, cells: 3n }, + timeDelta: 54_291n + }, + + expected: { + gasUsed: 2917n, + gasFee: 1_166_800n, + actionFee: 133_331n, + storageFee: 2233n, + importFee: 667_200n, + fwdFeeRemaining: 266_669n, + walletFee: 2_236_233n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/56d703d5a575c1ebebc1ca4c4d53a0fe153868f1819e90dce5454aaa60f85cbe + { + name: 'V3R1 - Deploy + Transfer', + tag: 'deploy-transfer', + txHash: '56d703d5a575c1ebebc1ca4c4d53a0fe153868f1819e90dce5454aaa60f85cbe', + + input: { + inMsgBoc: + 'te6cckECBAEAAUsAA+GIAeLUYmIo9oV3YGIl2vvMU5VfjMhHqwZIWcL0BV920kz2EZAlKodvdHYSVEbp2r8c7Ovqg9QjJz6RTyYGGJmk1EbVcqKWiZ/4OROX1EHhkLSDTpPgt4wwrrxtrfORRdd+8qBlNTRi/////+AAAAAAcAECAwDA/wAg3SCCAUyXupcw7UTQ1wsf4KTyYIMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOjRAaTIyx/LH8v/ye1UAFAAAAAAKamjF4jE4Brs3Pa3f2iTWmht+POgbEVqVeRb6lTT5vquGk3cAI5iABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMSBkZXBsb3kgdGVzdCOmdwU=', + walletVersion: TonWalletVersion.V3R1, + storageUsed: UNINIT_ACCOUNT_STORAGE, + timeDelta: 25_832n + }, + + expected: { + gasUsed: 2917n, + gasFee: 1_166_800n, + actionFee: 133_331n, + storageFee: 238n, + importFee: 1_182_400n, + fwdFeeRemaining: 266_669n, + walletFee: 2_749_438n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/a4dc775cbbfc14c46679159a8e9fac6d65439e25fa68dcceb91c0e3de9948943 + { + name: 'V3R1 - Multi-message Transfer', + tag: 'multi-transfer', + txHash: 'a4dc775cbbfc14c46679159a8e9fac6d65439e25fa68dcceb91c0e3de9948943', + + input: { + inMsgBoc: + 'te6cckECBAEAAVUAA+OIAeLUYmIo9oV3YGIl2vvMU5VfjMhHqwZIWcL0BV920kz2A1v/MHLv+Ml1GqL+s0qf7DWsmXD5MHneAK5FevAbsHo1W8COSizPAKXbac9yyyJPQFvrtyShlcz9YX89cYpGuCFNTRi7ShM/YAAAABAYGBwBAgMAkGIAFOFGEqPS/PDa9FlHVFZQWYRs3sUuXlIAuXN6eE1iGy0cxLQAAAAAAAAAAAAAAAAAAAAAAABWM1IxIG11bHRpIHRlc3QgMQCQYgAiycojb9h2/bbiQY7Ivk7EDqflQe+rQ6QPHeFP7K/K1pzEtAAAAAAAAAAAAAAAAAAAAAAAAFYzUjEgbXVsdGkgdGVzdCAyAJBiAHZvQ5onmVjHlys7khXWibBVj8TTEgE6DMssxFzEr+h1nMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMSBtdWx0aSB0ZXN0IDNwpkTh', + walletVersion: TonWalletVersion.V3R1, + storageUsed: { bits: 1195n, cells: 3n }, + timeDelta: 7261n + }, + + expected: { + gasUsed: 4201n, + gasFee: 1_680_400n, + actionFee: 399_993n, + storageFee: 299n, + importFee: 1_211_200n, + fwdFeeRemaining: 800_007n, + walletFee: 4_091_899n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V3R2 + // ============================================================ + + // https://tonviewer.com/transaction/2fa6487aaf22906418d98a8e20cb0c8fa1fb78c4d31661fb3ebc504ab5c9f9f7 + { + name: 'V3R2 - Simple TON Transfer', + tag: 'simple-transfer', + txHash: '2fa6487aaf22906418d98a8e20cb0c8fa1fb78c4d31661fb3ebc504ab5c9f9f7', + + input: { + inMsgBoc: + 'te6cckEBAgEAvAAB34gAiycojb9h2/bbiQY7Ivk7EDqflQe+rQ6QPHeFP7K/K1oGN2uEUMoi+OFETECx05z1AFr7rFZqUgxapDpSA0ZyN4et1EkPIow8h6Dwqgw/NXaa33DrEQJp9WT5aouP4q54SU1NGLtKEqPAAAAACBwBAI5iABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMiBzaW1wbGUgdGVzdI5CrMA=', + walletVersion: TonWalletVersion.V3R2, + storageUsed: { bits: 1315n, cells: 3n }, + timeDelta: 56_575n + }, + + expected: { + gasUsed: 2994n, + gasFee: 1_197_600n, + actionFee: 133_331n, + storageFee: 2431n, + importFee: 667_200n, + fwdFeeRemaining: 266_669n, + walletFee: 2_267_231n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/a3c4513865506e14d8eb05e0c2e508827125d615384bbb1f91b19c2147088c99 + { + name: 'V3R2 - Deploy + Transfer', + tag: 'deploy-transfer', + txHash: 'a3c4513865506e14d8eb05e0c2e508827125d615384bbb1f91b19c2147088c99', + + input: { + inMsgBoc: + 'te6cckECBAEAAVoAA+GIAIsnKI2/Ydv224kGOyL5OxA6n5UHvq0OkDx3hT+yvytaEYeccEtypc03z0Rh18v2sNOdvavY009n1UlBH0rd5oZPI7LZzgEdRGHhsFEwt5WlK8Okip/eG7g6a/GWGbNcceAFNTRi/////+AAAAAAcAECAwDe/wAg3SCCAUyXuiGCATOcurGfcbDtRNDTH9MfMdcL/+ME4KTyYIMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOjRAaTIyx/LH8v/ye1UAFAAAAAAKamjF4jE4Brs3Pa3f2iTWmht+POgbEVqVeRb6lTT5vquGk3cAI5iABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMiBkZXBsb3kgdGVzdKcWeY4=', + walletVersion: TonWalletVersion.V3R2, + storageUsed: UNINIT_ACCOUNT_STORAGE, + timeDelta: 25_861n + }, + + expected: { + gasUsed: 2994n, + gasFee: 1_197_600n, + actionFee: 133_331n, + storageFee: 238n, + importFee: 1_230_400n, + fwdFeeRemaining: 266_669n, + walletFee: 2_828_238n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/9758ce7b17d25f6f520d5c8be139de10abecd187fe16484263a0e5ae7fa1a298 + { + name: 'V3R2 - Multi-message Transfer', + tag: 'multi-transfer', + txHash: '9758ce7b17d25f6f520d5c8be139de10abecd187fe16484263a0e5ae7fa1a298', + + input: { + inMsgBoc: + 'te6cckECBAEAAVUAA+OIAIsnKI2/Ydv224kGOyL5OxA6n5UHvq0OkDx3hT+yvytaB34zOGFqj3w0evXwQI/ANdZwbVqjHPreRk+zMd9RNGEWZydn2T1g68qTAxs5RMAAdzQn5p/x+A5v57KYgvgzkFFNTRi7ShOIgAAAABAYGBwBAgMAkGIAFOFGEqPS/PDa9FlHVFZQWYRs3sUuXlIAuXN6eE1iGy0cxLQAAAAAAAAAAAAAAAAAAAAAAABWM1IyIG11bHRpIHRlc3QgMQCQYgB4tRiYij2hXdgYiXa+8xTlV+MyEerBkhZwvQFX3bSTPZzEtAAAAAAAAAAAAAAAAAAAAAAAAFYzUjIgbXVsdGkgdGVzdCAyAJBiAHZvQ5onmVjHlys7khXWibBVj8TTEgE6DMssxFzEr+h1nMS0AAAAAAAAAAAAAAAAAAAAAAAAVjNSMiBtdWx0aSB0ZXN0IDPXKVZl', + walletVersion: TonWalletVersion.V3R2, + storageUsed: { bits: 1315n, cells: 3n }, + timeDelta: 1101n + }, + + expected: { + gasUsed: 4278n, + gasFee: 1_711_200n, + actionFee: 399_993n, + storageFee: 48n, + importFee: 1_211_200n, + fwdFeeRemaining: 800_007n, + walletFee: 4_122_448n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V4R2 + // ============================================================ + + // https://tonviewer.com/transaction/da87f551960c619ce4a00737d84c3ac087d311e30b3d0f0a481b6c528f639a11 + { + name: 'V4R2 - Simple TON Transfer', + tag: 'simple-transfer', + txHash: 'da87f551960c619ce4a00737d84c3ac087d311e30b3d0f0a481b6c528f639a11', + + input: { + inMsgBoc: + 'te6cckEBAgEAvQAB4YgB2b0OaJ5lYx5crO5IV1omwVY/E0xIBOgzLLMRcxK/odYA/yGGDBicSwhIT4Px0xBMWKKXCRIh4qn6VaY0lmXkTTLKlvg+tIAxGf5fP04JxrwPT9EPGvHt/vwE+TsvkouoQU1NGLtKErSgAAAACAAcAQCOYgAU4UYSo9L88Nr0WUdUVlBZhGzexS5eUgC5c3p4TWIbLRzEtAAAAAAAAAAAAAAAAAAAAAAAAFY0UjIgc2ltcGxlIHRlc3QPg4u7', + walletVersion: TonWalletVersion.V4R2, + storageUsed: { bits: 5689n, cells: 22n }, + timeDelta: 53_817n + }, + + expected: { + gasUsed: 3308n, + gasFee: 1_323_200n, + actionFee: 133_331n, + storageFee: 13_705n, + importFee: 667_200n, + fwdFeeRemaining: 266_669n, + walletFee: 2_404_105n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/9cef3b6ed79a0026f702997011dfae56ed1e542869be96433b2a2ee95e10dbc6 + { + name: 'V4R2 - Deploy + Transfer', + tag: 'deploy-transfer', + txHash: '9cef3b6ed79a0026f702997011dfae56ed1e542869be96433b2a2ee95e10dbc6', + + input: { + inMsgBoc: + 'te6cckECFwEAA78AA+OIAdm9DmieZWMeXKzuSFdaJsFWPxNMSAToMyyzEXMSv6HWEZR9QrhoVohsiDIWH/WY1dxOdqwnAvf+v3obhBMYcdEyVVSqyAXvBIhl3JveXrQW/n7NFBiStGX16/QSgf/ZawHlNTRi/////+AAAAAAAHABFRYBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF4jE4Brs3Pa3f2iTWmht+POgbEVqVeRb6lTT5vquGk3cQACOYgAU4UYSo9L88Nr0WUdUVlBZhGzexS5eUgC5c3p4TWIbLRzEtAAAAAAAAAAAAAAAAAAAAAAAAFY0UjIgZGVwbG95IHRlc3QU7ERC', + walletVersion: TonWalletVersion.V4R2, + storageUsed: UNINIT_ACCOUNT_STORAGE, + timeDelta: 29_189n + }, + + expected: { + gasUsed: 3308n, + gasFee: 1_323_200n, + actionFee: 133_331n, + storageFee: 269n, + importFee: 3_740_000n, + fwdFeeRemaining: 266_669n, + walletFee: 5_463_469n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/f746a4a6347a56ad128bcd8e17831bbbd9776b0522c764e4c672512fa053196d + { + name: 'V4R2 - Multi-message Transfer', + tag: 'multi-transfer', + txHash: 'f746a4a6347a56ad128bcd8e17831bbbd9776b0522c764e4c672512fa053196d', + + input: { + inMsgBoc: + 'te6cckECBAEAAVYAA+WIAdm9DmieZWMeXKzuSFdaJsFWPxNMSAToMyyzEXMSv6HWAWW792PeJs2k3RpePkQV3QSb7A76RXTzp7YyH7Hl3TE5Xl23gFlLONfJWsx/ruBNK24WPIX/mjYtHdgkvYuGSAFNTRi7ShN44AAAABAAGBgcAQIDAJBiABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjRSMiBtdWx0aSB0ZXN0IDEAkGIAeLUYmIo9oV3YGIl2vvMU5VfjMhHqwZIWcL0BV920kz2cxLQAAAAAAAAAAAAAAAAAAAAAAABWNFIyIG11bHRpIHRlc3QgMgCQYgAiycojb9h2/bbiQY7Ivk7EDqflQe+rQ6QPHeFP7K/K1pzEtAAAAAAAAAAAAAAAAAAAAAAAAFY0UjIgbXVsdGkgdGVzdCAzaX8lDA==', + walletVersion: TonWalletVersion.V4R2, + storageUsed: { bits: 5689n, cells: 22n }, + timeDelta: 650n + }, + + expected: { + gasUsed: 4592n, + gasFee: 1_836_800n, + actionFee: 399_993n, + storageFee: 166n, + importFee: 1_211_200n, + fwdFeeRemaining: 800_007n, + walletFee: 4_248_166n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V5R1 + // ============================================================ + + // https://tonviewer.com/transaction/8612717faece81bf6a2c7b44c9b4609b71e06ae7d0c1aa356e6cd8f30c801056 + { + name: 'V5R1 - Simple TON Transfer', + tag: 'simple-transfer', + txHash: '8612717faece81bf6a2c7b44c9b4609b71e06ae7d0c1aa356e6cd8f30c801056', + + input: { + inMsgBoc: + 'te6cckEBBAEAygAB5YgB0V7MCQGWrUarwNn5pFno6Epa5D2XlmV7MfmQpFbdd5QDm0s7c///+ItKEsWAAAAADVL1hvMF+kXoAPfh8o5Si/90ln+7aUZX1+q49NXnnIx02PxrF0pgHtgV5DmXSIUI8apByap2eB+AIbWIi4vuGBEBAgoOw8htAwIDAAAAjmIAFOFGEqPS/PDa9FlHVFZQWYRs3sUuXlIAuXN6eE1iGy0cxLQAAAAAAAAAAAAAAAAAAAAAAABWNVIxIHNpbXBsZSB0ZXN0WOFYaQ==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 54_358n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 133_331n, + storageFee: 13_281n, + importFee: 763_200n, + fwdFeeRemaining: 266_669n, + walletFee: 3_152_081n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/8b399b6f07adfff9ebbc993f3e31955d28d01a0eca4e95927d221f793e01d5bb + { + name: 'V5R1 - Deploy + Transfer', + tag: 'deploy-transfer', + txHash: '8b399b6f07adfff9ebbc993f3e31955d28d01a0eca4e95927d221f793e01d5bb', + + input: { + inMsgBoc: + 'te6cckECGQEAA3kAA+eIAdFezAkBlq1Gq8DZ+aRZ6OhKWuQ9l5ZlezH5kKRW3XeUEY5tLO3P///iP////+AAAAAXRMD9rgTSL3siUO+VNqBj+rGQeQcxIl4tznudt+z+R7es1YgWsujT2Y5/87YkjeecfaLUxVnEk9b9Q4Oj8WD4RAEVFgEU/wD0pBP0vPLICwICASADDgIBSAQFAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hEQAgEgBg0CASAHCgIBbggJABmtznaiaEAg65Drhf/AABmvHfaiaEAQ65DrhY/AAgFICwwAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgABm+Xw9qJoQICg65D6AsAQLyDwEeINcLH4IQc2lnbrry4Ip/EAHmjvDtou37IYMI1yICgwjXIyCAINch0x/TH9Mf7UTQ0gDTHyDTH9P/1woACvkBQMz5EJoolF8K2zHh8sCH3wKzUAew8tCEUSW68uCFUDa68uCG+CO78tCIIpL4AN4BpH/IygDLHwHPFsntVCCS+A/ecNs82BED9u2i7fsC9AQhbpJsIY5MAiHXOTBwlCHHALOOLQHXKCB2HkNsINdJwAjy4JMg10rAAvLgkyDXHQbHEsIAUjCw8tCJ10zXOTABpOhsEoQHu/Lgk9dKwADy4JPtVeLSAAHAAJFb4OvXLAgUIJFwlgHXLAgcEuJSELHjDyDXShITFACWAfpAAfpE+Cj6RDBYuvLgke1E0IEBQdcY9AUEnX/IygBABIMH9FPy4IuOFAODB/Rb8uCMItcKACFuAbOw8tCQ4shQA88WEvQAye1UAHIw1ywIJI4tIfLgktIA7UTQ0gBRE7ry0I9UUDCRMZwBgQFA1yHXCgDy4I7iyMoAWM8Wye1Uk/LAjeIAEJNb2zHh10zQAFGAAAAAP///iMRicA12bntbv7RJrTQ2/HnQNiK1KvIt9Spp831XDSbuIAIKDsPIbQMXGAAAAI5iABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjVSMSBkZXBsb3kgdGVzdB3rhBA=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: UNINIT_ACCOUNT_STORAGE, + timeDelta: 5014n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 133_331n, + storageFee: 47n, + importFee: 3_565_200n, + fwdFeeRemaining: 266_669n, + walletFee: 5_940_847n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/9043311ef14e365b6a856a48d8126527363ef565c7aaa310948dcbfc691c526a + { + name: 'V5R1 - Multi-message Transfer', + tag: 'multi-transfer', + txHash: '9043311ef14e365b6a856a48d8126527363ef565c7aaa310948dcbfc691c526a', + + input: { + inMsgBoc: + 'te6cckECCAEAAXEAAeWIAdFezAkBlq1Gq8DZ+aRZ6OhKWuQ9l5ZlezH5kKRW3XeUA5tLO3P///iLShO3YAAAABUZ50fwccMDqEOvwu6DVhQAQOt4PsclSdfdktQsk5nDCob7nMpPMfCMCQeCBX297wU0F9hpGIJYOAsjb/Nx8RoTAQIKDsPIbQMCBwIKDsPIbQMDBgIKDsPIbQMEBQAAAJBiABThRhKj0vzw2vRZR1RWUFmEbN7FLl5SALlzenhNYhstHMS0AAAAAAAAAAAAAAAAAAAAAAAAVjVSMSBtdWx0aSB0ZXN0IDEAkGIAeLUYmIo9oV3YGIl2vvMU5VfjMhHqwZIWcL0BV920kz2cxLQAAAAAAAAAAAAAAAAAAAAAAABWNVIxIG11bHRpIHRlc3QgMgCQYgAiycojb9h2/bbiQY7Ivk7EDqflQe+rQ6QPHeFP7K/K1pzEtAAAAAAAAAAAAAAAAAAAAAAAAFY1UjEgbXVsdGkgdGVzdCAzCkLbdA==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 6338n + }, + + expected: { + gasUsed: 6373n, + gasFee: 2_549_200n, + actionFee: 399_993n, + storageFee: 1549n, + importFee: 1_419_200n, + fwdFeeRemaining: 800_007n, + walletFee: 5_169_949n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/9fc34b1f3bbea2afb2077224258c875b66fe468219d13eed32318aa3d72d2d2f + { + name: 'V5R1 - Send All Transfer', + tag: 'send-all', + txHash: '9fc34b1f3bbea2afb2077224258c875b66fe468219d13eed32318aa3d72d2d2f', + + input: { + inMsgBoc: + 'te6cckEBBAEAzAAB5YgAmqIIQCtBfrwEXiMfHOD/mctmZ1zSwCiA4D5Eav4qrfYDm0s7c///+ItKHuwwAAAALfm0UY5F646ThNTsVr5U1IpRiC2u40xbyGULBnenEi1OTCLRUcM9pCL9qzGl3Rwlm9hyB2RG+YNERLvVrRjSsg0BAgoOw8htggIDAAAAkmIAFOFGEqPS/PDa9FlHVFZQWYRs3sUuXlIAuXN6eE1iGy0YehIAAAAAAAAAAAAAAAAAAAAAAABWNVIxIHNlbmQtYWxsIHRlc3Rw/eVR', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 3488n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 133_331n, + storageFee: 853n, + importFee: 769_600n, + fwdFeeRemaining: 266_669n, + walletFee: 3_146_053n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V5R1 Jetton + // ============================================================ + + // https://tonviewer.com/transaction/4f148ce4f6ea7673dd7dce81e2f0cd23ca5e2e5baa68fa36ba0c689f324ce3ab + { + name: 'V5R1 - Deploy + Jetton Transfer', + tag: 'jetton-deploy-transfer', + txHash: '4f148ce4f6ea7673dd7dce81e2f0cd23ca5e2e5baa68fa36ba0c689f324ce3ab', + + input: { + inMsgBoc: + 'te6cckECGgEAA70AA+eIAAPOI+mE47pQOyxdNKF/avgxvWh9YbJ4Tcg7luVXDla6EY5tLO3P///iP////+AAAAAUXT6I0OuBJ/Jj8s0J5LMQqDo1L3ljAh2adGyCpls7/OX2XlaRAhVWxGZN+2wThSA7EULv22lQcfiEksiBwREQXAEVFgEU/wD0pBP0vPLICwICASADDgIBSAQFAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hEQAgEgBg0CASAHCgIBbggJABmtznaiaEAg65Drhf/AABmvHfaiaEAQ65DrhY/AAgFICwwAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgABm+Xw9qJoQICg65D6AsAQLyDwEeINcLH4IQc2lnbrry4Ip/EAHmjvDtou37IYMI1yICgwjXIyCAINch0x/TH9Mf7UTQ0gDTHyDTH9P/1woACvkBQMz5EJoolF8K2zHh8sCH3wKzUAew8tCEUSW68uCFUDa68uCG+CO78tCIIpL4AN4BpH/IygDLHwHPFsntVCCS+A/ecNs82BED9u2i7fsC9AQhbpJsIY5MAiHXOTBwlCHHALOOLQHXKCB2HkNsINdJwAjy4JMg10rAAvLgkyDXHQbHEsIAUjCw8tCJ10zXOTABpOhsEoQHu/Lgk9dKwADy4JPtVeLSAAHAAJFb4OvXLAgUIJFwlgHXLAgcEuJSELHjDyDXShITFACWAfpAAfpE+Cj6RDBYuvLgke1E0IEBQdcY9AUEnX/IygBABIMH9FPy4IuOFAODB/Rb8uCMItcKACFuAbOw8tCQ4shQA88WEvQAye1UAHIw1ywIJI4tIfLgktIA7UTQ0gBRE7ry0I9UUDCRMZwBgQFA1yHXCgDy4I7iyMoAWM8Wye1Uk/LAjeIAEJNb2zHh10zQAFGAAAAAP///iKJlC4pYpKjPnRwcPVBteZ/STb08sAm+NV8fKC+V2lhkoAIKDsPIbQMXGAAAAWhiAG86P6y6gCm5XLH3xO0tXwqnOO8/zrgSt4FIXPzeWbKfIBfXhAAAAAAAAAAAAAAAAAABGQCoD4p+pe5w+sQAAAABOYloCAE09rrD6bznPlKIejHNcWwNeRhPjI4FhxmqtwCTGqAlFwAAecR9MJx3Sgdli6aUL+1fBjetD6w2Twm5B3LcquHK10ICvS+pMw==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: UNINIT_ACCOUNT_STORAGE, + timeDelta: 1454n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 236_263n, + storageFee: 14n, + importFee: 3_813_200n, + fwdFeeRemaining: 472_537n, + walletFee: 6_497_614n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/e1087deb86086b1e8496ab968f99a5530170ed631a534c4d8256329cf454dd70 + { + name: 'V5R1 - Simple Jetton Transfer', + tag: 'jetton-transfer', + txHash: 'e1087deb86086b1e8496ab968f99a5530170ed631a534c4d8256329cf454dd70', + + input: { + inMsgBoc: + 'te6cckECBQEAAQ4AAeWIAAPOI+mE47pQOyxdNKF/avgxvWh9YbJ4Tcg7luVXDla6A5tLO3P///iLShQTaAAAAAwLgww5HSimZ7XvQOb7PfbRnm5uaH32GbNFdbb+ELWLhZwkUvP/wjlKG907QVOAUwG/8fakYMT0E2B3uE2vEAwfAQIKDsPIbQMCAwAAAWhiAFC9XmrYDrIpnVpvUkWdEZgLV9Lg+7dVJpKDrftKYkjqIBfXhAAAAAAAAAAAAAAAAAABBACoD4p+pe5w+sQAAAACMBhqCAE09rrD6bznPlKIejHNcWwNeRhPjI4FhxmqtwCTGqAlFwAAecR9MJx3Sgdli6aUL+1fBjetD6w2Twm5B3LcquHK10ICmd9p7Q==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 781n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 236_263n, + storageFee: 191n, + importFee: 1_011_200n, + fwdFeeRemaining: 472_537n, + walletFee: 3_695_791n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V5R1 Dedup & Exotic + // ============================================================ + + // https://tonviewer.com/transaction/0339b0c0720456038ecfcaedc19f287341cd2df5ee0891321540070da554d054 + { + name: 'V5R1 - Dedup Within Msg', + tag: 'dedup-within-msg', + txHash: '0339b0c0720456038ecfcaedc19f287341cd2df5ee0891321540070da554d054', + + input: { + inMsgBoc: + 'te6cckEBBQEA3AAB5YgAA84j6YTjulA7LF00oX9q+DG9aH1hsnhNyDuW5VcOVroDm0s7c///+ItKFGrAAAAAFbwDwxgL1Ime6rKH1Otm8lnAWH3J8aUH6Jw3sySXW+Lc/ZpaJ1qchT/sGJMcuaW5TIlZ1QK04gXiCtkHKo31Ih0BAgoOw8htAwIDAAACbmIATT2usPpvOc+Uoh6Mc1xbA15GE+MjgWHGaq3AJMaoCUWcxLQAAAAAAAAAAAAAAAAAAAAAAAAEBAA8EjRWeHNoYXJlZCBkYXRhIGZvciBkZWR1cCB0ZXN0je6JOA==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 2797n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 178_663n, + storageFee: 684n, + importFee: 848_000n, + fwdFeeRemaining: 357_337n, + walletFee: 3_360_284n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/139b5f7210fc0a86b54f447d0d060b1d843c1f52bf925057a7011461219a72c4 + { + name: 'V5R1 - Dedup Cross Msg', + tag: 'dedup-cross-msg', + txHash: '139b5f7210fc0a86b54f447d0d060b1d843c1f52bf925057a7011461219a72c4', + + input: { + inMsgBoc: + 'te6cckECCAEAAVwAAeWIAAPOI+mE47pQOyxdNKF/avgxvWh9YbJ4Tcg7luVXDla6A5tLO3P///iLShSOAAAAABzvOnnqwdRZiru+O8FlP0SOyPOk4d6GXn7lXwnoaPTbtHOFftayjNBC100WUm8nT68UbS91TZ/W/I4vkTl8qVoXAQIKDsPIbQMCBwIKDsPIbQMDBgIKDsPIbQMEBQAAAIJiAE09rrD6bznPlKIejHNcWwNeRhPjI4FhxmqtwCTGqAlFnMS0AAAAAAAAAAAAAAAAAAAAAAAAZGVkdXAgdGVzdACCYgAU4UYSo9L88Nr0WUdUVlBZhGzexS5eUgC5c3p4TWIbLRzEtAAAAAAAAAAAAAAAAAAAAAAAAGRlZHVwIHRlc3QAgmIAdFezAkBlq1Gq8DZ+aRZ6OhKWuQ9l5ZlezH5kKRW3XeUcxLQAAAAAAAAAAAAAAAAAAAAAAABkZWR1cCB0ZXN0/q/S9A==', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 1125n + }, + + expected: { + gasUsed: 6373n, + gasFee: 2_549_200n, + actionFee: 399_993n, + storageFee: 275n, + importFee: 1_352_000n, + fwdFeeRemaining: 800_007n, + walletFee: 5_101_475n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/743d84f69adba2e65532d233a4ba93881bb6ffc1ea3fa476474fb4b49df32ec3 + { + name: 'V5R1 - Library Cell Body', + tag: 'library-body', + txHash: '743d84f69adba2e65532d233a4ba93881bb6ffc1ea3fa476474fb4b49df32ec3', + + input: { + inMsgBoc: + 'te6cckEBBQEA3gAB5YgAA84j6YTjulA7LF00oX9q+DG9aH1hsnhNyDuW5VcOVroDm0s7c///+ItKFOcIAAAAJc98QMupXJWIfxDzyTVwthc5JpV/rwl0fMK9iENFr6Lk2KCeFQ4Ioplf5MoYJBn7/Zpbm1jgrauI3y6cNNdErAMBAgoOw8htAwIDAAABbmIATT2usPpvOc+Uoh6Mc1xbA15GE+MjgWHGaq3AJMaoCUWcxLQAAAAAAAAAAAAAAAAAAAAAAAAECEICj0Utek39dAZraCNlF3JZ7QVzRDW+drX9S9XYryt8PWhIvBQ+', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 2792n + }, + + expected: { + gasUsed: 4939n, + gasFee: 1_975_600n, + actionFee: 181_863n, + storageFee: 683n, + importFee: 857_600n, + fwdFeeRemaining: 363_737n, + walletFee: 3_379_483n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V5R1 Add Extension + // ============================================================ + + // https://tonviewer.com/transaction/0a1803894b487e63180e914013d3adcc227452c5ad9b646770bad745a8881f2a + { + name: 'V5R1 - Add First Extension', + tag: 'add-extension', + txHash: '0a1803894b487e63180e914013d3adcc227452c5ad9b646770bad745a8881f2a', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgAmqIIQCtBfrwEXiMfHOD/mctmZ1zSwCiA4D5Eav4qrfYMAQDlc2lnbn///xFpQ5RwAAAAAUCgB0V7MCQGWrUarwNn5pFno6Epa5D2XlmV7MfmQpFbdd5QvNbREC1rFe++nNUEYL7i/jFMan6sWaAcQkLcFhMQRdkdHH5FaquHUva9+ECMZuspGMjlVV45P48fwaRk8G6gDO+OSV0=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5012n, cells: 22n }, + timeDelta: 1734n, + existingExtensions: [] + }, + + expected: { + gasUsed: 6110n, + gasFee: 2_444_000n, + actionFee: 0n, + storageFee: 424n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_251_224n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/30575e1ea9c73215b623c560562bf26fbd9ca5e32a4b35e3449b5763bba05c11 + { + name: 'V5R1 - Add Second Extension', + tag: 'add-extension', + txHash: '30575e1ea9c73215b623c560562bf26fbd9ca5e32a4b35e3449b5763bba05c11', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgAmqIIQCtBfrwEXiMfHOD/mctmZ1zSwCiA4D5Eav4qrfYMAQDlc2lnbn///xFpQ5nrAAAAAkCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOMgSji7NZxA4UOBTvhF0xkwL27g64NPxmjxPQtAnno9A3oGEnAO11p+ilnMrEajHbiV65pR/ZZoWlLzqcrtAILHFWRfg=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5280n, cells: 23n }, + timeDelta: 1607n, + existingExtensions: [EXT.FIRST] + }, + + expected: { + gasUsed: 7210n, + gasFee: 2_884_000n, + actionFee: 0n, + storageFee: 412n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_691_212n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/c4fec1044bce37f1969b8fc8fb4c25b52655439230d02d8bf70d6eee384ad729 + { + name: 'V5R1 - Add Eighth Extension', + tag: 'add-extension', + txHash: 'c4fec1044bce37f1969b8fc8fb4c25b52655439230d02d8bf70d6eee384ad729', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgB7lShTiQGvDWcNypR0abkjZJcIz6oRLQndj9QuYtM4UgMAQDlc2lnbn///xFpQvtYAAAACUCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmd3fOQSyMWUSZO4N5SeZsrdUp2AjWLKBkD1H2kJyfIpAVO7JhYFrgxCAq1IJNy8AU5vgo+0Yz5AP+MQaG8UvgZGDSb/I=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 6349n, cells: 34n }, + timeDelta: 14_098n, + existingExtensions: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5, EXT.E6, EXT.E7] + }, + + expected: { + gasUsed: 7810n, + gasFee: 3_124_000n, + actionFee: 0n, + storageFee: 5023n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_935_823n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/6a16454aa6945d25787191caf686bf5df5bd2f9f581771ac1dc9adcc88315331 + { + name: 'V5R1 - Add Ninth Extension', + tag: 'add-extension', + txHash: '6a16454aa6945d25787191caf686bf5df5bd2f9f581771ac1dc9adcc88315331', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgB7lShTiQGvDWcNypR0abkjZJcIz6oRLQndj9QuYtM4UgMAQDlc2lnbn///xFpQ7IoAAAACkCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqOFBZ7pjTJNQIOWc/u9QNef9HkzpTWRow+9tis9REdamv1d6G7HLupjHN3bKbz+z5OUbPirI0MZ2KSXbfF2v4HJnWqds=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 6614n, cells: 36n }, + timeDelta: 43_152n, + existingExtensions: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5, EXT.E6, EXT.E7, EXT.E8] + }, + + expected: { + gasUsed: 8410n, + gasFee: 3_364_000n, + actionFee: 0n, + storageFee: 16_208n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 4_187_008n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // ============================================================ + // V5R1 Remove Extension + // ============================================================ + + // https://tonviewer.com/transaction/7e06fd2ade80900e47bd38db060b9533d09bb9d28a8798fefc52457ec5d508e5 + { + name: 'V5R1 - Remove Ext (LEAF sibling)', + tag: 'remove-extension', + txHash: '7e06fd2ade80900e47bd38db060b9533d09bb9d28a8798fefc52457ec5d508e5', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgB7lShTiQGvDWcNypR0abkjZJcIz6oRLQndj9QuYtM4UgMAQDlc2lnbn///xFpQ61cAAAAC0DgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAojDvyCh9biYXrKKfypktoIDRtKjxbZduuSFCMIcHso1YlvnQ+dU11C+JRQvSb2J8VAfHjRjZFLmiBdVymUH3wdHKe5q0=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 6612n, cells: 36n }, + timeDelta: 4662n, + existingExtensions: [ + EXT.E1, + EXT.E2, + EXT.E3, + EXT.E4, + EXT.E5, + EXT.E6, + EXT.E7, + EXT.E8, + EXT.E9 + ] + }, + + expected: { + gasUsed: 7690n, + gasFee: 3_076_000n, + actionFee: 0n, + storageFee: 1751n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_884_551n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/e169d1236176c803883fe6bd2e5b8e482b51fafa428655fb7a47faafccbccb2b + { + name: 'V5R1 - Remove Ext (FORK sibling)', + tag: 'remove-extension', + txHash: 'e169d1236176c803883fe6bd2e5b8e482b51fafa428655fb7a47faafccbccb2b', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgB7lShTiQGvDWcNypR0abkjZJcIz6oRLQndj9QuYtM4UgMAQDlc2lnbn///xFpQ69pAAAADEDgAwn98rwtMdTA2s8ZKvxso6TvVt1A6rLGwDYxmdDzUCkx+GSZ8tz1JN01ep+1qQ/KzjTqEiT3FmNUMJnOpxT/wcablE4fzDcJLezjzb5BrdZTBYtT0H3OOqLaJiIGzR54TIGfi5Y=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 6614n, cells: 36n }, + timeDelta: 511n, + existingExtensions: [EXT.E1, EXT.E2, EXT.E3, EXT.E4, EXT.E5, EXT.E6, EXT.E7, EXT.E8] + }, + + expected: { + gasUsed: 7765n, + gasFee: 3_106_000n, + actionFee: 0n, + storageFee: 192n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_912_992n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/4862b0d85ded1db98571493e2d0af72cec7fd5e86fcb4b29c2cbe8ee690caf47 + { + name: 'V5R1 - Remove Ext (2→1)', + tag: 'remove-extension', + txHash: '4862b0d85ded1db98571493e2d0af72cec7fd5e86fcb4b29c2cbe8ee690caf47', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgAmqIIQCtBfrwEXiMfHOD/mctmZ1zSwCiA4D5Eav4qrfYMAQDlc2lnbn///xFpQ8mYAAAAA0DgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM+ox14QmGm2ktksbaghTgG/OlpdAlQgbv1KoNQhwELXWy9RHsOIlCLmoVzoqKn7ElDbj0CMk4b2t9mGH0uqvgNDTc/mk=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5546n, cells: 25n }, + timeDelta: 14_042n, + existingExtensions: [EXT.FIRST, EXT.SECOND] + }, + + expected: { + gasUsed: 6565n, + gasFee: 2_626_000n, + actionFee: 0n, + storageFee: 3867n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_436_667n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + }, + + // https://tonviewer.com/transaction/9302d3bf88762bac10f62ea02e838eb6bd8f6c5330978143cc7edd24205d56e4 + { + name: 'V5R1 - Remove Ext (1→0)', + tag: 'remove-extension', + txHash: '9302d3bf88762bac10f62ea02e838eb6bd8f6c5330978143cc7edd24205d56e4', + + input: { + inMsgBoc: + 'te6cckEBAgEAmwABRYgAmqIIQCtBfrwEXiMfHOD/mctmZ1zSwCiA4D5Eav4qrfYMAQDlc2lnbn///xFpQ8/uAAAABEDgB0V7MCQGWrUarwNn5pFno6Epa5D2XlmV7MfmQpFbdd5TWBewP4woqHupijNFCZ3KWoGsCJ0hfRU2OiPO5sL27iJwfZBrjOT5CdY3zpWvkiPt9QklKXYWfpvJ1eUtusdARKRdmyQ=', + walletVersion: TonWalletVersion.V5R1, + storageUsed: { bits: 5280n, cells: 23n }, + timeDelta: 2880n, + existingExtensions: [EXT.FIRST] + }, + + expected: { + gasUsed: 5865n, + gasFee: 2_346_000n, + actionFee: 0n, + storageFee: 738n, + importFee: 806_800n, + fwdFeeRemaining: 0n, + walletFee: 3_153_538n + }, + + blockchainConfig: BLOCKCHAIN_CONFIG_2024_12 + } +]; diff --git a/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/tonapi-fetcher.ts b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/tonapi-fetcher.ts new file mode 100644 index 000000000..fd34335d8 --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/tonapi-fetcher.ts @@ -0,0 +1,205 @@ +/** + * Helper to fetch real transaction data from TON blockchain via tonapi. + * Used in tests when FETCH_REAL_FEES=1 environment variable is set. + */ + +import { ExpectedFees } from './utils'; + +const TONAPI_BASE_URL = 'https://tonapi.io/v2'; + +// tonapi transaction response types (partial) +interface TonApiComputePhase { + gas_fees?: number; + gas_used?: number; +} + +interface TonApiStoragePhase { + fees_collected?: number; +} + +interface TonApiActionPhase { + fwd_fees?: number; // total forward fees = actionFee + fwdFeeRemaining + total_fees?: number; // action fees only +} + +interface TonApiTransaction { + total_fees: number; + utime: number; + lt: number; + compute_phase?: TonApiComputePhase; + storage_phase?: TonApiStoragePhase; + action_phase?: TonApiActionPhase; +} + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +/** + * Fetch expected fees from tonapi by transaction hash. + * Includes retry logic for rate limiting (429). + */ +export async function fetchExpectedFees(txHash: string): Promise { + const url = `${TONAPI_BASE_URL}/blockchain/transactions/${txHash}`; + const maxRetries = 3; + const baseDelay = 1000; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + const response = await fetch(url); + + if (response.ok) { + const data = await response.json(); + return parseFees(data); + } + + if (response.status === 429 && attempt < maxRetries) { + const delay = baseDelay * Math.pow(2, attempt); + // eslint-disable-next-line no-console + console.log(`Rate limited, retrying in ${delay}ms...`); + await sleep(delay); + continue; + } + + throw new Error(`Failed to fetch transaction ${txHash}: ${response.status}`); + } + + throw new Error(`Failed to fetch transaction ${txHash} after ${maxRetries} retries`); +} + +function parseFees(data: TonApiTransaction): ExpectedFees { + const computePhase = data.compute_phase ?? {}; + const storagePhase = data.storage_phase ?? {}; + const actionPhase = data.action_phase ?? {}; + + const gasUsed = BigInt(computePhase.gas_used || 0); + const gasFee = BigInt(computePhase.gas_fees || 0); + const actionFee = BigInt(actionPhase.total_fees || 0); + const storageFee = BigInt(storagePhase.fees_collected || 0); + + // fwd_fees = actionFee + fwdFeeRemaining (full forward fee) + // If no action_phase (extension actions), fwdFeeRemaining = 0 + const totalFwdFees = BigInt(actionPhase.fwd_fees || 0); + const fwdFeeRemaining = totalFwdFees - actionFee; + + // Calculate importFee from total_fees + const totalFees = BigInt(data.total_fees || 0); + const importFee = totalFees - gasFee - actionFee - storageFee; + + // walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + + return { + gasUsed, + gasFee, + actionFee, + storageFee, + importFee, + fwdFeeRemaining, + walletFee + }; +} + +/** + * Check if we should fetch real data from blockchain. + * Set FETCH_REAL_FEES=1 to enable fetching. + */ +export function shouldFetchRealFees(): boolean { + return process.env.FETCH_REAL_FEES === '1'; +} + +export interface VerifyResult { + match: boolean; + actual: ExpectedFees; + diff: { + gasUsed: bigint; + gasFee: bigint; + actionFee: bigint; + storageFee: bigint; + importFee: bigint; + fwdFeeRemaining: bigint; + walletFee: bigint; + }; +} + +/** + * Verify that real transaction matches our predictions. + * Returns match status and detailed diff for debugging. + */ +export async function verifyTransactionFees( + txHash: string, + expected: ExpectedFees +): Promise { + const actual = await fetchExpectedFees(txHash); + + const diff = { + gasUsed: actual.gasUsed - expected.gasUsed, + gasFee: actual.gasFee - expected.gasFee, + actionFee: actual.actionFee - expected.actionFee, + storageFee: actual.storageFee - expected.storageFee, + importFee: actual.importFee - expected.importFee, + fwdFeeRemaining: actual.fwdFeeRemaining - expected.fwdFeeRemaining, + walletFee: actual.walletFee - expected.walletFee + }; + + const match = Object.values(diff).every(d => d === 0n); + + return { match, actual, diff }; +} + +/** + * Format verification result for console output. + */ +export function formatVerifyResult(result: VerifyResult): string { + const lines: string[] = []; + + if (result.match) { + lines.push('MATCH: All fees match predictions exactly!'); + } else { + lines.push('MISMATCH: Fees differ from predictions'); + } + + lines.push(''); + lines.push('Field | Predicted | Actual | Diff'); + lines.push('----------------|-----------|-----------|----------'); + + const format = (name: string, pred: bigint, act: bigint, diff: bigint): string => { + const diffStr = diff === 0n ? '0' : diff > 0n ? `+${diff}` : `${diff}`; + return `${name.padEnd(15)} | ${String(pred).padStart(9)} | ${String(act).padStart( + 9 + )} | ${diffStr}`; + }; + + // Reconstruct predicted from actual - diff + const predicted = { + gasUsed: result.actual.gasUsed - result.diff.gasUsed, + gasFee: result.actual.gasFee - result.diff.gasFee, + actionFee: result.actual.actionFee - result.diff.actionFee, + storageFee: result.actual.storageFee - result.diff.storageFee, + importFee: result.actual.importFee - result.diff.importFee, + fwdFeeRemaining: result.actual.fwdFeeRemaining - result.diff.fwdFeeRemaining, + walletFee: result.actual.walletFee - result.diff.walletFee + }; + + lines.push(format('gasUsed', predicted.gasUsed, result.actual.gasUsed, result.diff.gasUsed)); + lines.push(format('gasFee', predicted.gasFee, result.actual.gasFee, result.diff.gasFee)); + lines.push( + format('actionFee', predicted.actionFee, result.actual.actionFee, result.diff.actionFee) + ); + lines.push( + format('storageFee', predicted.storageFee, result.actual.storageFee, result.diff.storageFee) + ); + lines.push( + format('importFee', predicted.importFee, result.actual.importFee, result.diff.importFee) + ); + lines.push( + format( + 'fwdFeeRemaining', + predicted.fwdFeeRemaining, + result.actual.fwdFeeRemaining, + result.diff.fwdFeeRemaining + ) + ); + lines.push( + format('walletFee', predicted.walletFee, result.actual.walletFee, result.diff.walletFee) + ); + + return lines.join('\n'); +} diff --git a/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/utils.ts b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/utils.ts new file mode 100644 index 000000000..e5984a635 --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/__tests__/fixtures/utils.ts @@ -0,0 +1,221 @@ +import { + beginCell, + Cell, + Message, + Slice, + loadMessage, + storeMessageRelaxed, + OutActionSendMsg +} from '@ton/core'; +import { KeyPair, mnemonicToPrivateKey } from '@ton/crypto'; +import { loadOutListExtendedV5R1 } from '@ton/ton/dist/wallets/v5r1/WalletV5R1Actions'; +import { readFileSync, existsSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +import { assertUnreachable, TonWalletVersion } from '../../compat'; +import { CellStats, FeeConfig } from '../../fees'; + +export interface ExpectedFees { + gasUsed: bigint; + gasFee: bigint; + actionFee: bigint; + storageFee: bigint; + importFee: bigint; + fwdFeeRemaining: bigint; + walletFee: bigint; +} + +export type FixtureTag = + | 'simple-transfer' + | 'deploy-transfer' + | 'multi-transfer' + | 'send-all' + | 'jetton-transfer' + | 'jetton-deploy-transfer' + | 'dedup-within-msg' + | 'dedup-cross-msg' + | 'library-body' + | 'add-extension' + | 'remove-extension'; + +export type WalletFeeTestCase = { + name: string; + tag: FixtureTag; + txHash: string; + input: { + inMsgBoc: string; // base64 encoded BOC + walletVersion: TonWalletVersion; + storageUsed: CellStats; + timeDelta: bigint; + existingExtensions?: string[]; // only for extension tests + }; + expected: ExpectedFees; + blockchainConfig: FeeConfig; +}; + +export function normalizeHash(message: Message, normalizeExternal: boolean): Buffer { + if (!normalizeExternal || message.info.type !== 'external-in') { + return message.body.hash(); + } + + const cell = beginCell() + .storeUint(2, 2) // external-in + .storeUint(0, 2) // addr_none + .storeAddress(message.info.dest) + .storeUint(0, 4) // import_fee = 0 + .storeBit(false) // no StateInit + .storeBit(true) // body as reference + .storeRef(message.body) + .endCell(); + + return cell.hash(); +} + +/** + * Replace dummy signature in wallet message body. + * V3/V4: signature at start (first 512 bits) + * V5: signature at end (last 512 bits) + */ +export function replaceSignature( + dummyBody: Cell, + realSignature: Buffer, + version: TonWalletVersion +): Cell { + const slice = dummyBody.beginParse(); + const builder = beginCell(); + + if (version === TonWalletVersion.V5R1) { + // V5: signing message + signature (signature at end) + const totalBits = slice.remainingBits; + const signingBits = totalBits - 512; + builder.storeBits(slice.loadBits(signingBits)); + while (slice.remainingRefs > 0) { + builder.storeRef(slice.loadRef()); + } + builder.storeBuffer(realSignature); + } else if ( + version === TonWalletVersion.V4R2 || + version === TonWalletVersion.V3R2 || + version === TonWalletVersion.V3R1 + ) { + // V3/V4: signature + signing message (signature at start) + slice.skip(512); + builder.storeBuffer(realSignature); + builder.storeBits(slice.loadBits(slice.remainingBits)); + while (slice.remainingRefs > 0) { + builder.storeRef(slice.loadRef()); + } + } else { + assertUnreachable(version); + } + + return builder.endCell(); +} + +/** + * Parse V5R1 wallet body and extract out messages. + * Structure: opcode(32) | wallet_id(32) | timeout(32) | seqno(32) | out_list_extended | signature(512) + */ +function parseV5R1OutMsgs(bodySlice: Slice): Cell[] { + const opcode = bodySlice.loadUint(32); + + // 0x7369676e = "sign" (external signed) + // 0x73696e74 = "sint" (internal signed) + if (opcode !== 0x7369676e && opcode !== 0x73696e74) { + return []; + } + + bodySlice.loadUint(32); // wallet_id + bodySlice.loadUint(32); // timeout (valid_until) + bodySlice.loadUint(32); // seqno + + // Use @ton/ton to parse out_list_extended + const actions = loadOutListExtendedV5R1(bodySlice) as (OutActionSendMsg | { type: string })[]; + + // Filter sendMsg actions and serialize back to Cell + return actions + .filter((a): a is OutActionSendMsg => a.type === 'sendMsg') + .map(a => beginCell().store(storeMessageRelaxed(a.outMsg)).endCell()); +} + +/** + * Parse V3/V4 wallet body and extract out messages. + * V3: signature(512) | wallet_id(32) | timeout(32) | seqno(32) | [mode(8) | ^message]+ + * V4: signature(512) | wallet_id(32) | timeout(32) | seqno(32) | op(32)? | [mode(8) | ^message]+ + */ +function parseV3V4OutMsgs(bodySlice: Slice): Cell[] { + bodySlice.skip(512); // signature + bodySlice.loadUint(32); // wallet_id + bodySlice.loadUint(32); // timeout (valid_until) + bodySlice.loadUint(32); // seqno + + // Messages stored inline: [mode(8) | ^message]+ + const outMsgs: Cell[] = []; + while (bodySlice.remainingRefs > 0 && bodySlice.remainingBits >= 8) { + bodySlice.loadUint(8); // send_mode + outMsgs.push(bodySlice.loadRef()); + } + return outMsgs; +} + +/** + * Parse wallet external message and extract out messages as Cell[]. + * Uses @ton/core and @ton/ton for proper TL-B parsing. + */ +export function parseWalletOutMsgCells(inMsg: Cell, version: TonWalletVersion): Cell[] { + const message = loadMessage(inMsg.beginParse()); + const bodySlice = message.body.beginParse(); + + if (version === TonWalletVersion.V5R1) { + return parseV5R1OutMsgs(bodySlice); + } + + if ( + version === TonWalletVersion.V4R2 || + version === TonWalletVersion.V3R2 || + version === TonWalletVersion.V3R1 + ) { + return parseV3V4OutMsgs(bodySlice); + } + + assertUnreachable(version); +} + +export async function loadMnemonicKeyPair(): Promise { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const envPath = join(__dirname, '..', '.env'); + + if (!existsSync(envPath)) { + throw new Error('.env file not found. Create it with TON_MNEMONIC.'); + } + + const content = readFileSync(envPath, 'utf-8'); + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const match = trimmed.match(/^TON_MNEMONIC=(.*)$/); + if (match) { + let value = match[1].trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + const mnemonic = value.split(' '); + + if (mnemonic.length !== 24) { + throw new Error('TON_MNEMONIC must be 24 words'); + } + + const keyPair = await mnemonicToPrivateKey(mnemonic); + + return keyPair; + } + } + + throw new Error('TON_MNEMONIC not found in .env'); +} diff --git a/packages/core/src/service/ton-blockchain/fee/compat.ts b/packages/core/src/service/ton-blockchain/fee/compat.ts new file mode 100644 index 000000000..8943f457b --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/compat.ts @@ -0,0 +1,20 @@ +/** + * Compatibility layer for multiplatform fee module. + * + * The fee algorithm was ported from tonkeeper-multiplatform where it uses + * TonWalletVersion (string enum) and assertUnreachable from different paths. + * This file re-exports them so the algorithm code stays untouched. + */ + +export { assertUnreachable } from '../../../utils/types'; + +/** + * The web repo uses WalletVersion (numeric enum) from entries/wallet.ts, + * but this module uses the string enum for algorithm compatibility. + */ +export enum TonWalletVersion { + V3R1 = 'V3R1', + V3R2 = 'V3R2', + V4R2 = 'V4R2', + V5R1 = 'V5R1' +} diff --git a/packages/core/src/service/ton-blockchain/fee/fees.ts b/packages/core/src/service/ton-blockchain/fee/fees.ts new file mode 100644 index 000000000..bfedb2b60 --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/fees.ts @@ -0,0 +1,929 @@ +/** + * TON wallet fee estimation. + * + * Sections: + * 1. Types & Constants + * 2. estimateWalletFee (main entry point) + * 3. Basic fee formulas (gas, storage, forward, action) + * 4. Wallet gas parameters + * 5. Cell stats utilities + * 6. OutMsg processing + * 7. V5R1 message parser + * 8. Patricia trie internals + * 9. V5R1 extension gas (add / remove) + * 10. V4R2 plugin stubs + */ + +import { Address, Cell } from '@ton/core'; + +import { TonWalletVersion, assertUnreachable } from './compat'; + +// ============================================================================ +// Types +// ============================================================================ + +export type WorkchainId = -1 | 0; +export type CellStats = { bits: bigint; cells: bigint }; + +/** + * Storage stats for an uninitialized (uninit) TON account — before wallet deployment. + * Pass this as `storageUsed` when estimating fees for deploy transactions (seqno=0). + * + * An uninit account has no code/data/library, only the AccountStorage header: + * 103 bits = last_trans_lt(64) + Grams(4+32) + ExtraCurrency(1) + account_uninit(2), + * fitting in a single cell. + */ +export const UNINIT_ACCOUNT_STORAGE: CellStats = { bits: 103n, cells: 1n }; + +/** + * Complete wallet fee estimation with all components. + */ +export interface WalletFeeEstimation { + /** Gas fee for TVM execution */ + gasFee: bigint; + /** Action fee for sending outMsgs (≈1/3 of fwdFee, stays with sender) */ + actionFee: bigint; + /** Import fee for external message */ + importFee: bigint; + /** Storage fee for account state */ + storageFee: bigint; + /** + * Forward fee remaining (≈2/3 of fwdFee, deducted from outMsg value). + * For extension/plugin actions this is 0. + */ + fwdFeeRemaining: bigint; + + /** walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining */ + walletFee: bigint; +} + +/** + * block.tlb: MsgForwardPrices#ea — message forwarding fee parameters. + * ConfigParam 24 (masterchain) / 25 (basechain). + * @see https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb + * @see https://docs.ton.org/v3/documentation/network/configs/blockchain-configs#param-24 + */ +export interface MsgForwardPrices { + /** lump_price:uint64 — base forwarding fee */ + lumpPrice: bigint; + /** bit_price:uint64 — per-bit forwarding fee */ + bitPrice: bigint; + /** cell_price:uint64 — per-cell forwarding fee */ + cellPrice: bigint; + /** first_frac:uint16 — fraction kept as action fee (out of 2^16) */ + firstFrac: bigint; +} + +/** + * Fee parameters for a single workchain. + * @see https://docs.ton.org/v3/documentation/network/configs/blockchain-configs + */ +export interface WorkchainConfig { + /** GasLimitsPrices.gas_price — nanotons per 2^16 gas units. + * ConfigParam 20 (masterchain) / 21 (basechain) */ + gasPrice: bigint; + /** StoragePrices.bit_price_ps — nanotons per bit per 2^16 seconds. + * ConfigParam 18: bit_price_ps (basechain) / mc_bit_price_ps (masterchain) */ + storageBitPrice: bigint; + /** StoragePrices.cell_price_ps — nanotons per cell per 2^16 seconds. + * ConfigParam 18: cell_price_ps (basechain) / mc_cell_price_ps (masterchain) */ + storageCellPrice: bigint; + /** MsgForwardPrices for this workchain */ + fwd: MsgForwardPrices; +} + +/** Complete fee configuration for both workchains. */ +export interface FeeConfig { + basechain: WorkchainConfig; + masterchain: WorkchainConfig; +} + +export interface V5R1ExtensionAction { + type: 'addExtension' | 'removeExtension'; + address: Address; +} + +/** V4R2 plugin action (for future implementation) */ +export interface V4R2PluginAction { + type: 'installPlugin' | 'removePlugin'; + address: Address; +} + +interface EstimateWalletFeeBaseParams { + walletVersion: TonWalletVersion; + /** Workchain of the wallet. Defaults to 0 (basechain). */ + walletWorkchain?: WorkchainId; + inMsg: Cell; + timeDelta: bigint; + /** + * Account storage stats (bits & cells) at the moment of the transaction. + * - For deploy (seqno=0, account not yet active): use {@link UNINIT_ACCOUNT_STORAGE} + * - For active wallets: get from `account.storage_stat.used` (e.g. via liteserver or TonAPI) + */ + storageUsed: CellStats; +} + +/** Transfer estimation params */ +export interface EstimateTransferFeeParams extends EstimateWalletFeeBaseParams { + walletVersion: TonWalletVersion; + outMsgs: Cell[]; + existingExtensions?: never; + existingPlugins?: never; +} + +/** V5R1 Extension action estimation params */ +export interface EstimateExtensionFeeParams extends EstimateWalletFeeBaseParams { + walletVersion: TonWalletVersion.V5R1; + outMsgs?: never; + existingExtensions: string[]; + existingPlugins?: never; +} + +/** V4R2 Plugin action estimation params */ +export interface EstimatePluginFeeParams extends EstimateWalletFeeBaseParams { + walletVersion: TonWalletVersion.V4R2; + outMsgs?: never; + existingExtensions?: never; + existingPlugins: string[]; +} + +export type EstimateWalletFeeParams = + | EstimateTransferFeeParams + | EstimateExtensionFeeParams + | EstimatePluginFeeParams; + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/** + * Estimate wallet transaction fee. + * + * For transfers: pass outMsgs array + * For V5R1 extension actions: pass existingExtensions (hex hashes from get_extensions) + * For V4R2 plugin actions: pass existingPlugins (hex hashes from get_plugins) + * + * @returns WalletFeeEstimation with all fee components including fwdFeeRemaining + */ +export function estimateWalletFee( + config: FeeConfig, + params: EstimateWalletFeeParams +): WalletFeeEstimation { + const { walletVersion, inMsg, timeDelta, storageUsed } = params; + const walletWorkchain: WorkchainId = params.walletWorkchain ?? 0; + + // Source workchain config (gas, storage, import fee) + const workchainConfig = walletWorkchain === -1 ? config.masterchain : config.basechain; + + // Common fees + const { bits: msgBits, cells: msgCells } = sumRefsStats(inMsg); + const importFee = computeImportFee(workchainConfig.fwd, msgBits, msgCells); + const storageFee = computeStorageFee(workchainConfig, storageUsed, timeDelta); + + // === Transfer mode === + if ('outMsgs' in params && params.outMsgs) { + const gasUsed = computeWalletGasUsed(walletVersion, BigInt(params.outMsgs.length)); + const gasFee = computeGasFee(workchainConfig, gasUsed); + + // Action fee checks destination workchain for each outMsg + const actionFee = computeActionFeeForOutMsgs(config, params.outMsgs); + + // fwdFeeRemaining = sum(fwdFee - actionFee) for all outMsgs + const fwdFeeRemaining = computeFwdFeeRemaining(config, params.outMsgs); + + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + + return { gasFee, actionFee, importFee, storageFee, fwdFeeRemaining, walletFee }; + } + + // === V5R1 Extension mode === + if ('existingExtensions' in params && params.existingExtensions) { + const extensionAction = parseV5R1ExtensionAction(inMsg); + if (!extensionAction) { + throw new Error('Failed to parse extension action from inMsg'); + } + + if (extensionAction.type === 'addExtension') { + const newExtensionHash = extensionAction.address.hash.toString('hex'); + const gasUsed = computeAddExtensionGasFromExtensions( + params.existingExtensions, + newExtensionHash + ); + const gasFee = computeGasFee(workchainConfig, gasUsed); + const actionFee = 0n; + const fwdFeeRemaining = 0n; + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + return { gasFee, actionFee, importFee, storageFee, fwdFeeRemaining, walletFee }; + } + + if (extensionAction.type === 'removeExtension') { + const removeExtensionHash = extensionAction.address.hash.toString('hex'); + const gasUsed = computeRemoveExtensionGasFromExtensions( + params.existingExtensions, + removeExtensionHash + ); + const gasFee = computeGasFee(workchainConfig, gasUsed); + const actionFee = 0n; + const fwdFeeRemaining = 0n; + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + return { gasFee, actionFee, importFee, storageFee, fwdFeeRemaining, walletFee }; + } + } + + // === V4R2 Plugin mode === + if ('existingPlugins' in params && params.existingPlugins) { + if (walletVersion !== TonWalletVersion.V4R2) { + throw new Error('Plugins are only supported for V4R2 wallets'); + } + + const pluginAction = parseV4R2PluginAction(inMsg); + if (!pluginAction) { + throw new Error('Failed to parse plugin action from inMsg'); + } + + if (pluginAction.type === 'installPlugin') { + const newPluginHash = pluginAction.address.hash.toString('hex'); + const gasUsed = computeInstallPluginGasFromPlugins( + params.existingPlugins, + newPluginHash + ); + const gasFee = computeGasFee(workchainConfig, gasUsed); + const actionFee = 0n; + const fwdFeeRemaining = 0n; + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + return { gasFee, actionFee, importFee, storageFee, fwdFeeRemaining, walletFee }; + } + + if (pluginAction.type === 'removePlugin') { + const removePluginHash = pluginAction.address.hash.toString('hex'); + const gasUsed = computeRemovePluginGasFromPlugins( + params.existingPlugins, + removePluginHash + ); + const gasFee = computeGasFee(workchainConfig, gasUsed); + const actionFee = 0n; + const fwdFeeRemaining = 0n; + const walletFee = gasFee + actionFee + importFee + storageFee + fwdFeeRemaining; + return { gasFee, actionFee, importFee, storageFee, fwdFeeRemaining, walletFee }; + } + } + + throw new Error('Invalid params: provide outMsgs, existingExtensions, or existingPlugins'); +} + +// ============================================================================ +// Internal Constants +// ============================================================================ + +const ZERO_STATS: CellStats = { bits: 0n, cells: 0n }; + +/** ceil(x / 2^16) */ +const shr16ceil = (x: bigint): bigint => (x + 0xffffn) >> 16n; + +/** Gas cost for first cell read (transforming Cell → Slice) */ +const TVM_CELL_READ_GAS = 100n; +/** Gas cost for cell write (Builder → Cell) */ +const TVM_CELL_WRITE_GAS = 500n; +/** Gas cost for cell reload (re-reading already loaded cell) */ +const TVM_CELL_RELOAD_GAS = 25n; + +/** + * V5R1 extension action overheads — empirically derived from emulation. + * + * EXTENSION_OVERHEAD / REMOVE_EXTENSION_OVERHEAD: + * Gas for parsing action tag, checking dict existence, etc. + * ADD is higher because it also creates fork + leaf cells. + * + * REMOVE_EDGE_MERGE_GAS: + * +75 gas (3 × cell_reload = 3 × 25) when edge merging occurs. + * In TVM cell_builder_add_slice_bool, when sibling is FORK: + * - size_refs() = 2 → prefetch_ref called twice + * Applies to two cases: + * - siblingIsFork: load FORK sibling + prefetch its 2 child refs + * - rootCollapse (2→1): merge remaining leaf with former root + * + * Verified against blockchain: + * - DELETE from 9 ext (sibling = LEAF): gas = 7690 (no merge) + * - DELETE from 8 ext (sibling = FORK): gas = 7765 (+75) + * - DELETE from 2 ext (root collapse): gas = 6565 (+75) + */ +const V5R1_EXTENSION_OVERHEAD = 1388n; +const V5R1_REMOVE_EXTENSION_OVERHEAD = 1068n; +const V5R1_REMOVE_EDGE_MERGE_GAS = TVM_CELL_RELOAD_GAS * 3n; + +/** V5R1 action tags */ +const V5R1_ACTION_ADD_EXTENSION = 0x02; +const V5R1_ACTION_REMOVE_EXTENSION = 0x03; + +// ============================================================================ +// Basic Fee Calculation Functions +// ============================================================================ + +/** fwdFee = lumpPrice + ceil((bitPrice * bits + cellPrice * cells) / 2^16) */ +export function computeForwardFee(fwd: MsgForwardPrices, bits: bigint, cells: bigint): bigint { + return fwd.lumpPrice + shr16ceil(fwd.bitPrice * bits + fwd.cellPrice * cells); +} + +/** Import fee uses same formula as forward fee */ +export const computeImportFee = computeForwardFee; + +/** actionFee = floor(fwdFee * firstFrac / 2^16) — stays with sender, included in total_fees */ +export function computeActionFee(fwd: MsgForwardPrices, fwdFee: bigint): bigint { + return (fwdFee * fwd.firstFrac) >> 16n; +} + +/** gasFee = floor(gasUsed * gasPrice / 2^16) */ +export function computeGasFee(config: WorkchainConfig, gasUsed: bigint): bigint { + return (gasUsed * config.gasPrice) >> 16n; +} + +/** storageFee = ceil((bits * bitPrice + cells * cellPrice) * timeDelta / 2^16) */ +export function computeStorageFee( + config: WorkchainConfig, + storageUsed: CellStats, + timeDelta: bigint +): bigint { + if (timeDelta <= 0n) return 0n; + const used = + storageUsed.bits * config.storageBitPrice + storageUsed.cells * config.storageCellPrice; + return shr16ceil(used * timeDelta); +} + +// ============================================================================ +// Wallet Gas Calculation +// ============================================================================ + +export function walletGasParams(version: TonWalletVersion): { + baseGas: bigint; + gasPerMsg: bigint; +} { + switch (version) { + case TonWalletVersion.V5R1: + return { baseGas: 4222n, gasPerMsg: 717n }; + case TonWalletVersion.V4R2: + return { baseGas: 2666n, gasPerMsg: 642n }; + case TonWalletVersion.V3R2: + return { baseGas: 2352n, gasPerMsg: 642n }; + case TonWalletVersion.V3R1: + return { baseGas: 2275n, gasPerMsg: 642n }; + default: + return assertUnreachable(version); + } +} + +export function computeWalletGasUsed(version: TonWalletVersion, outMsgsCount: bigint): bigint { + const { baseGas, gasPerMsg } = walletGasParams(version); + return baseGas + gasPerMsg * outMsgsCount; +} + +// ============================================================================ +// Cell Stats Utilities +// ============================================================================ + +function sumStats(a: CellStats, b: CellStats): CellStats { + return { bits: a.bits + b.bits, cells: a.cells + b.cells }; +} + +/** Count unique cells (TON deduplicates by hash) */ +export function countUniqueCellStats(cell: Cell, visited = new Set()): CellStats { + const hash = cell.hash().toString('hex'); + if (visited.has(hash)) return ZERO_STATS; + visited.add(hash); + + return cell.refs + .map(ref => countUniqueCellStats(ref, visited)) + .reduce(sumStats, { bits: BigInt(cell.bits.length), cells: 1n }); +} + +/** Sum stats of refs only (excludes root cell) */ +export function sumRefsStats(cell: Cell): CellStats { + const visited = new Set(); + return cell.refs.map(ref => countUniqueCellStats(ref, visited)).reduce(sumStats, ZERO_STATS); +} + +// ============================================================================ +// OutMsg Workchain Parser +// ============================================================================ + +/** + * Parse destination workchain from outMsg. + * Internal message: int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + * src:MsgAddressInt dest:MsgAddressInt ... + * + * Note: src can be addr_none (00) in pre-send messages (TVM fills it on send_raw_message) + */ +function parseOutMsgDestWorkchain(outMsg: Cell): WorkchainId { + const slice = outMsg.beginParse(); + const prefix = slice.loadUint(1); + if (prefix !== 0) { + // External message (ext_out_msg_info$11) - use basechain + return 0; + } + // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice.loadBits(3); // ihr_disabled, bounce, bounced + slice.loadMaybeAddress(); // src (can be addr_none before send) + const dest = slice.loadAddress(); // dest (must be real address) + if (dest.workChain !== -1 && dest.workChain !== 0) { + throw new Error('Invalid destination workchain'); + } + + return dest.workChain; +} + +/** + * Compute action fee for outMsgs, checking destination workchain for each. + * Forward prices differ for masterchain (-1) vs basechain (0). + */ +function computeActionFeeForOutMsgs(config: FeeConfig, outMsgs: Cell[]): bigint { + return outMsgs.reduce((acc, msg) => { + const destWorkchain = parseOutMsgDestWorkchain(msg); + + if (destWorkchain === -1) { + console.warn( + 'Masterchain destination: fee calculation covers this path but lacks blockchain-verified tests. ' + + 'Please add masterchain transaction fixtures to fees.spec.ts' + ); + } + + const fwd = destWorkchain === -1 ? config.masterchain.fwd : config.basechain.fwd; + const { bits, cells } = sumRefsStats(msg); + const fwdFee = computeForwardFee(fwd, bits, cells); + return acc + computeActionFee(fwd, fwdFee); + }, 0n); +} + +/** + * Compute forward fee remaining for outMsgs. + * fwdFeeRemaining = fwdFee - actionFee ≈ 2/3 of forward fee. + * This amount is deducted from the outMsg value during delivery. + */ +export function computeFwdFeeRemaining(config: FeeConfig, outMsgs: Cell[]): bigint { + return outMsgs.reduce((acc, msg) => { + const destWorkchain = parseOutMsgDestWorkchain(msg); + + if (destWorkchain === -1) { + console.warn( + 'Masterchain destination: fee calculation covers this path but lacks blockchain-verified tests. ' + + 'Please add masterchain transaction fixtures to fees.spec.ts' + ); + } + + const fwd = destWorkchain === -1 ? config.masterchain.fwd : config.basechain.fwd; + const { bits, cells } = sumRefsStats(msg); + const fwdFee = computeForwardFee(fwd, bits, cells); + const actionFee = computeActionFee(fwd, fwdFee); + return acc + (fwdFee - actionFee); + }, 0n); +} + +// ============================================================================ +// V5R1 Message Parser +// ============================================================================ + +/** + * Parse V5R1 extension action from external message. + * Returns action type and address, or null if not an extension action. + */ +export function parseV5R1ExtensionAction(inMsg: Cell): V5R1ExtensionAction | null { + try { + const msgSlice = inMsg.beginParse(); + + // External-in message structure (TL-B): + // ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt import_fee:Grams + // init:(Maybe (Either StateInit ^StateInit)) + // body:(Either X ^X) + msgSlice.loadUint(2); // 10 = external-in + msgSlice.loadUint(2); // src: addr_none$00 + msgSlice.loadAddress(); // dest: wallet address + msgSlice.loadCoins(); // import_fee (usually 0) + + // Skip StateInit if present: Maybe (Either StateInit ^StateInit) + if (msgSlice.loadBit()) { + // Either: 0 = inline StateInit, 1 = ref + if (msgSlice.loadBit()) { + msgSlice.loadRef(); // ^StateInit + } else { + // Inline StateInit - skip all fields + // fixed_prefix_length: Maybe (## 5) + if (msgSlice.loadBit()) msgSlice.loadUint(5); + // special: Maybe TickTock (tick:Bool tock:Bool) + if (msgSlice.loadBit()) msgSlice.loadBits(2); + msgSlice.loadMaybeRef(); // code: Maybe ^Cell + msgSlice.loadMaybeRef(); // data: Maybe ^Cell + msgSlice.loadMaybeRef(); // library: Maybe ^Cell + } + } + + // Body: Either X ^X (0 = inline, 1 = ref) + const bodySlice = msgSlice.loadBit() ? msgSlice.loadRef().beginParse() : msgSlice; + + // V5R1 external signed request body structure: + // opcode(32) + wallet_id(32) + valid_until(32) + seqno(32) + actions + signature(512) + // + // Actions structure (storeOutListExtendedV5R1): + // - MaybeRef: basic out_actions (sendMsg list), null if empty + // - 1 bit: has_extended_actions + // - if has_extended: tag(8) + payload INLINE, then refs for more + // - signature at the END (512 bits) + const opcode = bodySlice.loadUint(32); + if (opcode !== 0x7369676e) { + // Not an external signed request (0x7369676e = "sign") + return null; + } + + bodySlice.loadUint(32); // wallet_id + bodySlice.loadUint(32); // valid_until + bodySlice.loadUint(32); // seqno + + // MaybeRef: basic actions (sendMsg list) + if (bodySlice.loadBit()) { + bodySlice.loadRef(); // skip out_list + } + + // has_extended_actions bit + if (!bodySlice.loadBit()) { + return null; // no extended actions + } + + // Extended action is stored INLINE: tag(8 bits) + address + const actionTag = bodySlice.loadUint(8); + + if (actionTag === V5R1_ACTION_ADD_EXTENSION) { + return { type: 'addExtension', address: bodySlice.loadAddress() }; + } else if (actionTag === V5R1_ACTION_REMOVE_EXTENSION) { + return { type: 'removeExtension', address: bodySlice.loadAddress() }; + } + + return null; + } catch { + return null; + } +} + +// ============================================================================ +// Patricia Trie for Extension Gas Calculation +// ============================================================================ + +interface LeafNode { + type: 'leaf'; + key: string; +} + +interface ForkNode { + type: 'fork'; + labelLength: number; + left?: TrieNode; + right?: TrieNode; +} + +type TrieNode = LeafNode | ForkNode; + +function hexToBinary(hex: string): string { + return hex + .split('') + .map(c => parseInt(c, 16).toString(2).padStart(4, '0')) + .join(''); +} + +function commonPrefixLength(a: string, b: string): number { + let i = 0; + while (i < a.length && i < b.length && a[i] === b[i]) i++; + return i; +} + +function buildTrie(keys: string[]): TrieNode | null { + if (keys.length === 0) return null; + if (keys.length === 1) return { type: 'leaf', key: keys[0] }; + + let prefix = keys[0]; + for (let i = 1; i < keys.length; i++) { + prefix = prefix.slice(0, commonPrefixLength(prefix, keys[i])); + } + + const left = keys.filter(k => k[prefix.length] === '0'); + const right = keys.filter(k => k[prefix.length] === '1'); + + return { + type: 'fork', + labelLength: prefix.length, + left: buildTrie(left) ?? undefined, + right: buildTrie(right) ?? undefined + }; +} + +function getAllKeys(node: TrieNode | undefined): string[] { + if (!node) return []; + if (node.type === 'leaf') return [node.key]; + return [...getAllKeys(node.left), ...getAllKeys(node.right)]; +} + +/** + * Count cells traversed during dict_set from root to insertion point. + * + * Based on TVM dict_set implementation (crypto/vm/dict.cpp): + * - Each LabelParser creation = 1 cell load + * - Traverse until mismatch (pfx_len < label.l_bits) or reach leaf + * - Count EVERY cell visited, including pure forks (forks with no label bits) + * + * @see https://github.com/ton-blockchain/ton/blob/master/crypto/vm/dict.cpp + */ +function countCellLoads(node: TrieNode | undefined, key: string, pos = 0): number { + if (!node) return 0; + + // LabelParser label{std::move(dict), n}; -> 1 cell load + const loads = 1; + + if (node.type === 'leaf') { + // Leaf node - always loaded, even if key doesn't match + return loads; + } + + // Fork node - check if label matches + const nodeKeys = getAllKeys(node); + const labelBits = node.labelLength - pos; // bits in this node's label + const nodePrefix = nodeKeys[0].slice(pos, node.labelLength); + const keySlice = key.slice(pos, node.labelLength); + + // Check common prefix length + let pfxLen = 0; + while (pfxLen < labelBits && nodePrefix[pfxLen] === keySlice[pfxLen]) pfxLen++; + + if (pfxLen < labelBits) { + // Mismatch in label - stop here (cell already loaded) + return loads; + } + + // Label matches (or pure fork with labelBits=0) - continue to child + const nextBit = key[node.labelLength]; + const child = nextBit === '0' ? node.left : node.right; + + return loads + countCellLoads(child, key, node.labelLength + 1); +} + +/** + * Find sibling node after removing a key from trie. + * Returns { cellLoads, siblingIsFork } or null if key not found. + * + * The sibling is the other branch of the parent fork after deletion. + * When we delete a leaf, its parent fork merges with the sibling. + */ +function findDeleteInfo( + node: TrieNode | undefined, + key: string, + pos = 0 +): { cellLoads: number; siblingIsFork: boolean } | null { + if (!node) return null; + + // Count cell load for this node + const loads = 1; + + if (node.type === 'leaf') { + // Found the leaf to delete - but no sibling info here + // (sibling is determined by parent, which we track below) + return node.key === key ? { cellLoads: loads, siblingIsFork: false } : null; + } + + // Fork node - check if label matches + const nodeKeys = getAllKeys(node); + const labelBits = node.labelLength - pos; + const nodePrefix = nodeKeys[0].slice(pos, node.labelLength); + const keySlice = key.slice(pos, node.labelLength); + + // Check common prefix length + let pfxLen = 0; + while (pfxLen < labelBits && nodePrefix[pfxLen] === keySlice[pfxLen]) pfxLen++; + + if (pfxLen < labelBits) { + // Mismatch - key not in trie + return null; + } + + // Label matches - continue to child + const nextBit = key[node.labelLength]; + const child = nextBit === '0' ? node.left : node.right; + const sibling = nextBit === '0' ? node.right : node.left; + + const childResult = findDeleteInfo(child, key, node.labelLength + 1); + if (!childResult) return null; + + // If child is the leaf being deleted, determine sibling type + if (child?.type === 'leaf' && child.key === key) { + // Sibling will be merged with parent fork + const siblingIsFork = sibling?.type === 'fork'; + return { cellLoads: loads + childResult.cellLoads, siblingIsFork }; + } + + // Propagate from deeper in the tree + return { cellLoads: loads + childResult.cellLoads, siblingIsFork: childResult.siblingIsFork }; +} + +// ============================================================================ +// V5R1 Extension Gas Calculation: Add +// ============================================================================ + +/** + * Compute gas for V5R1 AddExtension action. + * + * Based on TVM dict_set implementation (crypto/vm/dict.cpp): + * - Each cell traversed during insertion costs 100 gas (cell load) + * - Each cell created costs 500 gas (cell create) + * - For new key insertion: cellCreates = cellLoads + 2 + * + * Formula: + * gas = baseGas + overhead + cellLoads×100 + (cellLoads+2)×500 + * = 5610 + 100×L + 500×L + 1000 + * = 6610 + 600×cellLoads + * + * @param cellLoads - number of cells traversed from root to insertion point + */ +export function computeAddExtensionGas(cellLoads: bigint): bigint { + const { baseGas } = walletGasParams(TonWalletVersion.V5R1); + // baseGas(4222) + overhead(1388) + cellLoads×100 + (cellLoads+2)×500 + // = 5610 + 100×L + 500×L + 1000 = 6610 + 600×L + return ( + baseGas + + V5R1_EXTENSION_OVERHEAD + + 1000n + + cellLoads * (TVM_CELL_READ_GAS + TVM_CELL_WRITE_GAS) + ); +} + +/** + * Compute gas for adding first extension (empty dict → 1 extension). + * Special case: no trie traversal, just create one cell. + * Returns 6110 gas units. + */ +export function computeAddFirstExtensionGas(): bigint { + const { baseGas } = walletGasParams(TonWalletVersion.V5R1); + return baseGas + V5R1_EXTENSION_OVERHEAD + TVM_CELL_WRITE_GAS; +} + +/** + * Compute gas for V5R1 AddExtension action from existing extensions. + * + * @param existingExtensionHashes - hex hashes of existing extensions (from get_extensions) + * @param newExtensionHash - hex hash of new extension address (256-bit, 64 chars) + * @returns gas used in gas units + * + * @example + * ```typescript + * const existingHashes = ['613fbe57...', 'ba6ede49...']; + * const newHash = newExtensionAddress.hash.toString('hex'); + * const gasUsed = computeAddExtensionGasFromExtensions(existingHashes, newHash); + * ``` + */ +export function computeAddExtensionGasFromExtensions( + existingExtensionHashes: string[], + newExtensionHash: string +): bigint { + if (existingExtensionHashes.length === 0) { + return computeAddFirstExtensionGas(); + } + + const binaryKeys = existingExtensionHashes.map(hexToBinary); + const trie = buildTrie(binaryKeys); + + const newBinaryKey = hexToBinary(newExtensionHash); + const cellLoads = countCellLoads(trie ?? undefined, newBinaryKey); + + return computeAddExtensionGas(BigInt(cellLoads)); +} + +// ============================================================================ +// V5R1 Extension Gas Calculation: Remove +// ============================================================================ + +/** + * Compute gas for V5R1 RemoveExtension action. + * + * Based on TVM dict_delete implementation and emulation verification: + * - Same traversal cost as ADD (cell loads): cellLoads × (READ + WRITE) + * - Fewer cell creations: DELETE doesn't create fork/leaf, may merge edges + * - When sibling is FORK (has children), extra +75 gas for ref handling + * + * Formula derived from TVM gas costs (see docs.ton.org/tvm/gas): + * gas = baseGas + removeOverhead + cellLoads × (READ + WRITE) + (needsMerge ? 75 : 0) + * = 4222 + 1068 + cellLoads × 600 + (needsMerge ? 75 : 0) + * = 5290 + 600 × cellLoads + (needsMerge ? 75 : 0) + * + * The +75 gas (3 × cell_reload_gas = 3 × 25) is charged when edge merging occurs: + * - siblingIsFork: sibling has 2 children, each needs prefetch_ref (reload) + * - rootCollapse (2→1): root fork eliminated, remaining leaf promoted to root + * + * Compare to ADD formula: 6610 + 600 × cellLoads (1320 more gas) + * The difference accounts for: + * - No fork cell creation (-500 gas) + * - No leaf cell creation (-500 gas) + * - Different overhead operations (-320 gas) + * + * @param cellLoads - number of cells traversed from root to deletion point + * @param needsMergeGas - true if edge merge required (siblingIsFork OR rootCollapse) + */ +export function computeRemoveExtensionGas(cellLoads: bigint, needsMergeGas = false): bigint { + const { baseGas } = walletGasParams(TonWalletVersion.V5R1); + // baseGas(4222) + removeOverhead(1068) + cellLoads×(100 + 500) + mergeExtra + // = 5290 + 600×cellLoads + (needsMergeGas ? 75 : 0) + return ( + baseGas + + V5R1_REMOVE_EXTENSION_OVERHEAD + + cellLoads * (TVM_CELL_READ_GAS + TVM_CELL_WRITE_GAS) + + (needsMergeGas ? V5R1_REMOVE_EDGE_MERGE_GAS : 0n) + ); +} + +/** + * Compute gas for removing last extension (1 extension → empty dict). + * + * When removing the last extension, the result dict is null (empty). + * This saves 25 gas because store_dict(null) doesn't require a cell_reload + * for the reference, unlike store_dict(Cell) which needs to register the ref. + * + * Formula: 5290 + 600×1 - 25 = 5865 gas units. + * + * Analysis from TVM dict_lookup_delete + W5 contract: + * - dict_lookup_delete returns (value, null) for last element → no cb.finalize() + * - W5 calls store_dict(null) → writes 0-bit only, no cell reference + * - No cell_reload needed for null reference vs non-null Cell + */ +export function computeRemoveLastExtensionGas(): bigint { + // cellLoads = 1 (read single leaf) + // -25 because store_dict(null) doesn't need cell_reload for reference + return computeRemoveExtensionGas(1n) - TVM_CELL_RELOAD_GAS; +} + +/** + * Compute gas for V5R1 RemoveExtension action from existing extensions. + * + * The +75 gas penalty (3 × cell_reload = 3 × 25) applies when edge merging is needed: + * 1. siblingIsFork = true: sibling has 2 children requiring extra reloads + * 2. rootCollapse (2→1): root fork collapses, remaining leaf promoted to root + * + * Both cases require the same merge overhead because they both involve + * restructuring the Patricia trie with 3 additional cell reload operations. + * + * @param existingExtensionHashes - hex hashes of existing extensions (INCLUDING the one being removed) + * @param removeExtensionHash - hex hash of extension address to remove (256-bit, 64 chars) + * @returns gas used in gas units + */ +export function computeRemoveExtensionGasFromExtensions( + existingExtensionHashes: string[], + removeExtensionHash: string +): bigint { + if (existingExtensionHashes.length <= 1) { + return computeRemoveLastExtensionGas(); + } + + const binaryKeys = existingExtensionHashes.map(hexToBinary); + const trie = buildTrie(binaryKeys); + + const removeBinaryKey = hexToBinary(removeExtensionHash); + const deleteInfo = findDeleteInfo(trie ?? undefined, removeBinaryKey); + + // Root collapse: when removing from 2-element dict, root fork is eliminated + // and remaining leaf is promoted to root. This merge costs +75 gas (3 cell reloads). + const isRootCollapse = existingExtensionHashes.length === 2; + + if (!deleteInfo) { + // Key not found - fallback to simple calculation + const cellLoads = countCellLoads(trie ?? undefined, removeBinaryKey); + return computeRemoveExtensionGas(BigInt(cellLoads), isRootCollapse); + } + + // needsMergeGas = siblingIsFork OR rootCollapse (both cause +75 gas) + const needsMergeGas = deleteInfo.siblingIsFork || isRootCollapse; + return computeRemoveExtensionGas(BigInt(deleteInfo.cellLoads), needsMergeGas); +} + +// ============================================================================ +// V4R2 Plugin Gas Calculation (Stubs) +// ============================================================================ + +/** + * Parse V4R2 plugin action from external message. + * TODO: implement parsing (opcodes: 0x2 = install, 0x3 = remove) + */ +export function parseV4R2PluginAction(_inMsg: Cell): V4R2PluginAction | null { + throw new Error('V4R2 plugin parsing not implemented'); +} + +/** + * Compute gas for installing a plugin. + * TODO: implement (similar to extensions with Patricia trie) + */ +export function computeInstallPluginGasFromPlugins( + _existingPlugins: string[], + _newPluginHash: string +): bigint { + throw new Error('V4R2 plugin install gas calculation not implemented'); +} + +/** + * Compute gas for removing a plugin. + * TODO: implement + */ +export function computeRemovePluginGasFromPlugins( + _existingPlugins: string[], + _removePluginHash: string +): bigint { + throw new Error('V4R2 plugin remove gas calculation not implemented'); +} diff --git a/packages/core/src/service/ton-blockchain/fee/index.ts b/packages/core/src/service/ton-blockchain/fee/index.ts new file mode 100644 index 000000000..1e1a39dc1 --- /dev/null +++ b/packages/core/src/service/ton-blockchain/fee/index.ts @@ -0,0 +1,18 @@ +export { UNINIT_ACCOUNT_STORAGE, estimateWalletFee } from './fees'; + +export type { + MsgForwardPrices, + WorkchainConfig, + FeeConfig, + WalletFeeEstimation, + CellStats, + WorkchainId, + EstimateTransferFeeParams, + EstimateExtensionFeeParams, + EstimatePluginFeeParams, + EstimateWalletFeeParams, + V5R1ExtensionAction, + V4R2PluginAction +} from './fees'; + +export { TonWalletVersion } from './compat'; diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 000000000..909493290 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node' + } +}); diff --git a/yarn.lock b/yarn.lock index c1aedaec9..3e4750f87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1818,6 +1818,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -1839,6 +1846,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm@npm:0.17.19" @@ -1860,6 +1874,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-x64@npm:0.17.19" @@ -1881,6 +1902,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-arm64@npm:0.17.19" @@ -1902,6 +1930,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-x64@npm:0.17.19" @@ -1923,6 +1958,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-arm64@npm:0.17.19" @@ -1944,6 +1986,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-x64@npm:0.17.19" @@ -1965,6 +2014,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm64@npm:0.17.19" @@ -1986,6 +2042,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm@npm:0.17.19" @@ -2007,6 +2070,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ia32@npm:0.17.19" @@ -2028,6 +2098,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-loong64@npm:0.17.19" @@ -2049,6 +2126,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-mips64el@npm:0.17.19" @@ -2070,6 +2154,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ppc64@npm:0.17.19" @@ -2091,6 +2182,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-riscv64@npm:0.17.19" @@ -2112,6 +2210,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-s390x@npm:0.17.19" @@ -2133,6 +2238,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-x64@npm:0.17.19" @@ -2154,6 +2266,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.3": version: 0.25.3 resolution: "@esbuild/netbsd-arm64@npm:0.25.3" @@ -2161,6 +2280,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/netbsd-x64@npm:0.17.19" @@ -2182,6 +2308,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.3": version: 0.25.3 resolution: "@esbuild/openbsd-arm64@npm:0.25.3" @@ -2189,6 +2322,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/openbsd-x64@npm:0.17.19" @@ -2210,6 +2350,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/sunos-x64@npm:0.17.19" @@ -2231,6 +2385,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-arm64@npm:0.17.19" @@ -2252,6 +2413,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-ia32@npm:0.17.19" @@ -2273,6 +2441,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-x64@npm:0.17.19" @@ -2294,6 +2469,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -2741,6 +2923,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -3459,6 +3648,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.57.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-android-arm-eabi@npm:4.9.5" @@ -3473,6 +3669,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm64@npm:4.57.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-android-arm64@npm:4.9.5" @@ -3487,6 +3690,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.57.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-darwin-arm64@npm:4.9.5" @@ -3501,6 +3711,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.57.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-darwin-x64@npm:4.9.5" @@ -3515,6 +3732,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.57.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-freebsd-x64@npm:4.40.1" @@ -3522,6 +3746,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.57.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.1" @@ -3529,6 +3760,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.9.5" @@ -3543,6 +3781,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.40.1" @@ -3550,6 +3795,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.9.5" @@ -3564,6 +3816,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.9.5" @@ -3571,6 +3830,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-loong64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.1" @@ -3585,6 +3858,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-ppc64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.40.1" @@ -3592,6 +3879,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.9.5" @@ -3606,6 +3900,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.40.1" @@ -3613,6 +3914,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.57.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.40.1" @@ -3620,6 +3928,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.9.5" @@ -3634,6 +3949,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-linux-x64-musl@npm:4.9.5" @@ -3641,6 +3963,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-openbsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.57.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.57.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.40.1" @@ -3648,6 +3984,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.9.5" @@ -3662,6 +4005,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.57.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.9.5" @@ -3669,6 +4019,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.40.1": version: 4.40.1 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.40.1" @@ -3676,6 +4033,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.9.5": version: 4.9.5 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.9.5" @@ -3742,6 +4106,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 + languageName: node + linkType: hard + "@stencil/core@npm:4.20.0": version: 4.20.0 resolution: "@stencil/core@npm:4.20.0" @@ -4041,6 +4412,7 @@ __metadata: tweetnacl: "npm:^1.0.3" typescript: "npm:^4.9.4" uuid: "npm:^11.1.0" + vitest: "npm:^4.0.18" zod: "npm:^3.25.36" languageName: unknown linkType: soft @@ -4515,6 +4887,16 @@ __metadata: languageName: node linkType: hard +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10/e79947307dc235953622e65f83d2683835212357ca261389116ab90bed369ac862ba28b146b4fed08b503ae1e1a12cb93ce783f24bb8d562950469f4320e1c7c + languageName: node + linkType: hard + "@types/connect-history-api-fallback@npm:^1.3.5": version: 1.5.4 resolution: "@types/connect-history-api-fallback@npm:1.5.4" @@ -4612,6 +4994,13 @@ __metadata: languageName: node linkType: hard +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10/249a27b0bb22f6aa28461db56afa21ec044fa0e303221a62dff81831b20c8530502175f1a49060f7099e7be06181078548ac47c668de79ff9880241968d43d0c + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.3, @types/eslint-scope@npm:^3.7.7": version: 3.7.7 resolution: "@types/eslint-scope@npm:3.7.7" @@ -4646,6 +5035,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 + languageName: node + linkType: hard + "@types/eventsource@npm:^1.1.15": version: 1.1.15 resolution: "@types/eventsource@npm:1.1.15" @@ -5376,6 +5772,86 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/expect@npm:4.0.18" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + chai: "npm:^6.2.1" + tinyrainbow: "npm:^3.0.3" + checksum: 10/2115bff1bbcad460ce72032022e4dbcf8572c4b0fe07ca60f5644a8d96dd0dfa112986b5a1a5c5705f4548119b3b829c45d1de0838879211e0d6bb276b4ece73 + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/mocker@npm:4.0.18" + dependencies: + "@vitest/spy": "npm:4.0.18" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10/46f584a4c1180dfb513137bc8db6e2e3b53e141adfe964307297e98321652d86a3f2a52d80cda1f810205bd5fdcab789bb8b52a532e68f175ef1e20be398218d + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/pretty-format@npm:4.0.18" + dependencies: + tinyrainbow: "npm:^3.0.3" + checksum: 10/4cafc7c9853097345bd94e8761bf47c2c04e00d366ac56d79928182787ff83c512c96f1dc2ce9b6aeed4d3a8c23ce12254da203783108d3c096bc398eed2a62d + languageName: node + linkType: hard + +"@vitest/runner@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/runner@npm:4.0.18" + dependencies: + "@vitest/utils": "npm:4.0.18" + pathe: "npm:^2.0.3" + checksum: 10/d7deebf086d7e084f449733ecea6c9c81737a18aafece318cbe7500e45debea00fa9dbf9315fd38aa88550dd5240a791b885ac71665f89b154d71a6c63da5836 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/snapshot@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10/50aa5fb7fca45c499c145cc2f20e53b8afb0990b53ff4a4e6447dd6f147437edc5316f22e2d82119e154c3cf7c59d44898e7b2faf7ba614ac1051cbe4d662a77 + languageName: node + linkType: hard + +"@vitest/spy@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/spy@npm:4.0.18" + checksum: 10/f7b1618ae13790105771dd2a8c973c63c018366fcc69b50f15ce5d12f9ac552efd3c1e6e5ae4ebdb6023d0b8d8f31fef2a0b1b77334284928db45c80c63de456 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/utils@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + tinyrainbow: "npm:^3.0.3" + checksum: 10/e8b2ad7bc35b2bc5590f9dc1d1a67644755da416b47ab7099a6f26792903fa0aacb81e6ba99f0f03858d9d3a1d76eeba65150a1a0849690a40817424e749c367 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/ast@npm:1.11.6" @@ -7127,6 +7603,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10/a0789dd882211b87116e81e2648ccb7f60340b34f19877dd020b39ebb4714e475eb943e14ba3e22201c221ef6645b7bfe10297e76b6ac95b48a9898c1211ce66 + languageName: node + linkType: hard + "astral-regex@npm:^2.0.0": version: 2.0.0 resolution: "astral-regex@npm:2.0.0" @@ -7878,6 +8361,13 @@ __metadata: languageName: node linkType: hard +"chai@npm:^6.2.1": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10/13cda42cc40aa46da04a41cf7e5c61df6b6ae0b4e8a8c8b40e04d6947e4d7951377ea8c14f9fa7fe5aaa9e8bd9ba414f11288dc958d4cee6f5221b9436f2778f + languageName: node + linkType: hard + "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -9620,6 +10110,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.2 resolution: "es-set-tostringtag@npm:2.0.2" @@ -9901,6 +10398,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/aa74b8d8a3ed8e2eea4d8421737b322f4d21215244e8fa2156c6402d49b5bda01343c220196f1e3f830a7ce92b54ef653c6c723a8cc2e912bb4d17b7398b51ae + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -10267,6 +10853,15 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10/a65728d5727b71de172c5df323385755a16c0fdab8234dc756c3854cfee343261ddfbb72a809a5660fac8c75d960bb3e21aa898c2d7e9b19bb298482ca58a3af + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -10425,6 +11020,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.2.2": + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10/a5fada3d0c621649261f886e7d93e6bf80ce26d8a86e5d517e38301b8baec8450ab2cb94ba6e7a0a6bf2fc9ee55f54e1b06938ef1efa52ddcfeffbfa01acbbcc + languageName: node + linkType: hard + "expect@npm:^29.0.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -10606,6 +11208,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -13151,6 +13765,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10/57d5691f41ed40d962d8bd300148114f53db67fadbff336207db10a99f2bdf4a1be9cac3a68ee85dba575912ee1d4402e4396408196ec2d3afd043b076156221 + languageName: node + linkType: hard + "magic-string@npm:^0.30.3": version: 0.30.11 resolution: "magic-string@npm:0.30.11" @@ -13684,21 +14307,21 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.3, nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.8": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10/ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679 + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 languageName: node linkType: hard -"nanoid@npm:^3.3.8": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" +"nanoid@npm:^3.3.3, nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" bin: nanoid: bin/nanoid.cjs - checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 + checksum: 10/ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679 languageName: node linkType: hard @@ -14136,6 +14759,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10/bdcf9213361786688019345f3452b95a1dc73710e4b403c82a1994b98bad6abc31b26cb72a482128c5fd53ea9daf6fbb7d0e0e7b2b7e9c8be6d779deeccee07f + languageName: node + linkType: hard + "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -14581,6 +15211,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d + languageName: node + linkType: hard + "pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.1.2": version: 3.1.2 resolution: "pbkdf2@npm:3.1.2" @@ -14643,6 +15280,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 + languageName: node + linkType: hard + "pify@npm:^2.0.0": version: 2.3.0 resolution: "pify@npm:2.3.0" @@ -14806,6 +15450,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 + languageName: node + linkType: hard + "postject@npm:^1.0.0-alpha.6": version: 1.0.0-alpha.6 resolution: "postject@npm:1.0.0-alpha.6" @@ -16062,6 +16717,96 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.43.0": + version: 4.57.1 + resolution: "rollup@npm:4.57.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.57.1" + "@rollup/rollup-android-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-x64": "npm:4.57.1" + "@rollup/rollup-freebsd-arm64": "npm:4.57.1" + "@rollup/rollup-freebsd-x64": "npm:4.57.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.57.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.57.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-musl": "npm:4.57.1" + "@rollup/rollup-openbsd-x64": "npm:4.57.1" + "@rollup/rollup-openharmony-arm64": "npm:4.57.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.57.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.57.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.57.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.57.1" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10/0451371339e593967c979e498fac4dfd0ba15fadf0dac96875940796307a00d62ab68460366a65f4872ae8edd9339e3d9501e8e5764c1f23e25e0951f75047c6 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -16460,6 +17205,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10/e93ff66c6531a079af8fb217240df01f980155b5dc408d2d7bebc398dd284e383eb318153bf8acd4db3c4fe799aa5b9a641e38b0ba3b1975700b1c89547ea4e7 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -16754,6 +17506,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10/2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + "stacktracey@npm:^2.1.8": version: 2.1.8 resolution: "stacktracey@npm:2.1.8" @@ -16785,6 +17544,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.10.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10/19c9cda4f370b1ffae2b8b08c72167d8c3e5cfa972aaf5c6873f85d0ed2faa729407f5abb194dc33380708c00315002febb6f1e1b484736bfcf9361ad366013a + languageName: node + linkType: hard + "stop-iteration-iterator@npm:^1.0.0": version: 1.0.0 resolution: "stop-iteration-iterator@npm:1.0.0" @@ -17303,6 +18069,20 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10/cfa1e1418e91289219501703c4693c70708c91ffb7f040fd318d24aef419fb5a43e0c0160df9471499191968b2451d8da7f8087b08c3133c251c40d24aced06c + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10/cb709ed4240e873d3816e67f851d445f5676e0ae3a52931a60ff571d93d388da09108c8057b62351766133ee05ff3159dd56c3a0fbd39a5933c6639ce8771405 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.13": version: 0.2.13 resolution: "tinyglobby@npm:0.2.13" @@ -17313,6 +18093,23 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2 + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.0.3": + version: 3.0.3 + resolution: "tinyrainbow@npm:3.0.3" + checksum: 10/169cc63c15e1378674180f3207c82c05bfa58fc79992e48792e8d97b4b759012f48e95297900ede24a81f0087cf329a0d85bb81109739eacf03c650127b3f6c1 + languageName: node + linkType: hard + "tmp-promise@npm:^3.0.2": version: 3.0.3 resolution: "tmp-promise@npm:3.0.3" @@ -18268,6 +19065,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.0.0 || ^7.0.0": + version: 7.3.1 + resolution: "vite@npm:7.3.1" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10/62e48ffa4283b688f0049005405a004447ad38ffc99a0efea4c3aa9b7eed739f7402b43f00668c0ee5a895b684dc953d62f0722d8a92c5b2f6c95f051bceb208 + languageName: node + linkType: hard + "vite@npm:^6.3.5": version: 6.3.5 resolution: "vite@npm:6.3.5" @@ -18323,6 +19175,65 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^4.0.18": + version: 4.0.18 + resolution: "vitest@npm:4.0.18" + dependencies: + "@vitest/expect": "npm:4.0.18" + "@vitest/mocker": "npm:4.0.18" + "@vitest/pretty-format": "npm:4.0.18" + "@vitest/runner": "npm:4.0.18" + "@vitest/snapshot": "npm:4.0.18" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + es-module-lexer: "npm:^1.7.0" + expect-type: "npm:^1.2.2" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^3.10.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" + vite: "npm:^6.0.0 || ^7.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.0.18 + "@vitest/browser-preview": 4.0.18 + "@vitest/browser-webdriverio": 4.0.18 + "@vitest/ui": 4.0.18 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10/6c6464ebcf3af83546862896fd1b5f10cb6607261bffce39df60033a288b8c1687ae1dd20002b6e4997a7a05303376d1eb58ce20afe63be052529a4378a8c165 + languageName: node + linkType: hard + "vm-browserify@npm:^1.0.1": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" @@ -18701,6 +19612,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10/0de6e6cd8f2f94a8b5ca44e84cf1751eadcac3ebedcdc6e5fbbe6c8011904afcbc1a2777c53496ec02ced7b81f2e7eda61e76bf8262a8bc3ceaa1f6040508051 + languageName: node + linkType: hard + "wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5"