Skip to main content

Programmatic transactions (demos.run)

Every DEMOS transaction has historically been a three-step dance:
The programmatic transaction system collapses that into a single call per transaction type. One method fills its parameters from arguments and routes through a single shared runner that handles confirm → broadcast for you, auto-broadcasting within a configurable fee ceiling (default 5 DEM).
All programmatic methods live under demos.run.*. The classic demos.pay / demos.confirm / demos.broadcast methods are unchangeddemos.run is an additive facade, so nothing breaks.
Prefer this API for scripts and automation where you want one call per action. The step-by-step flow documented in Creating, Signing and Broadcasting remains fully supported and is the right choice when you need to inspect or gate each stage yourself.

Setup

Demos.start(rpc, wallet?, walletOptions?) returns the connected instance; omit wallet to connect read-only.

The uniform result

Every demos.run.* method (except attest.dahr, see below) resolves to a ProgrammaticTxResult:

The fee ceiling (maxFee)

In the default "auto" mode the runner broadcasts only if the confirmed fee is within maxFee (DEM, default 5). If it is exceeded, the tx is signed and confirmed but NOT broadcast, and the runner throws FeeCapExceededError carrying the fee details — a loud safety net against surprise fees.

Confirmation strategies (confirm)

In callback mode the callback is the sole authority — maxFee is not auto-enforced. Use info.withinFeeCap inside the callback if you still want to respect the ceiling.

Waiting for inclusion (wait)

Nonce management

When sending a single transaction using a Demos client, the nonce for that transaction is read from the network and incremented by 1 to match what’s expected on-chain. When broadcasting batch transactions, calling Demos transaction methods at the same time to create multiple transactions will result in multiple transactions having the same nonce. This means that only one of them will be executed while all the rest will fail.
To fix this, we need to assign increasing nonces for each transaction, in the order we want them to be executed.

Auto-nonce

The Demos client has an opt-in built-in auto-nonce tracking feature. You can enable it as shown below:
All the transactions get incrementing nonces and they will all be executed, even if they are not broadcasted in order.
The nonce for the connected wallet will only be tracked locally on that Demos instance. If you create a new instance, you’ll need to enable it again.
Auto-nonce applies to every transaction builder on the instance — demos.run.*, the classic demos.pay / demos.transfer, storage, escrow, tokens, and so on. You can inspect or switch it off at any time:
If the same key signs from another device or process, or a reserved transaction never lands on-chain, the counter drifts from the chain. You can recover as shown below:
Reconnecting to a different node resets all counters automatically.

Explicit nonces

Every demos.run.* method (and the classic builders) accepts a nonce option. You can track the nonce manually and override any internally calculated nonce as shown:

Namespaces

demos.run.pay / demos.run.transfer

Native value transfers. Amount is a DEM number (legacy) or OS bigint (preferred). See Amounts & Denominations for how the two formats are handled.

demos.run.attest.*

Identity attestations. Each returns a ProgrammaticTxResult.

demos.run.attest.dahr — the exception

DAHR (Demos Attestation Hash Response) is a web2 proxy attestation: the node performs the HTTP request and its confirm/broadcast lifecycle happens server-side. So dahr does not go through the fee-cap runner and returns the web2 result directly (not a ProgrammaticTxResult); maxFee / confirm options do not apply.

demos.run.tokens.*

Token creation and execution.

Coverage & caveats

demos.run.* namespaces: pay / transfer, attest.* (web2 + web3/xm + pqc + ud + nomis + humanpassport + ethos + tlsn + dahr), tokens.*, storage.* (store, program), escrow.*, validator.*, governance.*, xm.submit, bridge.submit, demoswork.submit, ipfs.*, d402.pay.
  • Live-verified against a node: pay / transfer, storage.store, storage.program, plus the fee-cap / confirm-mode behaviour.
  • ipfs.*, d402.pay and contracts.* are wired and type-checked but the underlying operations are not enabled on production nodes yet — they cannot be exercised end-to-end until the network turns them on.
  • contracts.* (deploy / call / deployTemplate / …) is RPC-native and NOT fee-capped: it bypasses the confirm → broadcast runner and returns the contract’s own result types (ContractInstance, ContractCallResult, gas bigint), not a ProgrammaticTxResult. It lives under demos.run.contracts for a uniform surface, but maxFee / confirm / wait do not apply there.
  • xm.submit takes an already-assembled XMScript; building that script (per-chain signed payloads) stays with the caller.

Design note

Under the hood a single runner (runProgrammaticTx) is the only place that calls confirm / broadcast. Each typed method just builds and signs its transaction and hands it to the runner, which normalises the three historical builder shapes (returns-signed-tx, returns-validityData, server-internal lifecycle) into one confirmation policy and one result type. This is the “common element” that makes demos.run.pay(addr) and demos.run.attest.dahr(params) feel the same despite very different underlying operations.