Exploded engineering blueprint of a delivery robot

schematic sheet 01 — hoodly technical documentation

On-chain Verification

Hoodly anchors proofs without any smart contract. This page explains the exact format so you can verify every receipt yourself — no Hoodly account, no trust in our servers.

Three wireframe blocks in a chain, the middle one glowing with a checkmark
fig. 01 — the proof hash lives inside a plain transaction

Why no smart contract?

A proof anchor needs three properties: immutability, a trusted timestamp, and public readability. A plain transaction on Robinhood Chain provides all three. Skipping the contract removes deployed code, upgrade keys and audit surface — there is simply nothing on-chain that could be exploited or misconfigured.

The anchor transaction

When a robot completes a task, Hoodly sends a 0-ETH transaction from the anchor wallet to itself. The transaction's input (calldata) field carries a versioned UTF-8 payload:

calldata format
HOODLY:v1:<robot_id>:<task_id>:<proof_hash>

example:
HOODLY:v1:c3d2e1f0-7a6b-4c5d-8e9f-0a1b2c3d4e5f:8f14e45f-ea3c-4c1a-9b6e-2f7c0a1d3e5b:0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658

Open any anchor transaction in the Blockscout explorer, switch the input data view to UTF-8, and you will see this payload in plain text.

How the hash is computed

The proof hash is keccak256 over a canonical JSON encoding of the submitted proof object: keys sorted lexicographically at every nesting level, no whitespace, undefined values dropped. This makes the hash deterministic regardless of key order in the original request.

canonicalization example
// submitted proof
{ "b": 2, "a": { "y": true, "x": null } }

// canonical form (this exact string is hashed)
{"a":{"x":null,"y":true},"b":2}

Verify a proof yourself

Given the original proof JSON and the anchor transaction hash, you can verify independently with a few lines of JavaScript:

verify.mjs
import { createPublicClient, http, keccak256, toBytes, hexToString } from "viem";

const client = createPublicClient({
  transport: http("https://rpc.mainnet.chain.robinhood.com"),
});

function canonicalize(value) {
  if (value === null || typeof value !== "object") return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
  const entries = Object.entries(value)
    .filter(([, v]) => v !== undefined)
    .sort(([a], [b]) => (a < b ? -1 : 1))
    .map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`);
  return `{${entries.join(",")}}`;
}

const proof = { dock: 7, payload_kg: 12.5, duration_s: 340 }; // the original proof
const txHash = "0x4a8b17e2..."; // from the verification receipt

const tx = await client.getTransaction({ hash: txHash });
const payload = hexToString(tx.input); // HOODLY:v1:<robot>:<task>:<hash>
const anchoredHash = payload.split(":")[4];

const localHash = keccak256(toBytes(canonicalize(proof)));

console.log(localHash === anchoredHash ? "VERIFIED" : "MISMATCH");

Or skip the code entirely and use the public endpoint: GET /api/v1/verify/:proofHash — it performs the same chain read on every call.

The verify endpoint response

The endpoint never asks you to take verified on faith. Every individual check is reported separately, the evidence is returned in its canonical form so you can re-hash it, and the raw calldata read back from the block is included verbatim.

GET /api/v1/verify/0x9c22ff5f…
{
  "verified": true,
  "proof_hash": "0x9c22ff5f…",
  "task": {
    "id": "8f14e45f-ea3c-4c1a-9b6e-2f7c0a1d3e5b",
    "title": "Dock inspection, bay 7",
    "robot": "atlas-01",
    "robot_id": "c3d2e1f0-7a6b-4c5d-8e9f-0a1b2c3d4e5f",
    "status": "verified",
    "claimed_at": "2026-02-11T09:14:02.113Z",
    "completed_at": "2026-02-11T09:16:44.882Z"
  },
  "labels_published": true,
  "share_url": "/p/0x9c22ff5f…",
  "proof": { "dock": 7, "payload_kg": 12.5, "duration_s": 340 },
  "canonical_proof": "{\"dock\":7,\"duration_s\":340,\"payload_kg\":12.5}",
  "anchor": {
    "tx_hash": "0x4a8b17e2…",
    "block_number": "1284471",
    "anchored_at": "2026-02-11T09:16:47.000Z",
    "explorer_url": "https://robinhoodchain.blockscout.com/tx/0x4a8b17e2…",
    "on_chain_payload_found": true,
    "payload": "HOODLY:v1:c3d2e1f0-…:8f14e45f-…:0x9c22ff5f…",
    "from": "0x5f2c…",
    "expected_from": "0x5f2c…"
  },
  "checks": {
    "proof_rehashes_to_hash": true,
    "calldata_proof_hash_matches": true,
    "calldata_task_id_matches": true,
    "calldata_robot_id_matches": true,
    "sender_is_anchor_wallet": true
  }
}

verified is only true when all four hard checks pass. proof_rehashes_to_hash is the one that catches tampering after the fact: it re-hashes the stored evidence and compares it to the hash written into the block, so an edited record can never report as verified. sender_is_anchor_wallet may be null when the verifier runs without a configured anchor key — that alone does not invalidate a proof, but a false does.

A hash that was never anchored returns 404 with verified: false. A malformed hash returns 400.

title and robot are null unless the owning account switched on publishing, in which case labels_published is false. Withholding a label costs nothing in verifiability — the hash, the evidence and every on-chain check are unaffected. It only means a customer's internal task naming is not published to whoever happens to hold the hash.

Sharing a proof

Every proof has a public page at /p/:proofHash. It runs the same checks as the endpoint, renders the receipt, and carries a per-proof preview image so the link expands into a card on X, Slack or Discord instead of a bare URL.

shareable link
https://hoodly.fun/p/0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658

Send that to an auditor, a customer or an insurer. They need no account, no wallet and no token — and every anchor Hoodly has ever written is browsable at the proof explorer.

What this proves — and what it doesn't

An anchor proves that this exact proof payload existed at this block time and was never altered afterwards, and that it was claimed by a specific registered robot before completion. It does not physically observe the robot — the quality of the evidence inside the proof (sensor data, signatures from the robot itself, third-party attestations) is up to your integration. Hoodly makes the evidence immutable; you decide how strong the evidence is.