Skip to content
Warrantv0.1

TypeScript SDK

@gowarrant/sdk — a typed client whose amount type will not let you pass a number.

@gowarrant/sdk wraps the API and makes the amount mistake unrepresentable: usdc("1.50") compiles, 1.50 does not.

import { Warrant, usdc } from "@gowarrant/sdk";

const warrant = new Warrant();               // reads WARRANT_API_KEY

const w = await warrant.open({
  amount: usdc("1.50"),                      // never a number
  payee: "api.exampledata.io",
  spec: { clauses: [{ kind: "http.status", equals: 200 }] },
  deadline: new Date(Date.now() + 3_600_000),
});

Install

npm install @gowarrant/sdk

The client reads WARRANT_API_KEY from the environment. A wr_test_ key points at sandbox; nothing else needs configuring.

export WARRANT_API_KEY=wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44

Amounts are a type, not a convention

Every other page in these docs tells you that an amount is an integer string of the smallest unit, ^[0-9]+$, and that 8400000 as a JSON number is rejected. The SDK is where that stops being something you have to remember.

import { usdc, amount, parseDecimal, formatAmount } from "@gowarrant/sdk";

usdc("8.40")            // Amount — 8400000 of the smallest unit
usdc("1.50")            // Amount — 1500000
amount("8400000", "USDC")   // Amount, from the smallest unit directly
formatAmount(usdc("8.40"))  // "8.40" for display

usdc(8.40)              // does not compile

Amount is an opaque type. open() takes one, so a raw number or a bare string is a type error at the call site rather than a 400 at runtime.

addAmounts and scaleAmount do arithmetic without ever converting to a float, and AmountError is thrown on a value that cannot be represented exactly.

Methods

MethodCalls
open(input, idempotencyKey?)POST /v1/warrants
get(id)GET /v1/warrants/:id
list(input)GET /v1/warrants
verify(id, input, idempotencyKey?)POST /v1/warrants/:id/verify
release(id, idempotencyKey?)POST /v1/warrants/:id/release
partialRelease(id, bps, idempotencyKey?)POST /v1/warrants/:id/partial
refund(id, idempotencyKey?)POST /v1/warrants/:id/refund
trace(id)GET /v1/warrants/:id/trace
receipt(id, detail?)GET /t/:id
bench(input)POST /bench/run

idempotencyKey is optional on every money-moving call because the client mints a UUID when you omit one. Pass your own when you want a retry to replay rather than open a second warrant — see idempotency.

release, partialRelease and refund need a person

These three call endpoints that refuse an API key with 403 user_session_required. They are on the client because the same client is used from the console's own server, not because an agent key can call them. An agent opens and verifies; a person settles.

A full run against sandbox

Runnable as-is with a wr_test_ key:

import { Warrant, usdc, formatAmount } from "@gowarrant/sdk";

const warrant = new Warrant();

const w = await warrant.open({
  amount: usdc("8.40"),
  payee: "api.exampledata.io",
  spec: {
    clauses: [
      { kind: "http.status", equals: 200 },
      { kind: "data.freshness", maxAgeSeconds: 60 },
    ],
  },
  deadline: new Date(Date.now() + 3_600_000),
});

console.log(w.id, w.state);             // wrt_… held

const after = await warrant.verify(w.id, {
  source_url: "https://api.exampledata.io/invoices/8812",
});

console.log(after.state);               // released | escalated | refunded | partially_released
console.log(formatAmount(usdc(after.amount)), after.asset);

verify returns the warrant in whatever state the policy put it in. Escalating is one of the answers, not a failure — see concepts: escalations.

Testing a spec with no money involved

const result = await warrant.bench({
  clauses: [{ kind: "data.freshness", maxAgeSeconds: 60 }],
  deliverable: {
    reached: true,
    status: 200,
    generated_at: new Date(Date.now() - 3_600_000).toISOString(),
  },
});

console.log(result.passed, result.score);   // false 0

bench needs no key, holds nothing and writes nothing.

Note that score here is a fraction 0–1 from the engine, while a stored score on a check or escalation is basis points 0–10000. See the callout on verifiers.

Errors

WarrantError carries the status and the parsed envelope, so you branch on code rather than parsing a message.

import { Warrant, WarrantError, usdc } from "@gowarrant/sdk";

try {
  await warrant.open({ /* … */ });
} catch (err) {
  if (err instanceof WarrantError && err.code === "insufficient_funds") {
    // Nothing was held. Top up and retry with the same idempotency key.
  }
  throw err;
}

See errors for the code list.

Licence

Apache 2.0. The mark and wordmark are not covered by it.