Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
129 lines
5.5 KiB
Solidity
129 lines
5.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
/**
|
|
* @title AereTravelRuleHashRegistry — on-chain anchor for FATF Travel Rule
|
|
* message exchanges (Notabene-compatible)
|
|
* @notice The FATF Travel Rule requires VASPs to exchange identity payloads
|
|
* for transfers above a jurisdiction-set threshold (typically
|
|
* USD 1,000 for the US, EUR 1,000 for the EU MiCA regime). The
|
|
* payloads themselves are confidential and exchanged off-chain via
|
|
* a message bus like Notabene IVMS-101.
|
|
*
|
|
* This contract is the on-chain ANCHOR for the off-chain exchange.
|
|
* The originating VASP commits a keccak256 hash of the IVMS-101
|
|
* payload before the transaction goes through, and the beneficiary
|
|
* VASP later verifies that hash matches what was exchanged. The
|
|
* on-chain anchor:
|
|
* 1. Proves the payload existed at a specific block.
|
|
* 2. Cannot be retroactively altered (commitments are
|
|
* append-only).
|
|
* 3. Is auditable by regulators without revealing the payload.
|
|
*
|
|
* No payload data is stored on chain. No identity is revealed.
|
|
*
|
|
* @dev PHASE 1 — minimal viable Travel Rule anchor.
|
|
* - `commit(payloadHash, beneficiaryVasp, threshold)` permissionless
|
|
* callable by the originator VASP. The VASP self-identifies via
|
|
* the msg.sender; consumers can query "did VASP X commit to a
|
|
* payload referencing this txid?" via the events index.
|
|
* - `acknowledge(commitmentId)` callable by the beneficiary VASP
|
|
* to mark receipt + verification of the off-chain payload.
|
|
* - `dispute(commitmentId, reason)` callable by the beneficiary
|
|
* if the payload doesn't match the on-chain hash.
|
|
*
|
|
* NO admin. NO Foundation interception. NO custody.
|
|
*
|
|
* INTEGRATIONS:
|
|
* - Notabene SDK: the JS SDK on each VASP calls
|
|
* contract.commit(hash, recipientVasp, threshold) automatically
|
|
* when the user initiates a > $1k transfer.
|
|
* - Compliance dashboards: index Commitments + Acknowledgements
|
|
* + Disputes by VASP address for audit trail.
|
|
*
|
|
* AERE Foundation Seychelles is registered as a VASP and uses this
|
|
* registry for its own institutional flows (e.g. via the
|
|
* SettlementHub for BUIDL/USDY/OUSG receipts).
|
|
*/
|
|
contract AereTravelRuleHashRegistry {
|
|
|
|
enum State { Committed, Acknowledged, Disputed }
|
|
|
|
struct Commitment {
|
|
bytes32 payloadHash;
|
|
address originatorVasp;
|
|
address beneficiaryVasp;
|
|
uint256 threshold; // jurisdiction threshold in USD units (cents)
|
|
uint64 committedAt;
|
|
uint64 statusUpdatedAt;
|
|
State state;
|
|
string disputeReason;
|
|
}
|
|
|
|
/// @notice commitmentId → Commitment
|
|
mapping(uint256 => Commitment) public commitments;
|
|
uint256 public nextCommitmentId;
|
|
|
|
/// @notice Per-VASP commitment index (for audit retrieval).
|
|
mapping(address => uint256[]) public vaspCommitments;
|
|
|
|
event Committed(uint256 indexed id, address indexed originator, address indexed beneficiary, bytes32 payloadHash, uint256 threshold);
|
|
event Acknowledged(uint256 indexed id, address indexed beneficiary);
|
|
event Disputed(uint256 indexed id, address indexed beneficiary, string reason);
|
|
|
|
error UnknownCommitment();
|
|
error NotBeneficiary();
|
|
error AlreadyAcknowledged();
|
|
error AlreadyDisputed();
|
|
error ZeroAddress();
|
|
|
|
function commit(bytes32 payloadHash, address beneficiaryVasp, uint256 threshold)
|
|
external
|
|
returns (uint256 id)
|
|
{
|
|
if (beneficiaryVasp == address(0)) revert ZeroAddress();
|
|
id = nextCommitmentId++;
|
|
commitments[id] = Commitment({
|
|
payloadHash: payloadHash,
|
|
originatorVasp: msg.sender,
|
|
beneficiaryVasp: beneficiaryVasp,
|
|
threshold: threshold,
|
|
committedAt: uint64(block.timestamp),
|
|
statusUpdatedAt: uint64(block.timestamp),
|
|
state: State.Committed,
|
|
disputeReason: ""
|
|
});
|
|
vaspCommitments[msg.sender].push(id);
|
|
vaspCommitments[beneficiaryVasp].push(id);
|
|
emit Committed(id, msg.sender, beneficiaryVasp, payloadHash, threshold);
|
|
}
|
|
|
|
function acknowledge(uint256 id) external {
|
|
Commitment storage c = commitments[id];
|
|
if (c.committedAt == 0) revert UnknownCommitment();
|
|
if (msg.sender != c.beneficiaryVasp) revert NotBeneficiary();
|
|
if (c.state == State.Acknowledged) revert AlreadyAcknowledged();
|
|
if (c.state == State.Disputed) revert AlreadyDisputed();
|
|
c.state = State.Acknowledged;
|
|
c.statusUpdatedAt = uint64(block.timestamp);
|
|
emit Acknowledged(id, msg.sender);
|
|
}
|
|
|
|
function dispute(uint256 id, string calldata reason) external {
|
|
Commitment storage c = commitments[id];
|
|
if (c.committedAt == 0) revert UnknownCommitment();
|
|
if (msg.sender != c.beneficiaryVasp) revert NotBeneficiary();
|
|
if (c.state == State.Disputed) revert AlreadyDisputed();
|
|
c.state = State.Disputed;
|
|
c.disputeReason = reason;
|
|
c.statusUpdatedAt = uint64(block.timestamp);
|
|
emit Disputed(id, msg.sender, reason);
|
|
}
|
|
|
|
/* ----------------------------------- views ---------------------------------- */
|
|
|
|
function vaspCommitmentCount(address vasp) external view returns (uint256) {
|
|
return vaspCommitments[vasp].length;
|
|
}
|
|
}
|