Arcaid
A Zero-loss Prediction Market Platform for Humanitarian Aid.
What it is
Introduction
Imagine a prediction market where you can never lose your investment, yet still earn rewards for being right - and where every trade automatically funds disaster relief NGOs. Arcaid is exactly that: a zero-loss prediction market powered by DeFi yield, where the entire lifecycle is automated by Chainlink CRE (Chainlink Runtime Environment).
The Core Innovation
Arcaid is a zero-loss prediction market for humanitarian aid where participants never lose their principal - funds earn yield on Aave V3 while markets run (typically 14-30 days), and that yield powers everything. Here's what makes it unique:
You Never Lose Your Principal: Buy YES or NO tokens with USDC. Win or lose, you get your full investment back. Your funds earn yield on Aave V3 while the market runs - that yield powers rewards and NGO funding.
NGOs Get Funded Automatically: 60% of all generated yield goes directly to humanitarian organizations responding to disasters. They receive funding regardless of market outcomes, enabling faster response when crises strike.
Winners Still Win: 30% of yield is distributed to participants who predicted correctly, proportional to their stake. You risk nothing, but accurate predictions are rewarded.
AI + CRE Do the Heavy Lifting: Chainlink CRE workflows run the full automation: disaster discovery (with AI + web search), NGO matching, market question generation, on-chain market creation, outcome verification, and resolution - all on Base Sepolia.
Dynamic Market Mechanics: The system implements a sophisticated hybrid automated market maker (AMM) that combines the fairness of constant-sum pricing with the efficiency of token-weighted distribution. When you participate:
- You pay a dynamic price based on current market sentiment (starting at 0.50 USDC per token)
- The price moves based on supply and demand - the more people buy YES tokens, the more expensive they become
- Your tokens represent both your prediction and your stake in the outcome
- All activity is on Base Sepolia for low fees and fast finality
Settlement: Winners receive principal + rewards; losers receive full principal refund. NGOs receive their allocation. All contracts and resolution logic live on Base Sepolia, driven by CRE workflows.
The entire system runs on Base Sepolia, but what makes Arcaid truly autonomous and revolutionary is Chainlink CRE.
Why CRE is Essential
CRE enables Arcaid to operate as a fully autonomous system that requires minimal human intervention. Without CRE, every market would need manual disaster discovery, NGO matching, question generation, on-chain deployment, and outcome verification - a process that would be slow, expensive, and error-prone. CRE workflows run on the Chainlink DON (Decentralized Oracle Network), providing:
- Autonomous Execution: Workflows run automatically when triggered, executing complex multi-step processes without human oversight
- AI Integration: Seamless integration with Claude AI for intelligent decision-making (disaster discovery, question generation, outcome verification)
- On-Chain Automation: Direct interaction with Base Sepolia smart contracts via the CRE Forwarder, eliminating the need for separate backend infrastructure
- Reliability: Running on the Chainlink DON ensures high availability and fault tolerance
- Cost Efficiency: No need to maintain dedicated servers or infrastructure for automation logic
How CRE Powers Arcaid
Arcaid uses two HTTP-triggered CRE workflows that handle the complete market lifecycle:
Workflow 1: Market Creation (Autonomous Disaster Detection → On-Chain Market)
The market-creation workflow transforms disaster detection into a live prediction market in a single automated flow:
- AI-Powered Disaster Discovery: CRE calls Claude AI with web search capabilities to find recent natural disasters, extracting title, description, category, location, and date - all without manual research
- Intelligent NGO Matching: Queries Supabase for NGOs operating in the disaster region, automatically selecting eligible organizations
- Question Generation: AI generates a specific, measurable market question: "Will [NGO] [action] [metric] in [location] within [timeframe]?"
- On-Chain Deployment: CRE encodes and executes
MarketFactory.createMarket()on Base Sepolia via the CRE Forwarder, deploying the Market contract and YES/NO OutcomeTokens with initial 0.50 USDC pricing - Database Sync: Records the new market in Supabase with state OPEN
Why This Matters: A disaster can be detected, matched to an NGO, and have a live prediction market deployed on-chain within minutes - enabling rapid response funding that would be impossible with manual processes.
Workflow 2: Market Verification (AI Outcome Verification → Full Resolution)
The verify-market workflow autonomously verifies real-world outcomes and executes complete on-chain resolution:
- AI Verification: Receives
marketIdandquestion, then uses Claude AI with web search to determine the outcome (YES/NO/INVALID), confidence level, and supporting evidence - Sequential On-Chain Resolution: CRE executes a precise sequence on Base Sepolia:
MarketFactory.forceCloseMarket(marketId)— Closes the market to new participationOutcomeOracle.submitOutcome(marketId, outcome, confidence, evidence)— Submits AI-verified outcome with evidenceOutcomeOracle.finalizeOutcome(marketId)— Finalizes the outcome (making it immutable)MarketFactory.resolveMarket(marketId)— Resolves the market, enabling payouts
- Database Update: Updates Supabase with outcome, resolution timestamp, and state PAID_OUT
Why This Matters: The entire resolution process - from verifying real-world events to finalizing immutable outcomes on-chain - happens autonomously. The AI provides transparency through on-chain evidence storage, and the multi-step resolution ensures proper market closure before payouts.
CRE Architecture Benefits
HTTP-Triggered Workflows: Both workflows are triggered via simple HTTP requests, making them easy to integrate with frontends, backends, or scheduled jobs. The workflows handle all complexity internally.
CRE Forwarder Integration: All on-chain calls go through the CRE Forwarder, which provides:
- Secure transaction execution on the Chainlink DON
- Automatic gas management
- Transaction reliability and retry logic
- No need to manage private keys in application code
Multi-Service Orchestration: CRE workflows seamlessly orchestrate:
- AI Services: Claude API calls with web search for intelligent decision-making
- Database Operations: Supabase queries and updates for NGO matching and market tracking
- Blockchain Interactions: Smart contract calls on Base Sepolia for market lifecycle management
TypeScript/WASM Execution: Workflows are written in TypeScript and compiled to WASM, running efficiently on the Chainlink DON with access to standard libraries and tooling.
The Complete Autonomous Flow
- Market Creation: Frontend or scheduled job triggers market-creation workflow → CRE discovers disaster, matches NGO, generates question, deploys market on Base Sepolia → Market appears in app
- User Participation: Users buy YES/NO tokens on Base Sepolia (funds earn yield on Aave V3)
- Market Resolution: Admin triggers verify-market workflow → CRE verifies outcome with AI, executes full on-chain resolution sequence → Market resolves, payouts can begin
- Settlement: PayoutExecutor calculates distributions (60% NGO, 30% winners, 10% protocol) and executes via Circle Gateway
Result
A fully autonomous prediction market system where disaster detection, market creation, outcome verification, and resolution all happen automatically - enabling rapid humanitarian funding that scales without proportional operational overhead.
How it Works
Contracts in the full flow (Base Sepolia)
All contracts below are deployed on Base Sepolia. The CRE workflows interact directly only with MarketFactory and OutcomeOracle; the rest form the full Arcaid system (participation, yield, payouts). Addresses and roles are taken from the cre README.
ProtocolRegistry (0x25f442fd07fc3eaC3a27F3E6AcaaBa0f15F3dbaD). Central configuration hub. It holds references to every other system contract (NGORegistry, PolicyEngine, OutcomeOracle, TreasuryVault, BridgeManager, MarketFactory, PayoutExecutor, USDC token). Other contracts query it by name (e.g. getContract("NGORegistry")) instead of hardcoding addresses, so upgrades are done by deploying a new implementation and updating the registry. Only the admin can register or update contract addresses.
NGORegistry (0xfdB164b1608b262D603BA90AAF8FAAf2776De913). Registry of NGOs eligible for payouts. Each NGO has an on-chain identity (a bytes32 id), name, wallet address, Circle Wallet ID (for off-chain payouts), and preferred chain id. Registration is two-step: registerNGO creates the record, verifyNGO marks them eligible after due diligence. The market-creation workflow does not call NGORegistry on-chain; it reads NGOs from Supabase and uses the stored arc_ngo_id (which must match an on-chain bytes32 id) when calling MarketFactory.createMarket with eligibleNGOs. PayoutExecutor uses NGORegistry to get NGO wallet or Circle details when executing payouts.
PolicyEngine (0x7117277Df09A3aA7ab3348E1251ce1e4CEfae6De). Defines payout policies in basis points (e.g. 6000/3000/1000 for 60% NGO, 30% winners, 10% protocol). A policy id (e.g. keccak256("DEFAULT")) is fixed when a market is created; PolicyEngine.validatePayout is a view function so the split cannot be changed after creation. PayoutExecutor queries PolicyEngine to determine how to distribute yield. Policies must sum to 10000 basis points.
OutcomeOracle (0x7028668D935346637224d4AfA31964dF3C0eac76). Receives AI-verified outcomes and is the on-chain source of truth for resolution. The verify-market workflow calls it twice: submitOutcome(marketId, outcome, confidence, evidence) stores the outcome and evidence (outcome 1 = YES, 2 = NO, 3 = INVALID; confidence in basis points; evidence as a string). Submissions below a minimum confidence are rejected. finalizeOutcome(marketId) makes the outcome immutable; after that, MarketFactory.resolveMarket can run and payouts can proceed. Only the CRE forwarder (or designated submitter) can submit; typically only admin can finalize. The Market contract reads the finalized outcome from OutcomeOracle when resolving.
TreasuryVault (0x632d3257b93B633bd3b890c330B3E3be1Ad94a51). Tracks all deposits and yield per market. It holds market-level state (totalPrincipal, totalYield, deployedToEthereum, isActive) and user-level state (userPrincipal per market and address). MarketFactory records each user deposit into TreasuryVault when users participate; BridgeManager or yield logic records deployments and yield. PayoutExecutor reads totalYield and getUserPrincipal from TreasuryVault to compute payouts. It is the single source of truth for financial state; every deposit and yield event is logged for audit.
BridgeManager (0x2B07922dd4A8BEC47C323932D39d18E6b3A70941). Coordinates cross-chain operations when principal or yield is bridged (e.g. to Ethereum or another chain). It records which market, amount, source and destination chains, and attestation or completion status. Used when the system deploys funds off Base Sepolia for yield or brings them back.
MarketFactory (0x083A25345eb0189Fb3E2398d89FFf2CC0dD1B9d7). The only contract the market-creation workflow calls on-chain, and one of two (with OutcomeOracle) that the verify-market workflow calls. It creates new prediction markets and manages participation. createMarket(question, disasterType, location, durationInDays, policyId, eligibleNGOs) deploys a new Market contract and YES/NO OutcomeTokens, stores the market id and address in a mapping, and uses the policy id to bind the market to a PolicyEngine policy. The CRE forwarder must be set (setCreForwarder) so that the factory accepts reports from the DON; the workflow sends the encoded createMarket calldata via report and writeReport to the factory address. MarketFactory also exposes participate (user approves USDC then calls; transferFrom user to factory, then recordDeposit on TreasuryVault and recordParticipation on the Market) and participateWithPreTransferredUSDC (for Circle-funded flows: backend calls after USDC has been sent to the factory; same recording steps). After resolution, forceCloseMarket(marketId) stops new participation, and resolveMarket(marketId) finalizes the market using the outcome from OutcomeOracle and enables payout logic. The factory holds USDC and forwards deposit data to TreasuryVault and to the per-market Market contract.
Market (deployed per market; address returned by getMarket(marketId)). Each prediction market is one Market contract. It implements the hybrid AMM: YES and NO prices sum to 1 USDC; prices are derived from virtual yes/no shares (e.g. 50,000 each at launch, so 0.50 USDC per token). Users buy YES or NO by calling into the factory (which calls recordParticipation on the Market); the Market mints YES or NO OutcomeTokens and updates virtual shares. getYesPrice() and getNoPrice() expose current prices. At resolution, the Market reads the finalized outcome from OutcomeOracle and settles so that winners can claim principal plus share of yield and losers get principal back.
OutcomeToken (YES and NO ERC20s per market). Minted by the Market contract when users participate. Six decimals, no burn; only the Market can mint. They represent a user’s position; at resolution, the Market uses token balances and TreasuryVault principal to compute payouts.
PayoutExecutor (0xeEe706A6Ef4a1f24827a58fB7bE6a07c6F219d1A). Runs after resolution. It calculates payouts using TreasuryVault (totalYield, userPrincipal), PolicyEngine (policy percentages), and NGORegistry (NGO payout details). It uses a hybrid formula: a geometric mean of token share and capital share to distribute the winner reward pool (e.g. 30% of yield). Losers get principal back; NGOs get their percentage (e.g. 60%); protocol gets the remainder. Payouts are executed via Circle Gateway (winners, losers, NGO) and optionally CCTP for cross-chain NGO payouts. The executor marks payouts as executed so they are not repeated.
YieldController (0x5D8F1E3a405f44DA334A87b8ecFf0Eb8408bE2a5). Manages deployment and withdrawal of funds to/from Aave V3 (or another yield source). Admin or backend calls deployToAave / withdrawFromAave; yield is then recorded in TreasuryVault so PayoutExecutor can distribute it.
External tokens and contracts Circle USDC (0x036CbD53842c5426634e7929541eC2318f3dCF7e) is the main deposit and payout token. Aave USDC (0xba50Cd2A20f6DA35D788639E581bca8d0B5d4D5f) is used when principal is deployed to Aave for yield. The Liquidity Pool scripts use Uniswap V3 (factory, NonfungiblePositionManager) and a Swap Router (0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4) to swap between Circle and Aave USDC when needed.
Flow summary: Market creation: CRE sends createMarket to MarketFactory (via forwarder); factory deploys Market and YES/NO tokens and ties the market to a policy and eligible NGO ids. Participation: users send USDC to MarketFactory and participate (or use Circle and participateWithPreTransferredUSDC); factory updates TreasuryVault and Market; Market mints OutcomeTokens. Yield: funds can be sent to Aave via YieldController; BridgeManager tracks cross-chain moves; yield is recorded in TreasuryVault. Resolution: CRE calls MarketFactory.forceCloseMarket, then OutcomeOracle.submitOutcome and finalizeOutcome, then MarketFactory.resolveMarket; Market reads outcome from OutcomeOracle and settles. Payouts: PayoutExecutor uses TreasuryVault, PolicyEngine, and NGORegistry to compute and execute winner, loser, NGO, and protocol payouts via Circle and optionally CCTP.
Market-creation workflow (market-creation/)
Purpose: Create a single prediction market on Base Sepolia by discovering a disaster (AI), selecting an NGO (Supabase), generating a question (AI), calling MarketFactory.createMarket via CRE, and inserting a row into Supabase markets.
Trigger: HTTP. The body is optional JSON; no fields are required. The workflow ignores the body for logic.
Steps in order:
Decode the HTTP payload (if any) and log it.
Load secrets: ANTHROPIC_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY.
Disaster discovery: Call Anthropic Messages API (Claude, model claude-sonnet-4-5) with the web_search tool (one use) to find exactly one recent natural disaster anywhere in the world in the last 21 days. The prompt asks for a single JSON object with title, description, category, location, date. If the response cannot be parsed as JSON, the workflow uses a minimal fallback object (Unknown disaster, category other, Unknown location) to avoid a second HTTP call and stay within CRE HTTP limits.
NGO fetch: GET the Supabase REST API at /rest/v1/ngos with select=arc_ngo_id,name,location. Filter rows that have arc_ngo_id and name. The result is a list of NGOs.
NGO selection: Normalize the disaster location string to a set of words (lowercase, split on commas and spaces, remove common stopwords). For each NGO, normalize its location the same way. If any word in the disaster set appears in the NGO set, the NGO is a match. If there are matches, pick one at random; if none, pick a random NGO from the full list. If there are no NGOs at all, the workflow throws.
Question generation: Call Claude again (same model, optional web search) with the disaster and selected NGO. The prompt asks for a prediction market question about cash assistance and/or dignity kits, measurable, with a random 7–30 day timeframe. The response is parsed for ngo_summary and question. Duration in days is parsed from the question text with a regex for "within N days"; if missing or invalid, default is 14.
On-chain create: Resolve the network from config (chainSelectorName ethereum-testnet-sepolia-base-1). Create an EVMClient for that chain. Read the current list of market IDs from MarketFactory via getAllMarketIds. Build policyId as keccak256 of the UTF-8 bytes of the string "DEFAULT". Encode createMarket(question, disasterType, location, durationDays, policyId, [ngoIdHex32]) with viem (ngoIdHex32 is the selected NGO's arc_ngo_id as 0x-prefixed 64-char hex). Call runtime.report with this calldata (base64-encoded), encoder evm, signing ecdsa, hashing keccak256. Call evmClient.writeReport with the MarketFactory address as receiver and the gas limit from config. If the returned tx hash is missing or all zeros (simulation without broadcast), return immediately with empty arcMarketId and arcMarketAddress and skip the following steps. Otherwise sleep 4 seconds (busy-wait), then poll getAllMarketIds up to 10 times until the count increases. The new market id is the last id in the list. Call getMarket(newId) to get the market contract address. Return txHash, arcMarketId (hex without 0x), and arcMarketAddress.
Supabase insert: POST to /rest/v1/markets with a JSON body containing question, category, location, duration_days (from question or 14), policy_id "DEFAULT", arc_market_id, arc_market_address, eligible_ngo_ids as a one-element array of the NGO id string, and state "OPEN". Use Prefer: return=minimal. Log success or failure; the workflow does not throw on POST failure.
Response: Return the string "Dynamic market created with tx " followed by the tx hash.
Config (config.json): One EVM entry with chainSelectorName, marketFactoryAddress (20-byte hex), and gasLimit (numeric string). workflow.yaml points to ./main.ts, ./config.json, and ../secrets.yaml for both local-simulation and staging.
Verify-market workflow (verify-market/)
Purpose: Resolve an existing prediction market by having AI verify the claim, then executing force close, submit outcome, finalize outcome, and resolve market on Base Sepolia, and updating the Supabase markets row.
Trigger: HTTP with required JSON body. The body must contain marketId (32-byte hex string, 64 characters with or without 0x prefix) and question (the claim text). If the payload is missing or empty, the workflow throws.
Steps in order:
Decode the payload and extract marketId and question. Load secrets: ANTHROPIC_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY.
AI verification: Call Claude (claude-sonnet-4-5) with the web_search tool (max_uses 2) and a system prompt that instructs the model to act as a fact-checker and return outcome (1 = YES, 2 = NO, 3 = INVALID), confidence (0–10000), and evidence_string. The user prompt contains the claim to verify. The response is parsed; outcome is coerced to 1, 2, or 3 (default 3); confidence is clamped to 0–10000; evidence_string defaults to "No evidence found" if missing.
Parse marketId to bytes32: strip optional 0x, ensure length 64 hex characters, then use as 0x-prefixed string for encoding.
On-chain sequence (four transactions). Each step is implemented by a shared sendReportCall helper that: encodes the calldata, calls runtime.report (evm encoder, ecdsa, keccak256), calls evmClient.writeReport to the given receiver with the report and gas limit from config, logs the tx hash, then polls getTransactionReceipt up to 12 times until a receipt is available (to avoid hitting in-flight transaction limits). After steps 1–3 the workflow sleeps 4 seconds before the next step.
Step 1: Encode MarketFactory.forceCloseMarket(marketId). Receiver: MarketFactory address from config. Step 2: Encode OutcomeOracle.submitOutcome(marketId, outcome, confidence, evidence_string). Receiver: OutcomeOracle address from config. Step 3: Encode OutcomeOracle.finalizeOutcome(marketId). Receiver: OutcomeOracle. Step 4: Encode MarketFactory.resolveMarket(marketId). Receiver: MarketFactory. No sleep after step 4.
Supabase update: PATCH /rest/v1/markets with query parameter arc_market_id=eq.
. Body: outcome (string "YES", "NO", or "INVALID" derived from the numeric outcome), resolved_at (current ISO timestamp), state "RESOLVED". Prefer: return=minimal. Log success or failure; the workflow does not throw on PATCH failure. Response: Return an object with marketId, outcome, confidence, evidence_string, market_resolved true, and resolution_tx_hash (the tx hash of the fourth step, resolveMarket).
Config (config.json): One EVM entry with chainSelectorName, marketFactoryAddress, outcomeOracleAddress (20-byte hex), and gasLimit. workflow.yaml points to ./main.ts, ./config.json, and ../secrets.yaml. types.ts defines VerifyRequest (marketId, question) and VerifyResponse (the fields above).
Express dev server (server.js)
The server runs in the cre directory (same folder as project.yaml and secrets.yaml). It uses dotenv to load .env from __dirname so that CRE_ETH_PRIVATE_KEY, ANTHROPIC_API_KEY_VAR, SUPABASE_URL_VAR, SUPABASE_ANON_KEY_VAR, and optionally NGROK_AUTH_TOKEN and PORT are available.
It exposes two POST endpoints:
/api/create-market-sim: Accepts any JSON body (or empty). Forwards the body to the CRE CLI by spawning a child process with: cre workflow simulate market-creation --non-interactive --trigger-index 0 --http-payload
--target local-simulation --broadcast. The working directory of the child is the cre directory (__dirname). Stdout and stderr are streamed to the server console and buffered. On exit, the server responds with a JSON object containing stdout, stderr, and an error field (null on success, or a message on failure or non-zero exit). Status is 200 on success, 500 on failure or non-zero exit./api/verify-market-sim: Same pattern but for the verify-market workflow. The body should be an object with marketId and question so the workflow can verify and resolve. The same spawn, buffer, and response shape apply.
The server listens on PORT from the environment or 4000. On startup, if NGROK_AUTH_TOKEN is set, it uses the @ngrok/ngrok package to open a tunnel to the listening port and prints the ngrok URL. The frontend can then be configured to use this URL as the "resolution service" so that when the user triggers market creation or resolution from the app, the Next.js API routes call this Express server, which in turn runs the CRE workflows locally with broadcast.
Liquidity Pool (Liquidity Pool/)
A separate Node.js script suite for Uniswap V3 on Base Sepolia. It does not use the CRE SDK. It has its own package.json (ethers v5, dotenv), .env.example (BASE_SEPOLIA_RPC, PRIVATE_KEY), and two main scripts.
createPool.js: Connects to Base Sepolia via an RPC URL from .env. Uses the Uniswap V3 factory and NonfungiblePositionManager addresses on Base Sepolia. Token addresses are hardcoded for Circle USDC and Aave USDC (with a comment that the user may need to replace them). It checks whether a pool already exists for the token pair and fee tier 500 (0.05%). If not, it calls createAndInitializePoolIfNecessary with a 1:1 sqrtPriceX96 so the pool starts at a 1:1 price. It logs the pool address and next steps (add liquidity, swap).
addLiquidity.js: Uses the same RPC and PRIVATE_KEY. It reads the wallet's balances of Circle USDC and Aave USDC, computes 80% of each, and mints a Uniswap V3 position with full-range ticks (tickLower -887270, tickUpper 887270, which are valid for fee tier 500 and tick spacing 10). It approves the NonfungiblePositionManager for both tokens if needed, then calls mint with the position params. It logs gas used, block number, and the position NFT id parsed from Transfer events. Error handling includes hints for common revert reasons (tick spacing TLU/TUM, slippage SPL).
Links
Created by
- Sam Felix
- Marshal AM