Stamp raw virtual outputs with the correct per-contract tapscripts (forfeit, intent, tap tree).
Resolves each vtxo's script to its owning contract via the contract
repository and attaches the matching tapscripts. Throws when any vtxo
references a script with no registered contract — callers are expected
to register the contract before asking for annotation. This is the
single shared path that replaces scattered extendVirtualCoin* calls
in wallet/handler code, and keeps the wallet from silently stamping the
default tapscript onto a non-default vtxo.
Create and register a new contract.
Contract parameters
The created contract
Delete a contract.
Contract script
Dispose of the ContractManager and release all resources.
Stops the watcher, clears callbacks, and marks the manager as uninitialized.
Implements the disposable pattern for cleanup.
Get every currently valid spending path for a contract.
Options for getting spending paths
Get contracts with optional filters.
Optionalfilter: ContractFilterOptional filter criteria
Filtered contracts TODO: filter spent/unspent
List contracts and their current virtual outputs.
If no filter is provided, returns all contracts with their virtual outputs.
Optionalfilter: ContractFilterOptionalpageSize: numberGet currently spendable paths for a contract.
Options for getting spendable paths
Check if currently watching.
Register a callback for contract events.
The manager automatically watches after initialize(). This method
allows registering callbacks to receive events.
Event callback
Unsubscribe function to remove this callback
Reconcile specific outpoints with the indexer's authoritative state and upsert the result into the wallet repository.
The cursor-derived delta sync filters by created_at, so a VTXO that
was created before the cursor but spent recently won't surface in a
standard refreshVtxos() call. This method is the surgical recovery
path for that case: when something hands us a stale outpoint (e.g. the
server returns VTXO_ALREADY_SPENT with a vtxo_outpoint in its
error metadata), call this to pull the latest state and unblock the
caller — no full re-scan, no cursor change.
Outpoints not owned by any tracked contract are silently dropped.
Force refresh virtual outputs from the indexer.
Without options, re-fetches every contract in the watcher's watched set and advances the global cursor.
scripts narrows the refresh to a specific list (subset query —
cursor is not advanced because contracts outside the list may
have data we'd skip).
includeInactive: true (and no scripts) widens the refresh to
every contract in the repository, including ones marked
inactive and ones that have dropped out of the watcher's
active set. This is a superset of the watched set, so the
cursor invariant still holds and the cursor advances normally.
after / before apply a caller-supplied time window. The
cursor never advances on a windowed query because the window
may skip data outside its bounds.
Optionalopts: RefreshVtxosOptionsExplicit, gap-limit contract discovery (see IContractManager.scanContracts).
Each hit is routed through persistAndWatchContract — the same
dedupe + watcher-register path as createContract minus the
per-contract indexer round-trip. The caller (Wallet.restore) follows
up with a single bulk refreshVtxos({ includeInactive: true }), so a
scan that finds N contracts costs one batched indexer call instead of
N + 1.
Safety-critical invariants (spec §2.C / §4):
opts.materialize(i) throwing is structural/fatal: it is NOT
wrapped — it propagates and aborts the scan.discoverAt rejection is collected into handlerErrors and the
loop continues (the gap counter still advances for that index if no
other handler hit it).persistAndWatchContract rejecting is operational/fatal and
propagates (only discoverAt is guarded).discoverables order to preserve the first-wins collision tie-break.batchSize at a time (a second concurrency layer
over the per-index probes), but each window is CAPPED to
gapLimit - unused indices — the most a serial scan could still reach
before the gap window is guaranteed to close. So every index probed in
a window is one a one-index-at-a-time scan would also reach: nothing is
over-scanned, nothing is discarded, and materialize/discoverAt are
invoked on exactly the same index set. The window's hits are still
processed strictly in ascending index order, so the discovered set,
persisted rows, lastIndexUsed, and handlerErrors are byte-for-byte
identical to the serial path — only the wall-clock differs.Update a contract's params. This method preserves existing params by merging the provided values.
Contract script
The new values to merge with existing params
StaticcreateStatic factory method for creating a new ContractManager. Initialize the manager by loading persisted contracts and starting to watch.
After initialization, the manager automatically watches all active contracts
and contracts with virtual outputs. Use onContractEvent() to register event callbacks.
ContractManagerConfig
Central manager for contract lifecycle and operations.
Responsibilities:
Notes:
onContractEvent()is just for subscribing).Example