@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 discoveryForks 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 minutesSimulation
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 rollbackRoyalties
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 — quicktransfer() 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; // 1000000nErrors
| Class | Thrown when |
|---|---|
| ApiError | non-2xx from the REST API (has .status, .code) — 429/5xx retried first |
| RpcError | JSON-RPC error from a Robinhood Chain node |
| InsufficientLoxError | balance can't cover a transfer — checked before broadcast |
| TimeoutError | a waitFor* helper hit its deadline |
Everything extends LoxleyError.
Networks
| Network | REST | RPC |
|---|---|---|
| mainnet | api.loxley.work | rpc.robinhood.exchange |
| testnet | api.testnet.loxley.work | rpc.testnet.robinhood.exchange |
const lox = new LoxleyClient({ network: "testnet" });