# OP Treasury & Liquidity Transparency Hub v0.1 Data Specification

Status: `v0.1`  
Network scope: OP Mainnet (`chainId: 10`)  
Capability: read only

## 1. Design goals

The data plane exists to answer a narrow question: **what can be independently observed about a token, its Treasury Safe and its Uniswap V3 liquidity at a specific OP Mainnet block?** It must not turn a project claim, an indexer response or a stale cache entry into an on-chain fact.

The following invariants apply to every implementation:

1. The chain is fixed to OP Mainnet and every chain-derived value is bound to `chainId`, block number, block hash and retrieval time.
2. Unknown, failed, stale-beyond-limit and conflicting values are `null`; they are never converted to zero.
3. Addresses and integer quantities are strings at the API boundary. JavaScript `Number` is not used for wei, token units, liquidity, ticks or token IDs.
4. Project-supplied values are useful configuration but remain project disclosures until independently observed.
5. No wallet, signer, private key or transaction method is part of this product.
6. v0.1 does not calculate price, market capitalization, FDV, return or investment score.

## 2. Manifest and snapshot separation

### 2.1 Project manifest

`project-manifest.schema.json` validates a static, reviewed configuration. A manifest identifies contracts and declares which fields the reader is expected to collect. It may include expected values for semantic checks, but those expected values are **not observations**.

A manifest changes only through a reviewed file change. It contains:

- project and chain identity;
- the server-side RPC policy;
- token, Safe, pool and LP position addresses;
- the exact read fields that are in scope;
- freshness profiles and fallback decisions;
- document hashes and project-disclosed metadata.

The manifest must never contain:

- live balances, current pool liquidity, current nonce or current owners as verified facts;
- RPC credentials or arbitrary user-supplied endpoints;
- calldata for a state-changing transaction;
- private contact data, wallet secrets or authentication material.

### 2.2 Observation snapshot

A snapshot is an immutable runtime result produced from one manifest. It is keyed by manifest digest, chain and block hash. A snapshot contains normalized `Evidence<T>` envelopes, source health and the exact finalized/provisional head used.

Recommended top-level form:

```ts
type Snapshot = {
  snapshotVersion: "0.1"
  snapshotId: string
  manifestId: string
  manifestSha256: `0x${string}`
  generatedAt: string
  chain: {
    chainId: 10
    requestedTag: "finalized" | "latest"
    blockNumber: string
    blockHash: `0x${string}`
    blockTimestamp: string
    finality: "finalized" | "provisional"
  }
  observations: Record<string, Evidence<unknown>>
  sourceHealth: SourceHealth[]
  warnings: string[]
}
```

Snapshots are not edited in place. A new block or corrected data produces a new snapshot. A cached snapshot is addressed by schema version, manifest digest, chain ID, block hash and source set.

## 3. Evidence tiers A-U

The closed evidence set is `A | B | C | D | U`, ordered from strongest to weakest. The tier says **where the evidence came from**; `state` says whether the value is current and internally consistent.

| Tier | Public label | Meaning | May override |
|---|---|---|---|
| `A` | `onchain_verified` | Direct OP Mainnet code, call, storage, balance, log or receipt at a stated block/hash, with contract identity checks. | All lower tiers. |
| `B` | `third_party_verified` | Independent audit, signed registry or reproducible artifact verification with an identified issuer and scope. | `C`, `D`; never a conflicting `A`. |
| `C` | `safe_service_indexed` | Reputable indexer/API result used for history or decoding and cross-checked where possible. | `D`; never a conflicting `A`. |
| `D` | `project_disclosed` | Manifest, project website, whitepaper or other project-controlled statement. | Nothing independently observed. |
| `U` | `unverified_or_unavailable` | Missing, invalid, stale beyond limit, conflicting, unverifiable or outside scope. | Nothing. |

Safe Transaction Service data is tier `C` even when it is operationally useful. Safe owners, threshold, nonce, modules, guard, fallback handler, singleton and balances must come from tier `A` reads. A document SHA-256 stored in the project manifest is tier `D`; a successful retrieval and independent byte-for-byte hash match may raise the **hash-match observation** to tier `B`, but it does not validate the document's claims.

## 4. Normalized Evidence envelope

Every displayable field is represented by an envelope, including failures:

```ts
type EvidenceState =
  | "verified"
  | "provisional"
  | "stale"
  | "conflict"
  | "unavailable"

type EvidenceTier = "A" | "B" | "C" | "D" | "U"

type Evidence<T> = {
  value: T | null
  state: EvidenceState
  tier: EvidenceTier
  label:
    | "onchain_verified"
    | "third_party_verified"
    | "safe_service_indexed"
    | "project_disclosed"
    | "unverified_or_unavailable"
  chainId: 10 | null
  blockNumber: string | null
  blockHash: `0x${string}` | null
  blockTimestamp: string | null
  retrievedAt: string
  freshUntil: string | null
  sources: Array<{
    sourceId: string
    kind: "rpc" | "indexer" | "explorer" | "document" | "manifest"
    method: string | null
    requestDigest: `sha256:${string}` | null
    uri: string | null
  }>
  warnings: string[]
  error: {
    code: string
    message: string
    retryable: boolean
  } | null
}
```

