This walks through the full Ownable deploy + lifecycle using the shipped OpenSilver tooling. The flow is identical to every other stateful pattern in the catalogue, so once this one clicks, the other 21 patterns follow the same shape.
The contract source lives at contracts/core/ownable.sil.
Its design notes are in docs/patterns/core/ownable.md.
- Browse the catalogue via the Web Wizard or CLI.
- Build a deploy plan — compile, derive the P2SH commitment, list entrypoints — from a single CLI invocation.
- Read the plan into a wallet to fund the covenant address.
- Spend the covenant through each of the three lifecycle paths:
propose_transfer→accept_transfer, withcancel_transferas an alternate branch.
This example does not broadcast to TN12; deployment is the wallet's
job. Every step below is exercised end-to-end by the runtime tests in
runtime-tests/tests/core_runtime.rs
(search for ownable_), so the on-chain shapes are not hypothetical.
npm install
npm run bootstrap:silverc # one-time pinned silverc buildnpm run wizard:build
xdg-open wizard/build/index.html # or `open` / `start` on macOS / WindowsFilter by core in the left pane and select Ownable. The detail
pane shows verification posture (compile ✓ / runtime ✓ / audit ✓), the
compiler bootstrap requirement (pinned upstream, no patch needed), and
copy-ready CLI snippets. Everything below is a paste of those snippets
with concrete values filled in.
npx opensilver get core.ownable --jsonReturns the same manifest entry the wizard renders. Useful for piping into wallet UIs or audit pipelines.
Pick three pubkeys you control. For this example we use placeholders
that match the constructor signature
Ownable(pubkey init_owner, bool init_has_pending_owner, pubkey init_pending_owner):
OWNER_PK=02$(openssl rand -hex 31) # 33-byte compressed; replace with a real key
PENDING_PK=02$(openssl rand -hex 31) # placeholder; ignored while flag=false
npx opensilver deploy-plan core.ownable \
--ctor "[\"$OWNER_PK\", false, \"$PENDING_PK\"]" \
--network kaspa:testnet-12 \
> ownable-deploy-plan.jsonThe JSON contains:
| Field | What it carries |
|---|---|
compiled.scriptHex |
Redeem-script bytes, hex-encoded |
compiled.scriptLength |
Length in bytes (for fee estimation) |
p2shCommitment.scheme |
Always "p2sh" for v0.x |
p2shCommitment.redeemScriptHex |
Mirror of compiled.scriptHex, passed to your wallet's payToScriptHash helper |
deployment.entrypoints |
["propose_transfer", "accept_transfer", "cancel_transfer"] |
deployment.networkHints |
Network-id strings the wallet should pin |
verification |
Compile/runtime/audit flags so the wallet can refuse unverified patterns |
The deploy plan stops at the redeem script — turning that into a Kaspa
address belongs to whatever kaspa-wasm version your wallet uses.
OpenSilver ships two paths:
integrations/materializeCovenantOutput— pure helper, takes the compiled script + aP2shAddressDerivercallback you wire to yourkaspa-wasm. Returns a fully-shapedTransactionOutput. Seeintegrations/src/index.ts.- Direct kaspa-wasm — call
addressFromScriptPublicKey(scriptPubKey, networkType)yourself; the redeem script lives atcompiled.scriptHex.
Either way you end up with a kaspa:qz… address. Fund it from any
wallet — that's the deploy.
Each spend is an ordinary Kaspa transaction whose sigscript selects the entrypoint and whose first output reconstructs the covenant address with the next state pushed in. The runtime tests are the canonical reference for the exact byte shapes — point your wallet code at them.
propose_transfer— owner signs, nominatesnext_owner. After this spend the on-chain state is{ owner: A, has_pending_owner: true, pending_owner: B }.accept_transfer—next_ownersigns. State becomes{ owner: B, has_pending_owner: false, pending_owner: B }. Ownership has moved.
propose_transfer— as above.cancel_transfer— owner signs. State becomes{ owner: A, has_pending_owner: false, pending_owner: B }. Thepending_ownerslot keeps its prior value (writing a fresh literal would hit the NUM2BIN 8-byte cap); the bool flag is the source of truth.
Negative coverage is also live: an unauthorized signer on either path
gets VerifyError from the engine. See
runtime-tests/tests/core_runtime.rs
for the exact failure-mode assertions.
- Other patterns — same flow, swap
core.ownablefor any id fromnpx opensilver list. - End-to-end deployment — the longer-form guide is at
docs/DEPLOY_GUIDE.md, which adds troubleshooting and known-issues notes. - Wizard —
npm run wizard:buildregenerates the static HTML catalogue whenever the SDK manifest changes. - Audit posture —
AUDIT_CHECKLIST.mddocuments what the internal-audit suite asserts for every pattern.