Build and sign covenant spends from JavaScript - #128
Open
lukachi wants to merge 2 commits into
Open
Conversation
A wallet driving this SDK from JavaScript could not express several things a covenant spend needs, and one of them was silently wrong. - Issuance is exposed on both input shapes, so a transaction that creates an asset can be assembled from JavaScript. - A contract's parameters carry their declared types across the boundary, instead of arriving as untyped values the caller had to guess at. - A covenant being spent is rebuilt from the same parts it was committed to, so the leaf that is revealed matches the one in the tree. - A transaction can declare the height it may not be mined before, which a timelocked covenant requires. A zero height is treated as no height, because writing zero into an input reads back as a constraint that can never be met and refuses every spend. - A covenant that refuses now says what the transaction declared, naming the locktime and the sequence, so a refusal can be diagnosed without a debugger.
lukachi
force-pushed
the
humid/wasm-issuance
branch
from
August 20, 2026 12:32
783ef77 to
ee09113
Compare
Arvolear
force-pushed
the
humid/wasm-issuance
branch
from
August 21, 2026 12:17
70a0682 to
be24fed
Compare
Arvolear
reviewed
Aug 21, 2026
| } | ||
|
|
||
| /// An id as it is written turned into the bytes it is made of, which run the other way. | ||
| fn read_id(written: &str) -> Result<[u8; 32], String> { |
Member
There was a problem hiding this comment.
This and the next functions are super weird. What do they do? Do they have to be in the root of the file?
Arvolear
reviewed
Aug 21, 2026
| /// rather than assumed, because the wallet derives the same asset for itself and two | ||
| /// derivations that agree only because neither was told anything have not agreed about | ||
| /// anything. | ||
| fn issuer_contract(written: Option<&str>) -> Result<[u8; 32], String> { |
Member
There was a problem hiding this comment.
This has nothing to do with the contract. This is just a 32-byte randomness.
Arvolear
reviewed
Aug 21, 2026
| } | ||
|
|
||
| /// The module's account of one issuance, in the form ids are written in. | ||
| fn issuance_report(details: &IssuanceDetails) -> IssuanceReport { |
Member
There was a problem hiding this comment.
Is this IssuanceReport constructor?
Arvolear
reviewed
Aug 21, 2026
| /// # Errors | ||
| /// Returns an error if the source does not parse or does not type-check. | ||
| #[wasm_bindgen(js_name = contractParameterTypes)] | ||
| pub fn contract_parameter_types(source: &str) -> Result<String, JsError> { |
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.
A wallet driving this SDK from JavaScript could not express several things a covenant spend needs, and one of them was silently wrong. Each change below was found while performing a deployed SimplicityHL lending protocol end to end on Liquid testnet from a browser extension: seven distinct actions, all confirmed on chain.
Issuance on both input shapes
addWalletIssuanceInputandaddContractIssuanceInputare exposed to JavaScript, so a transaction that creates an asset can be assembled from the browser. Both report the asset id, entropy and reissuance token they derived, which lets the caller compare them against its own derivation instead of trusting one side.Parameter types across the boundary
contractParameterTypes(source)answers what a contract's parameters are declared as, from the source alone and before there are any arguments to build with. SimplicityHL declares no type at a parameter site — the type checker works it out from the position the parameter is used at — so the compiler is the only thing that can say, and a caller was previously left guessing at the encoding.A covenant is rebuilt from the parts it was committed to
Contractnow takes the extra taproot leaves and the build mode alongside the source and arguments. All four decide the script a covenant locks to, so building with only the first two derives an address for a different contract — one whose spend the covenant refuses at execution, after signing.A transaction can declare its lock height
FinalTransaction::set_locktime_height(setLocktimeHeightin wasm) writes the height onto every input that does not already carry one. A covenant branch guarded bycheck_lock_heightreads the transaction's own locktime, and one that declares none satisfies no such branch.The bug this fixes is in
PartialInput::to_input. It wroteSome(Height(0))intorequired_height_locktimewhenever an input carried no lock height of its own. PSET reads a present-but-zero height as a constraint rather than as its absence, solocktime()returned zero for the whole transaction and every timelocked spend failed. A zero height is now treated as no height:A refusal that can be diagnosed
SignerError::CovenantExecutioncarries the transaction's locktime and the input's sequence in its message. Both decide whether a timelocked branch runs, and without them a refusal is indistinguishable from any other execution failure.Checks
cargo check --workspace --all-targetsandcargo test -p smplx-sdkpass on this branch, rebased ontodevat c9cae5d. The nineteen SDK tests include two new ones pinning a declared transaction locktime and its absence throughextract_pst.