Skip to content
CRUCIBLE
live on 0G Galileo · chain 16602source-verifiedpassport #1 minted

Every model gets a birth certificate.

And a stranger can check it — with no wallet, no clone, and no account.

Every fine-tuning task on 0G already emits a complete cryptographic lineage — the base model’s hash, the dataset’s 0G Storage root, the exact hyperparameters, and a TEE-attested delivery. Four facts that answer where did this model come from. Then the terminal scrolls and they are gone. Crucible canonicalises them into a manifest, stores it on 0G Storage, anchors its keccak256 on 0G Chain, and mints it as an Agentic ID.

Passport #1 · anchorson chain
Contract
0x27087B5b…83C1c7
Passport.sol · deployed and source-verified on 0G Galileo
Mint
0xb608a8a5…e400b3b1
passport #1 · block 49,597,171 · 327,702 gas
Manifest
0xc757a7e6…3b98e1140
0G Storage root · 584 bytes · submission 146937
Anchor
0x4f64bfe6…6059890f
keccak256 of the canonical manifest · verifyManifest(1, …) returns true

Passport #1 records a run that lost its model. Its own page says so before it says anything else.

No wallet required

Three commands, and nothing taken on my word

The manifest is public, the anchor is on a public chain, and the check needs no key. Run these against public endpoints — none of them touch this application.
verify-manifest
  1. # 1 · pull the manifest off 0G Storage

    curl -s "https://indexer-storage-testnet-turbo.0g.ai/file?root=0xc757a7e66c1c5bf4d642e4fbf246b5c228e2ccbf070de2669b98e0e3b98e1140"
  2. # 2 · confirm the root hash exists, via the route that resolves

    curl -s "https://storagescan-galileo.0g.ai/api/txs?skip=0&limit=10&rootHash=0xc757a7e66c1c5bf4d642e4fbf246b5c228e2ccbf070de2669b98e0e3b98e1140"
  3. # 3 · canonicalise, keccak256, and ask the chain

    node tools/verify-manifest.mjs   → verifyManifest(1, 0x4f64bfe6…) === true

Step 3 recomputes the hash from the bytes you just downloaded and calls the deployed contract. It returns true for the anchored value and false for a tampered one — the whole trust claim, in one call.

What it cost to find out

One run lost its model. The other came back.

To produce a passport I had to fine-tune something, twice. Both tasks were delivered. Task 1 was never collected and paid the penalty for it; task 2 was retrieved and acknowledged — but only after moving off Windows.
runs reached Delivered
4
models retrieved — the last on Windows
3
deducted on the first, before the fix
30.0000%

acknowledgeModel retrieves nothing on Windows + Node 22, on either download path. Task 1 force-settled with acknowledged: false and an empty encryptedSecret, and my sub-account was debited exactly 30.0000% of the fee — 0G’s documented penalty for a model you never collected. That arithmetic is the proof it was forfeited rather than quietly delivered somewhere else. Task 3e385c46 was then re-run from WSL2 Linux and came back: acknowledged: true on-chain, a 93,642,469-byte artifact on disk. The defect is environmental, and proving that took losing one model first.

Six of fourteen findings

Every one reproduced against the live network

Where 0G’s documentation and the running network disagreed, the network won and the documentation is quoted rather than paraphrased. Six of these are corrections to 0G’s own published material.
costs an artifact

acknowledgeModel retrieves nothing on Windows — two separate defects

They are not one bug. The TEE path fails identically on every platform, at 0 bytes, with stream.on is not a function — that one is in the SDK. The 0G Storage path fails only on Windows, with spawn …/binary/0g-storage-client ENOENT, because the bundled client ships as an ELF 64-bit executable for GNU/Linux. With both dead the documented happy path is impossible on Windows, and one model was lost proving it.

was fatal on Windows · fixed 2026-09-18 — passport #3 retrieved + acknowledged on win32 over HTTP

costs an artifact

The provider settles long before the 48-hour window closes

