Contract headers

Design stance: Settlement law is encoded in non-upgradeable cores (PoV Gate → EMT → Settlement flip; One-Claim; Fee burn). Tunables (checklists, clocks, splits) live behind a ParameterStore controlled by Governor+Timelock with bounds. All modules emit receipts (PoV hash, claim id, burn hash) for audit replay.

A. Common types & EIP-712

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library Types {
  // Role tags (Attestor Registry)
  bytes32 constant ROLE_INSPECTOR      = keccak256("INSPECTOR");
  bytes32 constant ROLE_TERMINAL       = keccak256("TERMINAL");
  bytes32 constant ROLE_CARRIER        = keccak256("CARRIER");
  bytes32 constant ROLE_DC3PL          = keccak256("DC_3PL");
  bytes32 constant ROLE_REGISTRY       = keccak256("REGISTRY");
  bytes32 constant ROLE_METROLOGY      = keccak256("METROLOGY");
  bytes32 constant ROLE_SENSOR_OEM     = keccak256("SENSOR_OEM");

  // Domain tags
  bytes8  constant CLM_DOMAIN = 0x45444d412f434c4d; // "EDMA/CLM"
  bytes4  constant CLM_V1     = 0x00000001;

  struct Attestation {
    bytes32 role;          // e.g. ROLE_INSPECTOR
    bytes32 entityId;      // registry id for the signer
    bytes32 keyId;         // current active key id
    bytes   signature;     // EIP-712 / secp256k1 signature
  }

  struct ProofBundle {
    bytes32  schemaId;     // keccak256("TRADE.ON_BOARD.v1")
    bytes32  povHash;      // sha256(canonical_json_bytes)
    bytes    dossierBytes; // canonical JSON (optional echo)
    bytes    keyBlob;      // normalized identity fields blob
    Attestation[] atts;    // role-signed attestations bound to povHash
  }
}

EIP-712 typed data (attestor side, summary): TypedData(domain="EDMA/POV", message={schemaId, orderOrListing, stageOrUnit, povHash}, roleKey).

B. PoV Gate

interface IPoVGate {
  function submitProof(Types.ProofBundle calldata pb, bytes32 claimIdPreview)
    external returns (bytes32 gatePassId, bytes32 claimId);

  event GatePass(bytes32 indexed schemaId, bytes32 indexed claimId, bytes32 povHash, address caller);

  // Errors (canonical)
  error E_CANONICAL_DRIFT(); error E_FORMAT_INVALID(); error E_SET_NOT_SORTED();
  error E_QUORUM_MISSING();  error E_STALE_ATTEST();  error E_HASH_MISMATCH();
  error E_PENDING_FUNDS();   error E_ONECLAIM_TAKEN(); error E_KEY_REVOKED();
}
  • Validates: schema, quorum, freshness, equality on povHash, must-fund (Trade post-production), and atomically reserves One-Claim.

  • Returns: a gatePassId consumed by Settlement (Trade) or Tokens (mint/settle).

C. One-Claim

interface IOneClaim {
  function reserve(bytes32 claimId) external;    // only Gate
  function finalize(bytes32 claimId) external;   // Gate/Settlement/Tokens
  function status(bytes32 claimId) external view returns (uint8); // 0 FREE,1 RESERVED,2 FINAL

  event ClaimFinalized(bytes32 indexed claimId);
  error AlreadyTaken(); error NotReserved();
}
  • Reserve→finalize: happens in the same tx as EMT/settle to prevent races.

D. EMT (Event/Milestone Token)

interface IEMT {
  function mint(bytes32 orderId, bytes32 stageId, bytes32 subLotId,
                bytes32 claimId, bytes32 povHash) external;

  event EMTMinted(bytes32 indexed orderId, bytes32 indexed stageId,
                  bytes32 indexed subLotId, bytes32 claimId, bytes32 povHash);

  error AlreadyMinted(); error InvalidStage();
}
  • Receipt with a switch: Only Settlement flips money.

E. Trade Settlement

interface ITradeSettlement {
  function release(bytes32 orderId, bytes32 stageId, bytes32 gatePassId)
    external returns (uint256 amountEdsd, uint256 fee, bytes32 burnHash);

  event ReleasePosted(bytes32 indexed orderId, bytes32 indexed stageId,
                      uint256 amountEdsd, uint256 fee, bytes32 burnHash);

  // Invariants enforced: must-fund, One-Claim FINAL, fee caps, 50% burn in EDM
  error E_NO_GATEPASS(); error E_NOT_FUNDED(); error E_DUP_RELEASE();
}

