diff --git a/.changeset/tx-size-exclude-isvalid.md b/.changeset/tx-size-exclude-isvalid.md new file mode 100644 index 00000000..fb0e4768 --- /dev/null +++ b/.changeset/tx-size-exclude-isvalid.md @@ -0,0 +1,5 @@ +--- +"@evolution-sdk/evolution": patch +--- + +Transaction size is now measured the way the ledger measures it: `toCBORForSizeComputation`'s 3-element array `[body, witnessSet, auxiliaryData]`, with the `isValid` flag omitted. The new `Transaction.toCBORBytesForSizeComputation` produces those bytes, and both `calculateTransactionSize` (builder fee estimation and the `maxTxSize` check) and `FeeValidation.validateTransactionFee` use them. Measuring the full 4-element encoding overstated the size by exactly one byte — the boolean — so every built transaction overpaid the minimum fee by `minFeeCoefficient` lovelace (44 lovelace at the usual coefficient), and fee validation could reject a transaction whose fee was exactly the ledger minimum. diff --git a/packages/evolution/src/FeeValidation.ts b/packages/evolution/src/FeeValidation.ts index f98cc9b9..9b4b80f3 100644 --- a/packages/evolution/src/FeeValidation.ts +++ b/packages/evolution/src/FeeValidation.ts @@ -103,8 +103,12 @@ export const validateTransactionFee = ( }) : transaction - // 3. Serialize transaction to CBOR to get exact size - const cborBytes = Transaction.toCBORBytes(txToMeasure) + // 3. Serialize transaction the way the ledger computes its size + // (toCBORForSizeComputation: [body, witnessSet, auxiliaryData], the + // isValid flag omitted) to get the exact size the ledger's fee rule + // uses. The full 4-element encoding is one byte longer, which would + // overstate the minimum fee by minFeeCoefficient lovelace. + const cborBytes = Transaction.toCBORBytesForSizeComputation(txToMeasure) const txSizeBytes = cborBytes.length // 4. Calculate minimum required fee using Cardano protocol formula diff --git a/packages/evolution/src/Transaction.ts b/packages/evolution/src/Transaction.ts index 4014343d..db8b0cb0 100644 --- a/packages/evolution/src/Transaction.ts +++ b/packages/evolution/src/Transaction.ts @@ -304,6 +304,40 @@ const gainsLowerKey = (witnessSet: ReadonlyMap, format: CBOR. ) } +/** + * CBOR bytes of the transaction as the ledger computes its size: a + * 3-element array `[body, witnessSet, auxiliaryData]` with the `isValid` + * flag omitted (`toCBORForSizeComputation` in the ledger's Alonzo Tx + * module). The ledger's one size — `tx ^. sizeTxF` — feeds both the + * minimum fee and the `maxTxSize` check, so anything measuring a + * transaction for fee or size purposes must measure these bytes, not the + * full 4-element encoding: the full encoding is exactly one byte longer + * (the boolean; the array header is one byte at both lengths). + * + * Each component is written as {@link toCBORBytes} writes it under the + * given options, joined exactly as the full encoding joins them. + * + * @since 2.0.0 + * @category encoding + */ +export const toCBORBytesForSizeComputation = ( + data: Transaction, + options: CBOR.TxCodecOptions | CBOR.CodecOptions = CBOR.TX_DEFAULT_OPTIONS +): Uint8Array => { + const txOptions = CBOR.toTxCodecOptions(options) + const { ledger } = txOptions + return encodeArray( + [ + TransactionBody.toCBORBytes(data.body, txOptions), + TransactionWitnessSet.toCBORBytes(data.witnessSet, txOptions), + data.auxiliaryData === null + ? CBOR.toCBORBytes(null, ledger) + : AuxiliaryData.toCBORBytes(data.auxiliaryData, ledger) + ], + ledger + ) +} + /** * Convert a Transaction to CBOR bytes using an explicit root format tree. * diff --git a/packages/evolution/src/sdk/builders/internal/txBuilder.ts b/packages/evolution/src/sdk/builders/internal/txBuilder.ts index 1590c1e5..6ab1d457 100644 --- a/packages/evolution/src/sdk/builders/internal/txBuilder.ts +++ b/packages/evolution/src/sdk/builders/internal/txBuilder.ts @@ -1035,7 +1035,14 @@ export const assembleTransaction = ( /** * Calculate the size of a transaction in bytes for fee estimation. - * Uses CBOR serialization to get accurate size, with `options` when given. + * + * This is the ledger's size (`toCBORForSizeComputation`): the + * 3-element `[body, witnessSet, auxiliaryData]` serialization with the + * `isValid` flag omitted. The ledger uses that one size for both the + * minimum fee and the `maxTxSize` check, so measuring the full + * 4-element encoding would overstate the size by one byte and the fee + * by `minFeeCoefficient` lovelace on every transaction. The size is + * measured with `options` when given, as the full encoding would be. * * @since 2.0.0 * @category fee-calculation @@ -1043,7 +1050,7 @@ export const assembleTransaction = ( export const calculateTransactionSize = ( transaction: Transaction.Transaction, options?: CBOR.TxCodecOptions | CBOR.CodecOptions -): number => Transaction.toCBORBytes(transaction, options).length +): number => Transaction.toCBORBytesForSizeComputation(transaction, options).length /** * Calculate minimum transaction fee based on protocol parameters. diff --git a/packages/evolution/test/Transaction.SizeComputation.test.ts b/packages/evolution/test/Transaction.SizeComputation.test.ts new file mode 100644 index 00000000..2ed0b7f3 --- /dev/null +++ b/packages/evolution/test/Transaction.SizeComputation.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest" + +import * as FeeValidation from "../src/FeeValidation.js" +import { calculateTransactionSize } from "../src/sdk/builders/internal/txBuilder.js" +import * as Transaction from "../src/Transaction.js" + +// --------------------------------------------------------------------------- +// Ledger size computation (#179) +// +// The ledger measures a transaction with `toCBORForSizeComputation` +// (Cardano.Ledger.Alonzo.Tx): a 3-element array [body, witnessSet, +// auxiliaryData] — the `isValid` flag is omitted. That one size feeds both +// the minimum fee (FeeTooSmallUTxO) and the maxTxSize check +// (validateMaxTxSizeUTxO reads `tx ^. sizeTxF`), so measuring the full +// 4-element encoding overstates the size by exactly one byte (the bool; +// the array header is one byte at both lengths) and the fee by +// 1 × minFeeCoefficient. +// --------------------------------------------------------------------------- + +// body = {0: #6.258([]), 1: [], 2: 200000}, witness set = {}, aux = null +const NO_AUX_TX_HEX = "84a300d90102800180021a00030d40a0f5f6" +const NO_AUX_SIZE_HEX = "83a300d90102800180021a00030d40a0f6" + +// same body, aux = metadata {0: 0} +const AUX_TX_HEX = "84a300d90102800180021a00030d40a0f5a10000" +const AUX_SIZE_HEX = "83a300d90102800180021a00030d40a0a10000" + +const hex = (bytes: Uint8Array): string => Buffer.from(bytes).toString("hex") + +describe("Transaction size computation (ledger, #179)", () => { + it("encodes a 3-element array without isValid when there is no auxiliary data", () => { + const tx = Transaction.fromCBORHex(NO_AUX_TX_HEX) + expect(hex(Transaction.toCBORBytesForSizeComputation(tx))).toBe(NO_AUX_SIZE_HEX) + }) + + it("encodes a 3-element array without isValid when auxiliary data is present", () => { + const tx = Transaction.fromCBORHex(AUX_TX_HEX) + expect(hex(Transaction.toCBORBytesForSizeComputation(tx))).toBe(AUX_SIZE_HEX) + }) + + it("does not depend on the isValid flag value", () => { + const tx = Transaction.fromCBORHex(NO_AUX_TX_HEX) + const invalid = new Transaction.Transaction({ + body: tx.body, + witnessSet: tx.witnessSet, + isValid: false, + auxiliaryData: tx.auxiliaryData + }) + expect(hex(Transaction.toCBORBytesForSizeComputation(invalid))).toBe(NO_AUX_SIZE_HEX) + }) + + it("is exactly one byte shorter than the full encoding", () => { + const tx = Transaction.fromCBORHex(AUX_TX_HEX) + const full = Transaction.toCBORBytes(tx) + const size = Transaction.toCBORBytesForSizeComputation(tx) + expect(full.length - size.length).toBe(1) + expect(size[0]).toBe(0x83) + }) + + it("calculateTransactionSize returns the ledger size", () => { + const tx = Transaction.fromCBORHex(NO_AUX_TX_HEX) + expect(calculateTransactionSize(tx)).toBe(17) + }) + + it("validateTransactionFee accepts a fee at the ledger minimum", () => { + const tx = Transaction.fromCBORHex(NO_AUX_TX_HEX) + // fee is 200000; with the ledger size of 17 bytes and a coefficient of + // 44, the constant that makes the fee exactly the minimum is + // 200000 - 44 * 17 = 199252. Measuring the full 18-byte encoding would + // demand 200044 and reject this transaction. + const result = FeeValidation.validateTransactionFee(tx, { + minFeeCoefficient: 44n, + minFeeConstant: 199_252n + }) + expect(result.txSizeBytes).toBe(17) + expect(result.minRequiredFee).toBe(200_000n) + expect(result.isValid).toBe(true) + }) +}) diff --git a/packages/evolution/test/TxBuilder.CodecOptions.test.ts b/packages/evolution/test/TxBuilder.CodecOptions.test.ts index 888735aa..702b8986 100644 --- a/packages/evolution/test/TxBuilder.CodecOptions.test.ts +++ b/packages/evolution/test/TxBuilder.CodecOptions.test.ts @@ -36,7 +36,9 @@ import { walletUtxos } from "./utils/codec-build-helpers.js" -// The goldens were recorded before the build option existed +// The goldens were recorded before the build option existed. +// Re-recorded with the ledger size computation (#179): every fee is +// 44 lovelace lower, because the size now excludes the isValid flag. const CASES = makeCases(585, golden.length) const PLUTUS_CASES = CASES.filter((c) => c.spend !== undefined || c.mints.length > 0) @@ -90,7 +92,9 @@ const concat = (...parts: ReadonlyArray) => { /** * Checks bytes written for a canonical build: they are the encoding of their * own content, the body hashes cover the written witness set and auxiliary - * data, and the fee covers the size of `sizedBytes`. + * data, and the fee covers the ledger size of `sizedBytes` — its size + * computation (`toCBORForSizeComputation`, #179), which is the full + * encoding without the one-byte `isValid` flag. */ const checkWritten = (bytes: Uint8Array, options: CBOR.TxCodecOptions | CBOR.CodecOptions, sizedBytes = bytes) => { const decoded = Transaction.fromCBORBytes(bytes, CBOR.CANONICAL_OPTIONS) @@ -130,7 +134,7 @@ const checkWritten = (bytes: Uint8Array, options: CBOR.TxCodecOptions | CBOR.Cod steps += Number(r.exUnits.steps) } const minFee = - FULL_PP.minFeeA * sizedBytes.length + + FULL_PP.minFeeA * (sizedBytes.length - 1) + FULL_PP.minFeeB + Math.ceil(FULL_PP.priceMem * mem + FULL_PP.priceStep * steps) expect(body.fee >= BigInt(minFee)).toBe(true) diff --git a/packages/evolution/test/TxBuilder.EdgeCases.test.ts b/packages/evolution/test/TxBuilder.EdgeCases.test.ts index 14c6de84..746c5ed3 100644 --- a/packages/evolution/test/TxBuilder.EdgeCases.test.ts +++ b/packages/evolution/test/TxBuilder.EdgeCases.test.ts @@ -252,12 +252,12 @@ describe("TxBuilder P0 Edge Cases - MinUTxO Boundary Precision", () => { // Change ADA must meet minUTxO (corrected Babbage/Conway formula with 160-byte overhead) const changeAda = changeOutput.assets.lovelace - expect(changeAda).toBe(1_148_515n) + expect(changeAda).toBe(1_148_559n) // 44 lovelace higher: the fee fell by 44 with the ledger size (#179) } // Fee validation const fee = tx.body.fee - expect(fee).toBe(171_485n) + expect(fee).toBe(171_441n) // 44 lovelace lower: ledger size without isValid (#179) }) it("maximum asset name lengths at minUTxO boundary", async () => { @@ -319,7 +319,7 @@ describe("TxBuilder P0 Edge Cases - MinUTxO Boundary Precision", () => { // Strict ADA validation: exact value from deterministic transaction const changeAda = changeOutput.assets.lovelace - expect(changeAda).toBe(3_825_655n) // 6M - 2M - 174,345 fee (431 bytes with Shelley format) + expect(changeAda).toBe(3_825_699n) // 6M - 2M - 174,301 fee (430 bytes, ledger size without isValid, #179) // Strict asset validation expect(changeOutput.assets.multiAsset).toBeDefined() @@ -342,7 +342,7 @@ describe("TxBuilder P0 Edge Cases - MinUTxO Boundary Precision", () => { // Fee validation const fee = tx.body.fee - expect(fee).toBe(174_345n) // 431 bytes * 44 + 155_381 (Shelley format saves 4 bytes) + expect(fee).toBe(174_301n) // 430 bytes * 44 + 155_381 (ledger size without isValid, #179) }) it("fee oscillation through 3 reselection attempts", async () => { @@ -416,7 +416,7 @@ describe("TxBuilder P0 Edge Cases - MinUTxO Boundary Precision", () => { // Change must be >= minUTxO (corrected Babbage/Conway with native asset) const changeAda = changeOutput.assets.lovelace - expect(changeAda).toBe(1_225_391n) + expect(changeAda).toBe(1_225_435n) // 44 lovelace higher: the fee fell by 44 with the ledger size (#179) // Change should have the native asset token expect(changeOutput.assets.multiAsset).toBeDefined() @@ -438,7 +438,7 @@ describe("TxBuilder P0 Edge Cases - MinUTxO Boundary Precision", () => { // Fee validation const fee = tx.body.fee - expect(fee).toBe(174_609n) + expect(fee).toBe(174_565n) // 44 lovelace lower: ledger size without isValid (#179) // Balance equation must hold after reselection attempts const utxoMap = new Map(utxos.map((u) => [u.transactionId, u.assets.lovelace])) diff --git a/packages/evolution/test/TxBuilder.InsufficientChange.test.ts b/packages/evolution/test/TxBuilder.InsufficientChange.test.ts index 8cb9df8c..8c515245 100644 --- a/packages/evolution/test/TxBuilder.InsufficientChange.test.ts +++ b/packages/evolution/test/TxBuilder.InsufficientChange.test.ts @@ -124,13 +124,13 @@ describe("Fallback Tier 3: onInsufficientChange Strategy", () => { // Verify: Transaction balances (inputs = outputs + fee) // Strict deterministic values expect(tx.body.outputs[0].assets.lovelace).toBe(2_000_000n) // Payment amount - expect(tx.body.fee).toBe(165_281n) // Deterministic fee (uses efficient Shelley format) + expect(tx.body.fee).toBe(165_237n) // Deterministic fee (ledger size without isValid, #179) // Verify leftover is burned const inputTotal = utxo.assets.lovelace // 2_170_000n const outputTotal = tx.body.outputs[0].assets.lovelace // 2_000_000n - const leftover = inputTotal - outputTotal - tx.body.fee // 2_170_000 - 2_000_000 - 165_281 = 4_719 - expect(leftover).toBe(4_719n) // Exact leftover amount that was "burned" (becomes excess) + const leftover = inputTotal - outputTotal - tx.body.fee // 2_170_000 - 2_000_000 - 165_237 = 4_763 + expect(leftover).toBe(4_763n) // Exact leftover amount that was "burned" (becomes excess) }) }) @@ -162,12 +162,12 @@ describe("Fallback Precedence: drainTo before onInsufficientChange", () => { // Assert: Transaction succeeds using drainTo expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(1) // Leftover merged into payment output - expect(tx.body.fee).toBe(165_281n) // Deterministic fee (uses efficient Shelley format) + expect(tx.body.fee).toBe(165_237n) // Deterministic fee (ledger size without isValid, #179) // Verify: Leftover was drained into first output (not burned) const firstOutput = tx.body.outputs[0] - // Payment (2M) + drained leftover (4_719) = 2_004_719 - expect(firstOutput.assets.lovelace).toBe(2_004_719n) + // Payment (2M) + drained leftover (4_763) = 2_004_763 + expect(firstOutput.assets.lovelace).toBe(2_004_763n) }) }) @@ -197,7 +197,7 @@ describe("Normal Path: Sufficient Change (No Fallbacks)", () => { // Assert: Change output created successfully expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(2) // Payment + change - expect(tx.body.fee).toBe(168_141n) // Deterministic fee (2 outputs with Shelley format) + expect(tx.body.fee).toBe(168_097n) // Deterministic fee (ledger size without isValid, #179) // Verify outputs const paymentOutput = tx.body.outputs[0] @@ -205,8 +205,8 @@ describe("Normal Path: Sufficient Change (No Fallbacks)", () => { expect(paymentOutput.assets.lovelace).toBe(10_000_000n) expect(Address.toBech32(changeOutput.address)).toBe(CHANGE_ADDRESS) - // Change: 100M - 10M payment - 168_141 fee = 89_831_859 - expect(changeOutput.assets.lovelace).toBe(89_831_859n) + // Change: 100M - 10M payment - 168_097 fee = 89_831_903 + expect(changeOutput.assets.lovelace).toBe(89_831_903n) }) it("should handle exact amount with drainTo without triggering fallbacks", async () => { @@ -233,12 +233,12 @@ describe("Normal Path: Sufficient Change (No Fallbacks)", () => { // Assert: Single output with drained leftover expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(1) // Payment with drained change - expect(tx.body.fee).toBe(165_281n) // Deterministic fee (uses efficient Shelley format) + expect(tx.body.fee).toBe(165_237n) // Deterministic fee (ledger size without isValid, #179) // Verify: Output has payment + leftover const output = tx.body.outputs[0] - // 2_170_000 - 165_281 = 2_004_719 - expect(output.assets.lovelace).toBe(2_004_719n) + // 2_170_000 - 165_237 = 2_004_763 + expect(output.assets.lovelace).toBe(2_004_763n) }) }) @@ -282,11 +282,11 @@ describe("Edge Cases", () => { // Assert: Transaction built successfully with drainTo expect(tx.body.inputs.length).toBe(2) expect(tx.body.outputs.length).toBe(1) // Drained into output[0] - expect(tx.body.fee).toBe(166_865n) // Deterministic fee for 2 inputs, 1 output (Shelley format) + expect(tx.body.fee).toBe(166_821n) // Deterministic fee for 2 inputs, 1 output (ledger size, #179) // Verify the output has the drained amount - // Total: 2_200_000 - 166_865 fee = 2_033_135 - expect(tx.body.outputs[0].assets.lovelace).toBe(2_033_135n) + // Total: 2_200_000 - 166_821 fee = 2_033_179 + expect(tx.body.outputs[0].assets.lovelace).toBe(2_033_179n) }) it("should respect burn strategy with very small leftover amounts", async () => { @@ -316,7 +316,7 @@ describe("Edge Cases", () => { expect(tx.body.outputs[0].assets.lovelace).toBe(2_000_000n) // Verify: Fee is deterministic - expect(tx.body.fee).toBe(165_281n) // Uses efficient Shelley format + expect(tx.body.fee).toBe(165_237n) // Ledger size without isValid (#179) }) }) diff --git a/packages/evolution/test/TxBuilder.Reselection.test.ts b/packages/evolution/test/TxBuilder.Reselection.test.ts index c5e4fda8..3f2ab7bd 100644 --- a/packages/evolution/test/TxBuilder.Reselection.test.ts +++ b/packages/evolution/test/TxBuilder.Reselection.test.ts @@ -95,13 +95,15 @@ describe("TxBuilder Re-selection Loop", () => { expect(size).toBeLessThanOrEqual(PROTOCOL_PARAMS.maxTxSize) // Strict expectations with deterministic values - expect(size).toBe(290) // Exact transaction size with 1 witness (Shelley format saves 4 bytes) - expect(validation.actualFee).toBe(168_141n) // Exact deterministic fee + // Size is the ledger size (#179): the 3-element size-computation + // encoding without isValid — one byte less than the full encoding. + expect(size).toBe(289) // Exact transaction size with 1 witness + expect(validation.actualFee).toBe(168_097n) // Exact deterministic fee: 44 * 289 + 155,381 // Verify exact output amounts expect(tx.body.outputs[0].assets.lovelace).toBe(2_000_000n) // Payment output - // Change: 10M - 2M payment - 168,141 fee = 7,831,859 - expect(tx.body.outputs[1].assets.lovelace).toBe(7_831_859n) // Change output (exact deterministic value) + // Change: 10M - 2M payment - 168,097 fee = 7,831,903 + expect(tx.body.outputs[1].assets.lovelace).toBe(7_831_903n) // Change output (exact deterministic value) }) it("should trigger re-selection with tight balance", async () => { @@ -151,12 +153,12 @@ describe("TxBuilder Re-selection Loop", () => { const size = calculateTransactionSize(txWithFakeWitnesses) expect(size).toBeLessThanOrEqual(PROTOCOL_PARAMS.maxTxSize) - expect(size).toBe(326) // 2 inputs, 1 witness, 2 outputs (Shelley format saves 4 bytes) - expect(validation.actualFee).toBe(169_725n) // Fee for 2-input TX + expect(size).toBe(325) // 2 inputs, 1 witness, 2 outputs (ledger size, #179) + expect(validation.actualFee).toBe(169_681n) // Fee for 2-input TX: 44 * 325 + 155,381 // Verify exact output amounts - reselection creates proper change output expect(tx.body.outputs[0].assets.lovelace).toBe(2_000_000n) // Payment output - expect(tx.body.outputs[1].assets.lovelace).toBe(1_030_275n) // Change output (3.2M - 2M - 169,725 fee) + expect(tx.body.outputs[1].assets.lovelace).toBe(1_030_319n) // Change output (3.2M - 2M - 169,681 fee) }) it("should throw error when insufficient total funds", async () => { @@ -217,11 +219,11 @@ describe("TxBuilder Re-selection Loop", () => { expect(size).toBeLessThanOrEqual(PROTOCOL_PARAMS.maxTxSize) // Strict expectations with deterministic values - expect(size).toBe(225) // Exact transaction size with 1 witness, drainTo (Shelley format saves 2 bytes) - expect(validation.actualFee).toBe(165_281n) // Exact fee: 225*44 + 155_381 + expect(size).toBe(224) // Exact transaction size with 1 witness, drainTo (ledger size, #179) + expect(validation.actualFee).toBe(165_237n) // Exact fee: 224*44 + 155_381 // Verify exact output amount (payment + drained leftover) - expect(tx.body.outputs[0].assets.lovelace).toBe(2_004_719n) // 2_170_000 - 165_281 fee + expect(tx.body.outputs[0].assets.lovelace).toBe(2_004_763n) // 2_170_000 - 165_237 fee }) it("should create change output instead of using drainTo when leftover contains native assets", async () => { @@ -328,13 +330,13 @@ describe("TxBuilder Re-selection Loop", () => { expect(size).toBeLessThanOrEqual(PROTOCOL_PARAMS.maxTxSize) // Strict expectations with deterministic values - expect(size).toBe(509) // Exact transaction size with 2 witnesses, multi-asset (Shelley format saves 4 bytes) - expect(validation.actualFee).toBe(177_777n) // Exact fee: 509*44 + 155_381 + expect(size).toBe(508) // Exact transaction size with 2 witnesses, multi-asset (ledger size, #179) + expect(validation.actualFee).toBe(177_733n) // Exact fee: 508*44 + 155_381 // Verify exact output amounts (payment output) expect(tx.body.outputs[0].assets.lovelace).toBe(2_000_000n) - // Change output: 5M - 2M - 177,777 fee = 2,822,223 - expect(tx.body.outputs[1].assets.lovelace).toBe(2_822_223n) + // Change output: 5M - 2M - 177,733 fee = 2,822,267 + expect(tx.body.outputs[1].assets.lovelace).toBe(2_822_267n) }) it("should trigger coin selection when native assets are missing from inputs", async () => { @@ -431,16 +433,16 @@ describe("TxBuilder Re-selection Loop", () => { // With corrected minUTxO calculation, builder picks 4 UTxOs (8M total) // to cover 5M payment, fees, and valid change output requirements // Strict expectations with deterministic values - expect(size).toBe(398) // Exact transaction size with 1 witness, 4 inputs - expect(validation.actualFee).toBe(172_893n) // Exact fee: 398*44 + 155_381 + expect(size).toBe(397) // Exact transaction size with 1 witness, 4 inputs (ledger size, #179) + expect(validation.actualFee).toBe(172_849n) // Exact fee: 397*44 + 155_381 // Verify transaction structure const tx = await signBuilder.toTransaction() expect(tx.body.inputs.length).toBe(4) // Coin selection picked 4 UTxOs expect(tx.body.outputs.length).toBe(2) // Payment + change expect(tx.body.outputs[0].assets.lovelace).toBe(5_000_000n) // Payment - // Change: 8M - 5M - 172,893 fee = 2,827,107 - expect(tx.body.outputs[1].assets.lovelace).toBe(2_827_107n) + // Change: 8M - 5M - 172,849 fee = 2,827,151 + expect(tx.body.outputs[1].assets.lovelace).toBe(2_827_151n) }) it("should pass size check with 2 different addresses (2 witnesses)", async () => { @@ -475,16 +477,16 @@ describe("TxBuilder Re-selection Loop", () => { const validation = await assertFeeValid(txWithFakeWitnesses, PROTOCOL_PARAMS) // Strict expectations with deterministic values - expect(size).toBe(427) // Exact transaction size with 2 witnesses, 2 inputs (2 outputs save 4 bytes) - expect(validation.actualFee).toBe(174_169n) // Exact fee: 427*44 + 155_381 + expect(size).toBe(426) // Exact transaction size with 2 witnesses, 2 inputs (ledger size, #179) + expect(validation.actualFee).toBe(174_125n) // Exact fee: 426*44 + 155_381 // Verify transaction structure const tx = await signBuilder.toTransaction() expect(tx.body.inputs.length).toBe(2) expect(tx.body.outputs.length).toBe(2) // Payment + change expect(tx.body.outputs[0].assets.lovelace).toBe(6_000_000n) // Payment - // Change: 10M total - 6M payment - 174,169 fee = 3,825,831 - expect(tx.body.outputs[1].assets.lovelace).toBe(3_825_831n) + // Change: 10M total - 6M payment - 174,125 fee = 3,825,875 + expect(tx.body.outputs[1].assets.lovelace).toBe(3_825_875n) }) it("should reject transaction exceeding size limit (many unique addresses)", async () => { @@ -592,7 +594,7 @@ describe("TxBuilder Re-selection Loop", () => { expect(tx.body.outputs[0].assets.lovelace).toBe(2_500_000n) // Change output: 3.9M total - 2.5M payment - actual fee = change - expect(tx.body.outputs[1].assets.lovelace).toBe(1_227_107n) + expect(tx.body.outputs[1].assets.lovelace).toBe(1_227_151n) }) it("should trigger multiple reselection attempts with cascading fee increases", async () => { @@ -754,7 +756,7 @@ describe("TxBuilder Reselection After Change", () => { expect(10_000_000n).toBe(totalOutput + tx.body.fee) // Fee should be exact - expect(tx.body.fee).toBe(168_141n) + expect(tx.body.fee).toBe(168_097n) }) /** @@ -802,7 +804,7 @@ describe("TxBuilder Reselection After Change", () => { const totalOutput = tx.body.outputs.reduce((sum, out) => sum + out.assets.lovelace, 0n) expect(totalInput).toBe(totalOutput + tx.body.fee) - expect(tx.body.fee).toBe(171_309n) + expect(tx.body.fee).toBe(171_265n) }) /** @@ -852,7 +854,7 @@ describe("TxBuilder Reselection After Change", () => { expect(changeOutput.assets.lovelace).toBe(expectedChange) // Fee should be exact (larger than minFeeConstant due to change output with native assets) - expect(tx.body.fee).toBe(169_989n) + expect(tx.body.fee).toBe(169_945n) }) /** @@ -899,6 +901,6 @@ describe("TxBuilder Reselection After Change", () => { // Change output should exist with exact value const changeOutput = tx.body.outputs[1] - expect(changeOutput.assets.lovelace).toBe(1_230_275n) + expect(changeOutput.assets.lovelace).toBe(1_230_319n) }) }) diff --git a/packages/evolution/test/TxBuilder.SendAll.test.ts b/packages/evolution/test/TxBuilder.SendAll.test.ts index 867151b2..c0adc10d 100644 --- a/packages/evolution/test/TxBuilder.SendAll.test.ts +++ b/packages/evolution/test/TxBuilder.SendAll.test.ts @@ -222,7 +222,7 @@ describe("TxBuilder SendAll", () => { const tx = await signBuilder.toTransaction() // Fee is deterministic for a 2-input, 1-output transaction - expect(tx.body.fee).toBe(166_865n) + expect(tx.body.fee).toBe(166_821n) // Ledger size without isValid (#179) }) }) }) diff --git a/packages/evolution/test/TxBuilder.UnfrackChangeHandling.test.ts b/packages/evolution/test/TxBuilder.UnfrackChangeHandling.test.ts index 2791ca6a..eecda952 100644 --- a/packages/evolution/test/TxBuilder.UnfrackChangeHandling.test.ts +++ b/packages/evolution/test/TxBuilder.UnfrackChangeHandling.test.ts @@ -95,7 +95,7 @@ describe("TxBuilder: Unfrack Change Handling Integration", () => { expect(changeOutputs[0].assets.lovelace).toBe(1_150_770n) expect(changeOutputs[1].assets.lovelace).toBe(1_150_770n) expect(changeOutputs[2].assets.lovelace).toBe(1_155_080n) - expect(changeOutputs[3].assets.lovelace).toBe(2_259_487n) + expect(changeOutputs[3].assets.lovelace).toBe(2_259_531n) // 44 lovelace higher: the fee fell by 44 with the ledger size (#179) // Verify token distribution: all 3 tokens should be preserved in change outputs let totalTokenTypes = 0 @@ -168,10 +168,10 @@ describe("TxBuilder: Unfrack Change Handling Integration", () => { // Input: 2,500,000, Payment: 100,000, Fee: exact // Expected change: 2,500,000 - 100,000 - fee const changeOutput = tx.body.outputs[1] - expect(changeOutput.assets.lovelace).toBe(2_226_447n) + expect(changeOutput.assets.lovelace).toBe(2_226_491n) // 44 lovelace higher: the fee fell by 44 with the ledger size (#179) // Verify fee is exact for single-output transaction - expect(tx.body.fee).toBe(173_553n) + expect(tx.body.fee).toBe(173_509n) // 44 lovelace lower: ledger size without isValid (#179) // Balance equation must hold expect(changeOutput.assets.lovelace + paymentOutput.assets.lovelace + tx.body.fee).toBe(2_500_000n) diff --git a/packages/evolution/test/TxBuilder.UnfrackDrain.test.ts b/packages/evolution/test/TxBuilder.UnfrackDrain.test.ts index 91cf42e0..47b2c57f 100644 --- a/packages/evolution/test/TxBuilder.UnfrackDrain.test.ts +++ b/packages/evolution/test/TxBuilder.UnfrackDrain.test.ts @@ -167,13 +167,13 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Coin selection picks only the first UTxO (200 ADA) since it's sufficient expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(4) // Exact: 1 payment + 3 subdivided change outputs - expect(tx.body.fee).toBe(173_861n) // Exact deterministic fee (420 bytes * 44 + 155_381, 4 outputs save 8 bytes) + expect(tx.body.fee).toBe(173_817n) // Exact deterministic fee (419 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify exact output amounts (from 200 ADA input - 1 ADA payment - fee) expect(tx.body.outputs[0].assets.lovelace).toBe(1_000_000n) // Payment - expect(tx.body.outputs[1].assets.lovelace).toBe(99_413_069n) // 50% of 198,826,139 change - expect(tx.body.outputs[2].assets.lovelace).toBe(59_647_841n) // 30% of 198,826,139 change (rounding) - expect(tx.body.outputs[3].assets.lovelace).toBe(39_765_229n) // 20% of 198,826,139 change (rounding) + expect(tx.body.outputs[1].assets.lovelace).toBe(99_413_091n) // 50% of 198,826,183 change + expect(tx.body.outputs[2].assets.lovelace).toBe(59_647_854n) // 30% of 198,826,183 change (rounding) + expect(tx.body.outputs[3].assets.lovelace).toBe(39_765_238n) // 20% of 198,826,183 change (rounding) // Verify all outputs are ADA-only (no tokens) tx.body.outputs.forEach((output) => { @@ -221,11 +221,11 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Coin selection picks only first UTxO (50 ADA) since it's sufficient expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(2) // Exact: 1 payment + 1 change (no subdivision) - expect(tx.body.fee).toBe(168_141n) // Exact deterministic fee (290 bytes * 44 + 155_381, 2 outputs save 4 bytes) + expect(tx.body.fee).toBe(168_097n) // Exact deterministic fee (289 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify exact output amounts (from 50 ADA input - 1 ADA payment - fee) expect(tx.body.outputs[0].assets.lovelace).toBe(1_000_000n) // Payment - expect(tx.body.outputs[1].assets.lovelace).toBe(48_831_859n) // Change (50M - 1M - fee) + expect(tx.body.outputs[1].assets.lovelace).toBe(48_831_903n) // Change (50M - 1M - fee) // All outputs should be ADA-only tx.body.outputs.forEach((output) => { @@ -271,7 +271,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations expect(tx.body.inputs.length).toBe(6) expect(tx.body.outputs.length).toBe(12) // Exact: 1 payment + 11 unfracked change outputs - expect(tx.body.fee).toBe(213_769n) // Exact deterministic fee (1327 bytes * 44 + 155_381, 12 outputs save 24 bytes) + expect(tx.body.fee).toBe(213_725n) // Exact deterministic fee (1326 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify we have both token outputs and ADA-only outputs const tokenOutputs = tx.body.outputs.filter((output) => output.assets.multiAsset !== undefined) @@ -311,7 +311,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations expect(tx.body.inputs.length).toBe(6) expect(tx.body.outputs.length).toBe(12) // Exact: 1 payment + 11 unfracked change outputs - expect(tx.body.fee).toBe(213_769n) // Exact deterministic fee (1327 bytes * 44 + 155_381, 12 outputs save 24 bytes) + expect(tx.body.fee).toBe(213_725n) // Exact deterministic fee (1326 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify separate outputs for fungibles vs NFTs const tokenOutputs = tx.body.outputs.filter((output) => output.assets.multiAsset !== undefined) @@ -351,7 +351,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations expect(tx.body.inputs.length).toBe(6) expect(tx.body.outputs.length).toBe(12) // Exact: 1 payment + 11 unfracked change outputs - expect(tx.body.fee).toBe(213_769n) // Exact deterministic fee (1327 bytes * 44 + 155_381, 12 outputs save 24 bytes) + expect(tx.body.fee).toBe(213_725n) // Exact deterministic fee (1326 bytes * 44 + 155_381, ledger size without isValid, #179) // NFTs from the same policy should be in the same output const tokenOutputs = tx.body.outputs.filter((output) => output.assets.multiAsset !== undefined) @@ -393,7 +393,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - full optimization expect(tx.body.inputs.length).toBe(6) // All 6 UTxOs consumed expect(tx.body.outputs.length).toBe(10) // Exact: 1 payment + 9 unfracked change outputs - expect(tx.body.fee).toBe(208_049n) // Exact deterministic fee (1197 bytes * 44 + 155_381, 10 outputs save 20 bytes) + expect(tx.body.fee).toBe(208_005n) // Exact deterministic fee (1196 bytes * 44 + 155_381, ledger size without isValid, #179) // Count output types const adaOnlyOutputs = tx.body.outputs.filter((output) => output.assets.multiAsset === undefined) @@ -482,7 +482,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - ADA subdivision only (no tokens) expect(tx.body.inputs.length).toBe(2) expect(tx.body.outputs.length).toBe(3) // Exact: 1 payment + 2 subdivided change outputs (60%, 40%) - expect(tx.body.fee).toBe(172_585n) // Exact deterministic fee (391 bytes * 44 + 155_381, 3 outputs save 6 bytes) + expect(tx.body.fee).toBe(172_541n) // Exact deterministic fee (390 bytes * 44 + 155_381, ledger size without isValid, #179) // All outputs should be ADA-only tx.body.outputs.forEach((output) => { @@ -532,11 +532,11 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - small leftover, no subdivision expect(tx.body.inputs.length).toBe(2) expect(tx.body.outputs.length).toBe(2) // Exact: 1 payment + 1 change (no subdivision) - expect(tx.body.fee).toBe(169_725n) // Exact deterministic fee (326 bytes * 44 + 155_381, 2 outputs save 4 bytes) + expect(tx.body.fee).toBe(169_681n) // Exact deterministic fee (325 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify exact total output const totalOutput = tx.body.outputs.reduce((sum, output) => sum + output.assets.lovelace, 0n) - expect(totalOutput).toBe(14_830_275n) // 15M - fee (15,000,000 - 169,725) + expect(totalOutput).toBe(14_830_319n) // 15M - fee (15,000,000 - 169,681) }) it("should work with multiple payment outputs and drainTo", async () => { @@ -575,11 +575,11 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - multiple payments with unfracked change expect(tx.body.inputs.length).toBe(6) expect(tx.body.outputs.length).toBe(13) // Exact: 2 payments + 11 unfracked change outputs - expect(tx.body.fee).toBe(216_629n) // Exact deterministic fee (1392 bytes * 44 + 155_381, 13 outputs save 26 bytes) + expect(tx.body.fee).toBe(216_585n) // Exact deterministic fee (1391 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify exact total output const totalOutput = tx.body.outputs.reduce((sum, output) => sum + output.assets.lovelace, 0n) - expect(totalOutput).toBe(539_783_371n) // 540M - fee (540,000,000 - 216,629) + expect(totalOutput).toBe(539_783_415n) // 540M - fee (540,000,000 - 216,585, ledger size without isValid, #179) }) it("should respect minimum UTxO requirements when subdividing small amounts", async () => { @@ -625,15 +625,15 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // so the builder correctly falls back to a single change output. expect(tx.body.inputs.length).toBe(1) expect(tx.body.outputs.length).toBe(2) // Exact: 1 payment + 1 change (subdivision skipped — below min UTxO) - expect(tx.body.fee).toBe(168_141n) // Exact deterministic fee for 2-output tx + expect(tx.body.fee).toBe(168_097n) // Exact deterministic fee for 2-output tx (ledger size, #179) // Verify change output gets all remaining ADA (3 ADA - 1 ADA payment - fee) const changeOutput = tx.body.outputs[1] - expect(changeOutput.assets.lovelace).toBe(1_831_859n) // 3_000_000 - 1_000_000 - 168_141 + expect(changeOutput.assets.lovelace).toBe(1_831_903n) // 3_000_000 - 1_000_000 - 168_097 // Payment and change already verified with exact values above expect(tx.body.outputs[0].assets.lovelace).toBe(1_000_000n) - expect(tx.body.outputs[1].assets.lovelace).toBe(1_831_859n) + expect(tx.body.outputs[1].assets.lovelace).toBe(1_831_903n) // 44 lovelace higher: the fee fell by 44 with the ledger size (#179) }) }) @@ -678,7 +678,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - full wallet optimization expect(tx.body.inputs.length).toBe(6) // All fragments consumed expect(tx.body.outputs.length).toBe(9) // Exact: 1 payment + 8 unfracked change outputs - expect(tx.body.fee).toBe(205_189n) // Exact deterministic fee (1132 bytes * 44 + 155_381, 9 outputs save 18 bytes) + expect(tx.body.fee).toBe(205_145n) // Exact deterministic fee (1131 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify we have a good mix of outputs const adaOnly = tx.body.outputs.filter((o) => o.assets.multiAsset === undefined) @@ -723,7 +723,7 @@ describe("TxBuilder Unfrack + DrainTo Integration", () => { // Strict deterministic expectations - wallet migration with full optimization expect(tx.body.inputs.length).toBe(6) // All source UTxOs expect(tx.body.outputs.length).toBe(10) // Exact: 1 payment + 9 change outputs - expect(tx.body.fee).toBe(208_049n) // Exact deterministic fee (1197 bytes * 44 + 155_381, 10 outputs save 20 bytes) + expect(tx.body.fee).toBe(208_005n) // Exact deterministic fee (1196 bytes * 44 + 155_381, ledger size without isValid, #179) // Verify balance: total input (540M ADA) = outputs + fee const totalOutput = tx.body.outputs.reduce((sum, output) => sum + output.assets.lovelace, 0n) diff --git a/packages/evolution/test/golden/build-codec-default.golden.json b/packages/evolution/test/golden/build-codec-default.golden.json index 2f4fb07e..ce060804 100644 --- a/packages/evolution/test/golden/build-codec-default.golden.json +++ b/packages/evolution/test/golden/build-codec-default.golden.json @@ -1,146 +1,146 @@ [ { - "tx": "35bf5e86a717d8cb687b6d4e6df2b1ca6ebf06cd18fbfa400792c422367b93a4", - "fake": "20b91301fee23bd25161aa0d54d0f1eea0139c77dbe530a28880e6d4cf09c40d", - "txId": "879a7f53538606507646d139acc4a54bff46fc4c13d268f78f0668428831ef38", - "fee": "168141", - "chainTxHash": "879a7f53538606507646d139acc4a54bff46fc4c13d268f78f0668428831ef38", - "signed": "4702e47ddfac11831d06588bd9459ebce1dfd4ec7dd861f7c9173a0cd1c3d6d3", - "assembled": "4702e47ddfac11831d06588bd9459ebce1dfd4ec7dd861f7c9173a0cd1c3d6d3" + "tx": "9d843dd2c8e0734a7f4f24586a763deff512de5f6b095e0e3a4167dea5d4ec21", + "fake": "fae0f4900ddea25d2d5ed641ebd55eaf3f4db6bda269f7c7b95b29458496ec56", + "txId": "19fe8c7660c9e41d0a34329da1eddd44e0d2397cccd6a1a5145b27bedac1eb5f", + "fee": "168097", + "chainTxHash": "19fe8c7660c9e41d0a34329da1eddd44e0d2397cccd6a1a5145b27bedac1eb5f", + "signed": "dffd69903b06cbaf839f92bf2b863a28404dd96d1b012dda4f396b37cde07236", + "assembled": "dffd69903b06cbaf839f92bf2b863a28404dd96d1b012dda4f396b37cde07236" }, { - "tx": "89448726792f3a00acfc51d887fd41fb34329cd6d93399a9b5526cd744975531", - "fake": "e3ebc1e1e92d2de14f2154449847d6a84b4f06e22883157b096d4475120d5685", - "txId": "98124232079477e6a12789f7c6d64fbbe5a57834ba96f0f1ec9a3f275449e6e4", - "fee": "194854", - "chainTxHash": "98124232079477e6a12789f7c6d64fbbe5a57834ba96f0f1ec9a3f275449e6e4", - "signed": "3f6c2e723dfe66edac694ee186e6fd1a3fd49e4de2a407e989bd9d1a6af99d5a", - "assembled": "3f6c2e723dfe66edac694ee186e6fd1a3fd49e4de2a407e989bd9d1a6af99d5a" + "tx": "0fdfab87551445efa9d4be6caf7aa9cf70aa15a65b991a7702a99dc155d04c7d", + "fake": "f2b21ea86125197bfb99a61fc9fb9d24b0db8974384ed826c4a8077edba2ed19", + "txId": "c3dbaa74990fdb8a7068fb6c006ec70c319f9c3fba06bd48fe6fa3a5f38ed587", + "fee": "194810", + "chainTxHash": "c3dbaa74990fdb8a7068fb6c006ec70c319f9c3fba06bd48fe6fa3a5f38ed587", + "signed": "18562f81f04e008870ba68c57714c83ed45ec0a15935903a1f9fa24eb135f34d", + "assembled": "18562f81f04e008870ba68c57714c83ed45ec0a15935903a1f9fa24eb135f34d" }, { - "tx": "669c79b9271bb8ae5bfcaaa23a68c19aabd42d4e980ca295ddc4f4268c651177", - "fake": "626266287ba0ae8a2d36210576d8b1328b0bc107f8963fc1903e671d912bb59c", - "txId": "b8953d193abcba29329f475a6832133c7e95db14dd48ac6f7e0d0e16addd7350", - "fee": "204268", - "chainTxHash": "b8953d193abcba29329f475a6832133c7e95db14dd48ac6f7e0d0e16addd7350", - "signed": "02a7a152e37eded19ee7dd70a856549274397261ae8b890376571fcf47f40b6b", - "assembled": "02a7a152e37eded19ee7dd70a856549274397261ae8b890376571fcf47f40b6b" + "tx": "4c80af47a4e82f18efe3506ae72670d77a46c00565967a8d0b5bb66decd5eb2a", + "fake": "621ce9fcec498359b74685387dcd8bd05e8f5d66d52c129c0ddf0569711bff72", + "txId": "75e610027d7ba1d39304745e33dcfb1aca5c463ce82e4d569f6c3d710893b949", + "fee": "204224", + "chainTxHash": "75e610027d7ba1d39304745e33dcfb1aca5c463ce82e4d569f6c3d710893b949", + "signed": "48963178f4b070be18b8304f9655d08673e2d606ad413599779f1cf4bd7cfc38", + "assembled": "48963178f4b070be18b8304f9655d08673e2d606ad413599779f1cf4bd7cfc38" }, { - "tx": "704d20253793250257252df592561cddd0e826b4988b6d37c5727b25c2519763", - "fake": "f68a6dbb91676a7354ffba9ef216c511422949491f7c817936ce4ac1c447f749", - "txId": "e3a67ab074be3af4b88878b62578d5a891d0165bd095682f52eb37dd3a4929e0", - "fee": "178833", - "chainTxHash": "e3a67ab074be3af4b88878b62578d5a891d0165bd095682f52eb37dd3a4929e0", - "signed": "e9bf626c4e61d1ade96d44978dbe17f495819e2137dfac723a551e1310613756", - "assembled": "e9bf626c4e61d1ade96d44978dbe17f495819e2137dfac723a551e1310613756" + "tx": "f3bf1b57d7f7cbf9e8fca8fbbc16cdccdbb27f3988101cc2789d76a637bda370", + "fake": "2cc01204fda35da0c6b7aa89d4282e1766fad27cc95faeea1559c9e33c8e2334", + "txId": "9c10e519ccc71ca36a60d129b1a1bfe0d6ec38c7169b1e1d9722742f32cc07d9", + "fee": "178789", + "chainTxHash": "9c10e519ccc71ca36a60d129b1a1bfe0d6ec38c7169b1e1d9722742f32cc07d9", + "signed": "73ca103c2f911ac641ac72522d9b3fcbc5328f1982cca60f18e4c1b036d6a634", + "assembled": "73ca103c2f911ac641ac72522d9b3fcbc5328f1982cca60f18e4c1b036d6a634" }, { - "tx": "faaf70714aa0e0cab256d1fbe22a9b729b3c3da6b2fa877964e27de901487dc3", - "fake": "7deb605ed11b694f7bda811fcd259731d595d058c2ed029234a2e28cc5212d8d", - "txId": "60778b3da4e5ac9f48cec1779be0497f001a9a8b8ee950eb1032095478213979", - "fee": "176281", - "chainTxHash": "60778b3da4e5ac9f48cec1779be0497f001a9a8b8ee950eb1032095478213979", - "signed": "3c80f2b36eace1ecf95fc2dbb318eab31adce3e5966f15d1f7c9d826bbcba551", - "assembled": "3c80f2b36eace1ecf95fc2dbb318eab31adce3e5966f15d1f7c9d826bbcba551" + "tx": "570e142660d911a466a96d296fbefda9e3a832ecd8061c09d8d0735ceebb0cf9", + "fake": "efa9a883e444dd3954ce338ce147a7a02b008e786d47eda37cf7ba8197aee167", + "txId": "820fb6c688bbd612d37a62d84779c19f4b70af5f45762604a917caa72d0b333c", + "fee": "176237", + "chainTxHash": "820fb6c688bbd612d37a62d84779c19f4b70af5f45762604a917caa72d0b333c", + "signed": "40c3f6bffcf30e98e4d7166c5225de83dc378a96a94a60ab447ef2fc5d7a2bdd", + "assembled": "40c3f6bffcf30e98e4d7166c5225de83dc378a96a94a60ab447ef2fc5d7a2bdd" }, { - "tx": "6805ba2e91d5f70fe6f0c689d9d691221c8822fa1874ad492455d7798e161221", - "fake": "2db5c49d576ebbb0c2552218412bfd86eb2113c66352f90da93781a7a39bc25b", - "txId": "4f920d00b6860e5ff14ec4069c90b70bcba3adcfce3d7f9678b112c86c97a5e6", - "fee": "274192", - "chainTxHash": "4f920d00b6860e5ff14ec4069c90b70bcba3adcfce3d7f9678b112c86c97a5e6", - "signed": "9646bc2b2c1cd0506b7e2f21db20741215112efa7e01def407ffa70140460d7b", - "assembled": "9646bc2b2c1cd0506b7e2f21db20741215112efa7e01def407ffa70140460d7b" + "tx": "4db4cb1e98ebd9173c5351e2593dffbcab2cd6a993afcbdee3f32e0072014bad", + "fake": "7a4ae53358699a604cdeac16e0ac3e425cd9a118db8e1bcbab215d4b609ac28d", + "txId": "377e071ee68e0ca950d8a08f55cb82e92eeb0e03bd92cb2007eb467d319e0a4f", + "fee": "274148", + "chainTxHash": "377e071ee68e0ca950d8a08f55cb82e92eeb0e03bd92cb2007eb467d319e0a4f", + "signed": "f0a8b3f96e281e770acd953537c0fe8d57b4c3cf3d41b78e94f745ba1fbaf883", + "assembled": "f0a8b3f96e281e770acd953537c0fe8d57b4c3cf3d41b78e94f745ba1fbaf883" }, { - "tx": "80ff64e20fc1d3cf7bd851634fbdcda42e2a74bb8b15a0ecd794fc313863af31", - "fake": "6445a0ee09e03f62cba039f9c570a9679f029a8124232b2e67efd76f2b1a6062", - "txId": "61ec06ac68d41714590325ee1fd1e6f2cd8f5daaff1325573d30e688f73127ac", - "fee": "171001", - "chainTxHash": "61ec06ac68d41714590325ee1fd1e6f2cd8f5daaff1325573d30e688f73127ac", - "signed": "dee198f5319362170422bd8fad90bc8ecdfc375fd570943f1fc89b632ec20910", - "assembled": "dee198f5319362170422bd8fad90bc8ecdfc375fd570943f1fc89b632ec20910" + "tx": "8a073e5141fa4e07490bf6eae78d5e1823e57c0c8d7c2ad222c2c41aa7d7f121", + "fake": "f4d4c10ff304bf4b441dba4900e082f85fafe8b91dc51c6104dd172b9f8fb15b", + "txId": "6dfc96e9a478a1a6dc8d830344fa929f03e0238167b256c8bcd1a977c8821f08", + "fee": "170957", + "chainTxHash": "6dfc96e9a478a1a6dc8d830344fa929f03e0238167b256c8bcd1a977c8821f08", + "signed": "c560acdf970e405ae815dce19460e2017619176a694ff744393ce240b0162f85", + "assembled": "c560acdf970e405ae815dce19460e2017619176a694ff744393ce240b0162f85" }, { - "tx": "3a94faff5efd02ae9532d341b2fad7870f76e056e6127613ebea60b703117002", - "fake": "556ea8f3affd8b249c7a18b93c82df6af6f2a2da0d31cb39df7b3245a60e8cdf", - "txId": "9304d5b5751cf8915f5f4ed5366931fc1037ebf08f2e0198e003d6c491b49481", - "fee": "177293", - "chainTxHash": "9304d5b5751cf8915f5f4ed5366931fc1037ebf08f2e0198e003d6c491b49481", - "signed": "2fecc663176a15a67468efbfc75b87a5dcc56c7e7d9db884b2abc8dbc11a4b59", - "assembled": "2fecc663176a15a67468efbfc75b87a5dcc56c7e7d9db884b2abc8dbc11a4b59" + "tx": "43f6b4d3cf28c3de99c83f8ee8d6ca5f877f94ed71eab2428229f5cb202fe266", + "fake": "5d9638f564bd0378aea56692ebac30819baef4285f5177e11ee36aa6cfda92ba", + "txId": "14be54cc5b7d552b68082aa09e12acc66dd027b939004fce40f91b3ec7dd9541", + "fee": "177249", + "chainTxHash": "14be54cc5b7d552b68082aa09e12acc66dd027b939004fce40f91b3ec7dd9541", + "signed": "49d888c148e66980275b843427be0b5710b4e68549e2ae42c5b6d8b34ae0051e", + "assembled": "49d888c148e66980275b843427be0b5710b4e68549e2ae42c5b6d8b34ae0051e" }, { - "tx": "a4f9e96cd7815952a43bd43cec59e87a506f74bd8e834174c60f1c90d2126e88", - "fake": "76d9c45b97f2845e4e4adffbdde27a845132d809aaeff7d6b17cf00788206689", - "txId": "934e631c3af28a65f9f91d403d8a2581777a600338800b30fe7f7e6cacd1b202", - "fee": "282290", - "chainTxHash": "934e631c3af28a65f9f91d403d8a2581777a600338800b30fe7f7e6cacd1b202", - "signed": "d36d734be92c17cda82189f88ff7ae281217c35e1d9ac1147c2d59d1ac83700f", - "assembled": "d36d734be92c17cda82189f88ff7ae281217c35e1d9ac1147c2d59d1ac83700f" + "tx": "4d610784728904e14fc284b2b43588f814012f91d1cc96bb205cf823768a7a72", + "fake": "ba7b2ab53f660e4485f9212b194bb1bf5a43cda413c616df37e4a88c7ac0f1a3", + "txId": "801e0188180d3f9f87a5df31234c050d3b95107ffe52fd8ca82fa0a2ae6a7899", + "fee": "282246", + "chainTxHash": "801e0188180d3f9f87a5df31234c050d3b95107ffe52fd8ca82fa0a2ae6a7899", + "signed": "14d5b667c9d421979c334b58d26f6f9fbaba00bb2b589039efec6d19e313a4fc", + "assembled": "14d5b667c9d421979c334b58d26f6f9fbaba00bb2b589039efec6d19e313a4fc" }, { - "tx": "7e2f627dfa745a92d41986b2d7e33bbe53cd232496d9a41f356fdc1308d53bd0", - "fake": "879de8659904775483e2c0dd4ac359bce968f9cab2fdf90b28578a152f80e2c9", - "txId": "2593215dbcb0615af409bdcb952fb773474cc66f76c95222248955d46fd93177", - "fee": "169813", - "chainTxHash": "2593215dbcb0615af409bdcb952fb773474cc66f76c95222248955d46fd93177", - "signed": "83d5128e2e68163049873134f93eb1257a2b7e29a0e4afa63630de80e7f56029", - "assembled": "83d5128e2e68163049873134f93eb1257a2b7e29a0e4afa63630de80e7f56029" + "tx": "7509809915a47efb0a7dbf66905723133e9a002d0dd743b4cd96eb08d208d1bb", + "fake": "b31a54fa9b29827a8c0d8fd29f546c9e16187b740266319ce57404017714fdc1", + "txId": "00c1742a856d03d85d3209d7ceb488749879dd7bbbb5208c0d70aebaff1c6259", + "fee": "169769", + "chainTxHash": "00c1742a856d03d85d3209d7ceb488749879dd7bbbb5208c0d70aebaff1c6259", + "signed": "c5400a9108bc2c8538bbef3d67ff21f923ac3913059598434cacd1be074bc001", + "assembled": "c5400a9108bc2c8538bbef3d67ff21f923ac3913059598434cacd1be074bc001" }, { - "tx": "5d519f6ce8d800e34fafd2f17777b1cb1d7bedfea66966c46af8a88c2e03b58e", - "fake": "0dd7f89e16988c115d97c29d00dbc58f549b051898a6181b3d18d6086cc7730a", - "txId": "47141c8ed0887a5f0211407c15db97a748c8e970d8afa8ac54cefbb2596cc8f7", - "fee": "198418", - "chainTxHash": "47141c8ed0887a5f0211407c15db97a748c8e970d8afa8ac54cefbb2596cc8f7", - "signed": "2f248df7380af8197ba774a7b34fadd71469a8da2ef2f953ab5aec13beca5d44", - "assembled": "2f248df7380af8197ba774a7b34fadd71469a8da2ef2f953ab5aec13beca5d44" + "tx": "dff7a23ed1e6ca95f2012380beb1e0b2740abf499d8170e89d62d5d7da379ac5", + "fake": "4d29cddb7dc48d70beb2a0fcb392be51f50de513de4ac4cff2a479776f719973", + "txId": "defcf658458c5e8d96adebff33ccd063ccdcd753b1807e6a8dca53616c0a1afb", + "fee": "198374", + "chainTxHash": "defcf658458c5e8d96adebff33ccd063ccdcd753b1807e6a8dca53616c0a1afb", + "signed": "bc6a335a9fe376fadbad9a0516f47bc635b19028d29ed017e90e002431538495", + "assembled": "bc6a335a9fe376fadbad9a0516f47bc635b19028d29ed017e90e002431538495" }, { - "tx": "5789786948cc5daa68b738f137d2a2fb2e78b20b3f04713e3c659ae2a13cbccb", - "fake": "b0b13f6098d1cfb924df0026250c2a0d8327d016f133b7c8c43e50ae45b0fc1a", - "txId": "af3fbcc33c9db62f3e5be3d567aa3a0a2e6b81ffae7e8a607364f613ff7e2d75", - "fee": "230145", - "chainTxHash": "af3fbcc33c9db62f3e5be3d567aa3a0a2e6b81ffae7e8a607364f613ff7e2d75", - "signed": "e2c545a3d6afcecef1107276ac99436852eaca74e09ab855b06d4b2192eb697b", - "assembled": "e2c545a3d6afcecef1107276ac99436852eaca74e09ab855b06d4b2192eb697b" + "tx": "f7d649396bbd1598b679b3970dd3c97585420428414211cf139d6e4608fb5c3d", + "fake": "c237f2248f41cdfb2ef424a4a41d92848216da5d692c6d5f8d3a0384644edad3", + "txId": "e3581e4081dacc1f3885659af0af2a5365eb30a51ebb2c04110a6277a22f54f2", + "fee": "230101", + "chainTxHash": "e3581e4081dacc1f3885659af0af2a5365eb30a51ebb2c04110a6277a22f54f2", + "signed": "403ba03239c7111ebef2594e1df2b326f20da4ad668272fda25e5bb548e2bbb0", + "assembled": "403ba03239c7111ebef2594e1df2b326f20da4ad668272fda25e5bb548e2bbb0" }, { - "tx": "8ec82115a24d63b4139ddc639fb56e61475d20fa53fc376b9effb1e8234a8188", - "fake": "1006043bd58c2e557498ec4549a96d54fa68a0bf90a51bfa99eba3e228eb0a61", - "txId": "ffc32da4befa838e8d1a766fb6dc69705cd682a9c40996152e62e7464a7002ae", - "fee": "197406", - "chainTxHash": "ffc32da4befa838e8d1a766fb6dc69705cd682a9c40996152e62e7464a7002ae", - "signed": "aa9e4a9d91d90fd75e0aa2b3104bfb7c67b195c4b2dbd5fc2d81c97f4f04953d", - "assembled": "aa9e4a9d91d90fd75e0aa2b3104bfb7c67b195c4b2dbd5fc2d81c97f4f04953d" + "tx": "d47f7bf45d11223bca465378cd78925fb34fe2f7ca3d77e10d55588232ddcaf0", + "fake": "000b9c7e58595f684ec0b1b238f24b4ca49f70d6f39a3836601c9bf35a2046ba", + "txId": "de0fb38deb2a02ce362bfa09e7ba107191697426e21baed2d09a02fb26631501", + "fee": "197362", + "chainTxHash": "de0fb38deb2a02ce362bfa09e7ba107191697426e21baed2d09a02fb26631501", + "signed": "662a0605b10e5d862fafab14a1be7ceeb679c3769ffec43708af898839db54d9", + "assembled": "662a0605b10e5d862fafab14a1be7ceeb679c3769ffec43708af898839db54d9" }, { - "tx": "95425dcfccece649c2581706f87eb4ce798007350220e564f7e66bf8c15bd31d", - "fake": "82e09f5acd6824d188a6b5563b65ab7ce8fe06d22b2e74670564c208ae370388", - "txId": "18b046bbf0af112bb2e11618e6f363d1beed2846a65e3d19795fe9261f73fda5", - "fee": "227021", - "chainTxHash": "18b046bbf0af112bb2e11618e6f363d1beed2846a65e3d19795fe9261f73fda5", - "signed": "543638f8641927fa1f502c645c91238b5e96766dbe7b0103dc704a84124a7363", - "assembled": "543638f8641927fa1f502c645c91238b5e96766dbe7b0103dc704a84124a7363" + "tx": "106beb7a696f6e3c21db40016743092ee26237e02eb480065528a74efe886af5", + "fake": "8468eec3781483d09cc42901c1c985faf395887f815ee45465f54a3763d63eca", + "txId": "d762e84208272692bcf64f59316cc860cf5cb158144275e5010d484eda43bea2", + "fee": "226977", + "chainTxHash": "d762e84208272692bcf64f59316cc860cf5cb158144275e5010d484eda43bea2", + "signed": "375de04990bf6a10fef4e56f2ef18558e25d98ae51e2580c0942786f68e1b965", + "assembled": "375de04990bf6a10fef4e56f2ef18558e25d98ae51e2580c0942786f68e1b965" }, { - "tx": "25e5d7351e0b0833810096f21d07b74943f726ad2e9fc258fe9d6c63fbbe9abb", - "fake": "27e3d8ec4304c3d788b7da899eee5783346d220e710dc681be93c8fc903ce5f7", - "txId": "07d60d9ad3d8e3ceee38f8cc12edd65bd2da806c9a298c9a4dce7a4fc3f508ae", - "fee": "204972", - "chainTxHash": "07d60d9ad3d8e3ceee38f8cc12edd65bd2da806c9a298c9a4dce7a4fc3f508ae", - "signed": "df6864fa9fc99ea9bcaece756ce761197605f1936eae5ff123b92e35550d06b9", - "assembled": "df6864fa9fc99ea9bcaece756ce761197605f1936eae5ff123b92e35550d06b9" + "tx": "e8f06a54a7dc5d11bbf44087b4c9a4c920f863a36345e828bff98b9c6e6bc390", + "fake": "53e2a896152b82f34f7f183390ed16ed1829b6d0d184c43accd403c8122d9278", + "txId": "7fabc1fd319a8d43605248cddee69171c541ee53ed7a3cea26cf4c128ee18388", + "fee": "204928", + "chainTxHash": "7fabc1fd319a8d43605248cddee69171c541ee53ed7a3cea26cf4c128ee18388", + "signed": "b20398f012a5500b1f6b3805c48953205735f5d17b55c786871d2549715ecedd", + "assembled": "b20398f012a5500b1f6b3805c48953205735f5d17b55c786871d2549715ecedd" }, { - "tx": "e942c9046add7a8f842566c38dd315407b175314805c97bf7ba92fb78b4e0d61", - "fake": "3da15dfdc40ad1b92fd64ee34313e9e630f30fb3abe8ab11fdac36a4bda92810", - "txId": "777a645ea328a1f7d65498dfbbd4db93335de40f9422bfd5b31d25ef6fba1234", - "fee": "208448", - "chainTxHash": "777a645ea328a1f7d65498dfbbd4db93335de40f9422bfd5b31d25ef6fba1234", - "signed": "fa2b8cd95ca1021bc385ed2290635cda38f963188fd2af7a09359424eb999d72", - "assembled": "fa2b8cd95ca1021bc385ed2290635cda38f963188fd2af7a09359424eb999d72" + "tx": "83e9cddd90a973d17d8a16dd0d39e12bb0406c4ce7caa4b30baf2746567ed97c", + "fake": "eca99552e30a644a911fa323a9499f9125c6e9a77bf7b114a82e66930d59a0f5", + "txId": "606b6103db87bd150a833dec5176ba4f4ff32d7143b64e6ed9fa8e32bf596058", + "fee": "208404", + "chainTxHash": "606b6103db87bd150a833dec5176ba4f4ff32d7143b64e6ed9fa8e32bf596058", + "signed": "a4b33c776a73f6c924c5a281415b4474e0c9ad3f4d470ee460acc5bcd7b87323", + "assembled": "a4b33c776a73f6c924c5a281415b4474e0c9ad3f4d470ee460acc5bcd7b87323" } ]