> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mnemom.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# On-Chain Verification

> Verify agent and team reputation scores, inspect Merkle anchors, and audit the on-chain trail — all via read-only Mnemom API calls or direct Base L2 RPC.

**Anyone can verify a Mnemom agent's reputation without trusting Mnemom's infrastructure.** Scores and Merkle roots are published to Base L2 contracts by Mnemom on a cron schedule; you read them back either through the Mnemom API or directly via any Ethereum-compatible client.

For the conceptual overview — why on-chain anchoring exists, how the Merkle tree maps to integrity checkpoints, and what ERC-8004 means for interop — see [On-Chain Verification](/concepts/on-chain-verification).

<Note>
  Anchoring and score publishing are **not customer-callable endpoints**. Mnemom's backend runs them on a 6-hour cron under an internal service key. Customers consume on-chain data through the three read endpoints below, or by querying the contracts directly.
</Note>

***

## Prerequisites

Before using on-chain verification features, ensure you have:

1. **A Mnemom API key** — created in your dashboard or via `POST /v1/api-keys`. Read-only access is enough.
2. **Agents with published reputation scores** — agents need at least 50 analyzed integrity checkpoints and a public [Mnemom Trust Rating](/concepts/reputation-scores) before they appear on-chain.

**API key authentication:** pass your key in the `X-Mnemom-Api-Key` header:

```
X-Mnemom-Api-Key: {api_key}
```

(Bearer JWTs from `/v1/auth/login` work too.)

***

## Verifying a score against the chain

Confirm that the score Mnemom reports off-chain matches what's published on-chain, and that the underlying Merkle root has been anchored.

**Endpoint:** `GET /v1/on-chain/verify-proof/{agent_id}` — public, no auth required.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.mnemom.ai/v1/on-chain/verify-proof/mnm-550e8400-e29b-41d4-a716-446655440000
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.mnemom.ai/v1/on-chain/verify-proof/mnm-550e8400-e29b-41d4-a716-446655440000',
  );
  const proof = await response.json();

  if (proof.verified) {
    console.log(`On-chain score: ${proof.on_chain_score} (${proof.grade})`);
    console.log(`Anchored at block: ${proof.block_number}`);
    console.log(`Tx: ${proof.tx_hash}`);
  } else {
    console.warn('Score could not be verified against the chain');
  }
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://api.mnemom.ai/v1/on-chain/verify-proof/mnm-550e8400-e29b-41d4-a716-446655440000",
  )
  proof = resp.json()

  if proof["verified"]:
      print(f"On-chain score: {proof['on_chain_score']} ({proof['grade']})")
      print(f"Anchored at block: {proof['block_number']}")
  ```
</CodeGroup>

**Response:** `200 OK`

```json theme={null}
{
  "agent_id": "mnm-550e8400-e29b-41d4-a716-446655440000",
  "on_chain_score": 782,
  "grade": "A",
  "metadata_hash": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba",
  "published_at": "2026-02-26T10:35:00.000Z",
  "block_number": 18234589,
  "tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
  "verified": true
}
```

`verified: true` means the off-chain Merkle root matches the anchored root and the off-chain score matches the `ScoreRecord` in the registry. Any `false` is a tamper signal — open a support ticket.

***

## Checking on-chain status

Lightweight "does this agent have any on-chain data?" check — useful before calling `verify-proof` in workflows where the agent may be new or pre-eligible.

**Endpoint:** `GET /v1/on-chain/status/{agent_id}` — public.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.mnemom.ai/v1/on-chain/status/mnm-550e8400-e29b-41d4-a716-446655440000
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.mnemom.ai/v1/on-chain/status/mnm-550e8400-e29b-41d4-a716-446655440000',
  );
  const status = await response.json();

  if (status.has_on_chain_score) {
    console.log(`Latest on-chain score: ${status.latest_score.score}`);
    console.log(`Last updated: ${status.last_updated}`);
  } else {
    console.log('Agent has no on-chain score yet.');
  }
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://api.mnemom.ai/v1/on-chain/status/mnm-550e8400-e29b-41d4-a716-446655440000",
  )
  status = resp.json()
  if status["has_on_chain_score"]:
      print(f"Latest on-chain score: {status['latest_score']['score']}")
  ```
