Moolam

API Reference

MoolamRegistry

En esta página

0xa19188801E5DC93CD925884d73e4DaFc2bcb80C0 on Monad mainnet, chain 143. ERC-721 Moolam Content Passport (MOOLAM), EIP-712 domain name Moolam, version 1. The ABI is in packages/contracts/abi/MoolamRegistry.json.

The token id of a passport is uint256(passportId), and passportId is the sha256 of the exact image bytes.

Types

enum Kind { Generated, Captured, Edited }
enum DisputeStatus { None, Open, Upheld, Rejected }

struct PassportInput {
    bytes32 exactHash;
    bytes32 parentId;
    address creator;
    uint256 generatorAgentId;
    uint64  phash;
    bytes32 blockhash256;
    bytes32 manifestHash;
    Kind    kind;
    uint8   fingerprintVersion;
    string  metadataURI;
    uint64  deadline;
}

struct Passport {
    bytes32 exactHash;
    bytes32 parentId;
    address creator;
    uint256 generatorAgentId;
    uint64  phash;
    bytes32 blockhash256;
    bytes32 manifestHash;
    Kind    kind;
    uint8   fingerprintVersion;
    uint64  registeredAt;
    bool    disputed;
    string  metadataURI;
}

struct Attestation {
    address receiver;
    bool    matched;
    uint16  distance;
    bytes32 recomputed;
    uint64  at;
}

struct Dispute {
    address        challenger;
    uint256        bond;
    bytes32        evidenceHash;
    DisputeStatus  status;
    uint64         openedAt;
}

Every field of PassportInput is inside the EIP-712 digest, so a change to any field invalidates both the passkey signature and the agent signature. registeredAt and disputed are set by the contract and are not part of the signed input.

Writes

bindPasskey

function bindPasskey(bytes32 qx, bytes32 qy, WebAuthn.WebAuthnAuth calldata auth) external

Binds a passkey to msg.sender, or rotates the one already bound.

ParameterMeaning
qxPasskey public key x coordinate
qyPasskey public key y coordinate
authWebAuthn assertion over hashBind(msg.sender, qx, qy, bindNonce(msg.sender))

Preconditions: neither coordinate is zero, and the assertion verifies against the pair with user presence and user verification both set. The wallet proves it holds the key by signing the bind digest with it, so a stolen public key cannot be parked on someone else's wallet. Each successful bind consumes one nonce.

RevertsWhen
InvalidPublicKeyqx or qy is zero
InvalidPasskeySignatureThe assertion does not check out

Emits PasskeyBound.

register

function register(
    PassportInput calldata p,
    WebAuthn.WebAuthnAuth calldata creatorAuth,
    bytes calldata generatorSig
) external returns (uint256 tokenId)

Registers an original image and mints its passport token to p.creator. Refused while paused.

Anyone may send this transaction. Authority comes from the two signatures, not from msg.sender, which is what lets a relayer pay the gas for a creator.

ParameterMeaning
pThe passport input. parentId must be zero and kind must not be Edited
creatorAuthThe creator's WebAuthn assertion over hashPassport(p)
generatorSigThe agent owner's ECDSA signature over the same digest. Empty for a Captured image
RevertsWhen
EnforcedPauseThe registry is paused
InvalidExactHashp.exactHash is zero
PassportAlreadyExistsThat image hash already has a record
ParentNotAllowedp.parentId is set
InvalidCreatorp.creator is the zero address
InvalidKindp.kind is Edited
InvalidFingerprintVersionp.fingerprintVersion is zero
EmptyMetadataURIp.metadataURI is empty
SignatureExpiredp.deadline is in the past
PasskeyNotBoundp.creator has no passkey bound
InvalidPasskeySignatureThe creator's assertion does not verify against the bound key
InvalidAgentIdkind is Generated and generatorAgentId is zero, or kind is not Generated and it is non-zero
UnknownAgentThe agent id does not exist on the ERC-8004 registry
InvalidGeneratorSignatureRecovery fails, the recovered address is not ownerOf(agentId), or a signature was supplied for a non-Generated passport

Emits PassportRegistered and the ERC-721 Transfer. Returns uint256(p.exactHash).

appendEdit

function appendEdit(PassportInput calldata p, bytes calldata generatorSig)
    external returns (uint256 tokenId)

Records an edit of an existing passport and mints the child token. Refused while paused.

No passkey signature here. Holding the parent token is the authority, which is what lets an editing session sign many edits without a fresh biometric prompt each time. An ERC-721 approval is not enough: approval is permission to move the token, granted to marketplaces and escrows, and it must never become permission to write history under the owner's name. A session signer works because it acts from the owner's own wallet.

ParameterMeaning
pThe passport input with parentId set and kind set to Edited
generatorSigThe agent owner's ECDSA signature. Empty when generatorAgentId is zero
RevertsWhen
EnforcedPauseThe registry is paused
InvalidParentp.parentId is zero
ParentNotFoundThe parent has no record
InvalidExactHashp.exactHash is zero
PassportAlreadyExistsThat image hash already has a record
InvalidKindp.kind is not Edited
InvalidFingerprintVersionp.fingerprintVersion is zero
EmptyMetadataURIp.metadataURI is empty
SignatureExpiredp.deadline is in the past
NotParentOwnermsg.sender does not own the parent token
InvalidCreatorp.creator is not the parent owner
UnknownAgentA non-zero agent id does not exist on the ERC-8004 registry
InvalidGeneratorSignatureRecovery fails or the recovered address is not ownerOf(agentId)

Emits PassportRegistered and the ERC-721 Transfer. The child is appended to the parent's getChildren list. Returns uint256(p.exactHash).

