Overview
Sell inference from your own AI model on Hedera, and let anyone check every output.
A Scaffold-HBAR (opens in a new tab) template by ColdAI (opens in a new tab).
Landing page (opens in a new tab) · Demo video (opens in a new tab) · Test app (opens in a new tab) · Docs (opens in a new tab) · Example project (opens in a new tab) · Evidence on HashScan (opens in a new tab)
npm create scaffold-hbar@latest -- --template shayansal/scaffold-hbar-onchain-aiWhy HOCA
When an AI answers you, you can't check it. You don't know which model ran, whether the result was cherry-picked, or whether the creator got paid. HOCA turns each answer into a Hedera receipt:
| What you get | How | |
|---|---|---|
| 🧾 | Every output is checkable | The example model's weights are Hedera contract bytecode. Anyone replays an answer for free through the mirror node and gets the identical text. |
| 🎲 | No cherry-picking | Each paid ticket's seed comes from Hedera's PRNG (HIP-351), in the same transaction as the payment. |
| 💵 | Priced in dollars, paid in anything | HBAR at a USD price from the median of Chainlink, Supra and Pyth, or any HTS token at its own price. |
| 🔒 | Private when you need it | Inputs and outputs sealed to the buyer and the model's creator; the seed stays unbiased yet secret. |
| 🤝 | Money-back delivery | Serve any model (even an LLM behind an API) from your own worker. Payment waits in escrow, and the buyer can refund if no answer arrives in time. |
| 🧬 | Provenance from data to bytecode | An EQTY Lab Integrity Manifest signs the lineage; its CID is anchored on HCS, and the app checks it against the contracts on Hedera. |
| 📏 | Any length | Long inputs and outputs travel as chunked events whose integrity the contracts enforce. |
The worked example is Ivy Ink 1, a 235k-parameter poetry model that runs inside the EVM, writing for Medusa Poets: HTS NFT poets whose holders earn a share of every poem. The template ships five example poets. The full example project, with 500 poets, is at coldai.org/poets (opens in a new tab).
What you can build
HOCA is a market for model outputs, not a poetry app. Keep the market, payments, receipts and provenance, and swap the model:
| You have | Configure | Buyers get |
|---|---|---|
| A small deterministic model | MODEL_HOSTING=hedera: weights as contract bytecode | Outputs anyone can replay for free, trusting nothing but the chain |
| Model weights you publish | MODEL_HOSTING=offchain + WEIGHTS_URI (IPFS or HTTPS) | The same replayability; the app checks the weights' SHA-256 stored on-chain |
| An LLM or a big model on your GPUs | FULFILLER + ATTESTOR, packages/worker pointed at any OpenAI-compatible endpoint | Pay-per-answer with escrow and refunds; every output signed by you |
| Sensitive prompts (legal, medical, internal) | PROVIDER_KEY (+ PRIVATE_ONLY) | Sessions only the buyer and you can read |
| A community or a token | Accept any HTS token, and give NFT holders a revenue share | Models owned and paid for in your own token |
How it works
- Buy a ticket.
InferenceMarket.requestInference(poet, token, prefix, priceUpdate)takes the payment and stores a ticket: who paid, which poet, the first line, and a seed from Hedera's PRNG system contract (HIP-351). - Generate for free. The app runs the model with that seed. With Hedera hosting it reads the weights from the 12 data contracts, and Verify replays
IvyInk1.generatethrough the mirror node's/contracts/callin 7-character steps, which costs nothing. The browser runtime is bit-exact with the contract, so both write the same poem. - Publish.
Anthology.publish(ticket, prefix, text, attestation)redeems the ticket once and emitsPoem(id, ticket, poet, author, seed, prefix, text, publisher). Anyone can regenerate the text from(poet, seed, prefix)and compare. - Get paid. Revenue accrues inside the market: the treasury's share, and
holderShareBpsfor whoever holds the poet NFT. Everyone withdraws their own balance, and ECDSA accounts can withdraw from their EVM alias (HIP-632).
Bring your own model
Train → quantize → export → EQTY manifest
Your model on Hedera
Integer weights stored as contract bytecode
Anyone replays it through the mirror node
HTS NFT collection (optional revenue share)
Holders earn a share of every payment
HCS provenance topic + EQTY manifest
Manifest CID and weight hashes anchored
Buyer
Pays with HBAR at a USD price or an HTS token
Sends the prompt in the clear
HTS tokens
INK, USDC or any token at a fixed price
Oracles
Chainlink + Supra (+ optional Pyth)
Median with a deviation guard
InferenceMarket
Ticket with a PRNG seed (0x169)
Pricing, revenue split, pause switch
Buyer runs it
Browser runtime or free mirror-node calls
Anthology (public)
Output text stored; ticket redeemed once
Inline in one transaction
Mirror node (verify)
Anyone replays the seed for free
Weights checked against the EQTY manifest
Your model lives in Hedera contract bytecode. Buyers pay in HBAR priced in USD by the oracle median or an HTS token at a fixed price; the buyer runs it with the ticket's seed; the answer is published to the Anthology for anyone to re-check.
# packages/foundry: yarn foundry:deploy --network hedera_testnet withMODEL_HOSTING=hedera# IVY_MODEL=0x0…0 deploys your own model contractsORACLES=chainlink,supra PRICE_USD_MICROS=10000# add pyth + PYTH_API_KEY for a third source # then accept a token (the HTS association runs on Hedera)yarn foundry:accept-token --token 0.0.x --price <smallest units> # owner switches at any timesetPaused(true)# stops new sales; refunds and deliveries still workHedera services used
| Service | Where | What it does here |
|---|---|---|
| Smart contracts (EVM) | IvyInk1, 12 data contracts, InferenceMarket, Anthology | The model itself runs in the EVM: an int8 GRU with SWAR matrix-vector products and lookup-table activations |
| HTS, non-fungible | Poets collection | Poet serial = the model's conditioning record; holders earn a revenue share (ownerOf through the HTS ERC-721 facade) |
| HTS, fungible | INK demo token, or USDC etc. | Accepted as payment through the ERC-20 facade; the market associates itself via the HTS system contract (0x167) |
| HCS | Provenance topic | Anchors the EQTY manifest CID, the weights' SHA-256 and each chunk's SHA-256 |
| PRNG (HIP-351) | 0x169 | Unbiased per-ticket seeds, drawn in the payment transaction |
| Account service (HIP-632) | 0x16a | Maps an ECDSA EVM alias to the long-zero account HTS reports as owner |
| Mirror node | App and worker | Free model execution (/contracts/call), weight bytecode, Anthology and payload events, HCS messages |
| JSON-RPC relay (Hashio) | Wallets, Foundry | Fee handling built in: gas price from eth_gasPrice, tinybars vs weibars |
| Hiero SDK | scripts-js/setupHedera.js, fund.js | Creates the NFT collection, INK token and HCS topic; funds test wallets |
Ecosystem integrations
| Integration | Role | Why it is load-bearing |
|---|---|---|
| Chainlink Data Feeds | HBAR/USD push feed | USD pricing without keys; the default |
| Supra push oracle | HBAR/USDT (pair 75) | Second independent source: the market halts if it and Chainlink disagree |
| Pyth | HBAR/USD pull feed | Optional third source; the app fetches signed updates server-side (Hermes needs an API key since 2026-08-26) |
EQTY Lab eqty_sdk | Integrity Manifest | Signed, content-addressed lineage from training data to the exact bytes deployed on Hedera |
| IPFS / any URL | Off-chain hosting | Where weights live when the model is not hosted on Hedera |
| OpenAI-compatible APIs (vLLM, Ollama, hosted) | Provider worker | Serve any model, with escrow-backed delivery |
| Hedera Harness | .harness/ (opens in a new tab) | A spec, PRD and validators so coding agents can extend the template safely |
Quick start
Prerequisites: Node 20.18.3+, Git, and Foundry (opens in a new tab) 1.4+ (foundryup). Yarn 3 is vendored in .yarn/releases. Python 3.10+ only if you work on the model.
yarn install
yarn next:dev # http://localhost:3000The app starts against the public testnet deployment listed in EVIDENCE.md (opens in a new tab). With no wallet you can pick a poet, write a free poem and verify it on Hedera. To commission, publish and withdraw, click Connect Wallet: the built-in burner wallet works on testnet out of the box (fund it with yarn foundry:fund <address>), or use MetaMask or any WalletConnect wallet on Hedera testnet (chain 296).
| Page | What you can do |
|---|---|
/ Studio | Pick a poet, try it free, commission (HBAR or any accepted token, public or private), publish, verify, refund an undelivered ticket |
/anthology | Every published poem from the chain, each with Verify and HashScan links; decrypt your private poems |
/provenance | The EQTY lineage, checked against the bytecode on Hedera and the HCS anchor |
/admin | Owner console: hosting, prices per token, oracles, holder share, attestor, privacy keys, delivery, pause; everyone's earnings and withdraw |
/debug | Scaffold-HBAR's contract debugger |
Deploy your own market
- Account. Create a testnet account at portal.hedera.com (opens in a new tab) with an ECDSA key and fund it from the faucet. Setup and deploy cost about 40 testnet HBAR.
Keys. Import it for Foundry, and give the same account to the setup script:
bashyarn foundry:account:import cp packages/foundry/.env.example packages/foundry/.env # then set HEDERA_OPERATOR_ID / HEDERA_OPERATOR_KEYNative resources. Create the Poets NFT collection (the 5 example poets), the INK token and the provenance topic, and anchor the manifest:
bashyarn foundry:setupResults go to
packages/foundry/deployments/hedera-setup.296.json, with HashScan links in the output.Contracts. Deploy the market, the Anthology and the oracle adapters. On testnet the public Ivy Ink 1 is reused unless you set
IVY_MODEL=0x0000000000000000000000000000000000000000, which deploys your own 14 model contracts.bashyarn foundry:deploy --network hedera_testnetThis regenerates
packages/nextjs/contracts/deployedContracts.ts, so the app now talks to your market.Payment tokens. Accept INK (or any HTS token) at a price in its smallest unit. This is a separate transaction because the market associates itself with the token through the HTS system contract, which
forge script's local simulation cannot run:bashyarn foundry:accept-token --price 100 # INK from setup: 1.00 INK per poem yarn foundry:accept-token --token 0.0.429274 --price 10000 # e.g. testnet USDC: $0.01
Serving the app under a sub-path, or from Docker on Railway, is covered in docs/deploy-under-subpath.md (opens in a new tab).
Configuration
packages/foundry/.env
| Variable | Default | Used by |
|---|---|---|
HEDERA_RPC_URL | https://testnet.hashio.io/api | fork tests |
LOCALHOST_KEYSTORE_ACCOUNT | scaffold-hbar-default | local deploys |
HEDERA_OPERATOR_ID, HEDERA_OPERATOR_KEY | (none) | yarn foundry:setup, yarn foundry:fund |
Deploy settings (environment variables for yarn foundry:deploy; all optional)
| Variable | Default | Meaning |
|---|---|---|
MODEL_HOSTING | hedera | hedera or offchain |
IVY_MODEL | public testnet Ivy Ink 1 | Reuse a deployed model; 0x0…0 deploys a new one |
WEIGHTS_URI | (none) | Required for offchain, e.g. ipfs://<cid>/ivy_ink_1.bin |
PRICE_USD_MICROS | 10000 ($0.01) | HBAR price in micro-dollars; 0 means a fixed tinybar price |
PRICE_TINYBARS | 10000000 (0.1 HBAR) | Fixed HBAR price off Hedera or when USD pricing is off |
ORACLES | chainlink,supra | Any of chainlink, supra, pyth, comma-separated |
ORACLE_MAX_DEVIATION_BPS | 200 | Largest spread allowed between oracles (2%) |
CHAINLINK_MAX_AGE / SUPRA_MAX_AGE | 86400 / 7200 s | Staleness limits; Pyth is always 60 s |
HOLDER_SHARE_BPS | 2500 | Poet holder's share of each payment (25%) |
ATTESTOR | (none) | Require this key's signature on every published output |
PROVIDER_KEY, PRIVATE_ONLY | (none), false | Turn on private inference; PRIVATE_ONLY sells nothing else |
FULFILLER, DELIVERY_WINDOW | (none), 3600 s | Provider-served delivery with escrow; refunds after the window |
POETS_TOKEN, PROVENANCE_TOPIC, MANIFEST_CID, TREASURY | from setup, provenance, deployer | Overrides |
Everything can be changed after deployment from /admin or with the owner functions setPrice, setUsdPrice, setOracles, setHolderShare, setModel, setAttestor, setPrivacy, setFulfillment, setPaused and setTreasury.
packages/nextjs/.env
| Variable | Needed for |
|---|---|
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | WalletConnect wallets (cloud.reown.com) |
NEXT_PUBLIC_BASE_PATH | Serving the app under a sub-path, e.g. /hederabounty/test (build-time) |
PYTH_API_KEY | Only if the market uses Pyth: /api/pyth-update adds it server-side |
ATTESTOR_PRIVATE_KEY | Only for off-chain hosting with an attestor: /api/attest signs outputs server-side |
PROVIDER_PRIVATE_KEY | Only for the creator's own decrypt-private tool |
No server key is ever sent to the browser. packages/worker/.env is documented line by line in its .env.example (opens in a new tab).
Off-chain hosting
Set MODEL_HOSTING=offchain and WEIGHTS_URI (pin packages/foundry/data/weights/ivy_ink_1.bin to IPFS, or serve it anywhere). The market records the URI and the blob's SHA-256; the app fetches the file, checks the hash and runs the model in the browser. Payment, seeds, provenance and the Anthology stay on Hedera. Because Ivy Ink is deterministic, anyone can still replay any poem from the published weights.
For a model that cannot be replayed (a hosted LLM, say), set ATTESTOR. Anthology.publish then requires the attestor's EIP-191 signature over (chainId, market, ticket, keccak256(text)), which /api/attest (or the provider worker) produces after running the inference.
Private inference
Off by default. The model creator turns it on by publishing an X25519 public key:
yarn workspace @sh/nextjs provider-key # writes PROVIDER_PRIVATE_KEY to packages/nextjs/.env, prints the public key
PROVIDER_KEY=0x<public key> yarn foundry:deploy --network hedera_testnet # or setPrivacy(key, privateOnly) from /adminWith privateOnly the market sells nothing else. A private session works like this:
- The buyer's browser draws a 32-byte
secretand sends onlykeccak256(secret)withrequestPrivateInference. The market draws its PRNG value in the same transaction, so the effective seedkeccak256(ticket.seed, secret)is still unbiased (the buyer committed before the PRNG value existed) but unknown to everyone else. - The first line or prompt goes on-chain only as a sealed envelope. One random AES-256-GCM key encrypts the payload, and that key is wrapped twice: for the creator's X25519 key (ECDH + HKDF), and for a key the buyer derives from a wallet signature. Only a hash of the first line sits in the ticket. The format is specified in
packages/nextjs/utils/ivy/privacy.ts. - The model runs locally with the effective seed. A private poem is never replayed through a mirror node, because that would reveal the seed to its operator.
Anthology.publishPrivatestores the sealed output andkeccak256(secret, text), a hiding commitment.- The creator reads everything with
yarn workspace @sh/nextjs decrypt-private, for their own use: it opens each envelope, checks the secret against the ticket, the first line against its hash and the commitment, and re-runs the model to confirm the text came from that paid seed. The buyer can decrypt their own poems from/anthologywith a wallet signature.
Hedera's PRNG output is public, so it provides unbiased randomness, never secrecy; secrecy comes from the keys. Private mode hides what was asked and answered, not who paid: the paying account is visible like any Hedera transaction.
Long inputs and outputs
Ivy Ink's 64-character first line is a limit of this model, not of Hedera, and generation is already chunked (7 characters per mirror-node call, state carried between calls). What Hedera does limit is one transaction: about 6 KB natively, more through the JSON-RPC relay, and 15M gas. Inputs and outputs longer than that travel as chunked events (contracts/lib/ChunkedPayloads.sol):
- The sender commits to the total length and a rolling hash,
h_i = keccak256(h_{i-1} ‖ part_i), then appends parts in separate transactions (appendInput,appendOutput). The app uses 4 KB parts: 90–170k gas plus calldata each, far under the cap. - Each part is a
PayloadPartevent. The contract finishes the payload only when the byte count and the rolling hash both match, so readers get integrity from the chain itself. - Sealed private inputs of any size gate their ticket: it cannot be redeemed until the input is complete. Long outputs are published with
beginPublishand appear asLongPoemonce the last part lands.
Alternatives, when you do not need the contract to check integrity: an HCS topic (1 KB messages, cheapest per byte; store the hash and sequence range on-chain) or a Hedera File Service file (up to 1 MB). Both need the Hiero SDK in the client, whereas chunked events work from any EVM wallet.
Provider-served inferenceNew
For models the buyer cannot run (an LLM behind an API, a large model on your GPUs), the model creator serves tickets with the provider worker in packages/worker:
cp packages/worker/.env.example packages/worker/.env # WORKER_PRIVATE_KEY, MODEL_ENDPOINT, provider keys
FULFILLER=0x<worker address> DELIVERY_WINDOW=3600 yarn foundry:deploy --network hedera_testnet # or /admin → Delivery
yarn worker:start # or: yarn worker:once --ticket 12- Escrow. With a fulfiller set, every payment is held in the market (
Escrowed) instead of credited. When the worker publishes the answer on the buyer's behalf, the escrow is released to the treasury and the poet's holder (Released). The Anthology records the buyer as author and the worker as publisher. Buyers can still publish their own tickets. - Refunds. If nothing is published within
deliveryWindow, the buyer callsrefund(ticket)(the Studio shows a countdown and the button) and gets the exact amount back in the token they paid with. A late answer is still accepted until the buyer refunds. - Any model.
MODEL_ENDPOINT=ivyruns Ivy Ink with the app's runtime; any OpenAI-compatible base URL (vLLM, Ollama, a hosted provider) works withMODEL_API_KEYandMODEL_NAME. Models that cannot be replayed from a seed should run with anATTESTOR, so every output carries the provider's signature. - Private tickets. The buyer's sealed input also carries the ticket secret and a reply key derived from their wallet signature; the worker opens it with the provider key, runs the model on the effective seed and seals the answer to the reply key (envelope v2), so only buyer and provider can read it.
- Operations. The worker keeps a cursor, retries per ticket with backoff, resumes chunked uploads after a restart, and logs JSON lines with HashScan links.
setPaused(true)stops new sales while refunds, withdrawals and deliveries keep working.setPrivacy(newKey, …)rotates the provider key; each ticket remembers its key version, and the worker anddecrypt-privatehold every version (PROVIDER_PRIVATE_KEY_V<n>,provider-key --rotate).
Bring your own model
The pipeline in model/ is the one that produced Ivy Ink 1:
cd model && pip install -r requirements.txt
python prep.py && python train.py 3000 # needs the corpus; see model/README.md
python export.py # writes packages/foundry/data/weights + contracts/IvyLayout.sol
python provenance.py # EQTY manifest + summary, re-running the export inside it
python verify_provenance.py # offline check of signatures, lineage and filesTo host a different architecture on Hedera, keep three constraints in mind: integer-only inference that the Solidity and TypeScript runtimes reproduce bit-for-bit, weights split into ≤24 KB data contracts, and each generate call under Hedera's 15M gas cap (Ivy Ink costs ~1.85M gas per character, so it writes 7 per call). Otherwise use off-chain hosting or the provider worker: the market, Anthology, oracles and provenance work unchanged. AGENTS.md (opens in a new tab) has the step-by-step for coding agents.
The model pipeline (model/)
The pipeline that produced Ivy Ink 1, and the EQTY provenance that ties it to the bytes deployed on Hedera.
| File | Purpose |
|---|---|
prep.py | Builds data.npz from the Gutenberg Poetry Corpus (opens in a new tab): stanza-sized chunks, a 38-character alphabet, heuristic labels for temperament, muse, voice and rhyme |
train.py | The model (character-level GRU, hidden 256, embedding 32, four conditioning tables; 235k parameters) and its training loop |
ivy_int.py | The specification of the on-chain model: int8 weights, per-row fixed-point multipliers, lookup-table sigmoid/tanh/exp, keccak-based sampling. Solidity and TypeScript mirror it exactly |
export.py | Quantizes a checkpoint and writes the weight blob, 12 chunks, poet registry, layout, reference vectors and IvyLayout.sol |
poets.json | Five example poets: name, trait indices and rank (the registry's source). The full example project (500 poets) is at coldai.org/poets (opens in a new tab) |
provenance.py | Signs the lineage with EQTY and re-runs the export inside a recorded computation |
verify_provenance.py | Offline verification of the manifest and the files |
ivy_ink_1.pt | The trained float checkpoint |
Setup
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txtRetrain
curl -LO http://static.decontextualize.com/gutenberg-poetry-v001.ndjson.gz && mv gutenberg-poetry-v001.ndjson.gz gpc.ndjson.gz
python prep.py # data.npz (~180k samples)
python train.py 3000 # ivy_ink_1.pt; about an hour on a laptop CPU
python export.py # writes ../packages/foundry/data/weights and contracts/IvyLayout.solExport is deterministic: from the shipped checkpoint it reproduces the deployed weights byte for byte.
Provenance
python provenance.py [--corpus gpc.ndjson.gz] [--train-steps 3000]
python verify_provenance.pyprovenance.py uses eqty_sdk (opens in a new tab) to register the corpus, code, roster and checkpoint as assets. It then runs the export inside an EQTY @compute, so the manifest records a computation whose inputs are the checkpoint, roster and code and whose outputs are the weight blob, registry and each chunk. Every statement is signed as a W3C Verifiable Credential by a did:key persisted in .eqty/signers (gitignored: it is a private key).
- With
--train-steps, training is recorded as a computation too (needs the corpus). - Without it, the checkpoint carries a signed declaration naming the corpus and code. The summary says which of the two you got (
"training"). - With
--corpus, the dataset is registered by its content hash; otherwise it is registered by reference and marked"hashed": false.
Outputs:
provenance/manifest.json, the EQTY Integrity Manifest (import it in EQTY Explorer (opens in a new tab))../packages/foundry/data/provenance.json, a summary with each artifact's EQTY CID (BLAKE3) and SHA-256, read by the deploy and setup scripts../packages/nextjs/public/provenance/, the same two files for the app's/provenancepage
yarn foundry:setup anchors the manifest CID and the SHA-256 of the blob and of each chunk in an HCS topic, and the deploy stores the manifest CID and topic in InferenceMarket.model(). The app closes the loop: it hashes each weight contract's bytecode from the mirror node and compares it with the manifest. The chain from corpus to on-chain bytes is then checkable end to end.
Project layout
model/ training, integer reference model, export, EQTY provenance (Python)
packages/foundry/
contracts/IvyInk1.sol the model: GRU inference over weights read with EXTCODECOPY
contracts/InferenceMarket.sol pricing, tickets with PRNG seeds, privacy, escrow and refunds, revenue, model record
contracts/Anthology.sol one published output per ticket, optional attestation, long outputs
contracts/lib/ChunkedPayloads.sol inputs and outputs of any length, with a rolling-hash check
contracts/oracles/ Chainlink, Supra and Pyth adapters behind IHbarUsdSource
data/weights/ weight blob, 12 chunks, poet registry, reference vectors
script/Deploy.s.sol model, market, Anthology, oracles and configuration
scripts-js/ Hiero SDK setup (NFT, INK, HCS anchor), accept-token, fund
packages/nextjs/ Studio, Anthology, Provenance and Admin pages; the TypeScript runtime
packages/worker/ provider worker: serves tickets from any model endpoint, escrow-backed delivery
.harness/ Hedera Harness spec, PRD and validators for coding agents
docs/ deployment guides and README imagesTesting
yarn foundry:test # 59 tests: market, oracles, privacy, chunking, escrow and refunds, the model vs the Python reference
yarn foundry:test:testnet # plus the live Chainlink and Supra feeds and the deployed model, on a testnet fork
yarn next:test # 54 tests: runtime vs the reference poems, envelopes, chunking, pricing, base path
yarn worker:test # 39 tests: ticket selection, sealing, publish plans, resume logic
yarn lint && yarn next:build
yarn model:verify # EQTY manifest and files (Python)IvyInk1.t.sol generates the reference poems inside the EVM and requires them to match the Python integer model exactly. The Next.js tests do the same for the browser runtime, so all three implementations agree. CI runs the lint, the contract, runtime and worker suites, the type checks, the production build and the EQTY verification on every push (the fork tests need testnet access and run locally). A second workflow scaffolds a fresh project with create-scaffold-hbar and builds it.
Security notes and limits
- A ticket's seed is public once bought, so a buyer sees the poem before choosing to publish it. What a buyer cannot do is choose the seed: every published poem is one paid draw.
- Supra quotes HBAR against USDT; the deviation check bounds how far that can drift from Chainlink's HBAR/USD.
- Testnet oracle heartbeats are slower than mainnet's. If a feed goes stale, the market stops selling (
StalePrice(i)) rather than using an old price; the owner can widenmaxAgeor change the oracle set. - Private mode protects content with the creator's key and the buyer's wallet-derived key. Whoever holds
PROVIDER_PRIVATE_KEYcan read every private session, which is the point of the feature, so guard it like any secret. - Off-chain hosting with an attestor trusts the attestor for that model's outputs. Hedera hosting trusts nothing but the chain.
IvyInk1.solandIvyLayout.solare excluded fromforge fmt: the first must stay byte-identical to the deployed source, and the second is generated.
Contract reference
Contracts
IvyInk1 (the model)
| Function | |
|---|---|
generate(tokenId, seed, prefix, state, maxSteps) view → (text, newState, done) | Writes up to maxSteps characters, resuming from state (empty to start). Deterministic in (tokenId, seed, prefix) |
poet(tokenId) view → Poet | Decoding parameters derived from the poet's 16-byte registry record |
weightChunks(i), registry() | The 12 data contracts holding the weights and the poet registry |
Weights are read with EXTCODECOPY from data contracts whose code starts with a 0x00 byte. A matrix-vector product packs eight int8 multiply-accumulates into each 256-bit MUL (SWAR). About 1.85M gas per character plus ~0.47M per call, so a mirror-node call writes 7 characters (~13.4M) under Hedera's 15M cap.
InferenceMarket
| Function | Who | |
|---|---|---|
requestInference(poet, token, prefix, priceUpdate) payable → ticket | anyone | Pay in HBAR (token = 0) or an accepted HTS token (approve first). Seed from PRNG 0x169 |
quoteHbar() view, hbarUsdE18() view | anyone | Current HBAR price in tinybars, and the combined oracle price |
acceptedTokens() view, model() view, oracles() view | anyone | Configuration for the app |
withdraw(token) | anyone | Withdraw earnings; ECDSA aliases resolve through HIP-632 |
redeem(ticket, caller, prefixHash) → (poet, seed, isPrivate, buyer) | consumer | Used once by the Anthology, for the buyer or the fulfiller; releases the ticket's escrow |
refund(ticket) | buyer | Reclaim an escrowed payment after its deadline; the ticket is spent |
setFulfillment(fulfiller, deliveryWindow) | owner | Serve tickets yourself: escrow payments until delivery; (0, 0) returns to self-serve |
setPaused(paused) | owner | Stop requestInference, requestPrivateInference and appendInput; redeem, refund and withdraw keep working |
setPrice, setUsdPrice, setOracles, setHolderShare, setModel, setAttestor, setTreasury, setConsumer, transferOwnership | owner | Settings; each emits an event |
fulfiller(), deliveryWindow(), escrows(ticket) → (token, amount, deadline), paused() | anyone | Fulfillment state |
Errors: NotAccepted, Underpaid, UnexpectedValue, PrefixTooLong, NoOracle, StalePrice(i), PriceDeviation(low, high), Hts(code), BadTicket, TransferFailed, BadWindow (fulfiller without a window), Paused, NothingToRefund (no escrow for the ticket), NotYetRefundable(deadline).
Serving inference yourself (fulfiller and escrow)
A creator running the model off-chain (say, an LLM behind an API) names a fulfiller account with setFulfillment(fulfiller, deliveryWindow). From then on:
- Each purchase is held in escrow instead of credited:
escrows[ticket] = (token, amount, purchase time + deliveryWindow), eventEscrowed(ticket, token, amount, deadline). - The fulfiller publishes the output through the Anthology on the buyer's behalf (the buyer may still publish too). Redeeming releases the escrow to the treasury and the poet's holder exactly like a self-serve sale, event
Released(ticket). The poem'sauthoris the buyer, itspublisherthe fulfiller. - If nothing was published once
block.timestamp > deadline, the buyer callsrefund(ticket)and gets the payment back (HBAR or the HTS token), eventRefunded(ticket, buyer, token, amount). The ticket is then spent. A late delivery still releases the escrow as long as the buyer has not refunded.
Unsetting the fulfiller only affects new sales: escrowed tickets can still be delivered by their buyer or refunded.
| Event | |
|---|---|
FulfillmentSet(fulfiller, deliveryWindow) | setFulfillment |
PausedSet(paused) | setPaused |
Escrowed(ticket indexed, token, amount, deadline) | purchase while a fulfiller is set |
Released(ticket indexed) | escrowed ticket redeemed |
Refunded(ticket indexed, buyer indexed, token, amount) | refund |
Anthology
publish(ticket, prefix, text, attestation) → id emits Poem(id, ticket, poet, author, seed, prefix, text, publisher): author is always the ticket's buyer, publisher whoever sent the transaction (the buyer, or the market's fulfiller). PrivatePoem(…, ciphertext, publisher) and LongPoem(…, length, publisher) carry it too, and pending(ticket) returns (publisher, buyer, poet, isPrivate, seed, commitment); only the publisher who began a long output may append to it. attestationDigest(ticket, text) is what an attestor signs (EIP-191 over chainId, market, ticket, keccak256(text)).
Private inference
| Function | |
|---|---|
setPrivacy(providerKey, privateOnly) | owner: publish the creator's X25519 key (zero disables), optionally sell private sessions only. Emits PrivacySet(providerKey, privateOnly, version) |
providerKeyVersion(), providerKeyAt(version), ticketKeyVersion(ticket) | key rotation: every new non-zero key gets the next version (from 1); old keys stay readable, and each private ticket records the version its input was sealed to |
requestPrivateInference(poet, token, prefixHash, secretCommit, inputHash, inputLength, inputPart, priceUpdate) | buyer commits to a secret; seed = keccak(PRNG seed, secret); sealed input chunked. Emits PrivateInferenceRequested(ticket, buyer, poet, token, paid, seed, inputHash, inputLength, keyVersion) |
appendInput(ticket, part) | buyer: remaining parts of the sealed input |
Anthology.publishPrivate(ticket, prefixHash, commitment, ciphertext, attestation) | sealed output up to 2 KB |
Anthology.beginPublish(…) / appendOutput(ticket, part) | outputs of any length, public or sealed; LongPoem on completion |
lib/ChunkedPayloads.sol
Commit (finalHash, length), append parts (≤12 KB each, 4 KB recommended) as PayloadPart(key, index, data) events; completes only if keccak256(h_{i-1} ‖ part_i) rolls to finalHash. Used for sealed inputs (inputKey(ticket)) and long outputs (outputKey(ticket)).
oracles/HbarUsdSources.sol
ChainlinkHbarUsd(feed), SupraHbarUsd(pushOracle, pairId) and PythHbarUsd(pyth, feedId) implement IHbarUsdSource.
| Network | Chainlink HBAR/USD | Supra push (pair 75) | Pyth |
|---|---|---|---|
| Testnet 296 | 0x59bC155EB6c6C415fE43255aF66EcF0523c92B4a | 0x6Cd59830AAD978446e6cc7f6cc173aF7656Fb917 | 0xA2aa501b19aff244D90cc15a4Cf739D2725B5729 |
| Mainnet 295 | 0xAF685FB45C12b92b5054ccb9313e135525F9b5d5 | 0xD02cc7a670047b6b012556A88e275c685d25e0c9 | 0xA2aa501b19aff244D90cc15a4Cf739D2725B5729 |
Pyth HBAR/USD feed id: 0x3728e591097635310e6341af53db8b7ee42da9b3a8d918f9463ce9cca886dfbd.
Scripts
| Command | |
|---|---|
yarn foundry:test | Unit tests. Hedera system contracts (0x167, 0x169, 0x16a) are mocked with vm.etch |
yarn foundry:test:testnet | Adds test/fork/ against live testnet oracles |
yarn foundry:setup [--network hedera_testnet] [--poets 12] | Creates the Poets NFT, INK token and provenance topic with the Hiero SDK; writes deployments/hedera-setup.<chainId>.json |
yarn foundry:deploy --network hedera_testnet | Runs script/Deploy.s.sol (settings via env, see root README and below) and regenerates the frontend's deployedContracts.ts |
yarn foundry:account:import / :generate | Keystore for deploys |
yarn foundry:verify:testnet <address> <Contract> | Verify on Sourcify (HashScan shows it) |
yarn foundry:lint / :format | forge fmt (IvyInk1 and IvyLayout are excluded) and prettier |
Deploy settings for self-served inference
On top of the deploy settings in the root README:
| Variable | Default | Meaning |
|---|---|---|
FULFILLER | (none) | Provider account that serves tickets; when set, the deploy calls setFulfillment and payments are escrowed until delivery |
DELIVERY_WINDOW | 3600 s | Seconds the fulfiller has after each purchase before the buyer may refund (used only with FULFILLER) |
Licence
MIT, see LICENCE (opens in a new tab). Created by ColdAI (opens in a new tab) for Hedera. Builds on Scaffold-HBAR (hedera-dev) and Scaffold-ETH 2 (BuidlGuidl). The Gutenberg Poetry Corpus is public-domain text in a CC0 arrangement. eqty_sdk is Apache-2.0.