Advanced Market Models & Chainlink Oracle Integration
5 outcomes at 50% each: Sum = 250% (Converges via trading)
5 outcomes at 20% each: Sum = 100% (No arbitrage opportunity)
Super Bowl (NegRisk CTF): โโโโโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโโ โ Team โ YES โ NO โ YES + NO โ โโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโผโโโโโโโโโโโค โ Chiefs โ 23ยข โ 77ยข โ 100ยข โ โ โ Eagles โ 19ยข โ 81ยข โ 100ยข โ โ โโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโดโโโโโโโโโโโ CFP (Grouped Binary): โโโโโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโโ โ Team โ YES โ NO โ YES + NO โ โโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโผโโโโโโโโโโโค โ Indiana โ 93.4ยข โ 99ยข โ 192.4ยข โ โ Ohio State โ 72ยข โ 98ยข โ 170ยข โ โโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโดโโโโโโโโโโโ
5 outcomes at 50% each: Sum = 250% Arbitrageurs: - Buy all NO tokens - Guaranteed profit when one outcome loses - Creator loses to arbitrageurs
Grouped Binary (5 outcomes ร 50%): Market 1: Team A YES (50ยข) + NO (50ยข) = $1 โ Market 2: Team B YES (50ยข) + NO (50ยข) = $1 โ Market 3: Team C YES (50ยข) + NO (50ยข) = $1 โ Market 4: Team D YES (50ยข) + NO (50ยข) = $1 โ Market 5: Team E YES (50ยข) + NO (50ยข) = $1 โ Sum of YES = 250%... but so what? Why no arbitrage? โข Each market is INDEPENDENT โข You CANNOT merge tokens across different markets โข Each market individually has YES + NO = $1 โ The 250% โ 100% convergence is just PRICE DISCOVERY, not arbitrage. Smart traders normalize it.
| Case | Condition | Action | Result |
|---|---|---|---|
| Single Market | YES + NO < $1 | Buy both | Guaranteed profit |
| Single Market | YES + NO > $1 | Mint & sell both | Guaranteed profit |
| CTF Multi-Outcome | Sum < $1 | Buy all, merge | Guaranteed profit |
| CTF Multi-Outcome | Sum > $1 | Split & sell all | Guaranteed profit |
| Cross-Platform | YES_A + NO_B < $1 | Buy both sides | Guaranteed profit |
Key: Arbitrage means zero risk, guaranteed profit. The 250% sum doesn't qualify.
| Outcome | Initial | Fair | Gap |
|---|---|---|---|
| A (favorite) | 50ยข | 60ยข | -10ยข |
| B | 50ยข | 15ยข | +35ยข |
| C | 50ยข | 10ยข | +40ยข |
| D | 50ยข | 10ยข | +40ยข |
| E | 50ยข | 5ยข | +45ยข |
Favorite: small underpricing (-10ยข)
Underdogs: extreme overpricing (+35~45ยข)
| Outcome | Initial | Fair | Gap |
|---|---|---|---|
| A (favorite) | 20ยข | 60ยข | -40ยข |
| B | 20ยข | 15ยข | +5ยข |
| C | 20ยข | 10ยข | +10ยข |
| D | 20ยข | 10ยข | +10ยข |
| E | 20ยข | 5ยข | +15ยข |
Favorite: large underpricing (-40ยข)
Underdogs: small overpricing (+5~15ยข)
When the favorite wins, creator holds worthless 80ยข NO tokens.
Whether it minimizes loss depends on trading patterns.
| Provider | Reality | When |
|---|---|---|
| Random traders | Won't come to empty market | Never initially |
| Professional MMs | Only for proven high-volume markets | After volume is established |
| Platform itself | Must bootstrap new markets | From day 1 |
For a 5-outcome market with $1000 liquidity: Split equally: $200 per market For each market (at 20% YES / 80% NO): - 200 YES @ $0.20 = $40 - 200 NO @ $0.80 = $160 - Total: $200 โ 200 YES + 200 NO (balanced) If A wins: - A: 200 YES ร $1 = $200 - B: 200 NO ร $1 = $200 - C: 200 NO ร $1 = $200 - D: 200 NO ร $1 = $200 - E: 200 NO ร $1 = $200 Return: $1000 โ (break even when balanced)
| Liquidity Level | Trading Quality | User Experience |
|---|---|---|
| $500/market | Poor | Frustrating, large slippage |
| $5,000/market | Okay | Acceptable for small traders |
| $50,000+/market | Good | Professional-grade |
Initial: Market A at 20% YES / 80% NO Creator holds: 200 YES + 200 NO Time passes... A becomes favorite (80% YES): Traders: "I want to BUY YES on A!" Creator: Sells YES, happy ๐ Traders: "I don't want NO on A, A will win!" Creator: Can't sell NO, stuck ๐ฐ Result: - YES: 200 โ 50 (sold 150, good!) - NO: 200 โ 200 (nobody bought, stuck!) If A wins: - 50 YES ร $1 = $50 - 200 NO ร $0 = $0 Creator started with $200, ends with $50 LOSS: $150 โ
Selling NO on losing markets (B, C, D, E) does NOT compensate:
Sell 200 NO on B @ 95ยข = $190 received But: If A wins, buyer gets 200 ร $1 = $200 Creator sold $200 value for $190 = LOSS Cross-market selling adds MORE losses, not compensation.
100 A + 100 B + 100 C + 100 D + 100 E โ $100 USDC No counterparty needed! Always can exit via merge. No stuck inventory problem.
| Scenario | Fee Income | Inventory Loss | Total Loss |
|---|---|---|---|
| Best case | $500 | $100 | +$500 profit |
| Average | $200 | $350 | -$150 (15%) |
| Bad case | $75 | $400 | -$325 (32.5%) |
| Worst case | $75 | $860 | -$785 (78.5%) |
MM posts orders: BUY YES @ 49ยข SELL YES @ 51ยข When both sides fill: - Trader A buys YES @ 51ยข โ MM receives 51ยข - Trader B sells YES @ 49ยข โ MM pays 49ยข MM profit: 51ยข - 49ยข = 2ยข per share If 10,000 shares traded: Profit = $200
Taker (takes order): Pays 2% fee Maker (provides order): Receives 0.5% rebate MM always provides orders โ earns rebate on every trade
Price moving up? โ Cancel BUY orders (avoid buying expensive) โ Raise SELL prices (sell higher) Amateur LP: Updates hourly โ Gets run over Pro MM: Updates every second โ Captures spread
Correlated markets: - "Chiefs win Super Bowl" (YES @ 25ยข) - "Mahomes wins MVP" (YES @ 30ยข) MM sells Chiefs YES, buys Mahomes YES (hedge) Net exposure: ~zero But: Keeps spread profit!
MM Profit = Spread Income
+ Fee Rebates
+ Arbitrage Gains
- Inventory Losses
- Operating Costs
| Method | Description | Cost to Platform |
|---|---|---|
| Pay Monthly Fee | $2-10K/month for liquidity | Fixed, predictable |
| Fee Rebates | MM earns 0.5% on trades | Reduced revenue |
| Revenue Sharing | 50% of trading fees | Variable |
| Token Incentives | NOSTRA tokens for liquidity | Token dilution |
Liquidity comes from:
Regular users just trade. They don't "provide liquidity" through UI.
Nostra employs a "Bridge Market Maker" bot that mirrors the order books of global leaders (e.g., Polymarket) in real-time.
| Fee Level | LP Yield | Trading Activity |
|---|---|---|
| High (3%+) | High | Low (expensive to trade) |
| Medium (2%) | Medium | Medium |
| Low (0.5%) | Low | High (cheap to trade) |
If volume is low AND market moves hard in one direction:
Fees help but don't guarantee profit.
| Platform | Fee Model | Details |
|---|---|---|
| Polymarket (Global/Crypto) | No Trading Fee / Spread |
|
| Polymarket US (Regulated/Upcoming) | Flat Trading Fee |
|
| Kalshi (US Regulated) | Maker-Taker |
|
The Bid-ask spread is an implicit cost representing the gap between the Price you can Buy at (Ask) and the Price you can Sell at (Bid).
LMSR AMM for 8 teams: Initial pool ($5000 liquidity): โโโโโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโโโโโโ โ Team โ Tokens โ Price โ Implied Odds โ โโโโโโโโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโโโโโโค โ Brazil โ 5000 โ 12.5ยข โ 12.5% โ โ France โ 5000 โ 12.5ยข โ 12.5% โ โ Argentina โ 5000 โ 12.5ยข โ 12.5% โ โ ... โ 5000 โ 12.5ยข โ 12.5% โ โโโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโดโโโโโโโโโโโโโโโ Trader buys $100 Brazil: โ Gets ~700 Brazil tokens โ Pool Brazil: 5000 โ 4300 โ Brazil price: 12.5% โ 25% (automatic!)
| Feature | Order Book | AMM |
|---|---|---|
| Always tradeable | No (need counterparty) | Yes |
| Capital needed | High ($50K+) | Lower ($5K) |
| Price discovery | Order matching | Formula |
| Large trades | Better (if deep) | Higher slippage |
| Complexity | Medium | Medium |
| Pro traders prefer | Yes | No |
They are fundamentally different systems. You choose ONE, not both.
Nostra currently has Order Book. Switching to AMM would require significant rebuild.
This is unavoidable. Every marketplace subsidizes early growth.
Nostra Year 1 Plan: Launch 50 markets Initial liquidity: $500 per market = $25,000 Expected loss rate: 20% Expected loss: $5,000 Think of it as: "$5,000 marketing spend to build a prediction market" Compare to: - Google Ads: $10,000 - Influencer marketing: $20,000 - Traditional marketing: $50,000+ $5,000 liquidity loss = CHEAP customer acquisition
| Component | Polymarket | Risk Bearer |
|---|---|---|
| Platform | Takes 2% fee on winnings | Zero inventory risk |
| Market Makers | Provide liquidity via API | Bear inventory risk |
| Users | Trade only | Market risk |
// Prisma schema
// Price is set at market creation
model Outcome {
currentPrice Decimal @default(0.5)
probability Decimal @default(50)
}
// When creating market with N outcomes
const totalOutcomes = outcomes.length;
const initialPrice = 1 / totalOutcomes;
const initialProbability = 100 / totalOutcomes;
for (const outcome of outcomes) {
await outcomeRepository.create({
...outcome,
currentPrice: initialPrice, // 0.20 for 5 outcomes
probability: initialProbability, // 20% for 5 outcomes
});
}
Buy A โ A price up Others unchanged Sum drifts from 100% Arbitrage corrects
Buy A โ A price up Others auto-decrease Sum stays at 100% Requires more logic
// Auto-rebalancing market maker bot
const MM_CONFIG = {
maxImbalance: 50, // Max YES/NO difference
spreadBps: 400, // 4% spread
rebalanceThreshold: 25 // Rebalance at 25 token imbalance
};
async function onTrade(market, side, amount) {
const position = getPosition(market);
const imbalance = Math.abs(position.yes - position.no);
if (imbalance > MM_CONFIG.rebalanceThreshold) {
await rebalance(market, position);
}
// Adjust spreads based on inventory
await adjustSpreads(market, position);
}
async function checkToxicInventory(market) {
const yesPrice = market.yesPrice;
// If YES is winning big, NO is toxic
if (yesPrice > 0.70) {
const noTokens = getPosition(market, 'NO');
if (noTokens > 0) {
// Sell at ANY price before it goes to zero
await placeSellOrder(market, 'NO', noTokens, yesPrice * 0.5);
}
}
}
For any LP feature (if built): โ ๏ธ RISK WARNING Providing liquidity involves significant risk. โข You may lose 30-80% of deposited funds โข Fee income may NOT cover losses โข Worst case: lose almost everything Only deposit what you can afford to lose.
| Decision | Recommendation | Rationale |
|---|---|---|
| Initial Probability | 100/n% or 50% | Neither prevents loss; trade-offs exist |
| Market Structure | Grouped Binary โ NegRisk | Already implemented; upgrade path available |
| Trading System | Keep Order Book | Already built; AMM requires rebuild |
| Initial Liquidity | Platform provides | No external LPs at start |
| Liquidity Amount | $1-5K per market | Balance between UX and risk |
| Expected Loss | ~20% of liquidity | Marketing/customer acquisition cost |
| Fee Structure | 2% fee | Industry standard |
| Public LP UI | No (like Polymarket) | Keep it simple; internal only |
Bottom Line: Accept initial liquidity losses as cost of bootstrapping. Focus on building volume. External LPs and MMs will come after platform proves itself.
Understanding how fixed-point arithmetic affects token calculations in prediction markets.
// Mathematical calculation
$10 / $0.55 = 18.181818181818181818... (infinite repeating)
// With 18 decimal precision (blockchain limit)
Stored: 18.181818181818181818 (truncated)
// Multiply back to verify
18.181818181818181818 ร $0.55 = $9.99999999999999999990
// The "dust"
$10.00000000000000000000
-$9.99999999999999999990
โโโโโโโโโโโโโโโโโโโโโโโโโ
$0.00000000000000000010 โ Lost to precision (~0.00000000000000001 cents)
In CTFExchange, the contract uses integer division which always rounds DOWN:
| Operation | Where Dust Stays |
|---|---|
| MINT (create new tokens) | CTFExchange's internal balances mapping |
| COMPLEMENTARY (match with seller) | Seller gets slightly less, or CTFExchange keeps remainder |
| MERGE (redeem tokens for collateral) | CTFExchange keeps the fractional collateral |
// CTFExchange contract - dust accumulates here
mapping(address => uint256) public balances;
When you buy 18.181818181818181818 shares for $10:
Perfect reversibility is impossible with finite decimals on repeating decimals like 1/0.55, 1/0.33, etc. This is inherent to all fixed-point arithmetic systems on blockchain.
Understanding the two primary market model architectures for prediction markets with multiple outcomes: Nostra's current Grouped Binary approach vs Polymarket's NegRisk Adapter solution.
| Feature | Grouped Binary (Nostra) | Grouped Binary + NegRisk (Polymarket) |
|---|---|---|
| Initial Probability | Flexible (50% or 100/N%) | Flexible (Market Decided) |
| Sum of YES Prices | Flexible (Unlinked) | ~100% (Arbitrage Enforced) |
| Market Linkage | Independent (No Connection) | Adapter Layer (NegRisk) |
| Stuck Inventory | Yes (Major Issue) | No (Convertible) |
| conditionId | Multiple (Per Outcome) | Multiple + negRiskMarketId |
| Position Conversion | Not Possible | Via Adapter |
| Liquidity Fragmentation | Severe (N Markets) | Unified |
| Implementation Complexity | Simple | 6-10 Weeks |
The Grouped Binary model is Nostra's current implementation. Each outcome in a multi-outcome question is treated as an independent binary market with its own YES/NO tokens.
Each
outcome = Independent binary market with separate conditionId
You hold 100 Spain YES tokens at $0.25 each. Spain gets eliminated. Now you want France YES instead:
Result: Double slippage, significant value loss. Your position is effectively "stuck."
Nostra's Grouped Binary model is simple, but serious liquidity issues arise in multi-outcome markets. Let's examine specific scenarios where problems occur.
Creating a "2026 World Cup Winner" market with 5 teams on Nostra:
Key Point: Each team is an independent market with no connection to others.
Alice invested expecting Spain to win:
// Alice's Initial Investment Purchased: Spain YES 1,000 tokens @ 20ยข Investment: $200 Expected Return: $1,000 if Spain wins (5x) // One week later: Spain eliminated in group stage! Spain YES price: 20ยข โ 3ยข (85% drop) Alice's position value: $200 โ $30
Alice now thinks France will win and wants to switch her position:
// Step 1: Sell Spain YES Sell: 1,000 tokens @ 3ยข Amount received: $30 Slippage: -10% โ Actual $27 // Step 2: Buy France YES France YES price: 25ยข Can purchase: $27 / 0.25 = 108 tokens Slippage: +8% โ Actual 100 tokens Result: 1,000 โ 100 tokens (90% loss)
// Direct Conversion via Adapter Spain YES โ France YES Mathematical conversion (no slippage) Value-based conversion: $30 value โ $30 value France @ 25ยข = 120 tokens Result: Value preserved, 20% more tokens
Bob wants to invest $10,000 in Brazil YES:
// Nostra Order Book State (Brazil YES) Total market liquidity: $50,000 (all 5 teams) Brazil market liquidity: $10,000 (1/5) Order book depth: 15ยข: 5,000 shares ($750) 16ยข: 8,000 shares ($1,280) 17ยข: 10,000 shares ($1,700) 18ยข: 15,000 shares ($2,700) 19ยข: 20,000 shares ($3,800) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Total depth: ~$10,230 // Bob executes $10,000 market order $750 @ 15ยข = 5,000 shares $1,280 @ 16ยข = 8,000 shares $1,700 @ 17ยข = 10,000 shares $2,700 @ 18ยข = 15,000 shares $3,570 @ 19ยข = 18,789 shares โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Total: 56,789 shares @ avg 17.6ยข Expected at 15ยข: 66,667 shares Slippage loss: ~15% (9,878 shares)
Because markets are independent, the sum of all YES prices may not equal 100%:
| Team | YES Price | Implied Probability | Issue |
|---|---|---|---|
| Spain | 25ยข | ~22% |
Sum: 112ยข 12% Overbook (Arbitrage Opportunity) |
| Brazil | 23ยข | ~20% | |
| France | 22ยข | ~19% | |
| Argentina | 21ยข | ~18% | |
| England | 21ยข | ~18% |
Nostra: Each market is independent โ No price adjustment mechanism โ Relies on manual arbitrage
Polymarket: NegRisk adapter โ NO token equivalence โ Automatic arbitrage maintains ~100%
// Charlie provides liquidity to Spain market Provided: YES side $5,000, NO side $5,000 (total $10,000) // Trading occurs: Users buy Spain YES heavily Result: Charlie sells Spain YES and receives USDC Position: Holding $8,000 Spain NO (skewed inventory) // After Spain is eliminated Spain NO = $1 (wins!) Charlie profit: +$8,000 // But what if Spain had won? Spain NO = $0 (loses) Charlie loss: -$8,000 Problem: Cannot convert inventory to another team! Even if Charlie wants to hedge with France NO: 1. Sell Spain NO (slippage) 2. Buy France NO (slippage) = Double slippage makes hedging too expensive
| Scenario | Nostra (Grouped Binary) | Polymarket (NegRisk) |
|---|---|---|
| Position Conversion Spain โ France |
Impossible Sell โ Buy (double slippage) |
Possible Mathematical conversion (0% slippage) |
| Large Orders $10K market order |
10-20% slippage Fragmented liquidity |
1-3% slippage Unified liquidity |
| Price Consistency ฮฃ YES = ? |
90-120% Relies on manual arbitrage |
~100% Automatic arbitrage |
| LP Hedging Inventory rebalancing |
High cost Requires double trade |
Low cost Direct conversion possible |
The key insight: Collateral is split directly into mutually exclusive YES tokens. This concentrates liquidity.
Sum(Price(YES)) = $1.00
Sum(Price(NO)) = $(N - 1).00
Instead of creating independent binary markets where YES + NO = $1 for each
outcome, NegRisk links them all together.
A single dollar of collateral is split across all outcomes.
Let's see how NegRisk solves the liquidity crisis that Grouped Binary cannot handle.
5 Countries: Spain, Brazil, France, Argentina, England Initial price: 20ยข YES / 80ยข NO each (20% probability) Market maker: $2,000 per country โ 2,000 (YES+NO) pairs each Total liquidity: $10,000
| Country | YES Left | NO Left | Status |
|---|---|---|---|
| Spain | 500 | 2,000 | 1,500 NO excess |
| Brazil | 1,600 | 2,000 | 400 NO excess |
| France | 1,800 | 2,000 | 200 NO excess |
| Argentina | 2,000 | 2,000 | Balanced |
| England | 2,000 | 2,000 | Balanced |
Trade 6: Buy $100 more Spain YES...
Only 500 Spain YES left!
Price jumps: 20ยข โ 30ยข โ 40ยข
$100 buys only 250 tokens (not 500)
1,500 Spain NO tokens sit useless ๐
Cannot move to other countries!
// Market Maker rebalances: Collect 200 NO from each of 5 countries = 200 complete "NO sets" (5 tokens each) = 200 ร $4 = $800 USDC Mint 400 new Spain YES+NO pairs โ Spain liquidity restored! โ
The Magic: In NegRisk, all NO tokens can be combined and converted back to USDC (because one of them MUST win). This "unlocks" the stuck inventory and allows market makers to continuously rebalance liquidity where it's needed most.
Result: Even with many outcomes, liquidity stays healthy because excess inventory can always flow to where demand is highest.
Polymarket's NegRisk implementation is open source and audited by ChainSecurity and OpenZeppelin.
GitHub: Polymarket/neg-risk-ctf-adapter โ
Contains: NegRiskAdapter, NegRiskOperator, NegRiskCtfExchange, WrappedCollateral, Vault, FeeModule
Core: USDC โ NO tokens โ YES tokens conversion
Spain YES/NO | France YES/NO | Brazil YES/NO | ...
All linked by negRiskMarketId
Base layer for all token operations
| Contract | Purpose | Nostra Has? |
|---|---|---|
| NegRiskAdapter | Core conversion logic (NO โ YES) | No |
| NegRiskOperator | Admin functions, oracle integration | No |
| WrappedCollateral | Wrapped USDC for atomic operations | No |
| ConditionalTokens (CTF) | Base token framework | Yes |
| MarketFactory | Create binary markets | Yes |
| CTFExchange | Order book trading | Yes |
| Component | Time | Complexity |
|---|---|---|
| Smart Contracts (1-3) | 2-3 weeks | High (security critical) |
| Backend API (4-5) | 3-5 days | Medium |
| Frontend UI (6) | 3-5 days | Medium |
| Testing & Audit | 2-4 weeks | Critical |
| Total | 6-10 weeks |
Before implementing NegRisk, consider if you really need it. Current arbitrage bots can provide economic position balancing with much simpler implementation.
When to implement NegRisk?
Nostra adopts a Multi-Oracle Strategy to handle various market types, ranging from crypto prices to niche local events. We benchmark against industry leaders like Opinion Labs (who use Hybrid AI + Optimistic Oracles) and Polymarket (UMA).
| Oracle Type | Mechanism | Pros | Cons | Best For |
|---|---|---|---|---|
| Manual Admin | Trusted admin key resolves outcome |
|
|
Beta launch, test markets, emergency resolution |
| Chainlink (Functions / Feeds) |
Decentralized nodes fetch API/Price data |
|
|
Sports, Crypto Prices, Election Results (with API) |
| UMA (Optimistic Oracle) |
"True until disputed" + Voter Jury |
|
|
Long-tail events, "Opinion" based markets (e.g., "Will AI take over?") |
Opinion Labs utilizes a sophisticated Hybrid Oracle approach:
Nostra will adopt this "Optimistic Verification" pattern for unstructured markets.
Goal: Validate market demand and UI/UX.
Method: Admin key resolves all markets. Fast, free, but centralized.
Goal: Automate high-volume markets.
Method: Introduce Chainlink for Sports & Crypto markets. Keep Admin for long-tail events.
Goal: Permissionless market creation.
Method: Integrate UMA (Optimistic Oracle). Admin key allows "veto" only (emergency brake).
Goal: Unstoppable protocol.
Method: Remove Admin key. Markets resolved only by Chainlink, UMA, or DAO governance.
For markets where verifiable on-chain data or APIs are unavailable (e.g., "Will this YouTuber reach 1M subscribers?" or highly subjective "Opinion" markets), we utilize UMA's Optimistic Oracle. This allows humans to resolve markets with crypto-economic guarantees.
To scale resolution for unstructured markets, we employ a Hybrid AI approach similar to Opinion Labs. This combines the speed of AI with the security of the Optimistic Oracle.
The OptimisticOracleV3 is the core contract governing assertion lifecycles. It utilizes an escalation game where truth is assumed unless disputed.
// Core OOv3 Interface interface IOptimisticOracleV3 { /** * @notice Asserts a truth about a world state. * @param claim Encoded data or string representing the statement (e.g. "BTC > 50k at block 100"). * @param asserter Address that receives the bond back if correct. * @param callbackRecipient Address to notify when settled. * @return assertionId Unique ID for this assertion. */ function assertTruthWithDefaults( bytes calldata claim, address asserter ) external returns (bytes32 assertionId); /** * @notice Settles the assertion. Can only be called after liveness period. * @return bool True if the assertion was resolved as TRUTHFUL. */ function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool); /** * @notice View function to check status. */ function getAssertionResult(bytes32 assertionId) external view returns (bool); }
assertTruthWithDefaults. This pulls the
Minimum Bond (usually WETH or USDC) from the asserter.
settleAndGetAssertionResult. If it returns true, Nostra pays out the
winners.
disputeAssertion.
This
moves the adjudication to UMA's Data Verification Mechanism (DVM).The UMA token is critical for the DVM (Data Verification Mechanism):
The optimistic model only works if someone is watching. Nostra must operate (or incentivize) Watchdog Bots that monitor all active assertions. If a malicious user asserts a false outcome, the bot must detect it and trigger a dispute to protect market participants.
Before integrating Chainlink oracles into Nostra, there are essential preparation steps to ensure smooth operation and cost management.
LINK is the native token of the Chainlink network. It's required to pay oracle nodes for their services (data retrieval, computation, consensus).
| Step | Action | Tool/Method |
|---|---|---|
| 1. Create Subscription | Register a new subscription ID | functions.chain.link UI |
| 2. Fund Subscription | Deposit LINK tokens (min 2 LINK recommended) | UI or addFunds() contract call |
| 3. Add Consumer | Whitelist your resolver contract address | addConsumer(subscriptionId, consumerAddress) |
| 4. Configure Limits | Set gas limits and callback gas | Contract constructor or setter functions |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol"; import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol"; contract NostraMarketResolver is FunctionsClient { using FunctionsRequest for FunctionsRequest.Request; // Subscription ID from Chainlink Functions UI uint64 private subscriptionId; // Gas limit for callback uint32 private constant GAS_LIMIT = 300000; // DON ID for the network (e.g., "fun-polygon-mainnet-1") bytes32 private donId; constructor( address router, uint64 _subscriptionId, bytes32 _donId ) FunctionsClient(router) { subscriptionId = _subscriptionId; donId = _donId; } // Request market resolution function requestResolution( bytes32 marketId, string calldata source, string[] calldata args ) external returns (bytes32 requestId) { FunctionsRequest.Request memory req; req.initializeRequestForInlineJavaScript(source); if (args.length > 0) { req.setArgs(args); } requestId = _sendRequest( req.encodeCBOR(), subscriptionId, GAS_LIMIT, donId ); } }
LINK costs vary by network and request complexity. Plan your budget accordingly:
| Service | Cost per Request | Notes |
|---|---|---|
| Price Feeds | Free (sponsored) | Common pairs already funded by protocols |
| Chainlink Functions | ~0.2-0.5 LINK | Depends on computation & callback gas |
| Chainlink Automation | ~0.1-0.3 LINK | Per upkeep execution |
| VRF (Randomness) | ~0.25 LINK | If random selection needed |
onlyOwner for admin functions| Product | Use Case | Best For | Cost |
|---|---|---|---|
| Data Feeds | Price data (BTC, ETH, stocks) | "Will BTC exceed $100K?" | FREE |
| Functions | Custom API calls | Sports scores, election results | ~0.25 LINK/request |
| Automation | Scheduled/conditional execution | Auto-resolve at deadline | ~0.1 LINK/execution |
| VRF | Verifiable randomness | Lottery-style markets | ~0.25 LINK/request |
What it does: Provides real-time, tamper-proof price data aggregated from multiple premium data providers.
How it works:
Best for:
// Simple usage - read price directly (, int256 price, , , ) = priceFeed.latestRoundData(); bool yesWins = price > threshold;
What it does: Allows your smart contract to fetch data from ANY web API (Web2) using JavaScript code.
How it works:
Best for:
What it does: Automatically executes your smart contract functions when specific conditions are met (e.g., time passed).
How it works:
Best for:
// Automation-compatible contract function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory) { upkeepNeeded = block.timestamp >= market.deadline && !market.resolved; } function performUpkeep(bytes calldata) external { resolveMarket(); }
What it does: Generates cryptographically secure, verifiable random numbers that cannot be manipulated by miners, nodes, or users.
How it works:
Best for:
// Request random number uint256 requestId = COORDINATOR.requestRandomWords(keyHash, subId, confirmations, gasLimit, numWords); // Callback with verified random function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override { winner = participants[randomWords[0] % participants.length]; }
What it does: A cost-saving pattern where results are proposed by an admin/proposer and only verified by Chainlink if disputed.
How it works:
Best for:
Pro: ~90% cost reduction if disputes are rare. Con: Delayed finality due to challenge period.
| Scenario | Recommended Product | Why |
|---|---|---|
| Price-based markets (crypto, forex) | Data Feeds + Automation | Free price data, auto-trigger at deadline |
| Sports, elections, custom events | Functions + Automation | Flexible API access, scheduled checks |
| High-volume with trusted admin | Optimistic + Functions | Cost savings, Chainlink as dispute resolver |
| Lottery/random selection markets | VRF | Provably fair, manipulation-proof |
| Long-tail/niche markets | Functions (custom JS) | Scrape any public data source |
Admin manually decides winner
Data-driven automatic resolution
// FunctionsClient.sol (Inherited by our market contract) import { FunctionsClient } from "@chainlink/.../FunctionsClient.sol"; import { FunctionsRequest } from "@chainlink/.../FunctionsRequest.sol"; contract NostraMarket is FunctionsClient { using FunctionsRequest for FunctionsRequest.Request; address router = 0x...; // Chainlink Router (Oracle Contract) bytes32 donId = 0x...; // DON ID uint64 subscriptionId = 1234; // My Subscription ID // This function calls the Chainlink Router contract function resolveMarket() external { // 1. Create Request (Include JS Logic) FunctionsRequest.Request memory req; req.initializeRequestForInlineJavaScript("const apiResponse = await Functions.makeHttpRequest(...)"); // 2. Send to Chainlink Router (Step 1 in Diagram) bytes32 requestId = _sendRequest( req.encodeCBOR(), subscriptionId, gasLimit, donId ); // 3. Store ID for verification pendingRequests[requestId] = true; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract PriceFeedResolver { AggregatorV3Interface internal priceFeed; IConditionalTokens public ctf; struct PriceMarket { bytes32 questionId; uint256 threshold; // Price threshold in 8 decimals bool resolveAbove; // true = YES wins if price > threshold uint256 deadline; bool resolved; } mapping(bytes32 => PriceMarket) public markets; function resolveMarket(bytes32 questionId) external { PriceMarket storage market = markets[questionId]; require(!market.resolved, "Already resolved"); require(block.timestamp >= market.deadline, "Too early"); // Get latest price from Chainlink (, int256 price, , , ) = priceFeed.latestRoundData(); // Determine winner bool yesWins = market.resolveAbove ? price > int256(market.threshold) : price < int256(market.threshold); // Report payouts to CTF uint256[] memory payouts = new uint256[](2); payouts[0] = yesWins ? 1 : 0; // YES outcome payouts[1] = yesWins ? 0 : 1; // NO outcome ctf.reportPayouts(questionId, payouts); market.resolved = true; } }
// This code runs in Chainlink Functions DON const apiEndpoint = args[0]; // e.g., "https://api.sportsdata.io/..." const jsonPath = args[1]; // e.g., "Games[0].Winner" const response = await Functions.makeHttpRequest({ url: apiEndpoint, headers: { "Ocp-Apim-Subscription-Key": secrets.apiKey } }); if (response.error) { throw Error("API request failed"); } // Navigate JSON path and return result const homeTeamWins = result === "HomeTeam" ? 1 : 0; return Functions.encodeUint256(homeTeamWins);
LINK: 0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06 BTC/USD: 0x5741306c21795FdCBb9b265Ea0255F499DFe515C ETH/USD: 0x143db3CEEfbdfe5631aDD3E50f7614B6ba708BA7 BNB/USD: 0x2514895c72f50D8bd4B4F9b1110F0D6bD2c97526
Functions Router: 0xC22a79eBA640940ABB6dF0f7982cc119578E11De BTC/USD: 0xe7656e23fE8077D438aEfbec2fAbDf2D8e070C4f ETH/USD: 0xF0d50568e3A7e8259E16663972b11910F89BD8e7
| Component | Cost per Use | Monthly (100 markets) |
|---|---|---|
| Data Feeds (read) | FREE | $0 |
| Functions Request | ~0.25 LINK | 25 LINK (~$250) |
| Automation Check | ~0.01 LINK | 30 LINK (~$300) |
| Automation Execute | ~0.1 LINK | 10 LINK (~$100) |
| Total | ~65 LINK (~$650) |
Prediction markets require frequent interaction (buying, selling, claiming). Gas must be < $0.01.
Deployment on a chain with existing USDC liquidity reduces friction for user onboarding.
Essential for supporting existing tooling, wallets (MetaMask), and Chainlink services.
Trading requires instant feedback. Sub-2s block time (like Polygon/BSC/Monad) is preferred for UX.
We should NOT deploy market contracts on multiple chains simultaneously. Prediction markets rely heavily on liquidity depth.
For optimal UX, our server will likely batch process user actions (e.g., matching 50 orders in one transaction) to provide instant confirmation and gasless experience for users. This makes gas fees a direct operating cost for the platform.
| Scenario | Cost on Polygon/BSC (~$0.01) | Cost on Arbitrum/Op (~$0.15) | Impact |
|---|---|---|---|
| 1 Single Trade | $0.01 | $0.15 | Manageable |
| Daily Batch (10,000 txs) | $100 / day | $1,500 / day | Critical Difference |
| Monthly Cost | $3,000 | $45,000 | Business Viability Risk |
Conclusion: If we subsidize gas or run batch processors, L2s like Arbitrum are currently too expensive at scale. Polygon or BSC is mandatory for economic viability.
| Category | Network | Gas Fee | Avg. Block Time | Finality | Pros | Cons | Verdict |
|---|---|---|---|---|---|---|---|
| L1 / Sidechain | BNB Smart Chain (BSC) | 0.05 Gwei (~$0.01) | 450ms (0.45s) | Probabilistic (15 block ร 0.45s โ 7s) |
|
More centralized validator set | Top Choice |
| Polygon PoS | ~ $0.001 | ~ 2.1s | Probabilistic (~128 block ร 2.1s โ 5m) |
|
Reorg risks (rare now) | Strong Alternative | |
| Monad | < $0.001 | 1.0s | Instant (1 block ร 1s = 1s) |
|
|
Future Contender | |
| Monero (XMR) | Low | ~ 2 mins | Slow (10 block ร 2m โ 20m) |
|
|
Incompatible | |
| Zcash (ZEC) | Low | ~ 75s | Slow (24 block ร 75s โ 30m) |
|
|
Incompatible | |
| Ethereum (L1) | $2.00 - $50.00+ | ~ 12s | Safe: ~12m (2 Epochs) |
|
|
Settlement Only | |
| Solana | ~ $0.00025 | ~ 400ms | Fast (~12s finalized) |
|
|
Incompatible | |
| Layer 2 | Base | < $0.01 | Instant (~2s) | Instant (Soft) Hard: ~12m (Eth L1) |
|
Newer ecosystem | Alternative |
| Arbitrum One | ~ $0.10 | Instant (~0.25s) | Instant (Soft) Hard: ~12m (Eth L1) |
|
Fees slightly higher than Polygon/BSC | Alternative | |
| zkSync Era | ~ $0.05 | Instant (~1s) | Instant (Soft) Hard: ~20m (Eth L1) |
|
Ecosystem fragmentation | Good Alternative | |
| Lighter | Low | Instant | Instant |
|
|
Incompatible |
While Polygon remains a viable backup due to its low fees, BSC's combination of raw performance and strategic ecosystem alignment makes it the optimal launchpad for Nostra.
Because both Polygon and BSC are fully EVM (Ethereum Virtual Machine) Compatible, there is NO risk of code divergence.
Users have funds on Ethereum Mainnet, Optimism, Arbitrum, etc. We don't want to force them to manually bridge to our specific chain using complex 3rd party bridges.
CCIP allows us to accept deposits from any supported chain and route them to our specific market implementation.
Nostra abstracts the complexity of blockchain interaction from market creators. When a user clicks "Start Batch Creation", the server handles all on-chain operations asynchronously.
| Step | Action | Status Message |
|---|---|---|
| 1. Init | Processor picks up job | "Job started processing" |
| 2. createBinaryMarket | Calls Smart Contract for each outcome | "Creating market X/Y: [Name]" |
| 3. Persistence | Saves Group/Markets to PostgreSQL | "Saving to database..." |
| 4. Liquidity | Mints collateral & calls `splitPosition` | "Minting collateral for market inventory..." |
| 5. Seeding | Places initial limit orders | "Seeding initial liquidity..." |
To ensure markets are tradable immediately, the Server Wallet acts as the initial liquidity provider. This involves a specific sequence of token transfers:
splitPosition() on CTFUsers often ask why claiming winnings requires two wallet confirmations. This is due to the architectural separation between the Conditional Tokens Framework (CTF) and the Nostra Exchange Contract.
contract.redeemPositions(...)
exchange.deposit(...)
A common question is: "Why can't the Redemption step automatically send funds to the Exchange?"
We prioritize continuous trading. By auto-depositing, we ensure users don't have to navigate to a separate "Deposit" page to re-use their winnings. The second confirmation ensures funds are ready for the next bet immediately.