Skip to content

Release v0.15.0 - xchain-sdk - #7

Merged
jdogresorg merged 21 commits into
masterfrom
release/v0.15.0
Sep 7, 2026
Merged

Release v0.15.0 - xchain-sdk#7
jdogresorg merged 21 commits into
masterfrom
release/v0.15.0

Conversation

@jdogresorg

Copy link
Copy Markdown
Contributor

Cuts xchain-sdk for the v0.15.0 release train. 20 commits from master.

Added

  • sdk.waitForContractState(), sdk.waitForContractBalance() and the bound contract.waitForState() / contract.waitForBalance() wait on a contract's own state instead of on transaction confirmation.
  • submitAction({ awaitContract }) gates a contract action on that state before returning, so a deposit cannot be settled before the indexer has executed it.
  • sdk.onXcall(callId, cb) follows one cross-chain call's completion, expiry and initial snapshot.
  • An encoderApiKey option (with an ENCODER_API_KEY env fallback) sends x-api-key, so the SDK can reach an xchain-encoder whose operator set API_KEY.

Fixed

  • onAddress delivers the XCALL_COMPLETED and XCALL_EXPIRED frames the explorer routes to an address instead of dropping them.
  • A MuSig2 sessionId is single-use: every repeat is refused, so one secret nonce can no longer be re-issued and spent in two signing sessions.
  • A malformed SDK_API_RATE_LIMIT such as 0junk or 0.5 falls back to the 300-request default and warns at startup instead of silently disabling the helper API's rate limiter; an exact 0 is still the only off switch.
  • The 64-bit value validator rejects -0.5 and +/-Infinity with stock bitcoinjs-lib's own error strings instead of a fractional-component message or a native BigInt conversion error.

Fixed

  • An explicit unconfirmed policy is honoured and hand-picked inputs are completed.
  • A submit change output is registered so consecutive session submits chain.
  • Preflight no longer files actions with nothing to verify under "Could not verify"; the batch handler is re-pinned and the indexer map re-anchored.

Changed

  • DEPOSIT, EXECUTE and WITHDRAW default to strictStatus, refusing to resolve on an action row the indexer has not yet written a status for.
  • The light client fetches an explorer validator set once per checkpoint height per session instead of once per proof.
  • The stripped-globals module is vendored from the VM and guarded against drift.
  • The vendored coin registry is resynced from the hub.

Levelling develop with master, the ceremony step that closes a train. Commits
that reach master without coming back leave the branch all work lands on missing
what shipped, so the next diff off develop reads as though it never happened.
The handler now reports a guard-inert controller refusal inside a probed BATCH as unjudged rather than passing a raw invalid status through, which the client already consumes as a shape. No check module moves; the map records the review.
…ld not verify"

The Tier-2 fallback checker emitted an unverified entry for BROADCAST and
MESSAGE reading "no chain-state preconditions beyond format". The wallet
renders that list under a "Could not verify (N)" heading, so a statement
that there was nothing to check read as a failure; a testnet user reported
it as an error. Those two actions now map to null and the checker emits
nothing for them. Three tests cover the two silent actions and one that
still declares itself unverified.
… frames

The explorer began serving a cross-chain call channel and emitting
XCALL_COMPLETED / XCALL_EXPIRED, and the SDK never followed, so two things were
broken in the quiet way: the Broadcaster routes every lifecycle event to the
address channel of each address it names, so those frames reached an onAddress
subscriber and were dropped for want of a registered handler; and the channel
itself had no typed wrapper, leaving consumers to hand-roll the call_id
correlation.

onXcall keys on the 64-hex call_id and lower-cases it, because the explorer
normalizes at subscribe time and routes on the normalized key: the upper-case
form would otherwise hold a valid subscription that receives nothing, which
reads as a call that never resolved. It registers no SNAPSHOT handler because
that channel's subscribe accepts call_id/call_ids and serves no snapshot.
…ards

One wave of the review round on the xchain-platform board.
Every change was re-derived from the code rather than applied from the
finding recommended option, and each carries a control that reproduces
the original failure.

Review findings: 0ee5c104e642 6431 6480 6481 6482 6524 6532
The round's #6388 spec-path pointer swaps touched all 13 xchain-contracts
templates, leaving the SDK's vendored copies drifted; `npm run sync:templates`
re-vendors them (4 templates + 5 patterns).

