Skip to Content
Smart ContractsOverview

Smart contracts

Every Artifact is anchored in a public smart contract. This section documents that contract directly — where it lives, what it stores, how to read it, and how to write to it yourself.

Why the on-chain record matters

A database row is only as durable as the company running it. Rows can be edited, lost, or disappear along with the service.

The on-chain entry can’t. Anyone can confirm that a fingerprint was registered, and when, by querying a public RPC — with no Monolith account, no API key, and no permission from us. Monolith cannot quietly rewrite an entry after the fact, and neither can anyone else.

The contract is the part of Monolith you don’t have to take our word for.

The network

NetworkGlobal Trust Network (Stability Protocol)
Chain ID101010
RPChttps://rpc.stabilityprotocol.com/zgt/try-it-out
Explorerexplorer.stabilityprotocol.com 
Contract0xfE9daFC133eD3e9726D4B2c24b8cC3c3AaDD8225

The chain is gasless. Transactions cost nothing to send — there is no token to acquire, no balance to fund, and no fee to estimate. Send writes with a gas price of 0 rather than relying on fee estimation.

That single address is the entry point for everything. Calls to it are routed internally to several implementation contracts, so you always call this one address — the implementations behind it are not meant to be called directly. One practical consequence: the published ABI is the union of all of them, which is why a single ABI covers both registries described below.

What is stored on-chain

Each entry is two 32-byte hashes and three short strings:

FieldTypeWhat it holds
fingerprintbytes32SHA-256 of the file’s exact bytes — the key for every lookup
manifestHashbytes32A hash committing to the record’s metadata
userIdstringIdentifier for the registering account
endpointsstring[]Soft Binding Resolution API URLs
manifestIdstringThe manifest identifier this fingerprint resolves to
timestampuint256Block time when the entry was written
registeredByaddressThe wallet that sent the transaction

Everything else stays off-chain: the file bytes, the metadata document itself, C2PA manifests and sidecars, thumbnails, cards, and all Mark and Organization records. There is no IPFS, no content addressing, and no merkle root — just the fields above.

For Artifacts created through Monolith, manifestHash is the keccak-256 hash of the Artifact’s metadata JSON, not of the C2PA manifest binary — and on-chain manifestId is the Artifact’s identifier, not a C2PA manifest label. The names are inherited from the C2PA soft binding spec; don’t assume either one hashes or points at the JUMBF manifest you get from manifestBinaryUrl.

Your file never needs to be uploaded for any of this. A fingerprint is computed locally and is all the contract ever sees — see Core concepts.

Two registries, one address

The contract holds two independent registries with separate storage:

CuratedPublic
Who writes itMonolith, when you create an ArtifactAny wallet, directly
What you needA Monolith account or API keyA wallet
What’s in itEvery ArtifactWhatever third parties register
Resolution priorityWinsUsed only when no curated entry exists
Read functionsgetManifestRecord, manifestExists, …getPublicManifestRecord, publicManifestExists, …

Both are keyed by fingerprint, and the same fingerprint can exist in each one independently. When it does, the curated entry is the one that resolves — see Public records.

If you use the Monolith API or apps, your Artifacts land in the curated registry and you never touch the contract yourself. The public registry is there for anyone who wants to anchor a binding without going through Monolith at all.

Getting the ABI

The canonical source is a public, unauthenticated endpoint on the Monolith API. It returns the network details, the contract address, and the full ABI together, and always matches what is deployed:

curl -s https://api.joinmonolith.com/c2pa/contract
{ "chain": { "id": 101010, "name": "Global Trust Network", "rpcUrl": "https://rpc.stabilityprotocol.com/zgt/try-it-out", "explorerUrl": "https://explorer.stabilityprotocol.com" }, "address": "0xfE9daFC133eD3e9726D4B2c24b8cC3c3AaDD8225", "abi": [ ] }

A static copy is also served from this site for offline use and codegen: /abi/monolith-c2pa-registry.json.

Prefer the live endpoint. The static copy is a convenience snapshot and will not track changes to the deployed contract.

Your first read

Fetching the endpoint gives you the address and the ABI in one call, so neither needs to be hard-coded:

import { JsonRpcProvider, Contract } from 'ethers' const RPC = 'https://rpc.stabilityprotocol.com/zgt/try-it-out' const { address, abi } = await fetch( 'https://api.joinmonolith.com/c2pa/contract' ).then(r => r.json()) const provider = new JsonRpcProvider(RPC, 101010) const registry = new Contract(address, abi, provider) console.log(await registry.c2paSpecVersion()) // '2.2.0' console.log(await registry.supportedAlgorithms()) // [ 'sha256' ] console.log(await registry.getTotalManifests()) // e.g. 134425n

Reads are free and open to anyone — no wallet, no key, no signature.

Where to go next

  • Public records — register a binding yourself, and what a public entry does and doesn’t prove
  • Soft binding — the C2PA spec the contract implements, and how to resolve a fingerprint end-to-end
  • Interface — every function, event, and revert
  • Core concepts — Mark, Artifact, Fingerprint, C2PA
Last updated on