Languages: Python · TypeScript

10-minute cold start

From an empty environment to a verified, signed GRIP (v0.1, stable) record in five steps. Setup takes about ten minutes, most of it the dependency install in step 1.

Step What happens
1. Install pip install grip-sdk (Python) or npm install @grip/sdk (TypeScript)
2. Start a sandbox MockSandbox() — in-process, no server, no network. A hosted sandbox zone with the same surface over HTTP/WS is coming soon.
3. Register register(...) with a generated Ed25519 keypair — returns a signed identity.cert
4. Request an envelope request_envelope(...) / requestEnvelope(...) — returns a signed envelope.grant, your first signed GRIP record
5. Verify check the record's signature and transparency-log inclusion (SPEC.md §6.2)

The quickstart below runs the full register → envelope → telemetry → signal flow with zero network calls. When you point at a live sandbox zone instead, the same steps run over HTTP — the wire-level version of steps 3-5:

# 1. Register (P1) — issues an identity.cert
curl -s -X POST http://127.0.0.1:8765/v1/register -d '{
  "node_label": "coldstart-demo",
  "public_key": "<base64url ed25519 pubkey>",
  "asset_class": "storage",
  "capability": {"import_max_w": 500000, "export_max_w": 500000, "ramp_max_w_s": 100000, "energy_capacity_wh": 2000000, "control_latency_ms": 500},
  "firmness": "flexible"
}'

# 2. Request an envelope (P2) — requires a `GRIP-Auth` cert-key
#    possession-proof header (SPEC.md §7.1); node_id comes from the
#    verified cert, not a query param. Raw curl can't sign the header
#    inline — both SDKs do this for you:
#      Python:     GripClient(base_url).get_envelope(keypair)
#      TypeScript: client.getEnvelope(keypair)
# The header shape, if you're hand-rolling a client:
#   GRIP-Auth: kid=<cert kid> ts=<rfc3339 utc ms> nonce=<16 hex> sig=<base64url Ed25519 sig>
curl -s http://127.0.0.1:8765/v1/envelope \
  -H 'GRIP-Auth: kid=<cert kid> ts=<rfc3339 utc ms> nonce=<16 hex> sig=<base64url sig>'

# 3. Confirm transparency-log inclusion (SPEC.md §6.2)
curl -s "http://127.0.0.1:8765/v1/log/entries?start=0"
curl -s "http://127.0.0.1:8765/v1/log/proof?leaf_index=0"

@grip/sdk (TypeScript)

TypeScript client SDK for GRIP protocol v0.1 (stable).

Install

npm install @grip/sdk

Requires Node >= 20. Runtime dependency: ws (signal subscription over WebSocket). Ed25519 signing uses Node's built-in node:crypto — no external crypto dependency.

Quickstart

Registers a storage asset, requests an operating envelope, signs and submits a telemetry report, and watches a few congestion-signal ticks — all against the in-process MockSandbox, so it runs with no server and no network access:

import { KeyPair, MockSandbox } from "@grip/sdk"; // MockSandbox: same method surface as GripClient
import type { Capability, EnvelopeRequest } from "@grip/sdk";

const sandbox = new MockSandbox();
const keypair = KeyPair.generate(); // private key never leaves this process (SPEC.md sec 1.5)

const capability: Capability = {
  import_max_w: 500_000, export_max_w: 500_000, ramp_max_w_s: 100_000,
  energy_capacity_wh: 2_000_000, control_latency_ms: 500,
};
const { cert } = sandbox.register("bldg7", "storage", capability, "flexible", keypair);
const nodeId = cert.payload.node_id as string;

const grant = sandbox.requestEnvelope(
  { node_id: nodeId, want_import_w: 450_000, want_export_w: 0, want_duration_s: 900 },
  keypair
);
console.log(grant.payload.import_max_w, grant.payload.valid_until);

When the hosted sandbox goes live, swap new MockSandbox() for new GripClient("https://sandbox.protocol.standardpower.co") — every call above is unchanged; both expose the identical method surface.

Conformance

The SDK's test suite reproduces the spec's Appendix A canonical-JSON vectors (V1-V4) byte-for-byte — the cross-language conformance contract shared with the Python SDK — plus Ed25519 sign/verify and the full mock-sandbox flow.

Module reference

Module Contents
canonical canonicalize(value), isSignable(value), NotSignableError — GRIP-CJ (RFC 8785 restricted profile), SPEC.md sec 2
identifiers recordHash(record), sigPreimage(record), kidForPublicKey(rawPub), instrumentId(listing), SPEC.md sec 2.3 / 4.2
keys KeyPair (generate / fromRawPrivateKey / sign), publicKeyFromB64url, verify — Ed25519 via node:crypto, SPEC.md sec 1.5
records GripRecord, newRecord, sign, verifyRecordSignature — the signed wrapper, SPEC.md sec 1.5
types Money, Firmness, AssetClass, and one interface per record kind (IdentityCert, EnvelopeGrant, SignalTick, TelemetryReport, ExchangeListing, …) — SPEC.md sec 3-6
errors GripError, GripProtocolError and per-urn:grip:error:* subclasses, fromProblemJson — SPEC.md sec 8
client GripClient — real HTTP/WS client for a zone's DEV CONSOLE surface (register, envelope, telemetry, signal subscription)
mock_sandbox MockSandbox — in-process stand-in for a live sandbox zone, same method surface as GripClient
brand BRAND — the single brand config point (SPEC.md sec 9); never hardcode a display name/domain elsewhere in this SDK

Every quantity in types is an integer in the units of SPEC.md sec 1.3 (_w, _w_s, _wh, _bp, _ms/_s); every money value is a Money ({ amount, currency, per? }) — integer minor units + ISO 4217, never a float. Optional fields use ?:; leave them undefined (never null) to omit them from the signed payload — canonicalize() drops undefined-valued keys automatically (SPEC.md sec 2.2).

Errors are UX

SDK errors carry the urn:grip:error:* type token from SPEC.md sec 8 and a hint telling you what to do next:

import { GripProtocolError } from "@grip/sdk";

try {
  await client.getEnvelope(keypair);
} catch (e) {
  if (e instanceof GripProtocolError) {
    console.log(e.typeUrn); // "urn:grip:error:envelope-expired"
    console.log(e.hint);    // what to do about it
  }
}