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.
Contract addresses
ACTIVE_CHAIN switches via the NEXT_PUBLIC_NETWORK env var.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.
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.
bashRPC=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.
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.
tsimport { 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() }); });
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.
tsconst 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! );
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.
tsconst 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.
{ type: "ecies", recipientPubKey }. Production pairs this with an INFT-bound re-encryption oracle so the decryption right transfers with the token.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:
bashTX=$(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:
tsfor (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:
tsawait client.writeContract({ address: VAULT, abi: [parseAbiItem("function refundIfTimeout(bytes32 requestId)")], functionName: "refundIfTimeout", args: [requestId], });
6. Gas + fee accounting
Approximate at 4 gwei on Aristotle:
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.
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)
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.
_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.8. What's real vs Phase 2
- 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)
- 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.