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.
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.
The SDK is TypeScript-first, Node 20+:
npm install @dynamoprotocol/sdk
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.
Dynamo.open({ coreUrl, token }) connected the thin wire client to YOURinstance's versioned owner-api. The engine and every enforcement decision live with the instance; the client re-implements nothing.
openBudget funded a ceiling (in Control Mode: a ledger entry; with amoney mode enabled on your instance: an on-chain deposit or a card authorization).
budget.stream(...) opened child allowances, each with a hard cap and a per-second rate envelope. Streams accrue allowance over time; a tick that outruns the envelope fails closed and bills nothing.
tick produced a monotonic EIP-712 voucher signed by thatstream's scoped session key, held by the core. Billing truth is the last SIGNED voucher — never in-memory state.
revoke cut one stream mid-session. Instant: any voucher stamped afterthe revocation instant is structurally unsettleable.
close settled the last funded vouchers and released the remainder; aggregate returned the reconciled session.
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.