/docs

Build on Mekar.

Mekar is on-chain royalty infrastructure on 0G — not a closed product. Pay an agent from a Discord bot, index the cascade for analytics, mint INFTs from your own UI. Same on-chain contract, same atomic royalty distribution.

Integration TL;DR: writeContract({ address: VAULT, fn: "payInference", args: [agentId], value}) escrows the fee, then settleInference walks the lineage, splits royalty across 4 generations, and sweeps dust to treasury in one atomic tx.

Aristotle mainnet · chain 16661

Contract addresses

AgentINFT (ERC-7857)
0x0e8e941c363dc1C06DD0bC02395B775dE94B48a4
MekarRegistry
0xF24C4B0f45a46E2d761770BA75e147DEb738d3A6
RoyaltyVault
0x55107dB2CB8399fbA7Fdd913fd5a0FBACd7134f6
AlignmentAuditor
0x66f6f49B80d4F705AB1b8Fe8E6b2cA51846EBDE8
TrainingDataRegistry
0x3917e0fcb2E865047A0cDAF4CB648DdCA3B4bB46
Note. Live on 0G Aristotle mainnet (chain 16661). All five contracts deployed fresh + wired. Explorer: chainscan.0g.ai. The same code path runs on Galileo testnet too — ACTIVE_CHAIN switches via the NEXT_PUBLIC_NETWORK env var.
Why Mekar · for AI creators

Earn from your model

Whether your model is open-source or proprietary, if it gets used you should get paid. Publish a base model, a fine-tune, or a LoRA — Mekar turns every downstream use into a royalty stream that flows back to you, with no platform, no invoicing, and no middleman taking a cut. Proprietary weights stay private: register in Strict mode and the weights are AES-encrypted on 0G Storage — only the royalty rail is public, never the model itself.

The three steps

ts
// 1. Register your model once — it becomes an ERC-7857 INFT. const agentId = await agentINFT.mintGenesis( weightsPointer, // 0G Storage rootHash of your weights trainingMerkle, // Merkle root of your training data teeProof, // TEE attestation hash royaltySchema, // your split — 50/25/15/7/3 by default ); // 2. Anyone who uses the agent pays through the vault: await royaltyVault.payInference(agentId, { value: fee }); // 3. A registered compute provider calls settleInference — // the fee then cascades on-chain in one atomic tx: // 50% → you (direct owner) // 25% → gen-1 parents you forked from // 15% → gen-2 ancestors // 7% → gen-3+ (capped at depth 10) // 3% → training-data contributors // Dust + any unpayable share sweeps to the protocol treasury.

What this means for you

  • Forks pay you back. When someone fine-tunes your model, their agent lists yours as a parent. Every inference on the fork sends a gen-1 share up to you — automatically, forever.
  • No account, no platform. Royalty lands in the wallet that minted the agent. Mekar is a contract, not a marketplace — it never holds or gates your earnings.
  • You set the split. The royalty schema is a mint-time parameter. Want training-data contributors to get more than 3%? Configure it when you mint genesis.
  • Alignment protects the rail. An agent slashed for bias drift earns a reduced share — so a misaligned fork can't dilute honest ancestors.
Note. The royalty cascade is live on chain today — see the settlement proof in section 8. What's Phase 2 is the inference compute itself (0G Compute TEE) — Mekar settles the payment rail regardless of who runs the model.
cast · CLI

1. Five-minute hello, MEKAR

Verify your wallet can pay an inference and trigger the royalty cascade. Need ~0.002 OG of real $0G on Aristotle mainnet — or run against Galileo testnet with free $0G from the 0G faucet.

bash
RPC=https://evmrpc.0g.ai VAULT=0x55107dB2CB8399fbA7Fdd913fd5a0FBACd7134f6 # Read the live price for agent #4 (Carol's compose) PRICE=$(cast call $VAULT \ "getInferencePrice(uint256)(uint256)" 4 \ --rpc-url $RPC | awk '{print $1}') # Pay — escrow opens, requestId emitted as first indexed topic cast send $VAULT "payInference(uint256)" 4 \ --value $PRICE \ --rpc-url $RPC \ --private-key $PK \ --legacy --async

