Sentinel TreasuryDocs

Architecture

Architecture

Sentinel Treasury is a single-chain HashKey MVP. Every component is read-only or draft-only on Sentinel's side; the customer's Safe owner quorum is the sole authority for any on-chain write.

Single-chain HashKey

The MVP targets HashKey Chain only (testnet 133; mainnet 177 is signal-gated). Multi-chain ingestion was retired to keep the trust surface small and the integration HashKey-native. Safe data is read through the HashKey-hosted Safe Transaction Service; on-chain reads go through a configurable HashKey RPC endpoint.

KYC SBT gate

The HashKey-native compliance core. Sentinel reads each Safe owner's tier from the configured HashKey KYC SBT via isHuman(address) and getKycInfo(address). Tiers are NONE / BASIC / ADVANCED / PREMIUM / ULTIMATE; statuses are NONE / APPROVED / REVOKED.

The gate is compliance-gated drafting, not compliance-enforced execution. The tier decides whether Sentinel's UI offers to prepare a proposal draft. It never blocks the chain: Safe owners can always operate their Safe directly, outside Sentinel, regardless of tier. The read is fail-open — an unreadable SBT surfaces tier UNKNOWN rather than inventing a tier.

Customer-Safe-anchored EvidenceRegistry

Evidence is anchored on-chain in a minimal append-only contract. Records are namespaced per customer by msg.sender:

  • anchor(bytes32 batchId, bytes32 root) writes to anchors[msg.sender][batchId]. Only the caller can write into their own namespace — there is no admin, no privileged writer.
  • Reverts on a zero root, a zero batchId, or an attempt to overwrite an existing anchor (append-only).
  • Emits EvidenceAnchored(customer, batchId, root, timestamp, blockNumber).

Because the namespace key is msg.sender, Sentinel cannot write into any customer's evidence record even in principle — it would have to be the customer's Safe to do so.

Hash-chained audit log

Before anything is anchored, every state-changing operation (policy change, draft created, warning raised, proposal surfaced) is appended to a hash-chained audit log. Each entry stores a prev_hash and an entry_hash, where the entry hash folds the previous hash and the customer Safe address into a canonical JSON body:

entry_hash = keccak256(canonical_json({...payload, _prev_hash, _customer}))

Folding _customer into the leaf binds every entry to one Safe by construction: two customers with identical payloads at identical chain positions produce different hashes, so a leaf from one customer's log can never be replayed as evidence in another's. Tampering any historical entry breaks the next entry's prev_hash link on re-walk.

Sorted-pair Merkle anchoring

A batch of audit entries is Merkle-rooted using commutative, sorted-pair keccak256 hashing — identical between Sentinel's generator and OpenZeppelin's MerkleProof.verifyCalldataused on-chain in EvidenceRegistry.verify. The same entry_hash doubles as the Merkle leaf, so there is a single canonical hash per entry across the off-chain log and the on-chain proof.

The anchor flow

Sentinel prepares; the customer signs. Concretely:

  • An operator-side tool computes the batch root, writes a pre_anchor_commit entry to the audit log (so Sentinel's stated intent is itself tamper-evident), and builds an unsigned MetaTransactionData for EvidenceRegistry.anchor(batchId, root).
  • The customer reviews the proposal in the demo UI and verifies the root locally (see For developers).
  • The customer's Safe owner quorum signs and executes through Safe Wallet. Sentinel never signs, never posts to the Safe Transaction Service, and never executes.