Release v0.15.0 - xchain-sdk - #7
Merged
Merged
Conversation
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.
…t drift from the VM
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.
…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
force-pushed
the
release/v0.15.0
branch
from
September 7, 2026 02:45
d4e2e76 to
45700cf
Compare
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.
Cuts xchain-sdk for the v0.15.0 release train. 20 commits from master.
Added
sdk.waitForContractState(),sdk.waitForContractBalance()and the boundcontract.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.encoderApiKeyoption (with anENCODER_API_KEYenv fallback) sendsx-api-key, so the SDK can reach an xchain-encoder whose operator setAPI_KEY.Fixed
onAddressdelivers the XCALL_COMPLETED and XCALL_EXPIRED frames the explorer routes to an address instead of dropping them.sessionIdis single-use: every repeat is refused, so one secret nonce can no longer be re-issued and spent in two signing sessions.SDK_API_RATE_LIMITsuch as0junkor0.5falls back to the 300-request default and warns at startup instead of silently disabling the helper API's rate limiter; an exact0is still the only off switch.-0.5and+/-Infinitywith stock bitcoinjs-lib's own error strings instead of a fractional-component message or a native BigInt conversion error.Fixed
Changed
strictStatus, refusing to resolve on an action row the indexer has not yet written a status for.