Address
- Address
- 0x7d7b5a95823bef99040e25312b114eb654ac0d6d
- Type
- contract
- Balance
- 0 tMIDX (0 wei)
- Nonce
- 1
- Code size
- 4,667 bytes
Contract
Source code ✓ verified
- Contract
- FxSettlement
- Compiler
- solc 0.8.36+commit.8a079791
- Optimizer
- enabled, 200 runs
- Verified
- 2026-09-22 20:53:44 UTC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
/// @title FxSettlement (v2: with post-trade corrections)
/// @notice Settles an FX fill from the MIDX maker as TWO real ERC-20 transfers -- the base leg and the
/// quote leg -- between the maker wallet and a taker wallet, atomically, and remembers the trade so
/// it can be CORRECTED afterwards. A ledger cannot amend a rate the way FX back offices do, so a
/// correction is a compensating transfer that references the original:
/// * amendPrice: same quantity, new rate => only the quote leg changes, so only the DIFFERENCE moves
/// (whoever underpaid pays it). Re-amendable. The original settlement stays on-chain untouched.
/// * bust: reverses both legs at their current (post-amendment) amounts.
/// * OffMarket: at settle time, if the fill price deviates from the maker mid by more than
/// `offMarketBps`, the trade still settles but is flagged -- mirroring FX practice (identify and
/// approve off-market trades rather than silently accept them).
/// Both wallets pre-approve this contract once; it custodies nothing. Owner-only corrections are a
/// TESTNET simplification: a production version needs the counterparty to consent (dual signature or
/// a dispute window) -- one party re-rating a settled trade unilaterally is what a ledger exists to
/// prevent. Conventions: `side` is the TAKER side (1 = taker buys base, 2 = taker sells base);
/// priceE8/midE8 are rates scaled by 1e8; amounts are in each token's own base units.
contract FxSettlement {
address public owner;
address public maker;
uint256 public settleCount;
uint256 public offMarketBps = 50; // flag threshold: |price - mid| in basis points of mid
enum Status { None, Settled, Amended, Busted }
/// @dev Calldata input to settle().
struct Fill {
bytes32 clOrdId;
string symbol; // e.g. "USD/MXN"
uint8 side; // taker side: 1 = buy base, 2 = sell base
address baseToken;
uint256 baseAmount; // base token units
address quoteToken;
uint256 quoteAmount; // quote token units
uint256 priceE8;
uint256 midE8; // maker mid at fill time; 0 = unknown (no off-market check)
address taker;
}
/// @dev Stored per settlement so it can be amended or busted later.
struct Trade {
bytes32 clOrdId;
uint8 side;
Status status;
address baseToken;
uint256 baseAmount;
address quoteToken;
uint256 quoteAmount; // current, i.e. after any amendment
uint256 priceE8; // current
address taker;
}
mapping(uint256 => Trade) public trades; // seq => trade
mapping(bytes32 => uint256) public seqOf; // clOrdId => seq (FIX ops key on ClOrdID)
event Settled(
uint256 indexed seq,
bytes32 indexed clOrdId,
string symbol,
uint8 side,
address baseToken,
uint256 baseAmount,
address quoteToken,
uint256 quoteAmount,
uint256 priceE8,
address taker
);
event OffMarket(uint256 indexed seq, bytes32 indexed clOrdId, uint256 priceE8, uint256 midE8, uint256 bps);
event PriceAmended(
uint256 indexed seq,
bytes32 indexed clOrdId,
uint256 oldPriceE8,
uint256 newPriceE8,
uint256 delta, // quote token units that moved
address payer,
string reason
);
event Busted(uint256 indexed seq, bytes32 indexed clOrdId, uint256 baseAmount, uint256 quoteAmount, string reason);
event OffMarketBpsChanged(uint256 previousBps, uint256 newBps);
event MakerChanged(address indexed previousMaker, address indexed newMaker);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
constructor(address _owner, address _maker) {
require(_owner != address(0) && _maker != address(0), "zero address");
owner = _owner;
maker = _maker;
emit OwnershipTransferred(address(0), _owner);
emit MakerChanged(address(0), _maker);
}
/// @notice Settle one fill: move the base leg and the quote leg in opposite directions, remember it.
function settle(Fill calldata f) external onlyOwner {
require(f.side == 1 || f.side == 2, "bad side");
require(f.taker != address(0), "zero taker");
require(seqOf[f.clOrdId] == 0, "duplicate clOrdId");
settleCount += 1;
_legs(f.baseToken, f.baseAmount, f.quoteToken, f.quoteAmount, f.side, f.taker, false);
_store(settleCount, f);
emit Settled(settleCount, f.clOrdId, f.symbol, f.side, f.baseToken, f.baseAmount,
f.quoteToken, f.quoteAmount, f.priceE8, f.taker);
_flagOffMarket(settleCount, f.clOrdId, f.priceE8, f.midE8);
}
/// @notice Re-rate a settled trade: same quantity, new price, so only the quote-leg difference moves.
function amendPrice(bytes32 clOrdId, uint256 newPriceE8, string calldata reason) external onlyOwner {
uint256 seq = seqOf[clOrdId];
Trade storage t = trades[seq];
require(seq != 0 && t.status != Status.Busted, "not amendable");
require(newPriceE8 > 0 && newPriceE8 != t.priceE8, "bad price");
// Same base quantity => the quote amount scales with the rate (integer division: dust <= 1 unit).
(uint256 delta, address payer) = _reprice(t, t.quoteAmount * newPriceE8 / t.priceE8);
emit PriceAmended(seq, clOrdId, t.priceE8, newPriceE8, delta, payer, reason);
t.priceE8 = newPriceE8;
t.status = Status.Amended;
}
/// @notice Cancel a settled trade: both legs go back at their current amounts.
function bust(bytes32 clOrdId, string calldata reason) external onlyOwner {
uint256 seq = seqOf[clOrdId];
Trade storage t = trades[seq];
require(seq != 0 && t.status != Status.Busted, "not bustable");
_legs(t.baseToken, t.baseAmount, t.quoteToken, t.quoteAmount, t.side, t.taker, true);
t.status = Status.Busted;
emit Busted(seq, clOrdId, t.baseAmount, t.quoteAmount, reason);
}
// --- admin ---
function setOffMarketBps(uint256 bps) external onlyOwner {
emit OffMarketBpsChanged(offMarketBps, bps);
offMarketBps = bps;
}
function setMaker(address newMaker) external onlyOwner {
require(newMaker != address(0), "zero maker");
emit MakerChanged(maker, newMaker);
maker = newMaker;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "zero owner");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
// --- internal ---
/// @dev Side 1 (taker buys base): base maker->taker, quote taker->maker. Side 2: the opposite.
/// `reverse` flips both legs (used by bust).
function _legs(address baseToken, uint256 baseAmount, address quoteToken, uint256 quoteAmount,
uint8 side, address taker, bool reverse) internal {
bool takerGetsBase = (side == 1) != reverse;
(address baseFrom, address baseTo) = takerGetsBase ? (maker, taker) : (taker, maker);
require(IERC20(baseToken).transferFrom(baseFrom, baseTo, baseAmount), "base leg");
require(IERC20(quoteToken).transferFrom(baseTo, baseFrom, quoteAmount), "quote leg");
}
function _store(uint256 seq, Fill calldata f) internal {
Trade storage t = trades[seq];
t.clOrdId = f.clOrdId;
t.side = f.side;
t.status = Status.Settled;
t.baseToken = f.baseToken;
t.baseAmount = f.baseAmount;
t.quoteToken = f.quoteToken;
t.quoteAmount = f.quoteAmount;
t.priceE8 = f.priceE8;
t.taker = f.taker;
seqOf[f.clOrdId] = seq;
}
/// @dev Moves the quote-leg difference to the new amount. The base BUYER owes the quote: the taker on
/// side 1, the maker on side 2. A higher price means the buyer pays more; lower, they get a refund.
function _reprice(Trade storage t, uint256 newQuote) internal returns (uint256 delta, address payer) {
(address buyer, address seller) = t.side == 1 ? (t.taker, maker) : (maker, t.taker);
address payee;
if (newQuote > t.quoteAmount) {
delta = newQuote - t.quoteAmount; payer = buyer; payee = seller;
} else {
delta = t.quoteAmount - newQuote; payer = seller; payee = buyer;
}
if (delta > 0) require(IERC20(t.quoteToken).transferFrom(payer, payee, delta), "amend leg");
t.quoteAmount = newQuote;
}
function _flagOffMarket(uint256 seq, bytes32 clOrdId, uint256 priceE8, uint256 midE8) internal {
if (midE8 == 0) return;
uint256 diff = priceE8 > midE8 ? priceE8 - midE8 : midE8 - priceE8;
uint256 bps = diff * 10000 / midE8;
if (bps > offMarketBps) emit OffMarket(seq, clOrdId, priceE8, midE8, bps);
}
}
ABI
[
{
"type": "constructor",
"inputs": [
{
"name": "_owner",
"type": "address",
"internalType": "address"
},
{
"name": "_maker",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "Busted",
"type": "event",
"inputs": [
{
"name": "seq",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "clOrdId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "baseAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "quoteAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "reason",
"type": "string",
"indexed": false,
"internalType": "string"
}
],
"anonymous": false
},
{
"name": "MakerChanged",
"type": "event",
"inputs": [
{
"name": "previousMaker",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newMaker",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OffMarket",
"type": "event",
"inputs": [
{
"name": "seq",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "clOrdId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "priceE8",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "midE8",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "bps",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "OffMarketBpsChanged",
"type": "event",
"inputs": [
{
"name": "previousBps",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "newBps",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "OwnershipTransferred",
"type": "event",
"inputs": [
{
"name": "previousOwner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "PriceAmended",
"type": "event",
"inputs": [
{
"name": "seq",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "clOrdId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "oldPriceE8",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "newPriceE8",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "delta",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "payer",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "reason",
"type": "string",
"indexed": false,
"internalType": "string"
}
],
"anonymous": false
},
{
"name": "Settled",
"type": "event",
"inputs": [
{
"name": "seq",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "clOrdId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "symbol",
"type": "string",
"indexed": false,
"internalType": "string"
},
{
"name": "side",
"type": "uint8",
"indexed": false,
"internalType": "uint8"
},
{
"name": "baseToken",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "baseAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "quoteToken",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "quoteAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "priceE8",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "taker",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "amendPrice",
"type": "function",
"inputs": [
{
"name": "clOrdId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "newPriceE8",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "reason",
"type": "string",
"internalType": "string"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "bust",
"type": "function",
"inputs": [
{
"name": "clOrdId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "reason",
"type": "string",
"internalType": "string"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "maker",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "offMarketBps",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "owner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "seqOf",
"type": "function",
"inputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "setMaker",
"type": "function",
"inputs": [
{
"name": "newMaker",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setOffMarketBps",
"type": "function",
"inputs": [
{
"name": "bps",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "settle",
"type": "function",
"inputs": [
{
"name": "f",
"type": "tuple",
"components": [
{
"name": "clOrdId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "side",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "baseToken",
"type": "address",
"internalType": "address"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteToken",
"type": "address",
"internalType": "address"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "priceE8",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "midE8",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "taker",
"type": "address",
"internalType": "address"
}
],
"internalType": "struct FxSettlement.Fill"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "settleCount",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "trades",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "clOrdId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "side",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "status",
"type": "uint8",
"internalType": "enum FxSettlement.Status"
},
{
"name": "baseToken",
"type": "address",
"internalType": "address"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteToken",
"type": "address",
"internalType": "address"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "priceE8",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "taker",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "transferOwnership",
"type": "function",
"inputs": [
{
"name": "newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]Transactions (6 indexed)
page 1 / 1
Temporary placeholder infrastructure for chain registration.
Not a production network - state may be reset without notice.