That's it. Royalty cascades automatically when a registered provider settles the requestId. No claim button needed.

Node.js · viem

2. Pay-per-inference Express bot

Wrap a MEKAR agent invocation as a REST endpoint. Your user POSTs, your service pays MEKAR, royalty cascades on chain.

ts
import { createWalletClient, createPublicClient, http, parseAbiItem } from "viem"; import { privateKeyToAccount } from "viem/accounts"; const VAULT = "0x55107dB2CB8399fbA7Fdd913fd5a0FBACd7134f6" as const; const account = privateKeyToAccount(process.env.PK as `0x${string}`); const wallet = createWalletClient({ account, chain: zg, transport: http() }); const pub = createPublicClient({ chain: zg, transport: http() }); app.post("/inference/:agentId", async (req, res) => { const id = BigInt(req.params.agentId); const price = await pub.readContract({ address: VAULT, abi: [parseAbiItem("function getInferencePrice(uint256) view returns (uint256)")], functionName: "getInferencePrice", args: [id], }); const hash = await wallet.writeContract({ address: VAULT, abi: [parseAbiItem("function payInference(uint256) payable returns (bytes32)")], functionName: "payInference", args: [id], value: price, }); res.json({ ok: true, txHash: hash, paid: price.toString() }); });
Analytics · viem

3. Royalty indexer in 30 lines

Build a leaderboard from RoyaltyPaid events. Same parallel-chunked scan pattern as the frontend's useUserStats hook — public 0G RPC tolerates ~5 concurrent log fetches.

ts
const event = parseAbiItem( "event RoyaltyPaid(uint256 indexed agentId, address indexed recipient, uint16 generation, uint256 amount)" ); const CHUNK = 50_000n; const ranges: { from: bigint; to: bigint }[] = []; for (let f = DEPLOY_BLOCK; f <= latest; f += CHUNK) { ranges.push({ from: f, to: f + CHUNK > latest ? latest : f + CHUNK }); } // 5 concurrent at a time — same trick as the frontend hook const results = await Promise.all( ranges.map(r => client.getLogs({ address: VAULT, event, fromBlock: r.from, toBlock: r.to })) ); // Group by recipient → leaderboard const byRecipient = new Map<string, bigint>(); for (const logs of results) for (const log of logs) byRecipient.set( log.args.recipient!, (byRecipient.get(log.args.recipient!) ?? 0n) + log.args.amount! );
0G Storage · AES-256

4. Encrypt weights before upload

The 0G SDK ships AES-256 encryption directly in UploadOption.encryption. The MEKAR /api/storage/upload route exposes it as an encryption: "aes256" flag — the route generates a fresh 256-bit key, encrypts client-side before chunks leave for storage nodes, and returns the key with the rootHash.

ts
const aesKey = new Uint8Array(32); crypto.getRandomValues(aesKey); const [result, err] = await indexer.upload( new MemData(Array.from(buf)), "https://evmrpc.0g.ai", signer, { encryption: { type: "aes256", key: aesKey } } ); // result.rootHash is what you anchor on chain. // Persist aesKey separately — only key-holders can decrypt.
Note. ECIES (public-key) encryption is also supported via { type: "ecies", recipientPubKey }. Production pairs this with an INFT-bound re-encryption oracle so the decryption right transfers with the token.
Network · gotchas

5. Error patterns that actually work

Three patterns we've hardened through actual development pain.

cast send hangs on receipt fetch

0G's RPC occasionally drops receipt fetches occasionally. Use --async, then poll cast receipt with backoff:

bash
TX=$(cast send $VAULT ... --async) for i in 2 3 4 5 6; do sleep $i status=$(cast receipt $TX --rpc-url $RPC 2>/dev/null | grep ^status | awk '{print $2}') [ "$status" = "1" ] && break done

getLogs silently returns []

Block ranges >100k return empty without error. Always chunk ≤50k:

ts
for (let from = startBlock; from <= latest; from += 50_000n) { const to = from + 50_000n > latest ? latest : from + 50_000n; const chunk = await client.getLogs({ address, event, fromBlock: from, toBlock: to }); }

