A verifiable provenance layer on Sui where both people and AI agents leave a tamper proof mark on the content they create.
Content is stored on Walrus. A signer (a person via wallet, or an AI agent via API key) writes an on chain attestation on Sui, routed through Tatum RPC, that records who signed, the Walrus blob id, the sha256 of the content, a declared provenance type, a timestamp, and an optional label. Anyone can then verify a file or a Sigil ID and get back authentic, tampered, or not found, with the signer, time, and provenance type.
Built for the Tatum x Walrus hackathon. Walrus storage and Tatum Sui RPC are both core, mandatory parts of every flow.
An attestation is a notarization, not a copyright claim. It cryptographically proves who signed a specific piece of content, and when. It does not prove the signer is the rightful author. The value comes from the identity behind the signature, the same way a notary stamp or a verified account does:
- Non repudiation: once you sign, you cannot deny you did, and nobody can forge your signature.
- Priority: a genuine author who signs early has an on chain timestamp that predates any later impersonator.
- AI disclosure: an AI provider signing "this was generated by our model" with its own key is self attesting its own output, where disclosure, not ownership, is the point.
The provenance type captures the intent: 0 human made, 1 AI generated,
2 AI assisted.
Walrus is the content store. It is accessed over plain HTTP against public operators (no SDK), which keeps the integration transparent.
- Store:
PUT {publisher}/v1/blobs?epochs=<n>with the raw bytes. The response is handled for both branches:newlyCreated.blobObject.blobId(first time this content is stored) andalreadyCertified.blobId(identical content already on Walrus). - Read:
GET {aggregator}/v1/blobs/<blobId>returns the bytes. - Public publishers cap uploads around 10 MiB; this is enforced with a clear error before any upload.
Code: lib/walrus.ts. Public testnet endpoints:
publisher.walrus-testnet.walrus.space and
aggregator.walrus-testnet.walrus.space.
During verification, Sigil fetches the stored blob back from the aggregator to confirm it is still retrievable, alongside the sha256 check.
Every Sui JSON-RPC call, both reads and transaction submissions, goes
through the Tatum gateway. There is a single chokepoint, suiRpc, in
lib/tatum.ts; nothing in the app talks to a fullnode directly.
- Gateway:
https://sui-testnet.gateway.tatum.io(mainnethttps://sui-mainnet.gateway.tatum.io), API key sent in thex-api-keyheader. - Reads: object reads (
sui_getObject), event queries (suix_queryEventsfor the registry and hash lookups), gas price, and coin selection all go through Tatum. - Writes: the Move package publish, the human sign flow, and the agent sign flow all submit their signed transactions through Tatum.
Two practical notes baked into the client:
- The Tatum free plan is rate limited to about 3 requests per second.
suiRpcserializes calls through a promise gate spaced byTATUM_MIN_INTERVAL_MS(default 350ms) and retries 429s with backoff, so the multi call publish, create, and verify flows stay under the limit. - Tatum's
sui_executeTransactionBlockreturns a minimal response. To get full effects,executeAndWaitcomputes the transaction digest locally (base58(blake2b256("TransactionData::" || bytes))) and then pollssui_getTransactionBlock.
move/sigil/sources/sigil.move is intentionally
minimal: an Attestation object plus a create function that validates the
provenance type, timestamps via the shared 0x6 Clock, emits an
AttestationCreated event for indexing, and transfers the object to its signer.
- Testnet package id:
0x66b4329479630cbdf74303cb5da63ad6bc185e20869ae9bdccd6bb5b63a666a2
Build is local (sui move build); the publish transaction is built with
@mysten/sui and submitted through Tatum, so even deployment stays on the Tatum
path. See scripts/publish.ts.
/app: connect a wallet, drop a file, choose a provenance type, press the seal.POST /api/store-and-preparestores the file on Walrus, computes the sha256 server side, and builds a fully resolvedcreatetransaction (gas resolved through Tatum).- The wallet signs the exact returned bytes.
POST /api/submitsubmits the signed transaction through Tatum and returns the tx digest and the created Sigil object id, shown as a certificate.
POST /api/sign with x-api-key (or Authorization: Bearer) and either JSON
{ content }, JSON { sha256, blobId }, or a multipart file. It stores on
Walrus, signs with a server side agent keypair, submits through Tatum, and
returns { blobId, sha256, digest, objectId, signer, verifyUrl }. Each API key
maps to one Sui address.
curl -X POST https://getsigil.xyz/api/sign \
-H "x-api-key: YOUR_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"A report my agent wrote.","provenanceType":1,"label":"summary"}'POST /api/verify with a sigilId, a file, or both. The primary tamper check
is the recomputed sha256 versus the sha256 stored on chain. Verdicts:
authentic, tampered, not_found.
mcp/sigil-mcp.ts exposes sigil_sign and sigil_verify
as MCP tools over stdio, as a thin client over the same API, so an MCP client
like Claude Desktop can sign and verify directly. Run with npm run mcp.
app/ Next.js App Router
page.tsx landing page
app/ human sign flow (wallet)
verify/ verify a file or a Sigil ID
agents/ agent API docs and key issuance
registry/ live feed of recent attestations
api/ route handlers (sign, verify, submit, registry, ...)
icon.svg wax seal favicon
components/ UI components (wax seal, flows, nav, footer, ...)
lib/ core logic
walrus.ts Walrus store and read over HTTP
tatum.ts single chokepoint for all Sui RPC through Tatum
sigil.ts build the on chain create transaction
agent-auth.ts agent API key to Sui address mapping
mcp/ MCP server exposing sigil_sign and sigil_verify
move/sigil/ the Sui Move package (Attestation + create)
scripts/ dev tools: publish, walrus/tatum checks, ascii bake
Requirements: Node 20+, a Tatum API key with Sui testnet access, a funded testnet Sui address.
npm install
cp .env.example .env.local # then fill in the values
npm run dev # http://localhost:3000Useful scripts:
npm run walrus:test # store a blob and read it back
npm run tatum:test # read chain data through Tatum
npm run sigil:publish # publish the Move package (writes the package id)
npm run sigil:create # create + read back an attestation end to end
npm run mcp # start the MCP server| Variable | Purpose |
|---|---|
TATUM_API_KEY |
Tatum API key, sent as x-api-key |
SUI_NETWORK |
testnet or mainnet |
SUI_RPC_URL |
Tatum Sui gateway URL |
WALRUS_PUBLISHER_URL |
Walrus publisher base URL |
WALRUS_AGGREGATOR_URL |
Walrus aggregator base URL |
SIGIL_PACKAGE_ID |
Published Move package id |
NEXT_PUBLIC_SIGIL_PACKAGE_ID |
Same, exposed to the browser for the wallet flow |
NEXT_PUBLIC_SUI_NETWORK |
Network for the wallet UI |
DEPLOYER_PRIVATE_KEY |
Server only, used by the publish and create scripts |
AGENT_PRIVATE_KEY |
Server only, the agent signer |
AGENT_API_KEY |
The API key an agent presents to /api/sign |
Secrets live only in .env.local, which is gitignored. Nothing secret is ever
exposed to the browser.
The app deploys to Vercel as a standard Next.js App Router project. Import the
repo, set the environment variables above in the Vercel project settings (mark
the NEXT_PUBLIC_* ones as exposed), and deploy. Keep SUI_NETWORK=testnet
until the final mainnet switch.
Next.js App Router, Tailwind v4, Motion, Phosphor icons, @mysten/dapp-kit and
@mysten/sui, Walrus over HTTP, Sui RPC through Tatum.