> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kynesys.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Programmatic Transactions

> Collapse the classic build → sign → confirm → broadcast flow into a single demos.run.* call per transaction type, with an automatic fee ceiling.

# Programmatic transactions (`demos.run`)

Every DEMOS transaction has historically been a three-step dance:

```typescript theme={null}
const tx           = await demos.pay(to, amount)   // build + sign
const validityData = await demos.confirm(tx)       // gas / validity check
const res          = await demos.broadcast(validityData)
```

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**).

```typescript theme={null}
// same payment, one call, auto-broadcast within the 5 DEM fee cap
const res = await demos.run.pay(to, amount)
```

All programmatic methods live under `demos.run.*`. The classic
`demos.pay` / `demos.confirm` / `demos.broadcast` methods are **unchanged** —
`demos.run` is an additive facade, so nothing breaks.

<Info>
  Prefer this API for scripts and automation where you want one call per action.
  The step-by-step flow documented in [Creating](/sdk/websdk/transactions/creating-a-transaction),
  [Signing](/sdk/websdk/transactions/signing-a-transaction) and
  [Broadcasting](/sdk/websdk/transactions/broadcasting-a-transaction) remains fully
  supported and is the right choice when you need to inspect or gate each stage
  yourself.
</Info>

## Setup

```typescript theme={null}
import { Demos } from "@kynesyslabs/demosdk/websdk"

// one-call bootstrap (node + wallet), ready for demos.run.*
const demos = await Demos.start("https://node2.demos.sh", mnemonic)

// …equivalent to the classic two-step setup, which still works:
// const demos = Demos.instance
// await demos.connect("https://node2.demos.sh")
// await demos.connectWallet(mnemonic)
```

`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`:

```typescript theme={null}
interface ProgrammaticTxResult {
    broadcasted: boolean          // did it hit the network?
    skippedReason?: "manual" | "rejected"
    transaction: Transaction      // the signed, confirmed tx
    hash: string                  // tx hash
    validityData: RPCResponseWithValidityData
    broadcast?: RPCResponse       // present when broadcasted === true
    feeOs: bigint                 // total fee in OS (smallest unit)
    feeDem: string                // total fee, human-readable DEM
}
```

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

```typescript theme={null}
import { FeeCapExceededError } from "@kynesyslabs/demosdk/websdk"

try {
    await demos.run.pay(to, amount)                 // cap = 5 DEM
} catch (e) {
    if (e instanceof FeeCapExceededError) {
        console.log(`fee was ${e.info.feeDem} DEM, over the cap`)
    }
}

// raise the ceiling for this call:
await demos.run.pay(to, amount, { maxFee: 25 })

// disable the ceiling entirely:
await demos.run.pay(to, amount, { maxFee: null })
```

## Confirmation strategies (`confirm`)

```typescript theme={null}
// 1. auto (default): broadcast within maxFee
await demos.run.pay(to, amount)

// 2. manual: build + sign + confirm only, broadcast yourself later
const r = await demos.run.pay(to, amount, { confirm: "manual" })
console.log("would cost", r.feeDem, "DEM")
await demos.broadcast(r.validityData)

// 3. callback: decide per-transaction from the fee/validity snapshot
await demos.run.pay(to, amount, {
    confirm: (info) => {
        console.log(`about to pay ${info.feeDem} DEM in fees`)
        return info.withinFeeCap            // return true to broadcast
    },
})
```

<Warning>
  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.
</Warning>

## Waiting for inclusion (`wait`)

```typescript theme={null}
// return only once the tx lands on-chain (or fails / times out)
const r = await demos.run.pay(to, amount, {
    wait: true,
    waitOptions: { timeoutMs: 30_000, pollIntervalMs: 500 },
})
```

## Namespaces

### `demos.run.pay` / `demos.run.transfer`

Native value transfers. Amount is a DEM `number` (legacy) or OS `bigint`
(preferred). See [Amounts & Denominations](/sdk/websdk/transactions/denominations)
for how the two formats are handled.

```typescript theme={null}
import { denomination } from "@kynesyslabs/demosdk"
await demos.run.pay("0x...", denomination.demToOs(100))   // 100 DEM
await demos.run.transfer("0x...", 1_500_000_000n)          // 1.5 DEM in OS
```

### `demos.run.attest.*`

Identity attestations. Each returns a `ProgrammaticTxResult`.

```typescript theme={null}
await demos.run.attest.github(gistProofUrl)
await demos.run.attest.twitter(tweetProofUrl)
await demos.run.attest.discord(messageProofUrl)
await demos.run.attest.telegram(signedAttestation)
await demos.run.attest.domain("example.com")
await demos.run.attest.removeWeb2({ context: "github", username: "alice" })
```

#### `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.

```typescript theme={null}
const result = await demos.run.attest.dahr({
    url: "https://api.example.com/data",
    method: "GET",
})
```

### `demos.run.tokens.*`

Token creation and execution.

```typescript theme={null}
await demos.run.tokens.create({ /* TokenCreationParams */ })
await demos.run.tokens.transfer(tokenAddress, to, "1000000000")
await demos.run.tokens.approve(tokenAddress, spender, "1000000000")
await demos.run.tokens.mint(tokenAddress, to, "1000000000")
await demos.run.tokens.burn(tokenAddress, from, "1000000000")
```

## 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`.

<Info>
  * **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.
</Info>

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