@arkade-os/sdk Documentation - v0.4.36
    Preparing search index...

    Class ContractManager

    Central manager for contract lifecycle and operations.

    Responsibilities:

    • Create and persist contracts
    • Query stored contracts (optionally with their virtual outputs)
    • Provide spendable path selection for a contract
    • Emit contract-related events (virtual output received/spent, connection reset)

    Notes:

    • Implementations typically start watching automatically during initialization (so onContractEvent() is just for subscribing).
    const manager = await ContractManager.create({
    indexerProvider: wallet.indexerProvider,
    contractRepository: wallet.contractRepository,
    });

    // Create a new VHTLC contract
    const contract = await manager.createContract({
    label: "Lightning Receive",
    type: "vhtlc",
    params: { sender: "ark1q...", receiver: "ark1q...", ... },
    script: "5120...",
    address: "ark1q...",
    });

    // Start watching for events
    const unsubscribe = manager.onContractEvent((event) => {
    console.log(`${event.type} on ${event.contractScript}`);
    });

    // Query contracts together with their current virtual outputs
    const contractsWithVtxos = await manager.getContractsWithVtxos();

    // Get balance across all contracts
    const balances = contractsWithVtxos.flatMap(({vtxos}) => vtxos).reduce((acc, vtxo) => acc + vtxo.value, 0)

    // Later: unsubscribe from events
    unsubscribe();

    // Clean up
    manager.dispose();

    Implements

    Index

    Methods

    • Symbol.dispose implementation for using with using keyword.

      Returns void

      {
      using manager = await wallet.getContractManager();
      // ... use manager
      } // automatically disposed
    • 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.

      Parameters

      Returns Promise<ExtendedVirtualCoin[]>

    • 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.

      Returns void

    • Get contracts with optional filters.

      Parameters

      • Optionalfilter: ContractFilter

        Optional filter criteria

      Returns Promise<Contract[]>

      Filtered contracts TODO: filter spent/unspent

      // Get all VHTLC contracts
      const vhtlcs = await manager.getContracts({ type: 'vhtlc' });

      // Get all active contracts
      const active = await manager.getContracts({ state: 'active' });
    • Register a callback for contract events.

      The manager automatically watches after initialize(). This method allows registering callbacks to receive events.

      Parameters

      Returns () => void

      Unsubscribe function to remove this callback

      const unsubscribe = manager.onContractEvent((event) => {
      console.log(`${event.type} on ${event.contractScript}`);
      });

      // Later: stop receiving events
      unsubscribe();
    • 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.

      Parameters

      Returns Promise<void>

    • 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.

      Parameters

      • Optionalopts: RefreshVtxosOptions

      Returns Promise<void>

    • Explicit, 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.
      • A 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).
      • Within an index the handler probes run concurrently (independent network reads); their hits are persisted sequentially in discoverables order to preserve the first-wins collision tie-break.
      • Indices are probed 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.

      Parameters

      Returns Promise<ScanResult>

    • Update a contract. Nested fields like params and metadata are replaced with the provided values. If you need to preserve existing fields, merge them manually.

      Parameters

      • script: string

        Contract script

      • updates: Partial<Omit<Contract, "script" | "createdAt">>

        Fields to update

      Returns Promise<Contract>

    • Update a contract's params. This method preserves existing params by merging the provided values.

      Parameters

      • script: string

        Contract script

      • updates: Record<string, string>

        The new values to merge with existing params

      Returns Promise<Contract>