Skip to main content
This page shows how to verify a Molpha attestation in a Solidity contract and act on its value in the same transaction. It is for EVM developers; the second half is the contract reference. The verifier is a stateless view contract. It checks that enough selected nodes signed the payload under the stated registry version, and optionally that the payload is fresh. Your contract owns source and threshold policy, replay protection and value decoding.

Install

The interface, result codes and consumer library live in molpha/evm-verifier (Apache-2.0). Add it as a Foundry dependency:
chivchyn is the release tag that matches the deployed verifier.
remappings.txt
These three files compile on >=0.8.4 <0.9.0 and import nothing outside the package. The Verifier contract itself (Solidity 0.8.31, Cancun) is only needed if you run the repository’s tests. Pin a deployed verifier address from Deployments.

Verify an attestation

1

Accept the attestation as calldata

Your function takes an IVerifier.Attestation calldata: the signed payload plus its aggregate signature. A relayer, keeper or user submits it in the transaction that uses the value.
2

Check source, threshold, freshness and signature

MolphaLib.requireValid checks that sourceId matches yours and that signaturesRequired is at least your minimum, then calls verify with your maxAge. It reverts on the first failure.
3

Guard against replay

verify is view, so the same attestation verifies any number of times. latest.acceptNewer accepts only a strictly newer timestamp.
4

Decode the value and act

value is an opaque bytes32. Decode it with your source’s encoding, for example MolphaLib.asInt256.
This follows examples/SettleOnWord.sol in the repository, which also has LatestStore.sol and StructuredPayload.sol. To get a sourceId, see Feeds. Without the library, call verify and apply the same checks yourself:
verify does not revert on a rejected attestation. A bad signature, stale timestamp or malformed field returns (false, code). A contract that ignores success accepts unverified data.
Enforce signaturesRequired as a minimum (>=), never as equality. sourceId does not commit the threshold: whoever requests the attestation chooses it. The full list of consumer duties is in the consumer checklist. For unit tests, src/test-utils/MockVerifier.sol exposes a verify with a configurable result (setResult, setResultFor) and no cryptography.

Build the arguments off-chain

buildEvmVerifierArgs maps the Attestation returned by sdk.gateway.requestSignedData (see the backend quickstart) onto the on-chain structs. It has no viem or ethers dependency. maxAge is required: seconds, 0 to disable freshness.
For a raw eth_call, encodeEvmVerifyCalldata({ attestation, maxAge }) returns the calldata and parseEvmVerifyResult also accepts the 64-byte return data.

Consumer library

MolphaLib (src/consumer/MolphaLib.sol) packages the checks verify leaves to you. It holds no storage and runs the cheap calldata comparisons before calling the verifier. Checks run in this order: The replay guards revert with NotNewer(last, actual) and AlreadyConsumed(key). With NO_MAX_AGE, acceptNewer enforces ordering but not recency, so the first attestation a contract accepts can be arbitrarily old.

Reference

Structs

Defined in IVerifier. Payload field order is the order of the signed message.
Bit i of signersBitmap maps to 0-based node index i in that registry version. Indices can change between versions, so an index is valid for one version only.

verify

The selector is 0x67e2907b. Both structs are static, so calldata is the selector followed by nine 32-byte words: value, sourceId, registryVersion, signaturesRequired, timestamp, signature, commitment, signersBitmap, maxAge. maxAge is in seconds and is compared against timestamp / 1000 and block.timestamp. An attestation passes while block.timestamp - timestamp / 1000 <= maxAge; 0 skips the check. Calldata that does not ABI-decode (for example a signaturesRequired word above 255) reverts in the decoder instead of returning a code. Checks run in this order and stop at the first failure:
  1. Calldata guards: signaturesRequired, signersBitmap, signature or commitment is zero, signature >= Q, or fewer set bits than signaturesRequired → R_MALFORMED.
  2. Freshness, only when maxAge != 0: timestamp in a future second → R_MALFORMED; older than maxAge → R_STALE.
  3. Registry version lifetime → R_BAD_REGISTRY_VERSION, R_NOT_YET_ACTIVE or R_VERSION_EXPIRED.
  4. Signers outside the derived selection set → R_BAD_QUORUM. Selection math is on Cryptography.
  5. Aggregate key is the point at infinity → R_BAD_AGGREGATE.
  6. Schnorr check fails → R_BAD_SIGNATURE.

Result codes

Defined in src/libs/VerifyCodes.sol. The numbering is shared with the Starknet verifier.

Registry versions

Every registry change publishes a new version whose activatesAt is the block timestamp of that transaction. A version accepts payloads whose timestamp / 1000 lies in [activatesAt(v), activatesAt(v + 1) + 60]; the latest version has no upper bound. The test uses the payload timestamp, not the current block time, so a round signed inside that window keeps verifying with maxAge = 0. Pass the exact registryVersion the nodes signed under. See Registry versions. Useful reads: getRegistryVersion(), activatesAt(uint256), retiredAt(uint256), isLatestVersion(uint256), getTotalNodes(), isNode(address), nodeStatus(address) (0 never registered, 1 active, 2 retired) and redundancyBuffer(). Version-keyed reads revert with InvalidRegistryVersion for a version that was never published.

Admin functions

These belong to the verifier’s owner (Solady Ownable); other callers revert with Unauthorized(). Each call publishes a new registry version and emits RegistryAdvanced(newVersion, newRoot, op).

Gas

Measured with test/gas/Verifier.gas.t.sol (VerifierGasTest, redundancy buffer 2, maxAge = 0, Solidity 0.8.31, Cancun, IR, 1M optimizer runs); re-run for your configuration:
Totals include calldata gas and the 21,000 base. Cost grows with the number of signers, not the registry size: verify reads only the signers’ keys and does one EC addition per signer. Report vulnerabilities through SECURITY.md, not public issues. Trust assumptions are on Security model.

Next steps

Smart contract quickstart

Request an attestation and verify it end to end.

Deployments

Verifier addresses per network.

Consumer checklist

Freshness, replay and threshold rules for every chain.