La documentación se publica en inglés.

DocumentationTrust

Anchoring

Not yet available

Built and tested, rehearsed on a test network, and starting on Base mainnet at launch. What the two contracts do, what the second channel is for, and how to confirm for yourself whether it is on.

Where this actually stands

The badge on this page is Not yet available, and unlike the placeholder badges of an earlier draft it is about the feature, not about the page. The page itself is accurate; what it describes is switched off.

This is documented rather than omitted for one reason: the vocabulary appears in responses you will actually receive. A proof bundle has an anchors object. A checkpoint in the overview has a channels array. If those fields are undocumented, the first reasonable guess about what they mean is wrong in the flattering direction.

Confirm it yourself, in one request

Do not take this page's word for it. The status is reported on an unauthenticated endpoint.

Read the channel status
curl -s "$UNIFYID_API/v1/public/transparency/overview" \
  | jq '.data.channels'
The recorded response
[
  {
    "channel": "unifyid_contract",
    "label": "UnifyID contract",
    "role": "authority",
    "enabled": false,
    "network": null,
    "chainId": null,
    "contractAddress": null,
    "contractUrl": null,
    "hookAddress": null,
    "schemaId": null,
    "deployment": null
  },
  {
    "channel": "sign_protocol",
    "label": "Sign Protocol",
    "role": "witness",
    "enabled": false,
    "network": null,
    "chainId": null,
    "contractAddress": null,
    "contractUrl": null,
    "hookAddress": null,
    "schemaId": null,
    "deployment": null
  }
]

Every checkpoint in the same response carries state: "sealed" with both channels at status: "pending", confirmations: 0 and every identifier null. enabled: false is the field that settles it, and it will change to true with a real network, chainId and contractAddress on the day this stops being true. On Base mainnet that is chain id "8453", with deployment naming the bound contract and governance reporting the contract's owner, its timelock, minDelaySeconds and whether it is paused.

Internally the effect is total rather than partial: with no publisher enabled, the delivery job short-circuits and reports the channel as disabled, so the outbox of unsent statements is never even drained. There is no half-anchored state to reason about.

What is built

Two contracts, tested against a simulated chain, fuzzed, checked with static analysis, and rehearsed on Base Sepolia. The self-audit and its refresh of 18 October 2026 are what they rest on; there was no external audit.

UnifyIDAnchor.solthe authority
anchor(bytes statement, bytes signature). It emits the full 256-byte statement and 64-byte signature, so a reader reconstructs everything from the event rather than from an API. Both an allow-listed submitter and a valid low-S P-256 signature by a registered key are required.
UnifyIDSignHook.solthe witness
A Sign Protocol schema hook. It reads the anchor contract's own submitter and key lists and refuses anything the anchor would refuse, so the second record cannot say something the first would not accept.

The design decision worth knowing is that the P-256 signature is intended to be checked by the contract, through the P256VERIFY precompile, rather than stored for somebody to check later. The consequence is that a stolen gas wallet could only submit statements the checkpoint key actually signed — it could not occupy a sequence with 256 bytes of its own choosing and fork the log.

The continuity rules the anchor is written to enforce, and the error each one raises.

RuleError
version must be 1StatementMalformed
logId must equal the deployment's immutable log idWrongLog
sequence must be head.sequence + 1 — no gapsOutOfSequence
periodStart must equal head.periodEndPeriodNotContinuous
periodEnd must be after periodStart and not in the futureInvalidPeriod, PeriodInFuture
combinedRoot must not be zeroEmptyCombinedRoot
previousCombinedRoot must equal head.combinedRoot — no forksContinuityBroken
The signing key must be registered, not revoked, not retired before periodEnd—
The signature must be low-SSignatureNotLowS

Governance is written the same way. The owner is an OpenZeppelin TimelockController driven by a Safe multisig, with a 48-hour delay on Base mainnet, so any change to the rules is visible on chain two days before it takes effect; a guardian may only pause() and nothing else; and renounceOwnership() always reverts, so the contract cannot be abandoned into a state with no one able to rotate a key.

Two channels, one chain

The design publishes the identical 256 bytes and the identical 64-byte signature to two places: UnifyID's own contract as the authority, and Sign Protocol as a corroborating second witness. Two records, two different parties storing them, the same bytes.

The production target is Base mainnet, chain id 8453, from launch. Base Sepolia, chain id 84532, is the test network the deployment was rehearsed on: a record there is described as "anchored on Base Sepolia (testnet)" and never as anything more, because a testnet record is a rehearsal.

A mainnet deployment cannot happen by accident or by a flag. The deployment script deploys to Base mainnet only for the production log, only with a 48-hour timelock, a separate guardian and two gas wallets, only after checking the live log, and only with its own confirmation. The API refuses to anchor the production log anywhere but Base mainnet, and refuses Base mainnet for any other log.

The fields you will meet meanwhile

These appear in responses today, populated with nulls and pending states. Read them as the placeholders they are.

stateon a checkpoint or a bundle
recorded, sealed, anchored_one_channel or anchored_both. Only the first two are reachable today.
channels[].status
not_configured, not_anchorable, pending, submitted, confirming, anchored or failed. Recorded today: pending. On Base mainnet a delivery is confirming until its block is finalized, and only then anchored. not_anchorable means the channel's contract begins after this checkpoint, so it will never carry it.
anchorson a proof bundle
{ unifyid_contract, sign_protocol }. Both null, or present with nothing in them.
confirmations
0.
transactionHash, transactionUrl, attestationId, signscanUrl, anchoredAt
null. Once a checkpoint is anchored, transactionUrl is its Basescan page and, on the Sign Protocol channel, signscanUrl is the attestation's page on SignScan (scan.sign.global for Base, testnet-scan.sign.global for Base Sepolia).
downloadPath
/v1/public/transparency/anchored-checkpoints/{sequence} — a real, fetchable path. The checkpoint in it is real; the anchors in it are empty.

A field that will one day carry a transaction hash is not evidence that a transaction exists. Check enabled on the channel before you read anything into a status.

What this does not do

  • Nothing on this page is in effect for UnifyID's log yet. It starts on Base mainnet at launch. Until the overview says enabled: true, do not describe any of it in the present tense, and do not let a security questionnaire answer imply it.
  • Two channels is not two chains. Both are designed for one chain, and production refuses to start if the configured chain ids differ.
  • A testnet record is not a guarantee. Base Sepolia is the rehearsal network. Anything anchored there should be described as a testnet record, in those words.
  • Every check that depends on this is check 9 and check 10. Eight checks work today and stop where the chain starts. See Proof bundles.
  • Switching this on is a founder action, not a configuration change you can request. It happens at launch, after a key ceremony with a new Safe, a separate guardian and fresh gas wallets.

Verified against API commit 234dba22 on 2026-10-18. The routes, scopes, checklist, events and limits on these pages are read from that export, not typed.