Your first capped call

Control Mode is the full protocol with no money anywhere: the funding backend is a ledger, every other layer — caps, rate envelopes, instant revocation, EIP-712 vouchers signed by scoped session keys, checkpoints, reconciliation — is the same machinery the money rails run. Every hosted instance ships with it on; it is the right first stop for every integration.

Prerequisites

Your instance credentials (from the signup email):

export DYNAMO_CORE_URL=https://your-instance-url
export OWNER_API_TOKEN=your-owner-api-token   # keep it like a password

Prefer a .env file with chmod 600 over shell exports — the tokens then never enter your shell history.

Install

The SDK is TypeScript-first, Node 20+:

npm install @dynamoprotocol/sdk

The flagship sample

This is the complete program — one budget, two capped streams, a mid-run revoke, and a to-the-unit reconciled session, all over the wire against your instance. CI executes exactly these bytes against a live core; the same file ships in the package as examples/quickstart.mjs.

// SPDX-License-Identifier: Apache-2.0
// Dynamo in 20 lines, over the wire: one budget, two capped streams, a
// mid-run revoke, and a to-the-unit reconciled session — against a running
// dynamo-core (Control Mode). Env: DYNAMO_CORE_URL, OWNER_API_TOKEN.
import { Dynamo, units, usd } from "@dynamo/sdk";

const coreUrl = process.env.DYNAMO_CORE_URL ?? "http://127.0.0.1:8500";
const token = process.env.OWNER_API_TOKEN ?? "";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const dynamo = await Dynamo.open({ coreUrl, token });
const budget = await dynamo.openBudget({ funding: "none", cap: usd("20") });
const alice = "0x00000000000000000000000000000000000000A1";
const bob = "0x00000000000000000000000000000000000000B2";
const research = await budget.stream({ to: alice, rate: units(500_000n), cap: units(6_000_000n) });
const writing = await budget.stream({ to: bob, rate: units(500_000n), cap: units(9_000_000n) });

await sleep(1_200);
await research.tick(units(300_000n)); // metered work — voucher signed by the stream's scoped key
await writing.tick(units(400_000n));
await sleep(1_100);
await budget.revoke(research); // instant; anything stamped later is unsettleable
await writing.tick(units(200_000n));
await sleep(1_100);
await writing.revoke();

const session = await budget.close().then(() => budget.aggregate());
console.log(`[${session.funding}] billed=${session.totals.billedAmount}µ settled=${session.totals.settledAmount}µ (${session.settlement.layer}) reconciled=${session.reconciled}`);
process.exit(session.reconciled ? 0 : 1);

Run it (the import specifier in the published package is @dynamoprotocol/sdk; the sample ships ready to run):

node node_modules/@dynamoprotocol/sdk/examples/quickstart.mjs

The final line prints the session's billed total, the settled total reported by the settlement layer, and reconciled=true — the two agree to the unit or the process exits non-zero. That reconciliation line is not demo garnish; it is the protocol's core claim, and every Dynamo sample ends with it.

What just happened

instance's versioned owner-api. The engine and every enforcement decision live with the instance; the client re-implements nothing.

money mode enabled on your instance: an on-chain deposit or a card authorization).

a per-second rate envelope. Streams accrue allowance over time; a tick that outruns the envelope fails closed and bills nothing.

stream's scoped session key, held by the core. Billing truth is the last SIGNED voucher — never in-memory state.

the revocation instant is structurally unsettleable.

aggregate returned the reconciled session.

Witness a halt

Spend a stream to its cap and tick once more: the call throws HaltError (reason: "cap"), nothing is billed, and gateway surfaces answer HTTP 402. Streams shows every refusal path — each one fails closed and bills zero.

Next: put a hard cap in front of a real tool with the guard, or go straight to the SDK guides.