Hedera On-Chain AI Service (HOCA)

HOCA documentation

The HOCA template's README, contract reference and model guide, rendered from the repository at commit c12234c (opens in a new tab).

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)

bash
npm create scaffold-hbar@latest -- --template shayansal/scaffold-hbar-onchain-ai

Why 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 getHow
🧾Every output is checkableThe 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-pickingEach paid ticket's seed comes from Hedera's PRNG (HIP-351), in the same transaction as the payment.
💵Priced in dollars, paid in anythingHBAR at a USD price from the median of Chainlink, Supra and Pyth, or any HTS token at its own price.
🔒Private when you need itInputs and outputs sealed to the buyer and the model's creator; the seed stays unbiased yet secret.
🤝Money-back deliveryServe 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 bytecodeAn EQTY Lab Integrity Manifest signs the lineage; its CID is anchored on HCS, and the app checks it against the contracts on Hedera.
📏Any lengthLong 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 haveConfigureBuyers get
A small deterministic modelMODEL_HOSTING=hedera: weights as contract bytecodeOutputs anyone can replay for free, trusting nothing but the chain
Model weights you publishMODEL_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 GPUsFULFILLER + ATTESTOR, packages/worker pointed at any OpenAI-compatible endpointPay-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 tokenAccept any HTS token, and give NFT holders a revenue shareModels owned and paid for in your own token

How it works

  1. 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).
  2. 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.generate through the mirror node's /contracts/call in 7-character steps, which costs nothing. The browser runtime is bit-exact with the contract, so both write the same poem.
  3. Publish. Anthology.publish(ticket, prefix, text, attestation) redeems the ticket once and emits Poem(id, ticket, poet, author, seed, prefix, text, publisher). Anyone can regenerate the text from (poet, seed, prefix) and compare.
  4. Get paid. Revenue accrues inside the market: the treasury's share, and holderShareBps for whoever holds the poet NFT. Everyone withdraws their own balance, and ECDSA accounts can withdraw from their EVM alias (HIP-632).
Model
Payment
Privacy
Delivery
Size
Build

Bring your own model

Train → quantize → export → EQTY manifest

Host

Your model on Hedera

Integer weights stored as contract bytecode

Anyone replays it through the mirror node

Set up

HTS NFT collection (optional revenue share)

Holders earn a share of every payment

Set up

HCS provenance topic + EQTY manifest

Manifest CID and weight hashes anchored

Buy

Buyer

Pays with HBAR at a USD price or an HTS token

Sends the prompt in the clear

Pay

HTS tokens

INK, USDC or any token at a fixed price

Price

Oracles

Chainlink + Supra (+ optional Pyth)

Median with a deviation guard

Sell

InferenceMarket

Ticket with a PRNG seed (0x169)

Pricing, revenue split, pause switch

Run

Buyer runs it

Browser runtime or free mirror-node calls

Publish

Anthology (public)

Output text stored; ticket redeemed once

Inline in one transaction

Verify

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.

config
# 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 work

Hedera services used

ServiceWhereWhat it does here
Smart contracts (EVM)IvyInk1, 12 data contracts, InferenceMarket, AnthologyThe model itself runs in the EVM: an int8 GRU with SWAR matrix-vector products and lookup-table activations
HTS, non-fungiblePoets collectionPoet serial = the model's conditioning record; holders earn a revenue share (ownerOf through the HTS ERC-721 facade)
HTS, fungibleINK demo token, or USDC etc.Accepted as payment through the ERC-20 facade; the market associates itself via the HTS system contract (0x167)
HCSProvenance topicAnchors the EQTY manifest CID, the weights' SHA-256 and each chunk's SHA-256
PRNG (HIP-351)0x169Unbiased per-ticket seeds, drawn in the payment transaction
Account service (HIP-632)0x16aMaps an ECDSA EVM alias to the long-zero account HTS reports as owner
Mirror nodeApp and workerFree model execution (/contracts/call), weight bytecode, Anthology and payload events, HCS messages
JSON-RPC relay (Hashio)Wallets, FoundryFee handling built in: gas price from eth_gasPrice, tinybars vs weibars
Hiero SDKscripts-js/setupHedera.js, fund.jsCreates the NFT collection, INK token and HCS topic; funds test wallets

Ecosystem integrations

IntegrationRoleWhy it is load-bearing
Chainlink Data FeedsHBAR/USD push feedUSD pricing without keys; the default
Supra push oracleHBAR/USDT (pair 75)Second independent source: the market halts if it and Chainlink disagree
PythHBAR/USD pull feedOptional third source; the app fetches signed updates server-side (Hermes needs an API key since 2026-08-26)
EQTY Lab eqty_sdkIntegrity ManifestSigned, content-addressed lineage from training data to the exact bytes deployed on Hedera
IPFS / any URLOff-chain hostingWhere weights live when the model is not hosted on Hedera
OpenAI-compatible APIs (vLLM, Ollama, hosted)Provider workerServe 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.