Envelope rules:

- `verified`: sources agree and required identity/semantic checks pass.
- `provisional`: a valid value was read from `latest` or only one provider succeeded; it must not be labelled finalized.
- `stale`: the last good value is older than `maxAgeSeconds` but not older than `maxStaleSeconds`.
- `conflict`: providers or authoritative sources disagree at the same block. `value` is `null`, tier is `U`, and both source results are retained in diagnostics.
- `unavailable`: no safe value can be returned. `value` is `null` and tier is `U`.
- A numeric zero is allowed only after a successful, schema-valid read that explicitly returned zero.
- `chainId`, block fields and `freshUntil` are `null` for tier `D` document/manifest statements that are not chain observations.

## 5. RPC boundary

The server-side adapter accepts only these JSON-RPC methods:

```text
eth_chainId
eth_blockNumber
eth_getBlockByNumber
eth_getCode
eth_getBalance
eth_call
eth_getLogs
eth_getTransactionByHash
eth_getTransactionReceipt
```

Method names and parameters are constructed by reviewed server code. Users cannot provide an RPC method, ABI, selector, origin or unbounded log range.

At minimum, the adapter rejects:

```text
eth_accounts
eth_requestAccounts
eth_sendTransaction
eth_sendRawTransaction
eth_sign
eth_signTransaction
eth_signTypedData
eth_signTypedData_v1
eth_signTypedData_v3
eth_signTypedData_v4
personal_sign
wallet_addEthereumChain
wallet_switchEthereumChain
wallet_sendCalls
wallet_grantPermissions
```

All other non-allowlisted methods are rejected by default, including every `wallet_*`, `personal_*`, signing, permission, simulation-with-state-override and send method. The public application must make zero calls to `window.ethereum`.

## 6. Data dictionary

All raw integer results use base-10 strings. Human-readable formatting is a UI derivative and must preserve the raw value alongside it.

### 6.1 Chain and contract identity

| Field | Type | Primary source | Tier | Freshness | Failure |
|---|---|---|---|---|---|
| `chain.chainId` | integer | `eth_chainId` from each RPC | A | per request | quarantine provider if not `10` |
| `chain.blockNumber` | decimal string | finalized/latest block | A | per snapshot | unavailable |
| `chain.blockHash` | 32-byte hex | `eth_getBlockByNumber` | A | immutable | invalidate cache on mismatch |
| `contract.code` | hex bytes/hash | `eth_getCode` | A | chain-static | unavailable if empty where code is required |

### 6.2 ERC-20 token

Required token observations:

- code presence and code hash;
- `name()`, `symbol()`, `decimals()`, `totalSupply()`;
- `balanceOf(Treasury Safe)` and `balanceOf(Uniswap pool)`;
- deployment transaction/receipt where configured;
- bounded `Transfer` logs only when an indexed history is built.

Token strings are capped, normalized as display text and escaped. Metadata never supplies HTML or a remote executable image. `totalSupply`, balances and event amounts are raw decimal strings. `decimals` must be an integer from `0` through `255`.

### 6.3 Treasury Safe

Required direct-chain observations:

- proxy code and singleton/master-copy address;
- `getOwners()`;
- `getThreshold()`;
- `nonce()`;
- all enabled modules through bounded `getModulesPaginated` traversal;
- guard storage/address;
- fallback handler storage/address;
- native balance and allowlisted ERC-20 balances.

Modules are enumerated until the Safe sentinel is reached. Repeated pages, repeated module addresses, an invalid sentinel, excessive page count or an unexpected module produce `conflict`/high-risk output. Safe Transaction Service may provide decoded transaction history and pending proposals, but a pending proposal is never an executed outflow and never changes tier `A` state.

### 6.4 Uniswap V3 pool

Required observations:

- pool contract code;
- factory result for `getPool(token0, token1, fee)`;
- `token0()`, `token1()`, `fee()`, `tickSpacing()`;
- `slot0()` components, including `sqrtPriceX96`, tick and unlocked flag;
- pool `liquidity()`;
- raw token balances held by the pool.

The factory, pool address, token order and fee must agree before the pool is described as verified. v0.1 shows raw configuration and balances only. It does not turn `slot0` into a public price, TVL or valuation.

### 6.5 Uniswap V3 LP position

Required observations through the configured Nonfungible Position Manager:

- `ownerOf(tokenId)`;
- `positions(tokenId)`: token0, token1, fee, tickLower, tickUpper, liquidity, fee-growth checkpoints and tokens owed;
- position-manager contract code.

The NFT owner is not a lock proof. A Safe-owned NFT is reported as `owned by Treasury Safe`; `locked` remains unverified unless a separately reviewed lock contract and unlock conditions are directly established.

### 6.6 Project documents

Document records contain language, URL, declared version, expected SHA-256 and tier `D`. Runtime fetching is not performed through a generic proxy. If an allowlisted build/release job retrieves a file and its bytes match, the result is a separate hash-match observation. Document content remains project disclosure unless independently reviewed.

## 7. Freshness profiles

| Profile | `maxAgeSeconds` | `maxStaleSeconds` | Intended fields |
|---|---:|---:|---|
| `chainCritical` | 120 | 900 | balances, Safe owners/threshold/nonce/modules/guard, pool state, LP owner/position |
| `chainStatic` | 86400 | 604800 | code, token metadata, factory, immutable contract relationships |
| `indexerHistory` | 300 | 3600 | decoded history and indexed event summaries |
| `projectDocument` | 86400 | 604800 | allowlisted document reachability/hash checks |

Freshness is calculated from successful retrieval time and block time, not the browser clock alone. A finalized block remains historically valid, but a dashboard claiming to show **current** state becomes stale according to its profile.

## 8. Provider fallback and quorum

1. Production requires two independently configured OP RPC providers; a public community endpoint is not the sole production dependency.
2. Each provider must independently return `eth_chainId == 10` before use.
3. Critical fields are read at the same finalized block number and hash. Two agreeing valid results produce `verified` tier `A`.
4. A single valid provider result may be shown only as `provisional` with a warning.
5. Same-block disagreement produces `conflict`; values are never averaged and a preferred provider is not silently selected.
6. When all live reads fail, a last-good value may be shown as `stale` only until `maxStaleSeconds`. It then becomes `unavailable` with `null` value.
7. Indexers supplement event/history data and never override conflicting direct chain state.
8. Manifest expected values never override snapshot observations.
9. Fallback must not change chain, contract address, ABI, block tag or semantic validation rules.

## 9. Semantic validation

Schema-valid JSON is necessary but not sufficient. The collector must also enforce:

### Global

- Reject anything except ASCII `0x` plus 40 hex characters as an address. Validate EIP-55 when input is mixed case; store and compare normalized bytes.
- Reject the zero address where a deployed contract or owner is required.
- Require code at token, Safe proxy, pool, factory and position-manager addresses.
- Require the configured finalized block number and hash to match across providers.
- Cap response bytes, string lengths, log ranges, page counts, retries, concurrency and total request time.

### Token

- `symbol` and `name` are escaped text within configured byte/character limits.
- `decimals` is `0..255`; raw supply/balance values are unsigned base-10 strings.
- `balanceOf` values cannot exceed unsigned 256-bit range.
- Any expected supply or metadata mismatch is visible; the observed value wins.

### Safe

- Owners are unique non-zero addresses; `1 <= threshold <= ownerCount`.
- Modules are unique and pagination terminates at the correct sentinel within bounds.
- Guard, fallback handler and singleton are recorded even when zero/disabled is valid.
- Unexpected owner, singleton, module, guard or fallback handler yields an explicit risk warning; the manifest expectation does not replace the read.

### Uniswap pool and position

- Official factory `getPool(token0, token1, fee)` must equal the configured pool.
- Pool `token0`, `token1` and `fee` must match the manifest and token ordering must be distinct.
- `tickLower < tickUpper`; both ticks align with `tickSpacing` and remain within protocol bounds.
- Position token pair and fee must match the pool; `ownerOf(tokenId)` determines current owner.
- Pool/position liquidity is an unsigned integer. A zero-liquidity result is valid only after a successful call.
- No lock, market quality, price stability or liquidity-depth claim is inferred from NFT ownership or raw liquidity alone.

## 10. Export and UI requirements

- JSON and CSV exports use the same normalized snapshot as the UI; no separate calculation path is allowed.
- Each displayed value exposes tier, state, block/time, source and warning details.
- Full addresses remain available even if the UI also shows a shortened label.
- Risk disclosures appear before or beside liquidity data: very small liquidity, LP not proven locked, no public sale, no independent audit, and unavailable/conflicting sources.
- External links are fixed or allowlisted and open with `noopener noreferrer` and no address-bearing referrer.

## 11. Montrevane fixture boundary

`montrevane.project-manifest.json` is the first case manifest. Its addresses, expected Safe configuration and document hash are project configuration. Acceptance item P-04 still requires a separately recorded finalized-block snapshot, two-provider agreement and a 20-field audit. Inclusion is not an endorsement, exchange listing or statement that the token is suitable for investment.