F. Tokens

interface ITokens {
  function mint(Types.ProofBundle calldata pb) external returns (uint256 tokenId, bytes32 claimId);
  function settle(uint256 tokenId, address buyer) external returns (uint256 fee, bytes32 burnHash);
  function retire(uint256 tokenId, address beneficiary, string calldata purpose)
    external returns (uint256 fee, bytes32 burnHash);

  event TokenMinted(uint256 indexed tokenId, bytes32 claimId, bytes32 povHash);
  event TokenSettled(uint256 indexed tokenId, address buyer, uint256 fee, bytes32 burnHash);
  event TokenRetired(uint256 indexed tokenId, address beneficiary, uint256 fee, bytes32 burnHash);

  error Frozen(); error AlreadyRetired(); error NotOwner();
}
  • Mint: binds PoV & One-Claim; settle/retire charges 4% and burns 50% in EDM.

G. Fee Router / Burner

interface IFeeRouter {
  // Trade: stage fee (0.5% with per-tranche caps) — fee escrowed at award
  function settleTradeFee(bytes32 orderId, bytes32 stageId, uint256 releaseAmountEdsd)
    external returns (uint256 fee, bytes32 burnHash);

  // Tokens: 4% per settlement/retirement
  function settleTokenFee(uint256 amountEdsd)
    external returns (uint256 fee, bytes32 burnHash);

  event FeeBurned(bytes32 indexed ref, uint256 fee, uint256 burnedEdm, bytes32 burnTx);

  error E_BURN_CONVERSION_UNAVAILABLE(); // burn-half EDM conversion failed (slippage bounds)
}
  • If fee paid in EDSD/USD: Router converts the burn half to EDM at release, then burns; treasury-half routed to buckets. No gas burn ever.

H. Registry Mirror

interface IRegistryMirror {
  function create(bytes32 programId, bytes32 serialHash, bytes32 claimId, bytes32 povHash,
                  bytes calldata registrySig) external returns (bytes32 mirrorId);

  function replace(bytes32 mirrorId, bytes32 newSerialHash, bytes calldata registrySig) external;

  event MirrorCreated(bytes32 indexed mirrorId, bytes32 indexed claimId, bytes32 programId);
  event MirrorReplaced(bytes32 indexed oldId, bytes32 indexed newId);

  error NotRegistry(); error Frozen();
}
  • Mirrors: bind a serial to a single claim; later changes freeze → replace (append-only).

I. Attestor Registry

interface IAttestorRegistry {
  function addAttestor(address entity, bytes32[] calldata roles, bytes32[] calldata schemaIds) external;
  function setKey(address entity, bytes32 keyId, bool active) external;
  function setStatus(address entity, uint8 status) external; // 0 ACTIVE,1 SUSPENDED,2 BANNED

  function isActive(address entity, bytes32 role) external view returns (bool);
  function keyActive(address entity, bytes32 keyId) external view returns (bool);

  event AttestorAdded(address indexed entity);
  event AttestorKeyRotated(address indexed entity, bytes32 keyId, bool active);
  event AttestorSuspended(address indexed entity);
  event AttestorBanned(address indexed entity);
}

J. ParameterStore

interface IParameterStore {
  function set(bytes32 key, bytes calldata value) external;
  function get(bytes32 key) external view returns (bytes memory);
  function bounds(bytes32 key) external view returns (bytes memory minV, bytes memory maxV, uint64 step, uint64 effectiveFrom);

  event ParamChanged(bytes32 indexed key, bytes oldVal, bytes newVal, uint64 effectiveFrom);
  error OutOfBounds(); error TooFast(); // rate-limits
}
  • Examples: TRADE_REVIEW_WINDOW_HOURS (0..4), TRADE_TOPUP_DEADLINE_HOURS (12..72), payout schedules (banded), tolerance tables, treasury split (non-burn half only).

K. Governor / Timelock

interface IGovernor {
  function propose(bytes calldata paramDiff) external returns (uint256 id);
  function castVote(uint256 id, uint8 support, uint256 weight) external;
  function queue(uint256 id) external;  // into Timelock (≥72h)
  function execute(uint256 id) external;
  event ProposalCreated(uint256 id); event VoteCast(uint256 id, address voter, uint8 support, uint256 weight);
}