</CodeGroup>

**Response:** `200 OK`

```json theme={null}
{
  "agent_id": "mnm-550e8400-e29b-41d4-a716-446655440000",
  "has_on_chain_score": true,
  "latest_score": {
    "score": 782,
    "grade": "A",
    "block_number": 18234589,
    "published_at": "2026-02-26T10:35:00.000Z"
  },
  "latest_anchor": {
    "merkle_root": "0xabc123def456789012345678901234567890123456789012345678901234abcd",
    "block_number": 18234567,
    "anchored_at": "2026-02-26T10:30:00.000Z"
  },
  "last_updated": "2026-02-26T10:35:00.000Z"
}
```

***

## Viewing history

Retrieve the full history of anchoring and publishing events across all agents in your org — useful for compliance audits and trend dashboards.

**Endpoint:** `GET /v1/on-chain/history` — auth required.

**Query parameters:**

| Parameter  | Type   | Required | Description                              |
| ---------- | ------ | -------- | ---------------------------------------- |
| `page`     | number | No       | Page number (default: 1)                 |
| `per_page` | number | No       | Results per page (default: 20, max: 100) |

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.mnemom.ai/v1/on-chain/history?page=1&per_page=10" \
    -H "X-Mnemom-Api-Key: $MNEMOM_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const history = await fetch(
    'https://api.mnemom.ai/v1/on-chain/history?page=1&per_page=10',
    { headers: { 'X-Mnemom-Api-Key': process.env.MNEMOM_API_KEY! } },
  ).then((r) => r.json());

  for (const anchor of history.anchors) {
    console.log(`Root anchored at block ${anchor.block_number}: ${anchor.merkle_root}`);
  }
  for (const pub of history.publications) {
    console.log(`${pub.agent_count} scores published at block ${pub.block_number}`);
  }
  ```

  ```python Python theme={null}
  import httpx

  history = httpx.get(
      "https://api.mnemom.ai/v1/on-chain/history",
      headers={"X-Mnemom-Api-Key": api_key},
      params={"page": 1, "per_page": 10},
  ).json()

  for anchor in history["anchors"]:
      print(f"Root anchored at block {anchor['block_number']}: {anchor['merkle_root']}")
  for pub in history["publications"]:
      print(f"{pub['agent_count']} scores published at block {pub['block_number']}")
  ```
</CodeGroup>

**Response:** `200 OK`

```json theme={null}
{
  "anchors": [
    {
      "anchor_id": "anc-7f8a9b2c",
      "merkle_root": "0xabc123def456789012345678901234567890123456789012345678901234abcd",
      "leaf_count": 347,
      "tree_depth": 9,
      "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "block_number": 18234567,
      "anchored_at": "2026-02-26T10:30:00.000Z"
    }
  ],
  "publications": [
    {
      "publication_id": "pub-3e4f5a6b",
      "agent_count": 2,
      "tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "block_number": 18234589,
      "published_at": "2026-02-26T10:35:00.000Z"
    }
  ],
  "page": 1,
  "per_page": 10,
  "total_anchors": 42,
  "total_publications": 18
}
```

***

## How anchoring + publishing actually happen

Mnemom's backend runs a **6-hour cron** that:

1. Computes agent-level Merkle trees from the latest integrity checkpoints.
2. Aggregates agent-level roots into a global root.
3. Calls `MnemoMerkleAnchor.anchorRoot(root, leafCount, treeDepth)` on Base.
4. Publishes eligible agents' reputation scores in a batch to `MnemoReputationRegistry.publishBatch(records)` (up to 200 agents per tx per contract limit).

This runs under an internal service key — customers never call the underlying anchor or publish endpoints themselves; those are gated behind `X-Service-Key` and reject any other auth. Gas is paid from the Mnemom operational wallet; there is no customer-facing billing line for on-chain operations. You do not need to hold ETH on Base to use any part of this system.

If you need an off-cycle anchor or publish (e.g., for a time-critical compliance filing), contact support — the backend team can run the cron manually.

***

## Verifying directly against Base L2

Everything above goes through the Mnemom API. If you prefer a fully trust-minimized path, read the contracts directly.

**Deployed addresses (Base mainnet, chain ID 8453):**

| Contract                  | Address                                      | Basescan                                                                        |
| ------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------- |
| `MnemoReputationRegistry` | `0xfba717a6c4eb481a74f6911954a625242a048425` | [view](https://basescan.org/address/0xfba717a6c4eb481a74f6911954a625242a048425) |
| `MnemoMerkleAnchor`       | `0xdfdbc9374907d5adea8f100a0d1e07e16c99816d` | [view](https://basescan.org/address/0xdfdbc9374907d5adea8f100a0d1e07e16c99816d) |

**Agent ID encoding:** human-readable IDs like `mnm-550e8400-...` are stored on-chain as `keccak256(abi.encodePacked(agentId))`. Compute the hash client-side before querying:

```typescript theme={null}
import { keccak256, toUtf8Bytes } from 'ethers';

