Parameters
Problem. Understand when a value belongs to a contract template's
fieldsversus an action'sparams, and how a value gets filled in without prompting.
The Pay action from recipe 1 takes one value from
you each time it runs, and bakes another into the covenant address forever. This
recipe pins down the difference.
Two kinds of parameters
This trips up everyone at first, so it's worth being precise:
template fields | action params | |
|---|---|---|
| When fixed | At deploy time, once. | Per transaction, every time you run the action. |
| Baked into the script? | Yes — they change the covenant's address. | No — they only affect this transaction. |
| Stored in | the instance | nowhere; supplied at run time |
| Referenced as | instance.NAME | params.NAME |
| Example | PUBKEY, LOAN_EXPIRATION_TIME | amount_sat, CURRENT_BLOCK_HEIGHT |
A useful test: "if I changed this value, would the on-chain address change?" If
yes, it belongs in a template's fields. If it only affects which inputs and
outputs this particular transaction picks, it's an action param.
A single-type contract that never needs an instance can skip
contract_templates altogether and declare everything as action params — which
is what the early recipes in this book do.
Params that fill themselves in: compute
A param that declares a compute block is never prompted for. The simplest form
is a bare formula string; the structured forms cover what an expression cannot
say:
"params": {
"BORROWER_PUB_KEY": {
"type": "pubkey",
"description": "Borrower's signing key.",
"compute": { "type": "wallet", "wallet": "key" }
},
"PRINCIPAL_INTEREST_AMOUNT": {
"type": "u64",
"compute": "instance.PRINCIPAL_AMOUNT * instance.INTEREST_RATE / 10000"
}
}
compute.type | Produces |
|---|---|
(bare string) or expr | The value of a formula |
wallet | A value from the executing wallet — see below |
script_hash | sha256(scriptPubKey) of an address |
tapleaf | A covenant script hash, by compiling a .simf |
simf_fn | The return value of a named function in a .simf |
hook | Nothing here — a hook sets this param later in the run |
The wallet variant takes a second key naming which wallet-derived value it wants:
compute.wallet | Resolves to |
|---|---|
"key" | The wallet's 32-byte x-only BIP340 pubkey |
"script_hash" | sha256(scriptPubKey) of the wallet's index-0 explicit output |
"address" | The explicit address matching "script_hash" — the two are a pair |
Declaring { "type": "hook" } looks pointless but is not: it is how a param that
a hook will set gets an identifier. Without the declaration, a hook writing to
params.SOMETHING would be inventing a name nothing checks.
If a param has no compute, the tool prompts you for it interactively (or you
supply it via --params, below). In recipe 1, PUBKEY has no compute, so
Pay prompts you for it.
Where a manifest's guarantees come from
There is no block for business rules — no place to write "amount must be greater than zero" and have the wallet check it. That is deliberate: a rule a wallet enforces is a rule a different wallet can skip. Guarantees come from three places instead, in descending order of strength:
- The covenant. Anything that actually protects value belongs in the
.simf, where the chain enforces it and no wallet can skip it. If a value matters, make the program check it. allow_change. An action defaults to"none", so a surplus that no declared output absorbs is an error rather than a silent change output moving value somewhere you never wrote down.- Parsing. Unknown and misspelled fields are hard errors, and
manifest_versionis checked before anything else runs, so a file written for a different format revision fails immediately rather than half-working.
Run it
Supplying params non-interactively
Instead of typing params at the prompt, pass a flat JSON file of string→string
values and reference it with --params:
{ "amount_sat": "50000", "pubkey": "<64-hex-char x-only pubkey>" }
txw run examples/p2pk/txmanifest.json Pay \
--network testnet --wallet wallet.json --params pay-params.json
Keys must match the action's param names exactly — pubkey, not PUBKEY.
The CLI also auto-discovers a per-network param file sitting next to the
manifest, named <manifest-stem>.<network>.json — so txmanifest.testnet.json
beside txmanifest.json is picked up whenever you pass --network testnet. An
explicit --params file is loaded after it and wins on any key they share.
Try next
That covers the values going into an action. Next we get precise about what comes out: the destinations a value can go to, and how confidentiality is decided: Outputs & destinations.