interface ITimelock {
  function queue(bytes32 txHash, uint64 eta) external;
  function execute(bytes32 txHash) external;
  event Queued(bytes32 indexed txHash, uint64 eta); event Executed(bytes32 indexed txHash);
}
  • Only ParameterStore/TreasurySplitter/allowlists are governed: Constitutional brakes (PoV/EMT/One-Claim/Locked→Unlocked/50% burn/fee constants) sit outside governance.

L. Treasury Splitter

interface ITreasurySplitter {
  function route(bytes32 ref, uint256 amountEdsd) external;  // called by FeeRouter
  function setSplits(uint16 attestors, uint16 ops, uint16 builders, uint16 ecosystem, uint16 stakers) external;

  event TreasuryRouted(bytes32 indexed ref, uint256 attestors, uint256 ops, uint256 builders, uint256 ecosystem, uint256 stakers);
  error SplitOutOfRange();
}

M. EDSD Treasury & Proof-of-Reserves

interface IProofOfReserves {
  function postSnapshot(bytes32 snapshotHash, uint256 tBillNotional, uint256 cashBalance, uint64 asOf)
    external; // bank/custodian attestations off-chain, hash on-chain

  event PoRSnapshot(bytes32 indexed snapshotHash, uint256 tBill, uint256 cash, uint64 asOf);
}
  • EDSD is platform-bound: mint/burn/transfer are restricted to settlement modules and whitelisted flows; cash-out after schedule completion only.

N. Bridge Gateway

interface IBridgeGateway {
  function exportToken(uint256 tokenId, bytes32 dstChain) external returns (bytes32 exportRef);
  function importToken(bytes32 burnProof, bytes32 claimId, bytes32 povHash) external returns (uint256 tokenId);
  event ExportLocked(uint256 indexed tokenId, bytes32 dstChain, bytes32 exportRef);
  event ImportMinted(uint256 indexed tokenId, bytes32 srcChain, bytes32 burnRef);

  error NotCanonical(); error InvalidProof();
}
  • Bridges: mint representations only; EDMA remains canonical for claims. EDSD and EMT are not bridged.

O. Error catalog

  • E_CANONICAL_DRIFT / E_FORMAT_INVALID / E_SET_NOT_SORTED / E_FILE_MISSING / E_HASH_MISMATCH: serialization & evidence hygiene

  • E_QUORUM_MISSING / E_STALE_ATTEST / E_KEY_REVOKED: attestor/quorum freshness

  • E_PENDING_FUNDS: must-fund before shipping unmet

  • E_ONECLAIM_TAKEN: uniqueness already finalized

  • E_DUP_RELEASE / AlreadyMinted / AlreadyRetired: idempotency/monotonicity

  • E_BURN_CONVERSION_UNAVAILABLE: burn-half conversion to EDM deferred (release waits)

P. Events you will index

  • GatePass(schemaId, claimId, povHash)

  • EMTMinted(orderId, stageId, subLotId, claimId, povHash)

  • ReleasePosted(orderId, stageId, amountEdsd, fee, burnHash)

  • TokenMinted(tokenId, claimId, povHash)

  • TokenSettled(tokenId, buyer, fee, burnHash)

  • TokenRetired(tokenId, beneficiary, fee, burnHash)

  • FeeBurned(ref, fee, burnedEdm, burnTx)

  • MirrorCreated(mirrorId, claimId, programId) / MirrorReplaced(oldId, newId)

  • AttestorAdded(entity) / AttestorSuspended(entity)

  • ParamChanged(key, oldVal, newVal, effectiveFrom)

  • PoRSnapshot(snapshotHash, tBill, cash, asOf)

Q. Invariants

  • No EMT → no release: release requires Gate PASS + EMT + One-Claim FINAL + must-fund (Trade post-production gates).

  • Conservation (Trade): Locked + Unlocked = funded – refunds per order at all times.

  • Fee constants/caps: Trade 0.5%/milestone with $5k/$25k/$50k caps; Tokens 4%; burn exact 50% in EDM at event; gas never burns.

  • Uniqueness: claim_id finalizes once globally; replacements are new ids with lineage; mirrors bind to exactly one claim.

  • Idempotency: (order, stage, subLot) or tokenId settle once; replays are no-ops.

  • Append-only: revocations & replacements never delete; proof pages show original + correction.

Use these headers to wire clients, index receipts, and write off-chain services (ERP/TMS/WMS/ESG). For full ABIs, event topics, and test vectors (including canonical JSON and claimId fixtures), pull the SDK or query /v1/contracts and /v1/schemas.

Last updated