Files
smom-dbis-138/contracts/bridge/atomic/AtomicObligationEscrow.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

80 lines
2.9 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract AtomicObligationEscrow is AccessControl {
using SafeERC20 for IERC20;
bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE");
struct EscrowRecord {
address token;
address payer;
uint256 totalAmount;
uint256 releasedAmount;
bool exists;
}
mapping(bytes32 => EscrowRecord) public escrows;
event EscrowFunded(bytes32 indexed obligationId, address indexed token, address indexed payer, uint256 amount);
event EscrowReleased(bytes32 indexed obligationId, address indexed to, uint256 amount);
event EscrowRefunded(bytes32 indexed obligationId, address indexed to, uint256 amount);
error EscrowExists();
error EscrowMissing();
error InsufficientEscrow();
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
function escrowFunds(bytes32 obligationId, address token, address from, uint256 amount)
external
onlyRole(COORDINATOR_ROLE)
{
if (escrows[obligationId].exists) revert EscrowExists();
escrows[obligationId] = EscrowRecord({
token: token,
payer: from,
totalAmount: amount,
releasedAmount: 0,
exists: true
});
IERC20(token).safeTransferFrom(from, address(this), amount);
emit EscrowFunded(obligationId, token, from, amount);
}
function release(bytes32 obligationId, address to, uint256 amount) external onlyRole(COORDINATOR_ROLE) {
EscrowRecord storage record = escrows[obligationId];
if (!record.exists) revert EscrowMissing();
uint256 availableAmount = record.totalAmount - record.releasedAmount;
if (availableAmount < amount) revert InsufficientEscrow();
record.releasedAmount += amount;
IERC20(record.token).safeTransfer(to, amount);
emit EscrowReleased(obligationId, to, amount);
}
function refundRemaining(bytes32 obligationId, address to)
external
onlyRole(COORDINATOR_ROLE)
returns (uint256 refunded)
{
EscrowRecord storage record = escrows[obligationId];
if (!record.exists) revert EscrowMissing();
refunded = record.totalAmount - record.releasedAmount;
record.releasedAmount = record.totalAmount;
IERC20(record.token).safeTransfer(to, refunded);
emit EscrowRefunded(obligationId, to, refunded);
}
function remaining(bytes32 obligationId) external view returns (uint256) {
EscrowRecord memory record = escrows[obligationId];
if (!record.exists) revert EscrowMissing();
return record.totalAmount - record.releasedAmount;
}
}