diff --git a/README.mediawiki b/README.mediawiki index 6240aca..04beb4e 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -109,6 +109,13 @@ Having an ELIP here does not make it a formally accepted standard until its stat | Tom Trevethan | Standards Track | Draft +|- style="background-color: #ffcfcf" +| [[elip-0205.mediawiki|205]] +| Wallet +| Transaction Manifests for Simplicity Contracts +| stringhandler +| Standards Track +| Draft |} diff --git a/elip-0205.mediawiki b/elip-0205.mediawiki new file mode 100644 index 0000000..a0886cd --- /dev/null +++ b/elip-0205.mediawiki @@ -0,0 +1,1018 @@ +
+ ELIP: 205 + Layer: Wallet + Title: Transaction Manifests for Simplicity Contracts + Author: stringhandler+ +==Introduction== + +===Abstract=== + +This ELIP defines the '''transaction manifest''', a JSON document that describes the transactions of a multi-step protocol on Elements: which UTXOs each transaction consumes, which outputs it creates, and which witnesses are needed to satisfy each input. A wallet that understands the format can execute any protocol described by a manifest, without hard-coded knowledge of that protocol. + +The format was designed for driving [https://github.com/BlockstreamResearch/simplicity Simplicity] contracts, where a spend must often recreate covenant outputs with particular amounts, assets and scripts, and where existing descriptor and PSET tooling gives no help with transaction construction. It is not limited to Simplicity, or to covenants, but they are the main use case. + +A manifest declares the covenant scripts (''UTXO types'') a contract uses, optional ''contract templates'' that group typed fields with the ''actions'' callable against them, and the actions themselves. Two companion files carry per-deployment data: an ''instance file'' holding the field values fixed when a contract was created, and a ''state file'' tracking the contract's live on-chain UTXO set. + +Alongside the transaction description, a manifest carries the signer-facing text a wallet shows before broadcast — an action's+ Comments-Summary: No comments yet. + Comments-URI: TBD + Status: Draft + Type: Standards Track + Created: 2026-07-02 + License: BSD-3-Clause +
intent line and per-leg ui labels — and defines a canonical form so a manifest can be hashed, published and signed under a stable identifier.
+
+===Copyright===
+
+This document is licensed under the 3-clause BSD license.
+
+===Motivation===
+
+Simplicity covenants can express rich spending conditions on Elements, but a covenant program says nothing about ''how'' a wallet should drive it: which UTXOs to select, what each output must contain, which signatures and typed witness values to supply, and how the contract moves from one state to the next. Today every Simplicity protocol needs bespoke wallet integration, so a protocol only works in the wallets whose authors wrote support for it by hand.
+
+Existing tooling does not fill this gap. Output descriptors describe how a wallet spends outputs it owns; they cannot express a multi-step contract in which a spend must recreate outputs with particular amounts, assets, indices and scripts. PSET standardises signing and finalising a transaction that has already been constructed, but not the construction itself. Construction is the part that differs from ordinary wallet behaviour, and it is the part this format describes.
+
+A transaction manifest captures the wallet-facing half of a protocol in one declarative document, in the same way that output descriptors captured the description of ordinary spendable outputs. The format is independent of any particular wallet implementation, and hands off to standard PSET at the signing boundary, so existing Elements wallet machinery can be reused.
+
+==Design==
+
+===Overview===
+
+A live contract deployment is described by up to four files:
+
+{| class="wikitable"
+! File !! Naming convention !! Purpose
+|-
+| Manifest || txmanifest.json || Protocol definition (static; shared across all deployments)
+|-
+| Instance file || .instance.json || Per-deployment field values, written by a constructor action
+|-
+| State file || .state.json || Live on-chain UTXO set for this deployment
+|-
+| State history || .state.history.json || Append-only log of every state transition (optional)
+|}
+
+Tools should auto-derive the companion paths from the manifest's location. Instance and state files are written per deployment; a tool MAY write each action's output as a numbered snapshot (.state.1.json , .state.2.json , …) so no earlier state is overwritten.
+
+The manifest declares:
+
+* '''UTXO types''': named covenant scripts (SimplicityHL .simf programs) that outputs lock to and inputs spend, together with the parameter interface each type's address derivation is allowed to read.
+* '''Actions''': transaction recipes. Each action declares typed runtime parameters, an ordered list of input descriptors, an ordered list of output descriptors, per-input witness specifications, and the signer-facing text describing what it does.
+* '''Contract templates''' (optional): typed field declarations plus the actions callable against an instance of the template. An action carrying a create_instance block is a constructor, and writes the instance file.
+* '''Formulas''': small build-time expressions (integer arithmetic, comparisons, boolean logic and a few functions) used for amounts, asset references, blinding factors, taproot leaf payloads and computed witness values.
+
+To execute an action, a tool resolves the parameters, selects inputs from the wallet or the state file, constructs the outputs, builds a PSET, blinds it, satisfies the covenant witnesses, shows the signer the clear-signing preview, signs, broadcasts, and updates the instance and state files. The PSET is the boundary between manifest-level reasoning and standard Elements wallet machinery.
+
+===Rationale and Drawbacks===
+
+JSON was chosen for ubiquity and ease of review. The format is not yet content-addressed for transport; compact verifiable transmission (including via QR) is planned as future work. It does, however, define a canonical form and a tagged hash over it, so a manifest can be published under a stable id and signed (see ''Canonical Form and Manifest ID'').
+
+The format is not tied to Simplicity, or to covenants at all. The script.type field is extensible, and Simplicity is currently the only defined program type. The same model (typed params, UTXO selection, output recipes, per-input witnesses) works for any protocol whose transactions must follow a set shape, whether that shape is enforced by a covenant, by counterparties, or only by convention.
+
+The formula language is intentionally small: no loops, no user-defined functions, integer-only arithmetic. This keeps independent implementations small and evaluation deterministic. Values that are not numbers — asset ids, entropies, script hashes — are read through reference resolution rather than arithmetic, which is why the $-prefixed string forms exist.
+
+The clear-signing metadata (intent, ui) is author-supplied and therefore only as trustworthy as the manifest's own signature chain. It is not a substitute for what a hardware device verifies. It is nevertheless covered by the manifest id, because leaving it out would let an attacker rewrite the confirmation screen while keeping a valid signature — precisely the attack clear signing exists to stop.
+
+===Specification===
+
+The key words "MUST", "MUST NOT", "SHOULD", and "MAY" are to be interpreted as described in RFC 2119.
+
+Two keys are accepted anywhere in the document and carry no meaning for execution: $comment (authoring notes) and $schema (an editor hint). Both are stripped before hashing. Every other unknown key MUST be rejected.
+
+====Top-Level Structure====
+
+manifest_version || string || YES || Semver of the manifest file format, not of any tool that reads it. Current value: "0.2.0".
+|-
+| protocol || string || YES || Kebab-case protocol identifier (e.g. "simplicity-lending")
+|-
+| description || string || NO || Human-readable protocol description. Developer documentation only; it MUST NOT be shown as signer-facing text.
+|-
+| chain || string || NO || The chain these transactions are built for, as a well-known alias or the BIP-122 chain ID it stands for (see below). Default: "liquid"
+|-
+| simplicity_hl || object || NO || SimplicityHL toolchain settings for this manifest's .simf programs (see below)
+|-
+| utxo_types || object || NO || Map of UTXO type name → UTXO type definition
+|-
+| actions || object || NO || Map of action ID → action definition, for actions that require no instance
+|-
+| contract_templates || object || NO || Map of template name → contract template definition
+|}
+
+====Chain Identification====
+
+chain names the chain the manifest's transactions are built for. It is written either as a '''well-known alias''' or as the '''[https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki BIP-122] chain ID''' the alias stands for. The two are exactly equivalent; a tool MUST accept both and MUST treat them as the same value.
+
+A BIP-122 chain ID takes the CAIP-2 form "bip122:" + reference, where reference is the first 32 characters of the network's genesis block hash — the same identifier [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-144] uses for account and asset identification.
+
+{| class="wikitable"
+! Alias !! BIP-122 chain ID !! Network
+|-
+| liquid || bip122:1466275836220db2944ca059a3a10ef6 || Liquid mainnet
+|-
+| liquid-testnet || bip122:a771da8e52ee6ad581ed1e9a99825e5b || Liquid testnet
+|-
+| liquid-regtest || bip122:cc2641af46f536fba45aab6016f63e12 || Elements regtest, default chain parameters
+|-
+| bitcoin || bip122:000000000019d6689c085ae165831e93 || Bitcoin mainnet
+|-
+| bitcoin-testnet || bip122:000000000933ea01ad0ee984209779ba || Bitcoin testnet
+|}
+
+Aliases exist because these files are written and reviewed by hand. "liquid-testnet" is legible at a glance and hard to typo into something else valid; a 32-character hex prefix is neither, and a manifest that quietly names the wrong network is exactly the mistake worth designing against. The BIP-122 form exists because it is unambiguous, extends to networks that have no alias, and is what the rest of the Liquid wallet-integration stack already speaks.
+
+A network with no alias in the table above MUST be named by its BIP-122 chain ID. Tools MUST NOT invent aliases: a name not in this table is either a chain ID or an error, never a guess. New aliases are added by amending this table.
+
+Every value of chain names exactly one network, and a tool MUST reject a manifest naming a network it is not connected to. A manifest whose actions span more than one chain has no spelling in this version; if one is needed it will be added, and adding it later is cheap precisely because there is no value to reinterpret.
+
+Wherever this document refers to behaviour "on Liquid" or "on Bitcoin" — blinding defaults, the policy asset — the distinction is between an Elements-family chain and Bitcoin, decided by the identified network.
+
+====SimplicityHL Toolchain Settings====
+
+simplicity_hl describes how the .simf programs are compiled, as distinct from what the protocol does.
+
+{| class="wikitable"
+! Field !! Type !! Default !! Description
+|-
+| debug_symbols || boolean || false || Whether covenant programs are compiled with debug symbols. This changes every program's CMR, and therefore every covenant address, because assert!/panic! embed source information into fail-node commitments. It MUST be set to match the toolchain of any protocol the manifest interoperates with.
+|-
+| unstable_features || array of string || [] || Unstable SimplicityHL compiler features the programs may use — the manifest form of simc -Z . Defined names: "imports" (module syntax) and "enums" (enum declarations and EnumName::Variant matches). Enabling a feature only lifts a syntax restriction; it never changes generated code, and therefore never changes a CMR or an address.
+|}
+
+There is deliberately no compiler-version field. SimplicityHL carries its own simc ""; source directive, which the compiler enforces across the entry file and every reachable dependency; a second declaration here could only disagree with it.
+
+====UTXO Types====
+
+A UTXO type names a covenant script that outputs lock to and inputs spend from. Inputs reference a UTXO type via utxo_source; outputs lock to one via destination; the state file tracks live UTXOs by their type name.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| description || string || YES || Human-readable purpose
+|-
+| script || object || NO || The covenant program (see below)
+|-
+| params || object || NO || This type's parameter interface; map of name → UtxoParamDef (see below)
+|-
+| asset || string || NO || Default asset locked in this UTXO type; a selection/validation hint. Same syntax as the input asset field.
+|-
+| state_vars || object || NO || Named state variables, each with a default_value, referenced from a taproot leaf payload by {"state_var": "name"}
+|}
+
+
+'''Script definition:'''
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| type || string || YES || Covenant program type. Currently only "simplicity".
+|-
+| source || string || NO || Relative path to the .simf source file
+|-
+| compile_params || object || NO || Wires the program's own .simf parameters to manifest values (see below)
+|-
+| extra_leaves || array || NO || Additional taproot leaves folded into the tap tree (see ''Taproot Leaves'')
+|}
+
+'''Compile-param wiring.''' A .simf program declares its own compile-time parameters, and the covenant's address cannot be computed until every one is given a value. The compile_params map supplies them: each '''key''' is a parameter name from the .simf, and each '''value''' is what to bind it to.
+
+script''', for values fixed across every use of the covenant. A value is a string literal (e.g. "1", "true"), a bare name resolved as an instance field, or a params.X reference resolved against the type's own params interface when one is declared.
+* '''Inline at a reference site''', on an input's utxo_source or an output's destination, for values known only per-action. Inline values are formula expressions in the action's context (e.g. params.pubkey).
+
+compile_params supplement, and override, any bound on the UTXO type's own script.
+
+'''The params / args boundary.''' Declaring params on a UTXO type closes its scope: script.compile_params and extra_leaves then resolve params.X against ''those'' params and nothing else. A site binds them with args, whose values are expressions evaluated in the ''action's'' scope:
+
+EXPIRY reaches the program as a compile-time constant, so it changes the CMR. STATE is hashed into a tapdata leaf, so it changes the tap tree. Neither is stored anywhere: the state ''is'' the address.
+
+That is also why Settle binds args twice. The input and the output name the same utxo_type, and the two STATE bindings derive two different addresses — spending the live market and paying the settled one. EXPIRY is bound at neither site, so both fall back to its default and land on the same value, which is what keeps the two addresses two states of one market rather than two unrelated covenants.
+
+Each UtxoParamDef carries a type (required; the same type vocabulary action params use), an optional description, and an optional default. A default is evaluated in '''instance scope''' — a literal, or instance.X naming a field fixed when the contract was instantiated. Action scope is deliberately unreachable there: a value that varies per run is exactly what a site must bind explicitly. Every param without a default MUST be bound by every site, and a tool MUST reject a site that binds a name the type does not declare, or that supplies args to a type with no params interface at all.
+
+A UTXO type without a params interface keeps the legacy behaviour, in which leaves and compile params resolve against whatever is ambient at each mention. New manifests SHOULD declare the interface.
+
+====Taproot Leaves====
+
+extra_leaves appends data leaves to the Simplicity program leaf, which is how a covenant carries mutable state in its own address. Each leaf is:
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| type || string || YES || Hashing scheme. Only "tapdata" is defined.
+|-
+| payload || array || YES || Ordered payload items, concatenated into the leaf's byte string
+|}
+
+A tapdata leaf hashes its payload as SHA256(SHA256("TapData") ‖ SHA256("TapData") ‖ payload) — the value a program computes with jet::tapdata_init(), sha_256_ctx_8_add_* and finalize — and the resulting leaves are folded into the tap tree in declaration order, matching jet::build_tapbranch.
+
+A payload item is one of:
+
+* a hex string literal taken as raw bytes ("0x01"), whole bytes only;
+* a computed value: {"value": , "type": "u8"|"u16"|"u32"|"u64"|"bytes32"|"bytes"|"pubkey", "endian": "le"|"be", "pad_to": , "align": "left"|"right"} . Integers default to little-endian; pad_to zero-pads to that total width, on the left when align is "right" (the default) and on the right when it is "left";
+* {"state_var": "name"}, whose default_value from the type's state_vars is encoded as a single u8.
+
+The payload's width MUST match what the .simf hashes: sha_256_ctx_8_add_32 wants exactly 32 bytes, add_8 exactly 8. A mismatch yields a perfectly valid address that the covenant then refuses to recognise as its own.
+
+description || string || NO || Human-readable purpose
+|-
+| fields || object || NO || Field declarations; map of name → FieldDef (type required, plus optional description and default). Names and types only — values are set by a constructor.
+|-
+| actions || object || NO || Actions callable on an instance of this template
+|}
+
+Template actions are structurally identical to top-level actions; the only difference is that they run against an instance, so their formulas may reference instance.*. An action carrying a create_instance block is a constructor for the template it is declared in. There is no separate flag, and the template is not named inside the block: create_instance is legal only inside contract_templates..actions.* , and always creates a .
+
+description || string || NO || Human-readable action purpose. Developer documentation; it MUST NOT be used as signer-facing text.
+|-
+| intent || string || NO || One-line statement of what this action does, shown as the first clear-signing screen (see ''Clear-Signing Metadata'')
+|-
+| params || object || NO || Runtime action parameters; map of name → ParamDef
+|-
+| inputs || array || NO || Ordered list of input descriptors
+|-
+| outputs || array || NO || Ordered list of output descriptors
+|-
+| allow_change || string || NO || "none" (default), "lbtc_only", or "any" (see below)
+|-
+| create_instance || object || NO || Constructor only: the new instance written after broadcast
+|-
+| on_pre_broadcast || object || NO || Hook run after inputs are resolved, before the PSET is built
+|-
+| on_post_broadcast || object || NO || Hook run after broadcast
+|}
+
+'''allow_change.''' Every output a transaction carries must be written in the manifest; the network fee is the single exception, because it has no manifest spelling. A change output is not an exception — its address and amount are chosen by the tool, so adding one silently moves value to a destination the manifest never named. The default is therefore "none": a surplus in any asset, L-BTC included, is an error, and the action must size its inputs to what it spends. "lbtc_only" permits an L-BTC surplus to be returned to the wallet, which is what ordinary funding actions want because the fee is only known once the size is; a surplus in any other asset remains an error. "any" permits a surplus in any asset. This governs ''undeclared'' change only: an output with "destination": "change" is declared, and permits change for its own asset regardless of the setting.
+
+'''Parameter resolution.''' An action's params are the values that vary from one run to the next. They are '''not''' necessarily prompted for: a manifest describes what an action needs, and it is up to the tool driving it to decide where those values come from. A conforming tool MUST resolve each parameter in this order, and MUST NOT prompt for one an earlier step has already supplied:
+
+# '''compute''', where the ParamDef declares one. Includes {"type": "hook"}, which defers to a hook later in the same run.
+# '''A caller-supplied value.''' A tool SHOULD accept a flat JSON object mapping parameter names to string values, passed alongside the manifest. This is the path an application drives: a dapp collects whatever input its own interface calls for, and hands the wallet the manifest, the action name and the finished parameter map, so the wallet's only job is to render the clear-signing screens and get an authorisation. An action all of whose parameters arrive this way executes without any prompt at all.
+# '''An interactive prompt''', pre-filled with default. This is the fallback for a tool driving a manifest directly — a CLI, or a wallet opening a manifest with no application in front of it — and it is why description and default exist on a ParamDef. A tool that cannot prompt MUST fail rather than guess.
+
+Nothing in the format requires a wallet to build a bespoke interface per protocol: a parameter's type and description are enough for a generic prompt, and an application that wants more than a generic prompt supplies the values itself.
+
+The name params is kept, rather than args, because args already names a different binding in this format: the values a reference site supplies for a utxo_type's declared params interface. The two are deliberately distinct — action parameters vary per run, args bind an address derivation at one site — and reusing one word for both would hide that.
+
+'''ParamDef:'''
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| type || string || YES || A type string from the table below
+|-
+| description || string || NO || Human-readable purpose
+|-
+| default || string || NO || Default value, pre-filled when the user is prompted
+|-
+| compute || string or object || NO || How the value is derived. When present the user is never prompted.
+|}
+
+'''Compute specs.''' A compute spec is either a bare expression string — "instance.PRINCIPAL_AMOUNT * 2", "$params.FOO" — or a structured object dispatched on type. The same shape is used for ParamDef.compute, for create_instance.fields values, and for hook set values.
+
+{| class="wikitable"
+! type !! Fields !! Resolves to
+|-
+| "expr" || expr || Arithmetic over other params and fields; pow(base, exp) is available
+|-
+| "tapleaf" || simf, optional params, depends_on, extra_leaves || Compiles a .simf and returns its Simplicity tapleaf hash (32 bytes, hex)
+|-
+| "script_hash" || address || sha256(scriptPubKey) of an address — exactly what the Simplicity output_script_hash / input_script_hash jets return for a UTXO paying it
+|-
+| "simf_fn" || simf, optional fn, input, compile_params || Calls a named function in a .simf and uses its return value
+|-
+| "wallet" || wallet || A value taken from the executing wallet (see below)
+|-
+| "hook" || — || Declares that a hook later in this run supplies the value
+|}
+
+The legacy key lang is accepted as an alias for the type discriminator.
+
+For "tapleaf", params gives an explicit map of simf param name → {"value": , "type": } ; omitting it passes all current compile params. depends_on names the subset the program actually consumes, so the leaf can be computed as soon as those are resolved rather than waiting for all of them — which is how apparent circular dependencies are broken. extra_leaves folds storage leaves into the tap tree before hashing, so the computed value is the script hash of the address ''with'' its storage leaves.
+
+For "script_hash", an address and its script hash are two views of one destination: the covenant commits to the hash, the transaction pays to the address, and if they ever disagree the spend fails on-chain. Deriving one from the other is the only way to keep them in step. Blinding is irrelevant — a confidential address has the same scriptPubKey as its unconfidential form.
+
+For "wallet", the wallet field selects which value:
+
+{| class="wikitable"
+! Value !! Resolves to
+|-
+| "key" || The wallet's x-only BIP340 pubkey. The wallet chooses the derivation path; the manifest does not constrain it, and MUST NOT be written to assume one. Where a protocol needs a particular key rather than whichever the wallet offers, that key is an action parameter of type pubkey, supplied by its holder.
+|-
+| "script_hash" || sha256(scriptPubKey) of the wallet's index-0 explicit output — the committed payout target a covenant checks repayment against
+|-
+| "address" || The explicit address matching "script_hash". The two are a pair and MUST be derived together.
+|}
+
+A "hook" spec declares a value an on_resolved or on_pre_broadcast block will set. It exists so a hook cannot invent an identifier: without the declaration a typo'd target fills a slot nobody reads and surfaces much later as a wrong covenant address. It also carries the type that byte-order handling depends on.
+
+u8 / u16 / u32 / u64 || u8/u16/u32/u64 || Unsigned integers
+|-
+| bool || bool || Boolean ("true" / "false")
+|-
+| bytes32 || u256 || 32-byte raw value
+|-
+| pubkey || u256 || 32-byte x-only BIP340 Schnorr public key, not byte-reversed
+|-
+| liquid.asset_id || u256 || Liquid asset ID (32 bytes), reversed from display order to internal order
+|}
+
+Asset-ness comes only from a declared liquid.asset_id type. A tool MUST NOT infer it from a parameter's name, because the inferred value would silently skip the required byte reversal.
+
+'''Param-only type.''' ParamDefs MAY additionally use address, a Liquid/Elements address supplied at build time. Unlike the types above it is resolved wallet-side and is '''not''' compiled into a covenant; its typical use is a params.NAME reference as an output destination, letting the caller choose where funds go:
+
+id || string || YES || Unique identifier within this action
+|-
+| description || string || NO || Human-readable purpose (developer documentation, not signer-facing)
+|-
+| utxo_source || string or object || YES || How to locate this UTXO (see below)
+|-
+| asset || string || NO || Required asset ID (see below)
+|-
+| amount_sat || number or object || NO || Required amount or constraint (see below)
+|-
+| from_address || string || NO || For "wallet" inputs: constrain coin selection to UTXOs whose scriptPubKey equals this address's. A literal address or a reference resolving to one. Use it to pin an input to a committed address — e.g. so a covenant's collateral is spent from the exact address whose hash it commits to.
+|-
+| required_index || integer || NO || Required transaction input index. Non-negative = absolute (0-based); negative = relative from end (−1 = last).
+|-
+| optional || boolean || NO || If true, the transaction proceeds even if this UTXO is not found. Default: false.
+|-
+| issuance || object || NO || Create or reissue a Liquid asset on this input (see below)
+|-
+| blinding || object || NO || The blinding factors the spent covenant UTXO was created with (see ''Blinding and Confidentiality'')
+|-
+| sequence || number or object || NO || Sets nSequence for BIP68 relative timelocks (see below)
+|-
+| witnesses || object || NO || Per-input witness map (see ''Witnesses'')
+|-
+| on_resolved || object || NO || Hook evaluated once this input's UTXO and issuance attributes are known (see ''Hooks'')
+|-
+| ui || string or object || NO || Clear-signing hint for this leg (see ''Clear-Signing Metadata'')
+|}
+
+'''utxo_source''' takes one of these forms:
+
+{| class="wikitable"
+! Value !! Type !! Description
+|-
+| "wallet" || string || Any wallet-controlled UTXO, selected by asset + amount
+|-
+| {"utxo_type": "name"} || object || Covenant UTXO looked up by type name, from the state file or an equivalent discovery mechanism
+|-
+| {"utxo_type": "name", "args": { ... }} || object || As above, binding the type's declared params interface for this site
+|-
+| {"utxo_type": "name", "compile_params": { ... }} || object || As above, with inline compile_params supplying values known only per-action
+|}
+
+When utxo_source is a covenant type, the input's script is derived from the named UTXO type's script definition; inline compile_params supplement and override those declared on the type's own script.
+
+provided_inputs map, keyed by the input's id. A pinned outpoint's amount and asset are read from the chain, and those on-chain values outrank anything the manifest or an operator supplies.
+
+'''asset''' takes one of these forms:
+
+* "lbtc" or "bitcoin" — the network policy asset;
+* a 64-character hex asset ID, in the display byte order block explorers print;
+* an [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-144] CAIP-19 asset ID — bip122:/elip144: — whose chain part MUST equal the manifest's chain once any alias is expanded;
+* a reference ("instance.NAME" / "params.NAME") resolving to any of the above.
+
+'''amount_sat:''' an exact satoshi value, a formula string evaluated to a u64, or {"min_amount": "expression"} (auto-select a UTXO meeting the threshold).
+
+'''issuance.''' Creates or reissues a Liquid asset as part of the input.
+
+{| class="wikitable"
+! Field !! Required !! Description
+|-
+| kind || YES || "new" for a first issuance, "reissue" to mint more of an existing asset
+|-
+| asset_amount_sat || YES || Asset units to issue, in satoshi denomination; formula or literal
+|-
+| inflation_amount_sat || "new" only || Reissuance token amount; 0 mints a fixed supply with no reissuance rights
+|-
+| entropy || "reissue" || Reference to the issuance entropy of the original mint
+|-
+| issued_asset || NO || Expected asset ID; a check, not an input
+|}
+
+A new issuance derives its entropy from the outpoint it spends, so entropy and issued_asset do not apply to it. After resolution the created ids are available as inputs..issued_asset and inputs..reissuance_token , and the entropy as inputs..issuance_entropy .
+
+A reissuance needs the '''issuance entropy''' of the original mint — fast_merkle_root([sha256d(defining outpoint), contract_hash]), the value the asset id itself is derived from. It cannot be recovered from anything on chain, because the reissuance token UTXO carries no trace of the outpoint that created it. A constructor MUST therefore capture it at the one moment it exists and hand it back later:
+
+issued_asset is optional and is a '''check''': the tool re-derives the asset id from the entropy and MUST refuse to build if the two disagree. An entropy is opaque, and the byte order block explorers print is the reverse of the one used here — without the check, a transposed value still builds a broadcastable transaction that reissues the wrong asset.
+
+Failing that, the entropy MAY come from provided_inputs..issuance_entropy in the instance file. That works, but it travels with an outpoint override that pins the input for ''every'' action sharing its id, long after the pin is correct.
+
+'''sequence.''' Sets the input's nSequence for BIP68 relative timelocks (the check_lock_distance / check_lock_duration Simplicity jets); omitted, the input stays at Sequence::MAX (relative locktime disabled). Forms: {"relative_blocks": ""} (spendable only after N blocks since confirmation, ≤ 65535), {"relative_seconds": ""} (rounded up to 512-second units), or a raw nSequence value (literal or formula).
+
+id || string || YES || Unique identifier within this action
+|-
+| description || string || NO || Human-readable purpose (developer documentation, not signer-facing)
+|-
+| destination || string or object || YES || Where funds are sent (see below)
+|-
+| amount_sat || number or string || NO || Output amount in satoshis; formula or literal. Omitted only where the amount is implied, as on a "change" output.
+|-
+| asset || string || NO || Asset ID; same syntax as the input asset field
+|-
+| required_index || integer || NO || Required transaction output index (same semantics as input required_index)
+|-
+| optional || boolean || NO || If true, output may be omitted. Default: false.
+|-
+| condition || string || NO || Reserved: a formula gating whether the output is created. Declared in the schema but not yet interpreted.
+|-
+| confidential || boolean || NO || Whether this output is blinded (see ''Blinding and Confidentiality'')
+|-
+| blinding || object || NO || Pins this confidential output's blinding factors instead of letting the tool choose them
+|-
+| data || string or object || NO || OP_RETURN payload. Only valid when destination is {"type": "op_return"} or {"type": "burn"}.
+|-
+| ui || string or object || NO || Clear-signing hint for this leg (see ''Clear-Signing Metadata'')
+|}
+
+'''destination:'''
+
+{| class="wikitable"
+! Value !! Description
+|-
+| "wallet" || A fresh wallet receive address
+|-
+| "change" || Wallet change address, amount auto-computed
+|-
+| "params.NAME" / "instance.NAME" || Address resolved from a reference (e.g. a type: "address" param)
+|-
+| a literal address string || That address
+|-
+| {"utxo_type": "name"} || Locks to the named covenant's P2TR address; optionally with args and/or inline compile_params
+|-
+| {"script_hash": ""} || P2TR output built from a 32-byte script hash
+|-
+| {"type": "op_return"} or {"type": "burn"} || OP_RETURN output (asset destroyed); the two forms are synonyms. The payload is the output's own data, or a bare data-less OP_RETURN when data is absent.
+|-
+| {"type": "fee"} || Declares the fee leg; produces no PSET output of its own
+|}
+
+'''OP_RETURN data''' takes either of two forms:
+
+* a concat(a, b, …) expression string, concatenating resolved references and hex literals;
+* an object {"parts": [ … ]} giving an exact binary layout. Each part is {"type": …, "value": } , where u8/u16/u32/u64 encode a fixed-width integer ("endian": "le" by default, "be" available), liquid.asset_id emits 32 bytes in internal (reversed) order, and pubkey/bytes32/bytes emit raw bytes in natural order.
+
+All part types are protocol-agnostic. A fixed byte prefix, such as a protocol id or message-type tag, is just a bytes part whose value is a constant.
+
+====Blinding and Confidentiality====
+
+Confidentiality is declared per output, never per UTXO type, because two outputs paying the same covenant address need not agree: a market's live address can hold blinded reissuance tokens beside an explicit collateral UTXO, because the program introspects one as a Pedersen commitment and the other as a plain amount.
+
+It cannot be derived from destination either, even though a Liquid address written in confidential form does carry a blinding key. None of the destinations this format uses is such an address at the point the decision has to be made:
+
+* a utxo_type destination is a covenant address the tool ''derives'', not one an author supplies. A confidential address and its unconfidential form share the same scriptPubKey, so the derivation has nothing to read the answer off;
+* "wallet" and "change" name an address the wallet has yet to generate, and it can generate either form;
+* {"script_hash": …} is a scriptPubKey commitment, which by construction says nothing about blinding.
+
+That leaves only a literal or params.NAME address, where the supplied form does decide it — and for those the field is simply omitted and the address believed. Everywhere else the manifest has to say, because the covenant's expectation, not the address, is what settles it.
+
+confidential defaults to true for wallet and address destinations on Liquid, and to false for covenant (utxo_type) destinations, where a Simplicity program usually has to read the value and the asset with jets such as current_amount and current_asset, which operate on explicit values. On a Bitcoin chain there is no blinding, and confidential: true MUST be rejected there. OP_RETURN outputs are always unblinded.
+
+'''blinding''' declares the asset and value blinding factors of a single output or input, as {"asset_bf": …, "value_bf": …}. Each factor is a 32-byte scalar written as a small decimal ("1"), a 0x-prefixed hex string of up to 64 characters, a params.X / instance.X reference resolving to either, or simple arithmetic over one ("instance.RT_FACTOR + 1").
+
+A wallet normally draws both factors at random, which is right when nothing but the receiver reads them. It is wrong when a ''covenant'' reads them: a program that checks its own outputs' commitments — requiring, say, that each recreated reissuance token advance both factors by exactly one — can only be satisfied by factors the spender chose deliberately, and Elements' blind_last offers no way to say which. A tool MUST therefore run its own blinding pass whenever this field appears.
+
+'''On an output''' blinding pins what the tool would otherwise choose. Omitting one factor leaves it random; omitting both makes the field a no-op. At least one confidential output MUST keep a free value_bf: a transaction's value blinding factors have to sum to zero and the tool solves the last free one to make that true, so pinning every one leaves the transaction unbalanceable. In practice that free output is the change. Pinning an output's asset_bf to a value an input of the same asset already carries MUST be rejected: the surjection proof would have a zero shift to prove.
+
+'''On a covenant input''' blinding is not a choice but a statement of fact — the factors the UTXO being spent was created with. Both halves are required. They are what lets the tool rebuild the confidential prevout that the sighash and the introspection jets need, and (for a reissuance) the assetBlindingNonce Elements demands. A wrong value is caught before signing, because the rebuilt commitments simply will not be the ones on chain.
+
+A reissuance MUST write the spent token's asset blinding factor as its issuance_blinding_nonce: that is the value Elements rebuilds the token's generator from and byte-compares.
+
+Declaring factors this way makes them public to anyone who reads the manifest, trading the output's confidentiality for reissuability. It hides nothing; it only keeps the commitment well-formed. Elements has no explicit reissuance token, so a token that must stay reissuable must stay blinded, with a factor its next spender can reproduce.
+
+====Witnesses====
+
+Witnesses appear in the witnesses field of an input descriptor. Each key is the SimplicityHL witness name as it appears in the .simf source.
+
+The map MUST name '''every''' witness the input's program declares, and nothing else. Nothing is inferred from an omission: anything left out MUST be an error, both at validation time against the .simf and again at run time against the compiled program. An earlier design zero-filled whatever the manifest left out, which made a mistyped witness name and a deliberate omission produce identical transactions.
+
+A value is either the bare string "unused", or an object carrying a type (and an optional description):
+
+'''"unused".''' Declares a witness this spending path does not depend on, and supplies the zero its pruned branch wants.
+
+'''"simplicityhl" (static SimplicityHL value).''' Fields: value (required; a SimplicityHL value expression — "0x" for byte arrays, "Left(())" / "Right(())" for Either types, tuples and arrays written as SimplicityHL writes them), an optional simplicity_type, and an optional inject array placing computed values inside value (see ''Composite witness values'' below).
+
+'''"Signature" (BIP340 Schnorr signature).''' Fields: sig_type (required; currently only "sig_hash_all") and source ({"type": "wallet", "key": } , where the reference resolves to a 64-hex-char x-only public key). The tool computes the hash, signs with the wallet key matching the given pubkey, and injects the 64-byte signature as a "simplicityhl" witness. This form replaces the '''whole''' witness; a signature that sits inside a composite value is placed with inject instead. "sig_hash_all" denotes Simplicity's own commitment over the whole transaction, computed from the Elements transaction environment — '''not''' the standard Elements SIGHASH_ALL.
+
+'''"taproot_leaf" (leaf selector).''' Fields: source, a compute spec resolving to the tapleaf hash of the branch to spend, e.g. {"type": "formula", "expr": "pre_lock_leaf"}. This is the one exempt entry: it selects which tap leaf to spend, is never read by the program, and so is matched against neither half of the exhaustiveness rule.
+
+Either, or an array — a single AUTH witness of type Either<(u256, u256), ()> carrying both a spend path and the signature that authorises it, say. A bare "Signature" entry cannot express that, because it replaces the whole witness with the 64-byte signature.
+
+For those, a "simplicityhl" witness MAY carry an inject array. value then gives the surrounding value with any well-typed placeholder at each target position, and each inject entry replaces one position:
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| path || array of string || YES || Route from the root of the witness value to the position to replace
+|-
+| value || object || YES || A computed witness spec — the same shapes as a standalone entry, e.g. a "Signature"
+|}
+
+A path segment is "Left" or "Right" to enter an Either branch, or a decimal index to enter a tuple or array position. An empty path addresses the whole witness, which is exactly what a bare "Signature" entry means. A tool MUST check each segment against the witness's resolved SimplicityHL type as it descends, and MUST reject a path that does not match — a route into the wrong branch would otherwise place a valid signature where the program never looks.
+
+value against the witness's type, descends by path, and rebuilds the composite around the replacement. Addressing by path rather than by a placeholder token inside the string keeps a signature from ever being confused with a literal that happens to look like one, and it is the approach the smplx SDK's witness injector already takes.
+
+The reference implementation currently supports only the whole-witness form, so a program that packs a signature into a composite value has to declare that signature as its own witness until inject lands. That constraint belongs to the implementation, not to the format: a manifest MUST NOT be written to depend on a covenant being reshaped to suit it.
+
+====Formula Language====
+
+Formulas are string expressions evaluated by the tool at transaction build time. They appear in input and output amount_sat, in asset and destination references, in blinding factors, in taproot leaf payload values, in compute specs, and in hook values.
+
+'''Operators:''' integer arithmetic + - * / (division truncates); comparisons == != < <= > >=; boolean logic && || !; grouping ( ).
+
+'''References''' (written bare, no $):
+
+{| class="wikitable"
+! Syntax !! Description
+|-
+| instance.NAME || Instance field by name
+|-
+| params.NAME || Action parameter by name
+|-
+| NAME || A bare, unqualified name resolves as an action parameter
+|-
+| inputs.. || A field of a resolved input; the explicit, unambiguous spelling, and the one new manifests SHOULD use
+|-
+| . || Legacy form of the above. It collides with every other namespace — an input named params would shadow one — and cannot be told apart by shape alone.
+|-
+| fee || Estimated network fee (sats). It starts at 0 and is recomputed from the transaction's vsize before signing, so an amount like will_in.amount_sat - fee lands on the right value.
+|}
+
+Input fields are:
+
+{| class="wikitable"
+! Field !! Description
+|-
+| amount_sat || Value of the spent UTXO
+|-
+| asset || Asset ID of the '''spent''' UTXO. On an input carrying a new issuance this is the asset going in, not the one created — a wallet L-BTC UTXO minting a new asset has asset = L-BTC.
+|-
+| issued_asset || Asset ID created by this input's issuance, if any
+|-
+| reissuance_token || Reissuance token ID created by this input's issuance, if any
+|-
+| issuance_entropy || The issuance entropy, hex. Available once the PSET is built, so a constructor can persist it.
+|}
+
+The newly created ids are exposed under distinct names rather than shadowing asset, because getting that wrong is silent and expensive: it writes L-BTC's id into a field meant to hold the asset the transaction just created.
+
+'''Functions:''' pow(base, exp) (integer power), concat(a, b, …) (byte concatenation; OP_RETURN data only).
+
+'''String vs. arithmetic evaluation.''' An unprefixed expression is arithmetic and yields a u64. A $-prefixed reference — $params.X, $instance.X, $inputs.. — is a direct string lookup, and is how a 32-byte value such as an asset id, a script hash or an entropy reaches a compute spec or a hook, since none of those are numbers.
+
+====Clear-Signing Metadata====
+
+An action carries the text a wallet shows the signer before broadcast. It renders as three screens: the action's intent; a net effect, per asset, of what the wallet gains and loses; and a detailed per-leg breakdown grouped by the account each leg touches.
+
+intent is a one-line statement of what the action does. It supports {ref} and {ref:symbol} interpolation against the execution context. An asset-typed reference MUST carry :symbol, so a wallet can substitute a friendly asset name rather than printing a raw id.
+
+ui hint, either a bare label string or an object:
+
+{| class="wikitable"
+! Field !! Type !! Description
+|-
+| label || string || One-line description of this leg, at most 64 characters so it fits one row beside the amount and asset symbol
+|-
+| role || string || Optional semantic tag (e.g. "collateral", "auth_nft", "fee")
+|-
+| group || string || Override the net-effect account heading, otherwise derived from the source or destination
+|-
+| hide || boolean || Suppress this leg from the net-effect diff (e.g. pure protocol data). Default false.
+|}
+
+label is the '''only''' signer-facing text for a leg. description MUST NOT be used as a fallback: it is developer documentation, and it is excluded from the manifest id, so treating it as signer-facing would put unsigned text on a confirmation screen.
+
+This metadata is author-supplied. Its trustworthiness rests entirely on the manifest's own publisher signature chain, not on anything a hardware device verifies.
+
+====Hooks====
+
+A hook is a flat map of setter targets to the values they take. One shape serves every hook position — an action's on_pre_broadcast and on_post_broadcast, and an input's on_resolved — because they differ only in when they run.
+
+instance.NAME sets a contract-template field, and params.NAME sets an action parameter. A tool MUST reject a target that names an undeclared parameter; a parameter a hook fills is declared with "compute": {"type": "hook"}.
+
+Values are compute specs, the same type create_instance.fields uses, so all three "name → how to produce a value" maps in the format read alike. In hook position only the expression forms are meaningful, and a tool MUST reject the rest.
+
+Within an input's own on_resolved, two bare keywords are self-referential: "asset" resolves to that input's computed issuance asset ID (or its UTXO asset for a non-issuance input), and "reissuance_token" to the computed reissuance token asset ID.
+
+on_resolved runs once the input's UTXO and issuance attributes are known; on_pre_broadcast after all inputs are resolved and before the PSET is built; on_post_broadcast after broadcast, when txids and derived asset ids exist.
+
+====Instance File====
+
+The instance file holds the values fixed when a contract was created. It is written by a constructor — an action carrying create_instance — after broadcast.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| instance || object || NO || {"template": "", "fields": { … }} . template matches a key in contract_templates; fields maps field names to their string values.
+|-
+| provided_inputs || object || NO || Pre-resolved input outpoints, keyed by action input id
+|}
+
+Each provided_inputs entry carries txid, vout, amount_sat, asset, and optionally issuance_entropy. A pinned entry applies to '''every''' action that declares an input with that id, so it should be used only where that is genuinely intended.
+
+instance.NAME in a formula reads instance.fields.NAME.
+
+====State File====
+
+The state file tracks the live on-chain UTXO set for a contract deployment and is updated after every successfully broadcast action.
+
+The state file is close to a standard of its own. Its fixed format makes it an interchange artifact: one tool can produce it, a website can distribute it, and another tool can read it to learn the contract's live UTXO set. A tool is not required to persist one: the manifest format requires only the ability to resolve the covenant UTXOs referenced by utxo_source. Tracking them in a state file is the standard mechanism, but a tool MAY substitute another (chain scanning, an external indexer). Note that chain scanning does not work for confidential protocols — one motivation for the state file.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| instance || string || NO || Relative path to the companion instance file
+|-
+| last_action || string || YES || Name of the last action that produced this state
+|-
+| utxos || array || YES || All live covenant UTXOs for this deployment
+|}
+
+Each entry in utxos:
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| utxo_type || string || YES || The UTXO type name (matches utxo_source.utxo_type in action inputs)
+|-
+| utxo_id || string || NO || The id of the output that produced this UTXO; disambiguates multiple UTXOs of the same type
+|-
+| txid || string || YES || Transaction ID (64 hex chars)
+|-
+| vout || integer || YES || Output index
+|-
+| amount_sat || integer || YES || Satoshi amount
+|-
+| asset || string || YES || Asset ID (64 hex chars)
+|}
+
+'''State history.''' A tool MAY additionally keep an append-only log alongside the state file, named by inserting .history before the final .json. It holds {"entries": [ … ]}, each entry recording the action that ran, the txid it broadcast, and the full utxos set that resulted.
+
+====Canonical Form and Manifest ID====
+
+A manifest is meant to be published under a stable id and signed. Hashing the file bytes would make that id depend on things that carry no meaning — key order, indentation, and prose — so a reformat or a typo fix in a description would mint a new id and invalidate the signature.
+
+The '''canonical form''' of a manifest is the parsed JSON with:
+
+* the keys description, $comment and $schema removed at any depth;
+* chain expanded from its alias to the BIP-122 chain ID it stands for, where an alias was used;
+* every object's keys in sorted order;
+* array order preserved, because input and output ordering is consensus-relevant;
+* serialised compactly as UTF-8.
+
+The '''manifest id''' is a BIP-340-style tagged hash over those bytes: sha256(sha256(tag) ‖ sha256(tag) ‖ canonical_bytes), with tag = "txmanifest/id/v1". Tagging keeps a manifest id from colliding with a hash computed for another purpose — a script hash, a tapleaf — over coincidentally identical bytes.
+
+The exclusion rule is not "prose versus structure" but "can a user read this before authorising?". Only developer documentation is dropped. Everything a signer reads — intent, ui.label, ui.role — '''is''' hashed.
+
+Expanding the chain alias is the one value normalisation the canonical form performs, and it is there because the alias and the chain ID are defined to be the same value. Without it the same manifest would carry two ids depending on which spelling its author typed, and rewriting "liquid" as its chain ID before publishing would invalidate a signature over a document that had not changed meaning. The expansion is a lookup in a fixed table, so any implementation reaches the same bytes.
+
+This is structural canonicalisation only. It is not yet full [https://www.rfc-editor.org/rfc/rfc8785 RFC 8785] JCS: numeric literals are re-serialised rather than normalised per that specification, and strings are not Unicode-normalised (NFC). Manifests carry amounts as strings and ASCII identifiers, so neither bites today, but a registry accepting third-party files SHOULD close both before treating an id as adversarially collision-resistant.
+
+====Behavioral Rules and Invariants====
+
+'''Ordering.''' Input resolution precedes output construction: outputs MAY reference resolved input amounts and assets; inputs MUST NOT forward-reference outputs. Compile params MUST be fully materialized before outputs are evaluated.
+
+'''Covenant address determinism.''' A Simplicity covenant address is fully determined by its .simf source, its compile parameters, the toolchain's debug_symbols setting, and its declared extra leaves:
+
++address = P2TR(internal_key=NUMS, merkle_root=tapbranch(cmr_leaf, extra_leaves...)) ++ +The internal key is the NUMS point
0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; extra leaves are appended in declaration order via TapBranch. The same source, compile parameters, toolchain settings and leaves MUST always produce the same address.
+
+'''Covenant prevout reconstruction.''' A covenant input's prevout is reconstructed, never fetched. A taproot sighash commits to a spent output's asset, value and scriptPubKey and nothing else, and Simplicity's ElementsUtxo — the value inputUTXOsHash covers — carries exactly those three. The asset id and amount, plus the declared blinding factors where the UTXO is confidential, are therefore enough to rebuild the prevout byte-for-byte as far as anything that reads it is concerned: no nonce, no rangeproof, and no network round-trip, so an offline run still works.
+
+'''Witness stack format.''' The witness stack for a Simplicity tapscript input MUST be exactly 4 items, in this order:
+
++[witness_bits_bytes, pruned_program_bytes, cmr_script_bytes, control_block_bytes] ++ +where
cmr_script_bytes is the CMR (32 bytes) as a bare script (no script opcodes) and control_block_bytes is a standard BIP341 control block with the Simplicity leaf version.
+
+'''Unknown fields.''' A tool MUST reject a manifest carrying a field it does not implement, rather than silently ignoring it. Silently skipping a field would change the meaning of a transaction. The two documentation keys, $comment and $schema, are the only exceptions.
+
+==Backwards Compatibility==
+
+The transaction manifest is a new, standalone document format. It requires no consensus changes and no changes to existing wallet formats. At the signing boundary the format hands off to a standard PSET, so a wallet that does not implement manifests can still receive, sign, and broadcast the resulting transaction.
+
+The manifest_version field carries the version of the '''format''', which is not the version of any tool that reads it. It moves under semver, with the qualification semver itself places on initial development: while the major version is 0, the '''minor''' version is the breaking slot. A tool MUST therefore reject a manifest whose major version it does not support, and — while that major version is 0 — whose minor version it does not support either.
+
+"0.2.0" is the version this document specifies. It succeeds "0.1.0" and is not compatible with it: "0.1.0" declared confidentiality per UTXO type, and this version declares it per output, so a given manifest is valid under exactly one of the two.
+
+The format carries no compatibility aliases: each thing it can express has exactly one spelling. Earlier drafts accepted a second spelling in three places — compose_version for manifest_version, compile_params.NAME for instance.NAME, and a flat instance_params map in place of instance.fields — and all three are removed rather than deprecated. A pre-1.0 format has no deployed third-party files to protect, and two spellings of one thing cost every implementation a second lookup path for no gain.
+
+One compatibility behaviour does remain, because it is a default rather than a spelling: a UTXO type that declares no params interface keeps the older open-scope derivation, in which its leaves and compile params resolve against whatever is ambient at each mention. New manifests SHOULD declare the interface.
+
+==Worked Example==
+
+The smallest complete manifest is a Simplicity pay-to-public-key: a Pay action that locks funds into a covenant output, and a Receive action that spends it back to the wallet with a BIP340 signature. Abridged:
+
+examples/. Each pairs a txmanifest.json with the .simf covenant source it references.
+
+==Reference Implementation==
+
+An example wallet that consumes transaction manifests, instance files, and state files is available at [https://github.com/stringhandler/txmanifest-wallet txmanifest-wallet], which also publishes a JSON Schema for the format at schema/txmanifest.schema.json. The format specification and a wallet integration guide describing the full execution lifecycle are maintained at [https://github.com/stringhandler/tx_manifest_spec tx_manifest_spec].