Smart Contracts
Architecture#
The SignalMarketplace contract (Solidity 0.8.24) is the core of AGDEL. It handles signal creation, escrow, settlement, and refunds. All monetary operations use USDC (ERC-20) via SafeERC20.
The contract implements a commit-reveal pattern: makers commit a hash of their prediction when creating a signal, and the keeper reveals the actual prediction at resolution time. The contract verifies the reveal matches the original commitment.
Data Structures#
Signal Struct#
struct Signal {
address maker;
string asset; // e.g. "BTC-PERP"
uint256 expiryTime; // Unix timestamp
uint256 costUsdc; // Per-buyer cost (6 decimals)
bytes32 commitmentHash; // keccak256(maker, targetPrice, direction, salt)
uint256 targetPrice; // Revealed at resolution (scaled 1e8)
uint8 direction; // 0 = LONG, 1 = SHORT
bool resolved;
bool outcome; // true = HIT, false = MISS
uint256 totalEscrow; // Sum of all buyer payments
uint256 buyerCount;
}MakerStats Struct#
struct MakerStats {
uint256 totalSignals;
uint256 wins;
uint256 losses;
uint256 activeSignals;
uint256 totalVolume; // Cumulative USDC volume
}Enums#
| Enum | Values |
|---|---|
| Direction | LONG (0), SHORT (1) |
| Duration | ONE_MIN, FIVE_MIN, FIFTEEN_MIN, THIRTY_MIN, ONE_HOUR, FOUR_HOUR, TWELVE_HOUR, TWENTY_FOUR_HOUR |
Core Functions#
createSignal()#
function createSignal(
string calldata asset,
uint256 expiryTime,
uint256 costUsdc,
bytes32 commitmentHash
) external whenNotPaused returns (uint256 signalId)Creates a new signal with public metadata and a hidden prediction. The commitment hash prevents the maker from changing their prediction after listing. Emits SignalCreated.
buySignal()#
function buySignal(
uint256 signalId
) external whenNotPausedBuyer pays costUsdc into escrow. Enforces the buying cutoff (time remaining must exceed the cutoff for the signal's duration). Reverts if already purchased by the same buyer, if the buyer is the maker, or if the signal is expired/resolved. Emits SignalPurchased.
resolveSignal()#
function resolveSignal(
uint256 signalId,
uint256 targetPrice,
uint8 direction,
bytes32 salt,
bool outcome
) external onlyKeeperKeeper reveals hidden params and settles the signal. The contract verifies keccak256(maker, targetPrice, direction, salt) == commitmentHash. On HIT: 90% to maker, 10% to protocol. On MISS: escrow held for buyer refund claims. Emits SignalResolved.
claimRefund()#
function claimRefund(
uint256 signalId
) externalBuyer claims 90% refund on a MISS signal. The 10% protocol fee is deducted from the buyer's share. Pro-rata calculation if multiple buyers. Emits RefundClaimed.
cancelSignal()#
Maker cancels an unsold signal. Only callable if buyerCount == 0.
Events#
| Event | Key Fields |
|---|---|
SignalCreated | signalId, maker, asset, expiryTime, costUsdc, commitmentHash |
SignalPurchased | signalId, buyer, amount |
SignalResolved | signalId, outcome, targetPrice, direction |
RefundClaimed | signalId, buyer, amount |
Buying Cutoffs#
To prevent last-minute front-running, each duration has a buying cutoff. Purchases are rejected if the remaining time before expiry is less than the cutoff:
| Duration | Cutoff |
|---|---|
| 1 minute | 15 seconds |
| 5 minutes | 30 seconds |
| 15 minutes | 2 minutes |
| 30 minutes | 5 minutes |
| 1 hour | 10 minutes |
| 4 hours | 15 minutes |
| 12 hours | 20 minutes |
| 24 hours | 30 minutes |
Security Properties#
- ReentrancyGuard: All functions that transfer tokens use OpenZeppelin's ReentrancyGuard + Checks-Effects-Interactions pattern
- SafeERC20: All USDC transfers use SafeERC20 to handle non-standard ERC-20 implementations
- Checked arithmetic: Solidity 0.8.24's built-in overflow protection, with explicit uint256 widening for multiplication
- Commitment integrity: Address prefix in hash prevents reuse across makers
- Pause mechanism: Owner can pause createSignal/buySignal but not resolveSignal/claimRefund/withdraw
- Access control: Owner, keeper (multiple allowed), maker, buyer roles with proper separation
For the full security assessment, see the security review.