0%

Build

Three calls and a grant. If a machine can sign, it can meter, spend and be checked.

  • NODE 20+
  • MIT
  • v0.4 · MAINNET
A swordsmith's forge at night, woodblock
PLATE II

Nothing here is generated for you.The key was already in the machine.

00

Install

NO ACCOUNT · NO KEY TO PASTE

There is no signup step, because there is nothing to sign you up for. The SDK talks to the secure element already sitting in the machine.

  • npmnpm i @katana/sdk
  • pnpmpnpm add @katana/sdk
  • bunbun add @katana/sdk
01

Attach the machine

Bind the SDK to the key already in the secure element. Nothing is generated, nothing is stored.

ts
import { attach } from "@katana/sdk";

const machine = await attach({
  did: "did:katana:p256:KL-0117",
  // the key never leaves the die; the SDK only asks it to sign
  signer: secureElement(),
});

await machine.attest();   // -> { ok: true, chain: "verified" }
02

Take a grant

Scope, ceiling, expiry. The grant is the only authority the machine has, and it dies on its own.

ts
const grant = await machine.grant({
  scope: ["arm.lift", "arm.place"],
  ceiling: { wh: 8_000, currency: "usd", max: 4.0 },
  expiry: "+45m",
});

grant.on("revoked", () => machine.park());
03

Meter and settle

Open against a dock, let it tick, close. The channel nets off-chain and clears in one transaction.

ts
const session = await grant.open({ dock: "dock-01" });

for await (const tick of session.meter()) {
  // { wh, ts, sig } — at most one second apart
  telemetry.push(tick);
}

await session.close();    // -> settled, sub-cent
04

Or declare it

NO JS UNTIL YOU TRULY NEED IT

For the common case there is no imperative code at all — the same four rails, expressed as attributes.

html
<katana-machine
  did="did:katana:p256:KL-0117"
  scope="arm.lift arm.place"
  ceiling="8kWh"
  expiry="45m"
  dock="dock-01"
></katana-machine>
05

What comes back

PROOF BESIDE THE RESULT

Every call returns the proof beside the result. Keep it or drop it — the ledger kept its own copy either way.

json
{
  "session": "sx_01J8Y…",
  "total_wh": 3417,
  "amount":   { "usd": 0.4521 },
  "ticks":    412,
  "settled":  { "tx": "0x9c1f…", "at": 1782413071 },
  "proof": {
    "machine": "did:katana:p256:KL-0117",
    "dock":    "did:katana:p256:dock-01",
    "grant":   "gr_01J8Y…",
    "sig":     "cose:…"
  }
}
06

When it refuses

THE FOUR YOU WILL ACTUALLY SEE
E_ATTESTSignature did not verifyThe key answered but the chain did not. Usually a maker root that has moved on.
E_SCOPECall outside the grantThe actuator is not named in scope. Widen it at issue — never at the call site.
E_CEILINGMeter reached the ceilingExpected, not exceptional. Close the session and open a new grant.
E_EXPIREDGrant diedTime-boxed authority did exactly its job. There is nothing here to recover.
07

Events

FOUR YOU CAN SUBSCRIBE TO
meter.tick{ wh, ts, sig }At most one second apart, for as long as a session is open.
grant.revoked{ grant_id, by, at }Fires before the next actuator call is refused, not after.
session.settled{ total_wh, amount, tx }The channel closed. This is the receipt.
machine.parked{ reason }Your own handler ran. Nothing about this touched the chain.

Everything here runson mainnet today.There is no waitlist.

Read the spec
KAI, keeper of the ledgerKAI
KEEPER OF THE LEDGER

Three calls, and the machine answers for itself from there... I will be here when the first job settles.