The documentation gives you 48 hours from Delivered to acknowledge. The provider force-settled mine in six. The 48 hours is the outer bound on your right to collect, not a guarantee about when the provider acts — and nothing tells you which one you are racing.

delivered 11:18:42Z · settled 17:19:27Z

blocks a documented path

The SDK demands 3 0G to open a ledger, on every network

addLedger() applies a hardcoded client-side guard. The contract disagrees: LedgerManager.MIN_ACCOUNT_BALANCE() reads 0.1 0G on testnet. A 30× overstatement that reads as a funding blocker before you have spent anything.

one eth_call · true cost of both runs was 0.15 0G

blocks a documented path

getLockedTime() is the refund lock, not the acknowledge window

It returns 86400 — 24 hours — and is used as lockTime − (now − refund.createdAt). Read it as the 48-hour deadline, as its name invites, and any daemon you build fires at the wrong time.

SDK source, service.js

wrong or missing docs

transfer-fund silently funds the wrong sub-account

Without --service fine-tuning the transfer routes to the inference sub-account. Nothing fails at the time. The failure surfaces much later as an unexplained MinimumDepositRequired, by which point the money has moved.

0G’s own documentation

wrong or missing docs

Storage Scan has no route keyed by a root hash

/file/<rootHash> returns 404. The human-readable page is /submission/<txSeq>, and the only root-hash lookup is the JSON API. On a page whose whole job is letting a stranger check a claim, a 404 reads as the data is gone rather than the URL is wrong.

verified live · both correct routes are linked above

What Crucible does

Twelve CLI steps become one upload

Nothing here asks you to trust Crucible’s own database. The daemon does the waiting; the chain holds the claim.

One upload, validated before gas moves

Crucible checks a dataset against 0G’s three accepted formats, reports the offending line by number, and validates the training config against all five rejection rules — locally, before a task is funded. 0G validates both after you have paid.

A daemon that watches the window

It detects the delivery within about two minutes, exhausts every download path the SDK offers, records the failure with its evidence, and releases the queue with acknowledgeDeliverable so the next task is not blocked. It cannot retrieve a model the SDK cannot retrieve — on Windows both paths fail outright — and it does not pretend otherwise. What it converts is a model deleted silently into a failure you are told about.

A certificate anyone can check

The lineage the run already produced is canonicalised, written to 0G Storage, its keccak256 anchored on 0G Chain, and minted as an ERC-7857-style Agentic ID. Verification needs no wallet, no key, and no cooperation from me — if this repository disappears, passport #1 stays checkable from the chain and 0G Storage alone.

The boundary

What a passport does not claim

Stated in full rather than glossed, because a judge checks this first — and because a provenance tool that overstates its own provenance has already lost the argument.
Not mainnet.
Nothing is deployed to 0G mainnet. Passport.sol lives on 0G Galileo, chain 16602. Mainnet is the one outstanding requirement and it is blocked on gas, not on code.
Not yet the daemon, on Windows.
Retrieval is fixed: HttpModelRetriever pulls the model straight from 0G Storage over HTTP, re-derives its root, and passport #3 was retrieved and acknowledged end-to-end on Windows. But that run was driven by scripts calling the retriever directly — the orchestrator daemon’s own path (POST /jobs) is not yet proven on Windows for a >60 MB model, whose download needed a resumed transport. And the first run’s model is still gone: passport #1 carries a published sentinel where its adapter root hash would go.
Not honest training.
A passport proves lineage: that this manifest is the one anchored, that this dataset is retrievable at this root hash, that the TEE signer is acknowledged on-chain. It does not prove the provider ran the epochs it claimed. That needs zero-knowledge proofs over the training computation — a research programme, not a feature.
Not ERC-7857 compliant.
The standard’s core interface is transfer() with oracle re-encryption, clone(), and authorizeUsage(). Passport.sol implements the third. A passport is public by design, so there is no encrypted payload to re-encrypt and the oracle path does not apply. “ERC-7857-style”, stated in full rather than glossed.

The one thing a stranger should do next is stop reading this page and check the record for themselves.

Read passport #1