Skip to content

SDK parity: contract-call funding under-provisions for merge/covenant inputs (6 non-TS tiers) #144

Description

@icellan

Follow-up to #142, which fixed this in the TypeScript SDK only. The same latent bug very likely
exists in the call-transaction builders of the other six SDK tiers that support multi-contract-input
(merge) calls (Go, Rust, Python, Zig, Ruby, Java).

The bug

The funding coin-selection (selectUtxos / estimateDeployFee and equivalents) models only the P2PKH
funding inputs (148 B each) + the continuation output + change. It is blind to the contract /
covenant input(s) being spent
in the same transaction. For a MERGE, each covenant input embeds both
parent transactions as method args (tens of KB), so ignoring them under-provisions the funding:
buildCallTransaction then sees change <= 0 and drops the change output, and the merge covenant —
which reconstructs [continuation][P2PKH change] unconditionally — fails its hashOutputs
OP_VERIFY. The merge transaction is invalid and the covenant becomes un-spendable via that path.

The fix (TS reference, PR #142)

  1. Hoist the per-input arg resolution + placeholder unlock scripts above the funding coin-selection
    so their byte sizes feed the fee estimate.
  2. Add an extraInputBytes parameter to estimateDeployFee / selectUtxos (default 0 — pure-deploy and
    funding-only selection are unaffected).
  3. Size the fee against the real serialized per-input bytes of every contract input, plus a
    per-contract-input overhead (≈ 2 * lockingScriptLen + buffer) covering the stateful unlock's
    codePart + BIP-143 preimage scriptCode. Over-provisioning is safe (slightly larger change; no dust
    limit on Chronicle); under-provisioning was the bug.

See packages/runar-sdk/src/contract.ts (prepareCall) and packages/runar-sdk/src/deployment.ts
(estimateDeployFee / selectUtxos).

Tiers to audit + candidate files

Only tiers that support additionalContractInputs (merge) calls are affected; single-input stateful/
stateless calls add a correspondingly small term and are unaffected.

  • Gopackages/runar-go/sdk_deployment.go (SelectUtxos), sdk_types.go, call builder
  • Rustpackages/runar-rs/src/sdk/contract.rs (select_utxos), mod.rs
  • Pythonpackages/runar-py/runar/sdk/contract.py (select_utxos), calling.py
  • Zigpackages/runar-zig/src/sdk_contract.zig, sdk_types.zig
  • Rubypackages/runar-rb/lib/runar/sdk/types.rb (select_utxos), contract.rb
  • Java.../runar/lang/sdk/UtxoSelector.java, FeeEstimator.java, TransactionBuilder.java

Severity / scope

HIGH for tiers used to build merges (produces an invalid, un-spendable merge tx); tiers without
merge-call support are unaffected. This is tier-local fee/coin-selection ergonomics, not a
wire-protocol primitive per CLAUDE.md — so per-tier correctness, not a cross-tier byte-parity gap. Each
affected tier should get the multi-contract-call regression coverage #142 exercised in TS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions