SDK Docsv0.4.2

@loxley/sdk

The official TypeScript SDK for Loxley. Publish robots to the on-chain registry, validate them in the physics sim, ship signed OTA rollouts and watch $LOX royalties settle in real time. Zero runtime dependencies, Node ≥ 18, MIT. Source on GitHub ↗

Installation

npm install @loxley/sdk    # pnpm add @loxley/sdk · bun add @loxley/sdk

Grab an API key from loxley.work/settings/keys and export it as LOXLEY_API_KEY.

Quickstart

The full platform loop — publish, simulate, deploy, earn:

import { LoxleyClient, parseLox, formatLox } from "@loxley/sdk";

const lox = new LoxleyClient({ apiKey: process.env.LOXLEY_API_KEY });

// 1 · publish — the manifest hash anchors on Robinhood Chain
const model = await lox.registry.publish({
  name: "wren-2-scout",
  category: "aerial-recon",
  version: "2.1.0",
  priceLox: parseLox("84"),
  components: [
    { ref: "wren_scout.chassis@2.1.0", kind: "chassis" },
    { ref: "lidar_v3@^1.4", kind: "sensor" },
    { ref: "nav_skill@^4", kind: "skill" },
  ],
});

// 2 · simulate until provably safe
const run = await lox.sim.createRun({
  modelId: model.id,
  scenario: "warehouse-dense-v2",
  steps: 1_200_000,
  maxCollisionRate: 0.001,
});
await lox.sim.waitForRun(run.id); // resolves on PASS

// 3 · deploy over the air, signed + staged
const manifest = await lox.deploy.signManifest(model.id);
await lox.deploy.createRollout({ manifest, fleet: "warehouse-eu-1" });

// 4 · earn — royalties settle in ~0.25s
for await (const ev of lox.royalties.streamEvents(model.owner)) {
  console.log(`+${formatLox(ev.amount)} from ${ev.modelId}`);
}

LoxleyClient

const lox = new LoxleyClient({
  apiKey: "…",          // from loxley.work/settings/keys
  network: "mainnet",   // or "testnet"
  baseUrl: "…",         // optional: self-hosted gateway
  rpcUrl: "…",          // optional: your own chain node
});

Every module hangs off the client: registry · marketplace · sim · deploy · royalties · chain. Requests retry on 429/5xx with exponential backoff.

Registry

The on-chain source of truth for robot models. Content-hashed manifests, semantic versions, fork lineage.

await lox.registry.publish(input)        // compile graph + anchor manifest
await lox.registry.get("wren-2-scout")   // fetch a model record
await lox.registry.list({ category: "aerial-recon" })
await lox.registry.fork("wren-2-scout", "wren-2-nightowl")
await lox.registry.deprecate(id, "2.0.0") // yank from discovery

Forks preserve the upstream royalty split on the lineage — original authors keep earning from every derivative license.

Marketplace

const page = await lox.marketplace.list({ sort: "downloads" });
const listing = await lox.marketplace.get("wren-2-scout");

// settles $LOX on-chain, routes royalties, returns a signed artifact URL
const receipt = await lox.marketplace.license("wren-2-scout", { units: 4 });
receipt.artifactUrl; // valid for 15 minutes

Simulation

Physics validation with pass/fail gates. A model that has not passed sim cannot be deployed.

const run = await lox.sim.createRun({
  modelId,
  scenario: "warehouse-dense-v2",
  steps: 1_200_000,
  seed: 42,                 // deterministic replays
  maxCollisionRate: 0.001,  // fail the run above this
});

await lox.sim.waitForRun(run.id, {
  pollMs: 3_000,
  onProgress: (r) => console.log(r.status, r.stepsCompleted),
}); // → SimRun with status "passed" | "failed" | "cancelled"

Deploy

Builds are signed twice: your account key signs the manifest, and the signature anchors on Robinhood Chain. Units verify the anchor before flashing.

const manifest = await lox.deploy.signManifest(modelId);

const rollout = await lox.deploy.createRollout({
  manifest,
  fleet: "warehouse-eu-1",
  waveSize: 0.25, // 4 staged waves
});

await lox.deploy.waitForRollout(rollout.id, {
  onProgress: (d) => console.log(d.status, `${d.unitsHealthy}/${d.unitsTotal}`),
}); // "healthy" | "degraded" | "aborted"

await lox.deploy.abortRollout(rollout.id); // one-call rollback

Royalties

Splits are basis points and must sum to exactly 10,000. They are enforced by the chain on every license event.

await lox.royalties.getSplits(modelId);
await lox.royalties.setSplits(modelId, [
  { recipient: "lox1aron…", bps: 7_000 },
  { recipient: "lox1sensor…", bps: 3_000 },
]);

// historical payouts
await lox.royalties.listEvents("lox1aron…");

// live feed — long-polls under the hood
for await (const ev of lox.royalties.streamEvents("lox1aron…")) {
  console.log(`+${ev.amount} uLOX @ block ${ev.block}`);
}

Chain RPC

await lox.chain.blockHeight();
await lox.chain.balance("lox1aron…");          // bigint micro-LOX

const tx = await lox.chain.transfer(from, to, parseLox("4.20"));
await lox.chain.waitForTx(tx);                 // blocks are 0.25s — quick

transfer() checks the sender balance before broadcasting and throws InsufficientLoxError client-side.

$LOX amounts

All amounts are integer micro-LOX as bigint — 1 LOX = 1,000,000 uLOX. No floats, no rounding drift.

import { LOX, parseLox, formatLox } from "@loxley/sdk";

parseLox("4.20");     // 4200000n
formatLox(4200000n);  // "4.20 LOX"
LOX;                  // 1000000n

Errors

ClassThrown when
ApiErrornon-2xx from the REST API (has .status, .code) — 429/5xx retried first
RpcErrorJSON-RPC error from a Robinhood Chain node
InsufficientLoxErrorbalance can't cover a transfer — checked before broadcast
TimeoutErrora waitFor* helper hit its deadline

Everything extends LoxleyError.

Networks

NetworkRESTRPC
mainnetapi.loxley.workrpc.robinhood.exchange
testnetapi.testnet.loxley.workrpc.testnet.robinhood.exchange
const lox = new LoxleyClient({ network: "testnet" });