Solidity interface reference for BlindOracle's RWA prediction market contracts on Robinhood Chain (chain ID 46630). All functions are on-chain -- interact via
cast, ethers.js, or any EVM-compatible client.
- Factory API (RWAMarketFactory)
- Market API (RWAPredictionMarket)
- Oracle API (DataStreamConsumer)
- Events Reference
- Error Codes
The factory deploys and indexes all RWA prediction markets. It enforces collateral whitelisting and wires up the policy engine and oracle consumer on each new market.
Creates a new binary prediction market for a tokenized stock.
function createMarket(
address collateral,
bytes32 feedId,
string calldata symbol,
int192 strikePrice,
uint256 expirationTime,
bool isAbove
) external onlyOwner returns (address)Parameters:
| Name | Type | Description |
|---|---|---|
collateral |
address |
ERC-20 token used for share purchases (must be approved) |
feedId |
bytes32 |
Chainlink Data Streams feed identifier (e.g., TSLA/USD) |
symbol |
string |
Human-readable asset symbol (e.g., "TSLA") |
strikePrice |
int192 |
Strike price in 18-decimal fixed point |
expirationTime |
uint256 |
Unix timestamp when the market expires |
isAbove |
bool |
If true, YES wins when price >= strike. If false, YES wins when price < strike |
Returns: Address of the newly deployed RWAPredictionMarket contract.
Reverts:
CollateralNotApproved()-- collateral token not whitelistedExpirationInPast()-- expirationTime <= block.timestampInvalidFeedId()-- feedId is bytes32(0)
# Create a TSLA market: "Will TSLA be above $300 by March 31, 2026?"
cast send $FACTORY "createMarket(address,bytes32,string,int192,uint256,bool)" \
$USDC \
0x00037da06d56d083fe599397a4769a042d63aa73dc4ef57709d31e9971a5b439 \
"TSLA" \
300000000000000000000 \
1743379200 \
true \
--rpc-url $RPC_URL --private-key $PKReturns the deployed market contract address for a given market ID.
function getMarket(bytes32 marketId) external view returns (address)Parameters:
| Name | Type | Description |
|---|---|---|
marketId |
bytes32 |
Keccak256 hash of (feedId, strikePrice, expirationTime, isAbove) |
Returns: Market contract address, or address(0) if not found.
cast call $FACTORY "getMarket(bytes32)(address)" $MARKET_ID --rpc-url $RPC_URLPaginated retrieval of market IDs. Use this instead of iterating the full array on-chain.
function getMarketPage(uint256 offset, uint256 limit) external view returns (bytes32[] memory)Parameters:
| Name | Type | Description |
|---|---|---|
offset |
uint256 |
Starting index |
limit |
uint256 |
Maximum number of IDs to return |
Returns: Array of market IDs, length <= limit.
# Get the first 10 markets
cast call $FACTORY "getMarketPage(uint256,uint256)(bytes32[])" 0 10 --rpc-url $RPC_URLReturns all market IDs associated with a specific Data Streams feed.
function getMarketsByFeed(bytes32 feedId) external view returns (bytes32[] memory)# Get all TSLA markets
cast call $FACTORY "getMarketsByFeed(bytes32)(bytes32[])" $TSLA_FEED_ID --rpc-url $RPC_URLReturns the total number of markets created by this factory.
function getMarketCount() external view returns (uint256)cast call $FACTORY "getMarketCount()(uint256)" --rpc-url $RPC_URLManages the whitelist of ERC-20 tokens that can be used as collateral.
function approveCollateral(address token) external onlyOwner
function revokeCollateral(address token) external onlyOwner# Approve USDC as collateral
cast send $FACTORY "approveCollateral(address)" $USDC --rpc-url $RPC_URL --private-key $PK
# Revoke a token
cast send $FACTORY "revokeCollateral(address)" $OLD_TOKEN --rpc-url $RPC_URL --private-key $PKUpdates the ACE policy engine or oracle consumer used by newly created markets.
function setPolicyEngine(address engine) external onlyOwner
function setDataStreamConsumer(address consumer) external onlyOwnerNote: These only affect markets created after the call. Existing markets retain their original references.
cast send $FACTORY "setPolicyEngine(address)" $NEW_ENGINE --rpc-url $RPC_URL --private-key $PK
cast send $FACTORY "setDataStreamConsumer(address)" $NEW_CONSUMER --rpc-url $RPC_URL --private-key $PKEach market is a standalone contract deployed by the factory. It holds collateral, tracks share balances, and resolves against oracle prices.
Purchase YES or NO shares. 1 share = 1 unit of collateral (fixed pricing model). Caller must have approved the market contract to spend their collateral tokens.
function buyShares(bool isYes, uint256 amount) external runPolicy nonReentrantModifiers:
runPolicy-- Executes ACE CompliancePolicyRules before proceeding. Reverts if the caller fails compliance checks.nonReentrant-- OpenZeppelin reentrancy guard.
Parameters:
| Name | Type | Description |
|---|---|---|
isYes |
bool |
true to buy YES shares, false to buy NO shares |
amount |
uint256 |
Number of shares to buy (in collateral token decimals) |
Reverts:
MarketNotActive()-- market is resolved or expiredInsufficientAllowance()-- caller has not approved enough collateral- Policy engine revert -- caller failed compliance check
# Buy 100 YES shares (assumes USDC with 6 decimals: 100e6 = 100000000)
cast send $MARKET "buyShares(bool,uint256)" true 100000000 \
--rpc-url $RPC_URL --private-key $PK
# Buy 50 NO shares
cast send $MARKET "buyShares(bool,uint256)" false 50000000 \
--rpc-url $RPC_URL --private-key $PKSell previously purchased shares back to the market. Returns collateral to the seller.
function sellShares(bool isYes, uint256 amount) external runPolicy nonReentrantParameters:
| Name | Type | Description |
|---|---|---|
isYes |
bool |
true to sell YES shares, false to sell NO shares |
amount |
uint256 |
Number of shares to sell |
Reverts:
MarketNotActive()-- market is resolved or expiredInsufficientShares()-- caller holds fewer shares thanamount
# Sell 25 YES shares
cast send $MARKET "sellShares(bool,uint256)" true 25000000 \
--rpc-url $RPC_URL --private-key $PKResolves the market by reading the latest oracle price and comparing it to the strike. Anyone can call this after the market has expired. The function is permissionless because the outcome is determined entirely by on-chain oracle data.
function resolve() externalRequirements:
block.timestamp >= expirationTime- Market status must be
Active - Oracle must have a price for the market's feedId
Resolution logic:
- If
isAbove == true: YES wins whenlatestPrice >= strikePrice - If
isAbove == false: YES wins whenlatestPrice < strikePrice
cast send $MARKET "resolve()" --rpc-url $RPC_URL --private-key $PKAfter resolution, winning shareholders claim their proportional share of the total collateral pool.
function claimWinnings() external runPolicy nonReentrantPayout calculation:
payout = (userWinningShares / totalWinningShares) * totalCollateralPool * (1 - feeRate)
Reverts:
MarketNotResolved()-- market has not been resolved yetNoWinningShares()-- caller holds zero winning sharesAlreadyClaimed()-- caller has already claimed
cast send $MARKET "claimWinnings()" --rpc-url $RPC_URL --private-key $PKOwner-only emergency resolution. Use when oracle data is unavailable or demonstrably incorrect. Emits a reason string for audit trails.
function emergencyResolve(bool _yesWins, string calldata reason) external onlyOwnerParameters:
| Name | Type | Description |
|---|---|---|
_yesWins |
bool |
true if YES side wins, false if NO side wins |
reason |
string |
Human-readable explanation for the emergency resolution |
cast send $MARKET "emergencyResolve(bool,string)" true "Oracle feed stale for 24h, resolving based on off-chain TSLA close" \
--rpc-url $RPC_URL --private-key $PKWithdraws accumulated protocol fees to a specified address.
function withdrawFees(address to) external onlyOwnercast send $MARKET "withdrawFees(address)" $TREASURY --rpc-url $RPC_URL --private-key $PKReturns the current lifecycle state of the market.
function getMarketStatus() external view returns (MarketStatus)MarketStatus enum:
| Value | Name | Description |
|---|---|---|
| 0 | Active |
Trading is open |
| 1 | Expired |
Past expiration, awaiting resolution |
| 2 | Resolved |
Outcome determined, claims open |
| 3 | EmergencyResolved |
Owner forced resolution |
cast call $MARKET "getMarketStatus()(uint8)" --rpc-url $RPC_URLReturns the YES and NO share balances for a given address.
function getShareBalances(address account) external view returns (uint256 yes, uint256 no)cast call $MARKET "getShareBalances(address)(uint256,uint256)" $USER_ADDR --rpc-url $RPC_URLReturns a comprehensive snapshot of the market's current state in a single call.
function getMarketSummary() external view returns (
string memory symbol,
int192 strikePrice,
uint256 expirationTime,
bool isAbove,
uint256 yesTotal,
uint256 noTotal,
address collateral,
MarketStatus status
)cast call $MARKET \
"getMarketSummary()(string,int192,uint256,bool,uint256,uint256,address,uint8)" \
--rpc-url $RPC_URLThe oracle layer decodes and stores Chainlink Data Streams V3 premium reports. It serves as the single source of truth for price data used in market resolution.
Decodes a signed Data Streams premium report, validates freshness, and stores the price on-chain.
function verifyAndStore(bytes calldata signedReport) externalWhat happens internally:
- Decodes the outer envelope to extract the report payload
- Decodes the
PremiumReportstruct from the payload - Validates
feedIdis registered - Checks
observationsTimestampis within the staleness threshold - Stores the price in the
latestPricesmapping - Emits
PriceUpdated(feedId, price, timestamp)
# signedReport is the raw bytes from the Data Streams API
cast send $CONSUMER "verifyAndStore(bytes)" $SIGNED_REPORT \
--rpc-url $RPC_URL --private-key $PKReturns the most recently stored price for a feed.
function getLatestPrice(bytes32 feedId) external view returns (int192 price, uint32 timestamp)cast call $CONSUMER "getLatestPrice(bytes32)(int192,uint32)" $TSLA_FEED_ID --rpc-url $RPC_URLReturns the complete stored price data including bid/ask spread.
function getFullPriceData(bytes32 feedId) external view returns (PriceData memory)PriceData struct:
struct PriceData {
int192 price;
int192 bid;
int192 ask;
uint32 observationsTimestamp;
uint32 storedAt;
}cast call $CONSUMER "getFullPriceData(bytes32)((int192,int192,int192,uint32,uint32))" \
$TSLA_FEED_ID --rpc-url $RPC_URLChecks whether the stored price for a feed is within the staleness threshold.
function isPriceFresh(bytes32 feedId) external view returns (bool)cast call $CONSUMER "isPriceFresh(bytes32)(bool)" $TSLA_FEED_ID --rpc-url $RPC_URLManages the set of recognized Data Streams feed IDs.
function registerFeed(bytes32 feedId, string calldata symbol) external onlyOwner
function deregisterFeed(bytes32 feedId) external onlyOwner# Register TSLA feed
cast send $CONSUMER "registerFeed(bytes32,string)" \
0x00037da06d56d083fe599397a4769a042d63aa73dc4ef57709d31e9971a5b439 \
"TSLA" \
--rpc-url $RPC_URL --private-key $PK
# Deregister a feed
cast send $CONSUMER "deregisterFeed(bytes32)" $FEED_ID --rpc-url $RPC_URL --private-key $PKAll events are indexed for efficient off-chain querying. Use these topics with cast logs or any indexer.
event MarketCreated(
bytes32 indexed marketId,
address indexed marketAddress,
bytes32 indexed feedId,
string symbol,
int192 strikePrice,
uint256 expirationTime,
bool isAbove
);
// Topic 0: keccak256("MarketCreated(bytes32,address,bytes32,string,int192,uint256,bool)")
event CollateralApproved(address indexed token);
// Topic 0: keccak256("CollateralApproved(address)")
event CollateralRevoked(address indexed token);
// Topic 0: keccak256("CollateralRevoked(address)")
event PolicyEngineUpdated(address indexed oldEngine, address indexed newEngine);
// Topic 0: keccak256("PolicyEngineUpdated(address,address)")
event DataStreamConsumerUpdated(address indexed oldConsumer, address indexed newConsumer);
// Topic 0: keccak256("DataStreamConsumerUpdated(address,address)")event SharesPurchased(
address indexed buyer,
bool indexed isYes,
uint256 amount
);
// Topic 0: keccak256("SharesPurchased(address,bool,uint256)")
event SharesSold(
address indexed seller,
bool indexed isYes,
uint256 amount
);
// Topic 0: keccak256("SharesSold(address,bool,uint256)")
event MarketResolved(
bool yesWins,
int192 finalPrice,
int192 strikePrice
);
// Topic 0: keccak256("MarketResolved(bool,int192,int192)")
event EmergencyResolved(
bool yesWins,
string reason
);
// Topic 0: keccak256("EmergencyResolved(bool,string)")
event WinningsClaimed(
address indexed claimer,
uint256 payout
);
// Topic 0: keccak256("WinningsClaimed(address,uint256)")
event FeesWithdrawn(
address indexed to,
uint256 amount
);
// Topic 0: keccak256("FeesWithdrawn(address,uint256)")event PriceUpdated(
bytes32 indexed feedId,
int192 price,
uint32 timestamp
);
// Topic 0: keccak256("PriceUpdated(bytes32,int192,uint32)")
event FeedRegistered(
bytes32 indexed feedId,
string symbol
);
// Topic 0: keccak256("FeedRegistered(bytes32,string)")
event FeedDeregistered(
bytes32 indexed feedId
);
// Topic 0: keccak256("FeedDeregistered(bytes32)")
event StalenessThresholdUpdated(
uint256 oldThreshold,
uint256 newThreshold
);
// Topic 0: keccak256("StalenessThresholdUpdated(uint256,uint256)")# Get all MarketCreated events from the factory
cast logs --from-block 0 --address $FACTORY \
"MarketCreated(bytes32,address,bytes32,string,int192,uint256,bool)" \
--rpc-url $RPC_URL
# Get all share purchases for a specific market
cast logs --from-block 0 --address $MARKET \
"SharesPurchased(address,bool,uint256)" \
--rpc-url $RPC_URL
# Filter by specific buyer (indexed topic1)
cast logs --from-block 0 --address $MARKET \
"SharesPurchased(address,bool,uint256)" \
--topic1 $(cast abi-encode "f(address)" $BUYER_ADDR) \
--rpc-url $RPC_URLCustom errors used across the contract system. These replace require strings for gas efficiency.
// RWAMarketFactory
error CollateralNotApproved();
error ExpirationInPast();
error InvalidFeedId();
error MarketAlreadyExists();
error ZeroAddress();
// RWAPredictionMarket
error MarketNotActive();
error MarketNotExpired();
error MarketNotResolved();
error MarketAlreadyResolved();
error InsufficientShares();
error InsufficientAllowance();
error NoWinningShares();
error AlreadyClaimed();
error TransferFailed();
// DataStreamConsumer
error FeedNotRegistered();
error StalePrice();
error InvalidReport();
error FeedAlreadyRegistered();For the cast examples throughout this document, set these environment variables:
export RPC_URL="https://robin-rpc.lavish.dev" # Robinhood Chain RPC
export FACTORY="0x..." # RWAMarketFactory address
export MARKET="0x..." # Specific RWAPredictionMarket
export CONSUMER="0x..." # DataStreamConsumer address
export USDC="0x..." # USDC on Robinhood Chain
export PK="0x..." # Deployer private key (NEVER commit)
export TSLA_FEED_ID="0x00037da06d56d083fe599397a4769a042d63aa73dc4ef57709d31e9971a5b439"Contract source: blindoracle-rwa-markets | Chain: Robinhood Chain (46630) | Compiler: Solidity 0.8.24