Receipts
Every receipt, sealed onchain
Each rental and each output on Texel gets a receipt. Every hour, Texel writes one root of that hour's receipts to a contract on Robinhood Chain, so a receipt changed later stops matching the chain.
Check it yourself
A leaf is keccak-256 of keccak-256 of the ABI encoding of the fields below, in their order. The receipts of an hour make a StandardMerkleTree of OpenZeppelin, so any copy of them rebuilds the root that the contract keeps.
The fields of a receipt
| Field | Type | What it holds |
|---|---|---|
| versionuint8 | uint8 | 1, the version of the format |
| houruint64 | uint64 | The UTC hour of its root, in seconds since 1970 |
| kindstring | string | rental, image, video, or voice |
| itemstring | string | The GPU of a rental, or the model of an output |
| widthuint32 | uint32 | The width of an image in pixels, else 0 |
| heightuint32 | uint32 | The height of an image in pixels, else 0 |
| secondsuint32 | uint32 | The seconds a rental ran or a video lasts, else 0 |
| charactersuint32 | uint32 | The characters of a voice, else 0 |
| creditsuint64 | uint64 | The micro-credits it took at the listed price, 0 when free |
| freebool | bool | Whether it was a free output with $TEXEL |
| codebytes32 | bytes32 | keccak-256 of the wallet, the second it finished, and a salt of its own |
Rebuild the root of an hour
Run it with Node.js 20 or later, with the hour as its argument.
// npm install @openzeppelin/merkle-tree viem
// node rebuild-hour.mjs 2026-11-07T05
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
import { createPublicClient, http, parseAbi } from "viem";
const hour = await (await fetch(`https://texel.run/receipts/hours/${process.argv[2]}.json`)).json();
const types = ["uint8","uint64","string","string","uint32","uint32","uint32","uint32","uint64","bool","bytes32"];
const values = hour.receipts.map((r) => [1, r.hour, r.kind, r.item, r.width, r.height, r.seconds, r.characters, r.creditsMicros, r.free, r.code]);
const root = values.length > 0
? StandardMerkleTree.of(values, types).root
: "0x0000000000000000000000000000000000000000000000000000000000000000";
const client = createPublicClient({ transport: http("https://rpc.mainnet.chain.robinhood.com") });
const [onchain, count] = await client.readContract({
address: "0x48c545Fdd2Ea5b286B319F03575683019C2f1EF5",
abi: parseAbi(["function seals(uint64 hour) view returns (bytes32 root, uint32 count, uint64 sealedAt)"]),
functionName: "seals",
args: [BigInt(hour.seal.hour)],
});
console.log(root === onchain && count === values.length
? "The root matches Robinhood Chain."
: "The root does not match Robinhood Chain.");To check one receipt, open its Verify link: the page checks it against Robinhood Chain in your browser. Verify a receipt