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