attest

function attest(bytes32 passportId, bool matched, uint16 distance, bytes32 recomputed) external

Writes one verifier's recheck of a passport. Only an address VerifierPolicy.isReceiver returns true for may call it, which in practice is the two SimulationReceiver contracts listed since 2026-09-27 and the public MoolamReceiver, listed again on 2026-10-02. The sealed MoolamReceiver has been off the list since 2026-09-26. It stays open while the registry is paused, so a verification already in flight can still land.

RevertsWhen
NotReceiverThe caller is not on the policy's receiver list
PassportNotFoundNo record for that id
AttestationCapReachedThe passport already holds MAX_ATTESTATIONS (64)

Emits VerificationAttested.

flag

function flag(bytes32 passportId, string calldata evidenceURI) external payable

Opens a dispute against a passport, backed by a bond. Reentrancy guarded, and refused while paused. The bond is held by the contract and never sent anywhere until resolve runs, so a flag costs the challenger nothing but the gas if they turn out to be right. Only the keccak256 of evidenceURI is stored, so the URI cannot be swapped.

RevertsWhen
EnforcedPauseThe registry is paused
PassportNotFoundNo record for that id
DisputeAlreadyOpenA challenge is already open on that passport
PassportAlreadyDisputedA previous challenge was upheld
InvalidBondmsg.value is not exactly VerifierPolicy.disputeBond()

Emits PassportFlagged.

resolve

function resolve(bytes32 passportId, bool upheld) external

Settles an open dispute. Callable only by VerifierPolicy.resolver(). Reentrancy guarded.

upheld true marks the passport disputed and credits the bond back to the challenger. False credits the bond to VerifierPolicy.treasury(), which since 2026-10-02 is the burn address 0x000000000000000000000000000000000000dEaD, so nobody can withdraw it. Credits are recorded, never pushed, so a resolver can never be blocked by a challenger or treasury that refuses native token.

RevertsWhen
NotResolverThe caller is not the policy's resolver
NoOpenDisputeNo open challenge on that passport

Emits DisputeResolved.

withdraw and withdrawTo

function withdraw() external
function withdrawTo(address to) external

Pull whatever the caller is owed. The balance is zeroed before the transfer, both are reentrancy guarded, and both stay open while paused so a pause can never trap someone's money.

withdrawTo is the escape hatch for a credited address that cannot hold native token itself, such as a treasury contract with no payable path. Nobody can move anyone else's credit: the balance spent is always the caller's own.

RevertsWhen
ZeroAddressto is the zero address (withdrawTo only)
NothingToWithdrawThe caller's credit is zero
TransferFailedThe send failed

Emits Withdrawn or WithdrawnTo.

Owner-only

function pause() external
function unpause() external
function rescueERC20(address token, address to, uint256 amount) external
function rescueNative(address to) external

pause stops new registrations, edits and flags. Attestations and withdrawals keep working.

rescueERC20 moves ERC-20 tokens sent here by mistake. The contract never holds ERC-20 for a user, so there is no user balance to touch. Reverts ZeroAddress.

rescueNative moves native token that is not backing a bond or an unclaimed credit. The amount is address(this).balance - totalBonds - totalOwed, so an open bond and an unclaimed credit can never be swept. Reverts ZeroAddress, NothingToRescue, TransferFailed.

All four emit their own events, and rescue emits Rescued.

Ownership uses Ownable2Step, so a transfer takes transferOwnership then acceptOwnership from the new owner and a typo cannot lose the contract.

Reads

FunctionReturns
hashPassport(PassportInput p)The 32 byte EIP-712 digest to sign. This is also the WebAuthn challenge
hashBind(address wallet, bytes32 qx, bytes32 qy, uint256 nonce)The 32 byte digest a passkey signs to bind itself
bindNonce(address wallet)The nonce to use in the next bindPasskey call
getPassport(bytes32 id)The whole Passport record. Zeroed if it does not exist
passportExists(bytes32 id)True when a record exists
getPasskey(address wallet)(qx, qy), both zero when nothing is bound
attestationCount(bytes32 id)How many attestations, never more than 64
getAttestations(bytes32 id, uint256 offset, uint256 limit)A page of Attestation, oldest first, clipped to what exists
childCount(bytes32 id)How many edits descend directly from a passport
getChildren(bytes32 id, uint256 offset, uint256 limit)A page of child passport ids, oldest first, clipped to what exists
getDispute(bytes32 id)The Dispute record. Status None means never challenged
withdrawable(address who)What that address can pull, in wei
totalBonds()Native token locked behind open disputes
totalOwed()Native token credited but not yet pulled
policy()The VerifierPolicy address, immutable
agentIdentity()The ERC-8004 Identity Registry address, immutable
MAX_ATTESTATIONS()64
tokenURI(uint256 tokenId)The metadata URI recorded at registration. Reverts ERC721NonexistentToken for an unminted id

getAttestations and getChildren are read only, so the loop costs an off-chain caller nothing and no on-chain function ever walks either array. An offset past the end returns an empty page rather than reverting.

The rest of the ERC-721 surface (ownerOf, balanceOf, approve, setApprovalForAll, transferFrom, safeTransferFrom, getApproved, isApprovedForAll, name, symbol, supportsInterface) is OpenZeppelin's, unmodified.

Constructor

constructor(address initialOwner, address policyAddress, address agentIdentityAddress)

Reverts ZeroAddress if either address is zero, and NotAContract if the identity registry address has no code. ownerOf is read through try/catch, and a call to an address with no code would return empty data that cannot be decoded, so deployment against a plain wallet is refused up front.