bash
yarn install
yarn next:dev          # http://localhost:3000

The 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).

PageWhat you can do
/ StudioPick a poet, try it free, commission (HBAR or any accepted token, public or private), publish, verify, refund an undelivered ticket
/anthologyEvery published poem from the chain, each with Verify and HashScan links; decrypt your private poems
/provenanceThe EQTY lineage, checked against the bytecode on Hedera and the HCS anchor
/adminOwner console: hosting, prices per token, oracles, holder share, attestor, privacy keys, delivery, pause; everyone's earnings and withdraw
/debugScaffold-HBAR's contract debugger

Deploy your own market

  1. 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.
  2. Keys. Import it for Foundry, and give the same account to the setup script:

    bash
    yarn foundry:account:import
    cp packages/foundry/.env.example packages/foundry/.env   # then set HEDERA_OPERATOR_ID / HEDERA_OPERATOR_KEY
  3. Native resources. Create the Poets NFT collection (the 5 example poets), the INK token and the provenance topic, and anchor the manifest:

    bash
    yarn foundry:setup

    Results go to packages/foundry/deployments/hedera-setup.296.json, with HashScan links in the output.

  4. 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.

    bash
    yarn foundry:deploy --network hedera_testnet

    This regenerates packages/nextjs/contracts/deployedContracts.ts, so the app now talks to your market.

  5. 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:

    bash
    yarn 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

VariableDefaultUsed by
HEDERA_RPC_URLhttps://testnet.hashio.io/apifork tests
LOCALHOST_KEYSTORE_ACCOUNTscaffold-hbar-defaultlocal deploys
HEDERA_OPERATOR_ID, HEDERA_OPERATOR_KEY(none)yarn foundry:setup, yarn foundry:fund

Deploy settings (environment variables for yarn foundry:deploy; all optional)

VariableDefaultMeaning
MODEL_HOSTINGhederahedera or offchain
IVY_MODELpublic testnet Ivy Ink 1Reuse 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_MICROS10000 ($0.01)HBAR price in micro-dollars; 0 means a fixed tinybar price
PRICE_TINYBARS10000000 (0.1 HBAR)Fixed HBAR price off Hedera or when USD pricing is off
ORACLESchainlink,supraAny of chainlink, supra, pyth, comma-separated
ORACLE_MAX_DEVIATION_BPS200Largest spread allowed between oracles (2%)
CHAINLINK_MAX_AGE / SUPRA_MAX_AGE86400 / 7200 sStaleness limits; Pyth is always 60 s
HOLDER_SHARE_BPS2500Poet holder's share of each payment (25%)
ATTESTOR(none)Require this key's signature on every published output
PROVIDER_KEY, PRIVATE_ONLY(none), falseTurn on private inference; PRIVATE_ONLY sells nothing else
FULFILLER, DELIVERY_WINDOW(none), 3600 sProvider-served delivery with escrow; refunds after the window
POETS_TOKEN, PROVENANCE_TOPIC, MANIFEST_CID, TREASURYfrom setup, provenance, deployerOverrides

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

VariableNeeded for
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_IDWalletConnect wallets (cloud.reown.com)
NEXT_PUBLIC_BASE_PATHServing the app under a sub-path, e.g. /hederabounty/test (build-time)
PYTH_API_KEYOnly if the market uses Pyth: /api/pyth-update adds it server-side
ATTESTOR_PRIVATE_KEYOnly for off-chain hosting with an attestor: /api/attest signs outputs server-side
PROVIDER_PRIVATE_KEYOnly 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:

bash
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 /admin

With privateOnly the market sells nothing else. A private session works like this:

  1. The buyer's browser draws a 32-byte secret and sends only keccak256(secret) with requestPrivateInference. The market draws its PRNG value in the same transaction, so the effective seed keccak256(ticket.seed, secret) is still unbiased (the buyer committed before the PRNG value existed) but unknown to everyone else.
  2. 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.
  3. 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.
  4. Anthology.publishPrivate stores the sealed output and keccak256(secret, text), a hiding commitment.
  5. 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 /anthology with 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 PayloadPart event. 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 beginPublish and appear as LongPoem once 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:

bash
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 calls refund(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=ivy runs Ivy Ink with the app's runtime; any OpenAI-compatible base URL (vLLM, Ollama, a hosted provider) works with MODEL_API_KEY and MODEL_NAME. Models that cannot be replayed from a seed should run with an ATTESTOR, 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 and decrypt-private hold 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:

bash
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 files

To 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.

FilePurpose
prep.pyBuilds 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.pyThe model (character-level GRU, hidden 256, embedding 32, four conditioning tables; 235k parameters) and its training loop
ivy_int.pyThe 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.pyQuantizes a checkpoint and writes the weight blob, 12 chunks, poet registry, layout, reference vectors and IvyLayout.sol
poets.jsonFive 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.pySigns the lineage with EQTY and re-runs the export inside a recorded computation
verify_provenance.pyOffline verification of the manifest and the files
ivy_ink_1.ptThe trained float checkpoint

Setup

bash
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt

Retrain

bash
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.sol

Export is deterministic: from the shipped checkpoint it reproduces the deployed weights byte for byte.

Provenance

bash
python provenance.py [--corpus gpc.ndjson.gz] [--train-steps 3000]
python verify_provenance.py

provenance.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 /provenance page

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 images

Testing

bash
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 widen maxAge or change the oracle set.
  • Private mode protects content with the creator's key and the buyer's wallet-derived key. Whoever holds PROVIDER_PRIVATE_KEY can 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.sol and IvyLayout.sol are excluded from forge 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 → PoetDecoding 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

FunctionWho
requestInference(poet, token, prefix, priceUpdate) payable → ticketanyonePay in HBAR (token = 0) or an accepted HTS token (approve first). Seed from PRNG 0x169
quoteHbar() view, hbarUsdE18() viewanyoneCurrent HBAR price in tinybars, and the combined oracle price
acceptedTokens() view, model() view, oracles() viewanyoneConfiguration for the app
withdraw(token)anyoneWithdraw earnings; ECDSA aliases resolve through HIP-632
redeem(ticket, caller, prefixHash) → (poet, seed, isPrivate, buyer)consumerUsed once by the Anthology, for the buyer or the fulfiller; releases the ticket's escrow
refund(ticket)buyerReclaim an escrowed payment after its deadline; the ticket is spent
setFulfillment(fulfiller, deliveryWindow)ownerServe tickets yourself: escrow payments until delivery; (0, 0) returns to self-serve
setPaused(paused)ownerStop requestInference, requestPrivateInference and appendInput; redeem, refund and withdraw keep working
setPrice, setUsdPrice, setOracles, setHolderShare, setModel, setAttestor, setTreasury, setConsumer, transferOwnershipownerSettings; each emits an event
fulfiller(), deliveryWindow(), escrows(ticket) → (token, amount, deadline), paused()anyoneFulfillment 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:

  1. Each purchase is held in escrow instead of credited: escrows[ticket] = (token, amount, purchase time + deliveryWindow), event Escrowed(ticket, token, amount, deadline).
  2. 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's author is the buyer, its publisher the fulfiller.
  3. If nothing was published once block.timestamp > deadline, the buyer calls refund(ticket) and gets the payment back (HBAR or the HTS token), event Refunded(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.

NetworkChainlink HBAR/USDSupra push (pair 75)Pyth
Testnet 2960x59bC155EB6c6C415fE43255aF66EcF0523c92B4a0x6Cd59830AAD978446e6cc7f6cc173aF7656Fb9170xA2aa501b19aff244D90cc15a4Cf739D2725B5729
Mainnet 2950xAF685FB45C12b92b5054ccb9313e135525F9b5d50xD02cc7a670047b6b012556A88e275c685d25e0c90xA2aa501b19aff244D90cc15a4Cf739D2725B5729

Pyth HBAR/USD feed id: 0x3728e591097635310e6341af53db8b7ee42da9b3a8d918f9463ce9cca886dfbd.

Scripts

Command
yarn foundry:testUnit tests. Hedera system contracts (0x167, 0x169, 0x16a) are mocked with vm.etch
yarn foundry:test:testnetAdds 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_testnetRuns script/Deploy.s.sol (settings via env, see root README and below) and regenerates the frontend's deployedContracts.ts
yarn foundry:account:import / :generateKeystore for deploys
yarn foundry:verify:testnet <address> <Contract>Verify on Sourcify (HashScan shows it)
yarn foundry:lint / :formatforge fmt (IvyInk1 and IvyLayout are excluded) and prettier

Deploy settings for self-served inference

On top of the deploy settings in the root README:

VariableDefaultMeaning
FULFILLER(none)Provider account that serves tickets; when set, the deploy calls setFulfillment and payments are escrowed until delivery
DELIVERY_WINDOW3600 sSeconds 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.