Texel

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

The fields of a receipt
FieldTypeWhat it holds
versionuint8uint81, the version of the format
houruint64uint64The UTC hour of its root, in seconds since 1970
kindstringstringrental, image, video, or voice
itemstringstringThe GPU of a rental, or the model of an output
widthuint32uint32The width of an image in pixels, else 0
heightuint32uint32The height of an image in pixels, else 0
secondsuint32uint32The seconds a rental ran or a video lasts, else 0
charactersuint32uint32The characters of a voice, else 0
creditsuint64uint64The micro-credits it took at the listed price, 0 when free
freeboolboolWhether it was a free output with $TEXEL
codebytes32bytes32keccak-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