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
}
}