const agentId = 'mnm-550e8400-e29b-41d4-a716-446655440000';
const agentIdBytes32 = keccak256(toUtf8Bytes(agentId));
// → 0x3a8c...
```

**Query the registry:**

```typescript theme={null}
import { Contract, JsonRpcProvider } from 'ethers';

const provider = new JsonRpcProvider('https://mainnet.base.org');
const registryAbi = [
  'function getScore(bytes32 agentId) view returns (tuple(uint16 score, bytes3 grade, uint64 publishedAt, uint64 blockNumber, bytes32 metadataHash))',
  'function getScoreHistory(bytes32 agentId) view returns (tuple(uint16,bytes3,uint64,uint64,bytes32)[])',
];
const registry = new Contract(
  '0xfba717a6c4eb481a74f6911954a625242a048425',
  registryAbi,
  provider,
);

const latest = await registry.getScore(agentIdBytes32);
console.log('score:', latest.score, 'grade:', decodeGrade(latest.grade));
```

**Query the Merkle anchor:**

```typescript theme={null}
const anchorAbi = [
  'function isRootAnchored(bytes32 merkleRoot) view returns (bool)',
  'function getLatestRoot() view returns (tuple(bytes32 merkleRoot, uint64 anchoredAt, uint64 blockNumber, uint32 leafCount, uint16 treeDepth))',
];
const anchor = new Contract(
  '0xdfdbc9374907d5adea8f100a0d1e07e16c99816d',
  anchorAbi,
  provider,
);

const latestRoot = await anchor.getLatestRoot();
const anchored = await anchor.isRootAnchored(latestRoot.merkleRoot);
```

Both `getScore` and `isRootAnchored` are view calls — zero gas, no wallet required. Any public Base RPC endpoint works.

<Tip>
  Use the API path when possible — it's cached and doesn't hit the RPC on every request. Drop to direct RPC when you need trust-minimized verification or are composing with other smart contracts.
</Tip>

***

## See also

* [On-Chain Verification Concepts](/concepts/on-chain-verification) — ERC-8004 framing, Merkle tree structure, cost model
* [On-Chain API Reference](/api-reference/on-chain-overview) — Full API reference for all on-chain endpoints
* [Mnemom Trust Rating](/concepts/reputation-scores) — How reputation scores are computed
* [Cryptographic Verification](/concepts/reputation-scores#cryptographic-verification) — Off-chain proof chain structure
* [Improving Your Score](/guides/improving-reputation) — Strategies for improving your Trust Rating