Stuck escrow recovery

If settleInference never fires, any caller can refund after the 1h timeout:

ts
await client.writeContract({ address: VAULT, abi: [parseAbiItem("function refundIfTimeout(bytes32 requestId)")], functionName: "refundIfTimeout", args: [requestId], });
Operation costs

6. Gas + fee accounting

Approximate at 4 gwei on Aristotle:

mintGenesis
~340k gas · ~0.00136 OG
mintFork
~270k gas · ~0.00108 OG
mintCompose
~580k gas median · ~0.00232 OG
payInference
~165k gas · ~0.00066 OG
settleInference (3-deep)
~165k gas · ~0.00066 OG
settleInference (5-deep)
~225k gas · ~0.00090 OG
Indexer.upload (tiny anchor)
~0.00003 OG
flagAgent (AlignmentAuditor)
~75k gas · ~0.00030 OG

Cascade math

Each payInference attaches 0.0012 OG by default (base 0.001 + 10% protocol + 10% provider). Base splits:

  • 50% to direct owner
  • 25% to gen1 parents (split equally)
  • 15% to gen2 (deduplicated)
  • 7% to gen3+ (capped at depth 10)
  • 3% to training contributors (or creator if none registered)

Undistributable share (deep gen, alignment slash, burned recipient) consolidates into the protocol treasury.

DoS resistance · gas bounds

7. Safety & limits

Mekar is designed so that hostile or accidental fan-out (mass fork, mass compose, deep lineage) can't grief the protocol or blow up the royalty walk. Three bounds enforce this on chain.

Hard limits (contract-enforced)

MAX_PARENTS (compose)
8 — revert if parentIds.length < 2 or > 8
MAX_LINEAGE_DEPTH (royalty walk)
10 generations — BFS stops here
MAX_GENERATION (mint)
100 — circular lineage guard
MAX_LINEAGE_DEPTH (view query)
50 — registry getAncestors cap

What happens when…

  • 10,000 forks descend from one agent: safe. Registration is O(1) per fork (one _descendants.push). Parent only earns royalty per child inference, not in aggregate — no DoS on the parent.
  • Compose with 8 parents: safe. Gen-1 share (25%) splits equally, integer-division dust sweeps to treasury. Storage cost: 256 bytes for the parent array.
  • Deep lineage (gen 50+): royalty walk caps at gen-10, treasury collects beyond. Gas remains bounded regardless of chain depth.
  • Ancestor token burned: distribution catches via try/catch (Q5 fix), share routes to treasury.
  • Alignment-slashed agent: share is reduced amount × alignmentHealth / 10000; reduction goes to treasury.
Note. Known soft limit: the per-parent _descendants[] array is unbounded. This is read-only — never blocks transactions — but a hyper-popular parent can make getDescendants() view calls expensive. Indexer-based readers (option 3 above) sidestep this entirely.
Honesty audit

8. What's real vs Phase 2

✓ Live + working
  • 0G Chain (16661) — 5 contracts deployed + wired
  • INFT / ERC-7857 — mint/fork/compose flows tested + live
  • 0G Storage Log — real Indexer.upload, anchored on Flow contract
  • AES-256 encryption at upload — SDK-direct, key returned to caller
  • AlignmentAuditor — score scales ancestor royalty (real economic effect)
  • Royalty cascade — atomic, wei-perfect math across 13 mainnet settlements
  • 0G Compute Broker SDK — verified callable (see smoke-compute.ts)
🟡 Phase 2
  • 0G Storage Specialized Flow tier — pointer plumbing in place, premium permanence next
  • Real TEE-attested inference via 0G Compute — broker reachable but no DSN services registered on 0G yet
  • Multi-auditor oracle network — currently single approved auditor
  • 0G Storage KV writeback for mutable metadata — localStorage proxy ships today
  • Data Serving Network provider registration — operational layer, post-mainnet

Need the full reference?

Every contract method, event signature, error code, and the deeper architecture lives in the markdown docs in the repo.

Docs — Build on Mekar · Mekar