Also re-syncs checkpoint_commitment_activation.js to the corrected header
(#6494), which is byte-identity gated across five repos.
…in the handler map at 334d8117

The indexer now rejects, behind dispenser_amount_positivity_activation, a self-priced
dispenser whose GET_AMOUNT is not a positive, well-formed amount (a native-coin price
is checked against COIN_DECIMALS, which only the token-priced path had), and at
settlement a fill count that is not strictly positive. checks/dispenser.js warns on
both shapes with the inputs the client holds: the create's own fields, and the
resolved dispenser's stored price for a DISPENSE. Warnings, not errors, on the same
reasoning as the GIVE_AMOUNT rule beside them: the handler reads the block's
consensus time and pre-flight has none.

INDEXER-MAP.md re-pins issue.js (docblock only), dispenser.js and dispense.js at
indexer 334d8117 and records the review; the drift gate reads 11 handlers in sync.
Every balance proof that falls through to the explorer's /verify tier
re-fetched the same validator set for the same checkpoint height, so a
wallet with five addresses on three chains issued fifteen identical reads
per session under the explorer's checkpoint limiter. The set for a closed
checkpoint never changes, so it is cached per (explorer, coin, height) for
the process lifetime: the fetch promise is cached so concurrent proofs
share one in-flight read, the map is bounded to 64 entries, and a rejected
fetch is evicted so a transport blip is not pinned. clearValidatorSetCache()
is exported for tests and embedders.
…d input

The wallet annotated its pre-flight report as an SDK type index.d.ts did not
declare. Declaring it naively made the annotation resolve to a type its own
producers violate, so the fix is a split rather than a declaration.

PreflightReport is now stated as sdk.preflight()'s return and explicitly not
what every consumer accepts: _stampedAt is declared with its lifecycle
rationale (isStale reads it and every engine report carries it), and elapsedMs
is required. The wallet's panel keeps a separate, wider accepted-input typedef,
because a restricted partial producer is a design the constants doc already
blesses by name, and an AST producer census found a second non-restricted wallet
producer that fails the engine type too.

Evidence is seven tsc probes over byte-verbatim AST-extracted literals: the
engine literal is red against the previous declaration and green against this
one, and this type deliberately stays red against the wallet producers, which is
the positive proof the split is real.

Also fails the vendored-copy parity guard closed when a canonical file is
missing, instead of dying inside sha256 with a raw ENOENT that reads as a broken
harness.

Review round 7 findings #6874, #6865, #6830.
Review-round fixes. Each claim was re-verified against current code and each
remedy re-derived rather than applied as recommended.

Two MCP handlers broke the SDK's positional path-builder contract, rendering
"/executions/42/[object Object]" and "/attestations/42/undefined" and dropping
pagination; both now pass the contract in the type slot and refuse a query
without its required type before the client is reached. Neither signing path
bound the OP_RETURN carrier of the transaction it actually signs to the action
that was authorized, so a substituted payload could be signed and a smuggled
action could ride an envelope past co-signer policy.

Suite: 4194 passing, 0 failing.
…amount rule

The indexer grew a leg-amount consolidation rule behind a flag day: above the
threshold a leg whose raw amount fails its tick's format is held out of the
merge, so it reaches the per-leg check instead of being summed into a total that
passes. Two 0.5 legs of a 0-decimals token merged to '1' and settled while
either leg alone was rejected.

That is client-visible and it WIDENS rejection, so pre-flight would have
predicted acceptance for a send the chain now rejects. checks/send.js does no
decimals-aware per-leg format check today: isValidAmountFormat is called from
mint, issue and dispenser and from nowhere in that module.

It gains a declared-unverified aspect rather than a mirrored error, and that is
the whole review. Deciding the rule needs the tick's DECIMALS and the activation
state of the block that will carry the action. Mainnet is on the unarmed
sentinel while testnet and regtest run from genesis, so raising the error
unconditionally would reject on mainnet what mainnet still accepts. That is the
false-block the module's contract forbids and the regression validator.js:396-404
records shipping once already. Re-mirror it as a real error when mainnet is
armed, alongside a decimals lookup the SDK can actually read.

BATCH owes nothing: its only change is a documentation path in a comment.

Three rows re-pinned, anchor and review command both moved, review log entry
added. Verified: drift gate reports 11 mapped handlers in sync; preflight and
validator suites 569 passing, 0 failing.
…elling rule

Two indexer handlers changed validity logic and the drift gate held the push
until the client side was read.

SEND, the gated-handoff match, NARROWS rejection. The gate compared the sibling
MESSAGE's raw wire DESTINATION against the SEND's. Siblings hold unresolved wire
params and MESSAGE.DESTINATION is compaction-eligible while SEND.DESTINATION is
multi-valued and never compacted, so one address spelled two ways was recorded
invalid with the fee spent. Above the flag day a caret spelling resolves first,
fail-closed on a rejected reference.

This SDK is the composer that triggers it: addressRefFields.js lists
MESSAGE.DESTINATION as single-valued with no opt-out and the batch builder
compacts by default, so an SDK-composed gated handoff carries exactly the
spelling that pairs only where the flag day is armed, and mainnet is unarmed. So
a declaration is owed even though no false block exists today. It is declared,
not mirrored: predicting the rule needs the including block's activation state,
and mirroring the old wire compare as an error would reject on testnet and
regtest what those planes accept.

DISPENSE, the payment tally scale, moves BOTH ways. An 8-decimal tally rounded a
token-denominated amount, so a sub-half-satoshi charge rendered zero and every
dispenser behind the paid address filled off one payment, while a charge just
above half rounded up and refused a sibling the buyer had paid for. Nothing is
owed on the client side: the payment is a transaction output that does not exist
at pre-flight, and all three DISPENSE checks read the untouched GIVE side. The
standing settlement declaration gains a stanza naming the scale so its rationale
stays current.

Both hashes re-pinned, anchor and review command moved together, review log
entry added. Drift gate reports 11 mapped handlers in sync; preflight and
validator suites 569 passing, full unit suite 4194 passing, 0 failing.
@jdogresorg jdogresorg changed the title Release v0.15.0 Release v0.15.0 - xchain-sdk Sep 7, 2026
…the shipped tree

The production audit went red without a commit here: advisories were published
against qs, fast-uri and hono after the same tree passed earlier the same day.
The floors in the overrides block exist for exactly this, so each moves to the
first fixed release, and hono gains a floor it did not have.

All three land on versions already resolved elsewhere in the platform, so this
is the resolution the rest of the tree is proven on rather than a new one. The
remaining advisories sit below the job's threshold and are unchanged.
The repo publishes two packages on the train version stream, and the bump
reached only the root one: mcp/package.json still declared 0.12.0 and depended
on ^0.12.0, a range that does not admit the version this train publishes, so an
mcp install would have resolved the superseded SDK.
@jdogresorg
jdogresorg merged commit c7ffcec into master Sep 7, 2026
7 checks passed
@jdogresorg
jdogresorg deleted the release/v0.15.0 branch September 7, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant