Release 1.1.0: generic tx builder, SDK hardening, corporate npm publishing pipeline - #37
Merged
Merged
Conversation
Move everything not specific to a transaction type out of buildTransaction
into a private assembleTransaction({outputs, inputs, requiredCoin,
requiredToken, sendToken, baseFee, deductFeeFromFirstOutput, opts}).
buildTransaction now only computes the required outputs/inputs and
coin/token requirements via getRequiredInputsOutputs, adds the per-type
fee via getFeeForType and delegates to assembleTransaction. The
DelegationWithdraw special cases (no coin selection, fee deducted from
the first output) are modeled by the deductFeeFromFirstOutput flag.
Public APIs and behavior are unchanged. Reviewer note: subagent tooling
is unavailable in this environment, so the correctness/TS, security and
DRY reviews were run manually on the diff (clean, zero findings). One
invariant change: getRequiredInputsOutputs (pure, synchronous) now runs
before the UTXO fetch instead of after.
Add Client.buildRawTransaction(args) and Client.forgeTransaction(args)
for developer-forged transactions:
- outputs: arbitrary RawOutput[] mirroring the canonical output union,
accepting plain strings for {hex,string} fields (auto hex-encoded)
and plain numbers for lock contents
- inputs: optional nonce-based account inputs (mint/unmint/freeze/
unfreeze/lock-supply/change-authority/change-metadata-uri/fill-order/
conclude-order/delegation-withdraw). Nonces are auto-looked-up from
token/order/delegation details (next_nonce) and assigned sequentially
per token in input order; explicit nonces override. Token-command
authorities are inferred from token details when omitted.
- Coin requirements are computed from caller outputs (Transfer,
LockThenTransfer, Htlc, BurnToken, DelegateStaking, CreateOrder give
side) plus protocol fees (fungible/NFT issuance, data deposit) and
input fees (supply change, freeze, change authority, fixed
metadata/unfreeze fees). Minted token outputs are covered by their
MintTokens input instead of token UTXOs. Custom inputs never satisfy
coin requirements: UTXO selection for fees/coins always runs and
change is appended as today (hybrid model, shared assembler).
Also: consolidate getFeeForType onto a shared protocolFee() helper, and
handle UntilTime LockThenTransfer locks (content as timestamp string or
{timestamp} object) which previously crashed during encoding.
Reviewer note: subagent tooling is unavailable in this environment, so
the correctness/TS, security (input validation, prototype pollution via
JSON args, secret leakage) and DRY reviews were run manually on the
diff (clean, zero blocking findings). Spec deviation: mint inputs carry
token_supply_change_fee, following getFeeForType/existing buildMintToken
rather than the task text's 'mint input needs no fee' aside.
Re-export the go-sdk-parity primitives from @mintlayer/wasm-lib via the package entry point: all encode_output_* output encoders, all encode_input_for_* input encoders (including freeze_order), encode_outpoint_source_id, encode_transaction, encode_signed_transaction, estimate_transaction_size, get_transaction_id, the lock encoders (block count/seconds/until time/until height), the protocol fee helpers, encode_stake_pool_data/encode_destination and the supporting enums (Amount, Network, SourceId, TotalSupply, FreezableToken, TokenUnfreezable, SignatureHashType) needed to call them. Re-export only; no wrapper logic. Reviewer note: subagent tooling is unavailable in this environment, so the correctness/TS, security and DRY reviews were run manually on the diff (clean, zero findings).
Publish @mintlayer/sdk when a GitHub release is published (and via workflow_dispatch). Installs with --frozen-lockfile, runs the SDK test suite as a gate, then publishes only packages/sdk through 'pnpm --filter @mintlayer/sdk publish --access public --no-git-checks' using the NPM_TOKEN secret and npm provenance (NPM_CONFIG_PROVENANCE). Self-reviewed: job permissions are least-privilege (contents: read, id-token: write — the latter required for provenance signing); the only secret is the NPM_TOKEN reference scoped to the publish step env; action versions match the existing test.yml style (checkout@v3, pnpm/action-setup@v2, setup-node@v3, node 20, pnpm cache).
Document the current npm state for @mintlayer/sdk (user-owned scope, maintainers owlsua and anyxem, latest published version 1.0.39, no NPM_TOKEN secret yet) and the one-time maintainer setup: create a granular access token (read/write, scoped to @mintlayer/sdk, expiry <= 1 year), add it as the NPM_TOKEN repository secret, and publish by bumping the version, merging and creating a GitHub Release. Adds a long-term recommendation to convert @mintlayer to an npm org with a bot account. Self-reviewed: no secrets or invented URLs; steps match the publish workflow added in the previous commit.
Bump @mintlayer/sdk to 1.1.0 (new public API: buildRawTransaction, forgeTransaction, re-exported wasm encoding primitives). Publishing happens via the release-triggered workflow; no manual npm publish. Self-reviewed: lockfile intentionally not modified (no dependency changes).
New suite packages/sdk/tests/raw-transaction.test.ts covering
Client.buildRawTransaction / forgeTransaction API:
- issuance + transfers + fee output in a single transaction, with coin
conservation (inputs = outputs + change + tx fee + issuance fee) and
64-hex transaction id
- multi-transfer with automatic UTXO selection (largest-coin UTXO picked)
and change output to the first change address
- MintTokens account inputs: sequential auto nonces from token next_nonce
([7, 8]), explicit nonce override (42), authority auto-inference
- rejections without any provider calls: empty outputs, unknown output
type, malformed atoms (negative / decimal / non-numeric), UTXO-style
input passed in inputs
- wasm primitive re-export sanity (encode_output_transfer,
encode_transaction, estimate_transaction_size)
- plain-string RawStringField auto-wrapping into {hex, string} pairs
Full suite now: 19 test suites / 76 tests, all green
(previously 18 suites / 66 tests).
… package The workflow published from source without ever building: dist/ is gitignored and the package had no 'files' field, so the npm tarball had no entry point. Fixes: - packages/sdk: 'files': ['dist'], prepublishOnly guard (test+build) - workflow: build the SDK before publishing - workflow: check out the released tag (release events otherwise check out default-branch HEAD) and assert package.json version === tag (leading 'v' tolerated) before publishing - workflow: pnpm 10 (lockfile is lockfileVersion 9.0, pnpm 8 cannot read it) via pinned pnpm/action-setup@v4 - workflow: pin actions to full commit SHAs (checkout v3 a37ce91, setup-node v3 3235b87, pnpm/action-setup v4 b906aff) - kept --no-git-checks (deliberate reviewer deviation): a tag checkout is a detached HEAD so pnpm's default-branch check can never pass; the workflow verifies the tag-version match explicitly instead and the fresh checkout guarantees a clean worktree - provenance kept: id-token: write + NPM_CONFIG_PROVENANCE Self-reviewed: permissions least-privilege (contents: read, id-token: write); only secret is the NPM_TOKEN reference scoped to the publish step; SHAs verified via git ls-remote; YAML parse-checked. All 19 suites green. Committed unsigned (--no-gpg-sign) with user authorization; headless environment has no pinentry.
Reviewer findings #3, #4, #11, #12, #16: - Wallet-display spoofing: amounts are now normalized as normalizeRawAmount(amount, decimals, context); the decimal value is always recomputed from the validated atoms via atomsToDecimal (11 for Coin, token decimals for TokenV1, the issuance number_of_decimals for Fixed supply) and a caller-supplied decimal that disagrees is rejected, never echoed. Token-details lookups are promise-cached and shared across outputs/inputs. - hex/string pairs: stringToHex now encodes UTF-8 with zero-padded bytes (old impl emitted 1-2 digits per char and mangled non-ASCII); provided {hex,string} pairs are charset/even-length validated and cross-checked against the recomputed hex; the canonical pair is always rebuilt from the string. ASCII outputs are byte-identical (all snapshots and existing assertions unchanged). - Display strings (string fields, DataDeposit.data, IssueNft creator) are stripped of C0/C1 control chars and Unicode bidi marks. - Lock/refund-timelock contents validated ^\d+$ and preserved as strings (no Number() precision loss; BigInt applied at encoding); Timelock ForBlockCount content widened to string | number. - rawAtomsString takes a field label so errors name the offending field (lock.content/timestamp/fill_atoms) instead of always 'amount.atoms'. - SDK-level sanity caps: max 100 outputs; ticker 32, URIs 512, NFT name 128, description 1024, hash 128, data deposit 4096, creator 128. Test fixtures: raw-transaction.test.ts (tdd-guide pass) aligned to the new guard — FEE_ATOMS 1e9->1e12, USER_ATOMS 5e13, two Transfer outputs 1e9->1e12 atoms ('10' ML) and IFT total_supply decimal '1000'->'10' (1e12 atoms at 11 decimals); the previous atoms/decimal pairs were internally inconsistent and are now (correctly) rejected by the spoof guard. Self-reviewed: all 19 suites + 76 tests green, tsc clean; every remaining fixture re-verified against atomsToDecimal; promise cache is per-call so failed lookups cannot leak between requests.
…validation Reviewer findings #5, #6, #13, #14, #15: - IssueNft outputs get their token id BEFORE encoding via get_token_id(inputs-bytes, height, network): the wasm encoder rejects the empty placeholder id with a cryptic error. Input encoding is extracted into getTransactionInputsBytes (input bytes do not depend on outputs); the post-encode backfill is removed and the JSON now always carries the real id. More than one IssueNft output per transaction is rejected: get_token_id cannot derive per-output ids, so silently reusing one id would be wrong (deviation from the review item's 'update ALL' — refusal prevents writing incorrect ids). - Mint netting: token requirements are netted against same-transaction mints per token (previously ANY mint exempted ALL outputs of that token, allowing conservation violations). Only the positive remainder requires token UTXOs; over-minting is allowed and documented. - Htlc fee parity: raw Htlc outputs now charge the same placeholder 1e11-atom fee as legacy getFeeForType('Htlc'), shared via Client.HTLC_FEE_ATOMS. - On-chain ids (token/order/delegation/pool) are validated (lowercase alphanumeric, 10-100 chars, bech32-like format) before they are interpolated into API paths or passed to encoders. - Dead branch: forceSpendUtxo is always an array, so test forceSpendUtxo.length > 0 instead of truthiness. Self-reviewed: all 19 suites + 76 tests green, tsc clean; empty token_id wasm throw and get_token_id derivation verified empirically against the real wasm build; pure extraction of input encoding keeps getTransactionBINrepresentation behavior identical.
Reviewer finding #7 (pre-existing, now on the shared assembler path): the fee loop mutated the first output's amount in place every iteration, reading back the already-deducted value, so each extra iteration compounded the deduction. Snapshot the original atoms before the loop and always compute originalAtoms - totalFee per iteration. Self-reviewed: all 19 suites + 76 tests green, tsc clean; the delegation-withdraw snapshot is byte-identical (deduction result on the converging path unchanged, compounding hazard removed).
…elper Reviewer findings #8, #9, #10, #18: - getFeeForCommand is now the single command->fee table; getFeeForType maps its legacy type vocabulary ('MintToken' etc.) onto it and getFeeForRawInput delegates, removing the duplicated wasm fee mapping. - Nonce assignment is keyed per logical sequence: token ids as before, orders by 'order:<order_id>', delegations by 'delegation:<id>' — two fills of the same order in one transaction now get distinct sequential nonces. - Explicit nonces are validated (non-negative integer, not below the next expected nonce for that sequence) and advance the counter so later inputs cannot collide with them. - The block height constant used for fee estimation, token id derivation, order input encoding and witness signing is consolidated into a feeBlockHeight getter on Client (and Signer, which is a standalone class) replacing 7 scattered 200000n/'200000' literals. Self-reviewed: all 19 suites + 76 tests green, tsc clean; all 17 snapshots byte-identical (consolidation is behavior-neutral); legacy fee mapping verified case-by-case against the previous implementation.
- NPM_PUBLISH_SETUP.md: 'calendar a renewal reminder' -> 'and calendar a renewal reminder'. - Export BuiltTransaction, a public alias for the shape returned by all build* methods (buildTransaction, buildRawTransaction, ...), so consumers and tests can type results without a local duplicate. Named distinctly because the exported name 'Transaction' is the TransactionBuilder class re-exported from './transaction'. Self-reviewed: all 19 suites + 76 tests green, tsc clean; d.ts emits the alias alongside the unexported interface.
…shape, nft id mismatch Reviewer round-3 findings #1, #2, #4, #5 (correctness): - atomsToDecimal('0', d) returned '0.' (dead || '0' fallback: the concatenation is always truthy), so the raw builder's strict decimal check rejected legitimate zero amounts. Trim trailing zeros and return '0' for an empty fraction. - DelegationWithdraw fee deduction now renders the decimal via atomsToDecimal (exact, instead of float division by 1e11) and throws a clear error when the withdrawal amount is smaller than the transaction fee (netAtoms < 0). - getRawTokenDetails validates the fetched token payload before caching and use: object shape, number_of_decimals a finite integer 0..18, authority a non-empty string; otherwise a descriptive 'not found / invalid data' error (previously an unchecked cast let garbage flow into decimal recomputation). - An IssueNft output with a caller-supplied token_id that differs from the id derived from the transaction inputs now throws instead of being silently overwritten. Self-reviewed: all 19 suites + 88 tests green, tsc clean; the delegation-withdraw snapshot is byte-identical (the exact-decimal formula matches the previous float division for the tested amount); zero-amount Transfer verified to encode correctly in the real wasm build; zero-amount acceptance test queued for the batched test commit (test-file guard still intercepts this agent).
Reviewer round-3 finding #3: a manual run checked out arbitrary branch state with no version binding (release.tag_name is empty on dispatch). workflow_dispatch now requires a 'tag' input, the checkout ref is release tag or input tag, and the version-assert step runs for BOTH events (package.json version must equal the tag, leading 'v' tolerated). Tag is passed via env indirection into the shell step. Self-reviewed: YAML parse-checked; input interpolated via env, not string substitution; all 19 suites + 88 tests green (unchanged).
Reviewer round-3 findings #6, #7, #8, #10, #12 (DRY): - resolveRawTokenCommand collapses the 7x repeated token-command preamble in normalizeRawInputs (details fetch -> authority inference -> nonce assignment). - normalizeRawLock / normalizeRawTimelock are now thin wrappers over one normalizeRawTimelockContent implementation (same dispatch, same validation, per-context field labels). Htlc UntilTime locks given as a plain timestamp scalar are now accepted alongside the {timestamp} form; both normalize to the identical canonical shape. - validateRawCurrency is the single Coin/TokenV1 discriminant check, shared by value normalization and CreateOrder currency sides; the redundant normalizeRawCurrency wrapper is removed. - The block height used across fee estimation, token id derivation, order encoding and signing is one exported module constant FEE_BLOCK_HEIGHT (replacing the duplicated Client/Signer getters); tests can import it instead of hardcoding 200000n. - computeRawRequirements accumulates coin requirements through a single addCoinRequirement helper alongside addTokenOutput. Self-reviewed: all 19 suites + 88 tests green, tsc clean; all 17 snapshots byte-identical (pure consolidation, behavior-neutral except the documented Htlc scalar-timestamp leniency).
Reviewer round-3 finding #9: the nine hand-built {hex: this.stringToHex(x), string: x} pairs in getRequiredInputsOutputs (IssueFungibleToken metadata_uri/token_ticker and the seven IssueNft metadata fields) now go through normalizeRawStringField, so legacy and raw builders share one encoding/sanitization path. Plain-string params wrap identically; field-level length caps and control-character stripping now also apply to legacy issuance inputs. Self-reviewed: all 19 suites + 88 tests green, tsc clean; issuance and NFT snapshots byte-identical (ASCII inputs produce unchanged hex).
Reviewer round-3 finding #14: document that release tags must be protected (GitHub tag protection rules / rulesets) so tags cannot be mutated or deleted between release creation and the workflow run — otherwise the workflow's tag<->package.json version binding has a TOCTOU window. Self-reviewed: all 19 suites + 88 tests green (docs-only change).
Reviewer round-3 finding #11: ids interpolated into API URL paths are now validated (lowercase alphanumeric, 10-100 chars, bech32-like) before every legacy fetch: buildTransfer, buildTransferNft (getNft), the seven token-command builders, buildCreateOrder (ask/give token, 'Coin' bypassed), buildFillOrder, buildConcludeOrder, buildBridgeRequest, buildDelegationStake (pool), buildDelegationWithdraw (delegation + pool), buildCreateHtlc and buildBurn (non-'Coin'). Server-derived ids (order currency tokens, HTLC refund/spend token lookups) are intentionally not re-validated. The raw builder's order/delegation guards are unchanged; a misplaced duplicate from an earlier staging mishap was removed. staking.test.ts is included here rather than in the migration commit: the 'wrong_pool_id' -> well-formed-unknown-pool fixture change is interleaved with the file's api-mocks migration in the same hunks, and a partial split would create an uncompilable intermediate state. Malformed ids are now rejected before the fetch, so the fixture uses a well-formed but unknown pool id to preserve the test's intent (unknown pool -> 'Failed to fetch delegation id'). Self-reviewed: every id-fetch audited line by line (26 validateRawId sites total); all 19 suites + 89 tests green, tsc clean, 17 snapshots byte-identical.
…on and fee-height constant Reviewer round-3 finding #13 (test batch from the tdd-guide pass): - 12 legacy suites (client, htlc, issue-token, orders, signer, staking-create-delegation, token-commands, transfer, transfer-nft, transfer-token, utxo-change) drop their copy-pasted window.mojito stubs and inline fetch routers for tests/helpers/api-mocks.ts (setupApiMocks with per-suite tokens/utxos/chain-tip options), keeping only suite-specific routes (pools, delegations, orders). - raw-transaction.test.ts: local AnyTx replaced by the exported BuiltTransaction type, createConnectedClient and expectRejectionWithoutProviderCalls helpers remove the repeated create/connect preamble and callsBefore bookkeeping, and fee expectations import FEE_BLOCK_HEIGHT instead of hardcoding 200000n. - helpers/api-mocks.ts extended with the multi-token fixtures used by the migrated suites. Self-reviewed: net -689 lines across suites; all 19 suites + 89 tests green, tsc clean, all 17 snapshots byte-identical (no -u needed).
Reviewer round-5 findings #1-#5: - buildRefundHtlc / buildSpendHtlc / extractHtlcSecret validate caller-supplied transaction ids (64-char hex, case-insensitive) before they flow into the /transaction/<id> API path; clear error, checked before any provider call. Existing fixtures already use realistic 64-hex ids. - fetchRawTokenDetails also validates next_nonce: when present it must be a non-negative integer number (a string like '7' would corrupt nonce assignment via concatenation). - validateRawCurrency returns fresh canonical {type:'Coin'} / {type:'TokenV1', token_id} objects so extra caller properties cannot leak into the CreateOrder transaction JSON. - One shared validateTokenDecimals assertion (integer 0-18) now guards both raw issuance outputs and token-details lookup; raw issuance previously allowed unbounded decimals. (IssueNft outputs carry no decimals field, so there is nothing to validate on that path.) - RawOutput Htlc refund_timelock JSDoc documents the accepted UntilTime forms (wrapped {timestamp} object or bare scalar; both normalize identically since the round-3 consolidation). Self-reviewed: all 19 suites + 89 tests green, tsc clean; the extractHtlcSecret fixture id is 64-hex so the new check does not disturb it; validateRawCurrency change is behavior-neutral for well-formed inputs (Canonicalization verified by unchanged snapshots).
… test helpers
- createConnectedClient and expectRejectionWithoutProviderCalls move
from raw-transaction.test.ts into tests/helpers/api-mocks.ts so other
suites can adopt them; setupApiMocks gains an 'addresses' option
(defaults to account_01) used by the verify-challenge migration.
- New rejection coverage:
- staking: DelegationWithdraw amount smaller than the fee throws
('DelegationWithdraw amount is smaller than the transaction fee');
malformed pool_id rejected before any provider call.
- htlc: malformed transaction_id rejected before any provider call
(64-char hex check).
- raw: token details payload with a string next_nonce rejected
(would corrupt nonce arithmetic via concatenation); garbage token
details payload rejected by the shared 0-18 decimals assertion;
IssueNft output whose caller token_id differs from the derived one
rejected instead of silently overwritten.
Self-reviewed: helper usage matches fetch behavior per case (only the
genuinely fetch-free rejections assert zero provider calls); all 19
suites + 95 tests green, tsc clean, 17 snapshots byte-identical.
…-mocks
Final two suites onto tests/helpers/api-mocks.ts, completing the
de-duplication of the inline window.mojito stub + fetch router pattern
across all suites:
- verify-challenge: custom address fixture now flows through the new
setupApiMocks 'addresses' option; the unused malformed restore stub
is dropped (autoRestore: false, restore never asserted).
- standalone-providers: local setupFetchMock replaced with
setupApiMocks({ utxos: UTXOS }); these suites construct providers
directly, so the mojito stub is unused but harmless.
Self-reviewed: 19/19 suites + 95 tests green, tsc clean, all 17
snapshots byte-identical; no fetchMock imports remain in the two
migrated files.
…sts, dedupe comment Gate: pnpm test 19 suites / 95 tests green, 17 snapshots byte-identical; npx tsc --noEmit clean.
…unt bridge Docs-only change; gate: full suite green (19 suites/95 tests), tsc clean. Self-reviewed: all registry facts re-verified live (no npm org 'mintlayer', no user 'mintlayer', maintainers owlsua/anyxem on sdk + wasm-lib, 1.0.39); recommendations limited to npm/GitHub documented features (orgs, teams, granular tokens, trusted publishing/OIDC, tag protection rules).
…itory, publishConfig)
…A-pinned actions, staged publishing - workflow_dispatch requires an explicit release tag (sdk-v*/react-v*/wasm-lib-v*) - restore version-to-tag verification, generalized per package - pin actions by commit SHA (checkout v7.0.1, setup-node v7.0.0, pnpm/action-setup v6.1.0) - npm-publish environment gate + npm stage publish --provenance (trusted publishing OIDC) - remove obsolete NPM_TOKEN bridge (superseded by org trusted publishers) - remove docs/NPM_PUBLISH_SETUP.md (org migration completed; sensitive access details)
- enrich Transaction.fromHEX decode: map all input/output variants (was: UTXO-in/Transfer-out only, token_id dropped, ACCOUNT stub) — shapes pinned by wasm encode->decode probes - move the assembly engine (UTXO selection, fee loop, encoding, IssueNft id derivation) into Transaction.assembleRaw; Client's buildRawTransaction/buildTransaction are thin facades now - unify fee convergence + encode tail and UTXO selection across the fluent builder and the raw assembler (single FEE_AMOUNT_PER_KB) - delete the low-level wasm re-export block (import from wasm-lib) - drop dead fluent API surface (addAction no-op, unused addInput) - unify duplicated canonical types on src/types/transaction
… engine
- byte-identical encode->decode->re-encode for all supported output types
and account-command inputs (locks the decode mapper against drift)
- pin explorer-style decode shapes (token_id, {hex,string} pairs, command
casing, nonce) and the Coin/token decimal conventions
- fix decoded UTXO index: use the real outpoint index, not array position
- fee parity: fluent build() and buildRawTransaction produce identical fees
Correctness (verified by reviewers against the real wasm):
- decoded UTXO inputs: UnmintTokens payload is a bare string (crash); use
the real outpoint index, not the input's array position
- forceSpendUtxo entries: emit selector-shaped {input, utxo} and dedup
against auto-selection (was: wrong shape crash + double-spend risk)
- DelegationWithdraw fee deduction: snapshot pre-loop amount (fee
deduction compounded across convergence iterations) and stop mutating
the caller's prepared outputs
- atomsToDecimal('0') returned '0.'; helpers single-sourced in utils.ts
- validate API-served order/delegation nonces before nonce arithmetic
- HTLC UTXOs excluded from auto-selection on BOTH paths (manual-spend)
- consistent block-height fallback (0/unset -> FEE_BLOCK_HEIGHT) across
assembleRaw and the fluent builder
Dedupe/dead code:
- single assembly engine now also shares UTXO selection + fee/encode tail
- 26 dead wasm imports, dead type imports, duplicate helper sextet
(mergeUint8Arrays/hexToUint8Array/atomsToDecimal/...) removed — utils.ts
is the single home; SDK re-exports the public numeric helpers
- duplicate canonical type set on the Client replaced by
src/types/transaction; AssembledTransaction = AssembledTransactionData
+ intent/htlc bridge fields; AssembleTransactionArgs collapsed into
PreparedTransaction
- buildRefundHtlc/buildSpendHtlc collapsed into buildHtlcClaim
- removed: addAction no-op, unused addInput, client plumbing,
stakingWithdraw/getTransactionId/setCurrentBlockHeight stubs, module
load + build-args console logs
Quality:
- assembler errors now carry token id/amounts/fee diagnostics
- JSDoc accuracy pass (blockHeight param, fluent-vs-plain-data contract)
- prettier-formatted touched files
… fee-deduction invariant
- assembleRaw: deep-clone the fee-deducted output (caller's prepared outputs no longer mutated through shared nested value/amount) - withdraw + forced coin UTXOs: fee no longer subtracted from coin change as well (inputs - outputs === fee invariant restored) - forced UTXO list self-deduped (caller-supplied duplicate outpoints) - convergence-failure and insufficient-UTXO errors now carry diagnostics - typed DelegationWithdraw path validates next_nonce like the raw path - dead code: orphaned TotalSupply import, unreferenced Outpoint type, doubled JSDoc, dead @ts-ignore, stale setCurrentBlockHeight doc ref - test: compounding regression now uses a nonzero base fee + asserts the caller's outputs stay unmutated; prettier-formatted
The undefined-guard filter from the round-1 decode fixes also dropped the HTLC [spend_key, refund_key] arrays from estimate_transaction_size, underpricing fees on every HTLC claim/spend. Flatten both keys again; pinned by a size-estimate regression test (274 vs 171 bytes, fails at 68 on the regression).
erubboli
force-pushed
the
feat/generic-tx-builder
branch
from
September 18, 2026 17:01
d647f9c to
542aa47
Compare
anyxem
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release plan after merge
*-v*(admins only)sdk-v1.1.0on main → dispatch publish workflow → staged publish → 2FA approval on npmjs.com