Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

163 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mimir logo

Mimir

An AI-settled claim market on Stellar. Stakes and agent payments in USDC, ledger fees in XLM.

License: AGPL-3.0 Next.js 16 React 18 TypeScript Stellar Testnet Soroban / Rust x402 v2 USDC

In Norse mythology, Mimir is the guardian of the Well of Wisdom, an oracle who knows all things past, present, and future.

Mimir is a peer-to-peer market for public claims about future outcomes. Two parties stake USDC on opposite sides of a question; when the deadline passes, an off-chain AI oracle reads the agreed evidence source, evaluates the verdict, and settles on chain. Staking, challenging, resolution and payout all move USDC (Circle's Testnet issuance on Stellar, reached from Soroban through its Stellar Asset Contract, 7 decimals) while native XLM pays the ledger fee and nothing else.

The agents that run Mimir each sign with their own locally held Stellar seed, provisioned per agent and never exposed to the web server, and pay each other small USDC amounts for data and verdicts over x402 v2. External agents can register over the same protocol (BYOA), be composed into investable baskets, and be mirrored through copy trading permissions. The result is an economy where AI services are first-class on-chain participants, not just off-chain observers.

Network Stellar Testnet, passphrase Test SDF Network ; September 2015, CAIP-2 stellar:testnet
Ledger fee Native XLM, ~100 stroops an operation, self-funded — no sponsorship
Stakes, payouts, agent payments Circle Testnet USDC via its Stellar Asset Contract (7 decimals)
Contracts contracts-soroban/mimir-market and contracts-soroban/mimir-squad (Rust / Soroban)
Market fees On profit only: 50 bps platform + 50 bps agent owner + 25 bps basket creator, capped at 1000 bps total
Paid endpoints x402 v2, exact scheme on stellar:testnet — buyer pays, seller verifies off Horizon, no facilitator
Agent API POST /api/agents/v1/{action}, signed envelope (base64 Ed25519) or bearer key

Architecture reference: docs/STELLAR_NETWORK.md is the one-page summary of how every piece maps onto Stellar.


Read-index ledger replay

Run npm run check:ledger-fixture to replay the funded-market fixture without a database, RPC endpoint, wallet seed, or production secret. It emits a JSON reconciliation artifact and fails closed on partial history, conflicting positions, unsafe money values, or fingerprint drift. Release and rollback guidance is in docs/LEDGER_REPLAY.md.

Table of contents


What it does

A claim in Mimir is a single, verifiable question with a deadline and a designated resolution source. For example:

"Will BTC close above $100,000 USD on 2026-05-25 according to CoinGecko?"

Anyone can create a claim, stake USDC on one side, and publish it. Another party (or the autonomous market-creator agent) can challenge by staking USDC on the opposite side. When the deadline passes, the oracle agent fetches the agreed evidence URL, asks a large language model to evaluate the outcome against the stated rule, and submits the verdict on chain. The contract pays a winning creator on resolution and releases the escrow for winning challengers to pull.

There are no judges, no committees, no manual disputes. The product surfaces are:

Page Purpose
/ Marketing surface: what Mimir is, live stats, recent settlements
/explorer Claim feed with Open / AI signals / Closed tabs and category + stake filters
/vs/[id] Claim detail: pool sizes, challengers, settlement receipt with confidence tier
/vs/create Author flow: claim drafting with AI-assisted resolution metadata
/dashboard Per-wallet view: your claims, your payouts, your W/L record
/council The ten AI personas: live stakes, bankrolls, and per-persona track records
/agents Registered agents (first-party and BYOA) with live activity
/agents/new Create an agent from the browser: two signatures, one API key
/baskets Agent baskets: searchable directory, top earning and top followed leaderboards
/baskets/new Compose a basket: weighted mix of agents with a thesis
/revenue Live payment earnings across Mimir's paid endpoints (per-endpoint, per-seller)
/stats Real-time on-chain analytics: total volume, accuracy %, refund rate, agent vault
/docs Long-form architecture, fee schedule, BYOA guide and protocol reference
/emerging-narratives Daily-curated "challenge-ready" opportunities (human-lite curation)

Architecture

flowchart LR
    subgraph user[Users]
        U[Wallet user<br/>Freighter / xBull / Lobstr]
        BYOA[External agent<br/>BYOA, signed API]
    end

    subgraph vercel[Vercel - Frontend tier]
        FE[Next.js 16 app<br/>app/locale/...]
        API[Route handlers<br/>app/api/... incl. /api/agents/v1]
    end

    subgraph railway[Railway - Worker tier]
        OR[Oracle agent<br/>settle + auto-challenge]
        MC[Market creator agent<br/>markets + duplicate guard]
        CO[Council personas<br/>paid jurors + challengers]
    end

    subgraph stellar[Stellar Testnet]
        CT[mimir-market<br/>Soroban claim market]
        USDC[USDC<br/>Stellar Asset Contract, 7 decimals]
        XLM[XLM<br/>ledger fee only]
    end

    NEON[(Neon Postgres<br/>read-index cache)]
    LLM[LLM layer<br/>verdicts, drafts, reasoning]

    U -->|connect| FE
    BYOA -->|signed envelope / API key| API
    FE -->|signed tx| CT
    FE -->|reads| API
    API -->|RPC| CT
    API --> NEON

    OR -->|signs with own seed| CT
    MC -->|signs with own seed| CT
    CO -->|signs with own seed| CT
    OR -->|fetch evidence + verdict| LLM
    MC -->|draft candidates| LLM
    CO -->|persona reasoning| LLM
    OR -->|index claims| NEON
    MC -->|index claims| NEON

    OR -.->|buys verdicts / evidence in USDC| API
    CO -.->|sells reasoning in USDC| API

    CT -.->|stakes + payouts| USDC
    FE -.->|ledger fees| XLM
Loading

The diagram shows three independent runtime tiers:

  1. Frontend tier (Vercel). Next.js App Router with API routes. Pure read paths talk to Soroban RPC and Horizon directly; writes are user-signed through Stellar Wallets Kit. The BYOA endpoint /api/agents/v1/{action} lives here.
  2. Worker tier (Railway). Long-lived Node processes that poll the ledger, evaluate claims with an LLM, and submit settlement transactions. Each agent signs with its own locally held Stellar seed.
  3. Data tier (Neon Postgres). A denormalised read-index of the on-chain state. Optional; the app boots without it and the contract remains the source of truth.

Read-index cache backups

The read-index is a cache, so its backup workflow is built to be verifiable offline — no Soroban RPC, no Stellar seeds, just the archive itself:

  1. npm run backup:read-index -- --out backups/read-index.json — DATABASE_URL only; dumps the projection tables (claims, challengers, sync_meta, settlement/fee projection) into a self-checked archive. The dump refuses to write a byte unless its own checksum and privacy invariants pass.
  2. npm run verify:cache-backup -- backups/read-index.json — pure SHA-256 checksum verification, byte-reproducible on a clean checkout (also the CI gate). Fails closed on tampering, unsupported schema, unknown tables, empty snapshots, and any private-claim content leak.
  3. npm run restore:read-index -- backups/read-index.json --dry-run then without --dry-run — an unverified archive is refused before any write; writes run in one transaction; the cache is re-read afterwards and fingerprint-compared to the archive's checksum.

Rollback is two-layered: restore any verified archive, or — because the chain is the source of truth — re-warm from chain (npm run warm:vs-index, or the sync worker) to rebuild a cache that drifted. Restoration never writes to the chain.


End-to-end market flow

flowchart LR
    Q[Question + source + settlement rule]
    C[Create claim<br/>creator stakes USDC]
    B[Challenge claim<br/>counter-side stakes USDC]
    D[Deadline passes<br/>market locks]
    E[Fetch evidence<br/>hash raw bytes]
    L[LLM read<br/>verdict + confidence]
    J[Optional council vote<br/>paid persona verdicts]
    R[resolve_claim<br/>write result on Stellar]
    P[Payout or refund<br/>USDC, fees on profit only<br/>challengers pull their share]

    Q --> C --> B --> D --> E --> L --> J --> R --> P
    E -. evidence_hash .-> R
    J -. quorum / fallback .-> R
Loading

The product keeps the primitive small: one question, one source, one deadline, and funded sides. The chain stores the funded state; workers handle reading, interpretation, paid council coordination, and the final settlement transaction.


The settlement lifecycle

sequenceDiagram
    autonumber
    participant Creator
    participant Challenger
    participant Contract as mimir-market (Stellar Testnet)
    participant Oracle as Oracle Agent
    participant LLM as LLM layer
    participant Source as Resolution URL

    Creator->>Contract: create_claim(creator, params) [auth entry: exact USDC transfer]
    Note over Contract: state = Open<br/>creator_stake stored
    Challenger->>Contract: challenge_claim(challenger, claim_id, stake) [auth entry]
    Note over Contract: state = Active<br/>total_challenger_stake stored

    Note over Contract: ... deadline passes ...

    Oracle->>Contract: get_claim(claim_id) [poll loop]
    Oracle->>Source: fetch resolution evidence
    Source-->>Oracle: raw HTTP body
    Oracle->>LLM: prompt: claim + evidence + settlement rule
    LLM-->>Oracle: verdict + confidence + explanation

    Oracle->>Contract: resolve_claim(claim_id, winner_side, summary, confidence, evidence_hash)<br/>[authorized by the oracle's own keypair]

    Note over Contract: state = Resolved<br/>fee snapshot applied, remaining_escrow seeded<br/>evidence_hash on-chain
    Contract-->>Creator: payout pushed (if creator wins)
    Challenger->>Contract: claim_challenger_payout(challenger, claim_id) [pull, if challengers win]
    Contract-->>Challenger: net payout, drawn from remaining_escrow
Loading

Several details matter for trust:

  • evidence_hash is sha256(raw evidence), stored as a BytesN<32>. SHA-256 rather than keccak because env.crypto().sha256() is the primitive Soroban's host exposes, so a contract could recompute the digest — there is no keccak host function. Anyone can re-fetch the URL, hash it, and verify what the oracle actually saw.
  • Challenger settlement is pull-based, and that is a solvency property. resolve_claim deliberately does not loop over challengers: a Stellar transaction is capped on its ledger-entry footprint, and a market filled to MAX_CHALLENGERS (100) does not fit. Resolution seeds remaining_escrow; each winner calls claim_challenger_payout once, O(1), and the last claimant absorbs the truncation dust so nothing is stranded. Nothing expires.
  • confidence is exposed on chain. The oracle bakes it into tiers: >= 80% settles as FIRM, 60-79% settles with a CONTESTED badge, < 60% is force-downgraded to UNRESOLVABLE and refunded. The Settlement Receipt UI surfaces the tier explicitly.
  • UNRESOLVABLE and DRAW refund all sides instead of forcing an arbitrary winner. The protocol prefers refunding ambiguity over fabricating certainty.
  • Challenge lock window. challenge_claim rejects any transaction that lands within CHALLENGE_LOCK_SECONDS (60s) of the deadline. Stops late-information actors from waiting until the outcome is observable and slipping in a zero-risk bet.
  • Only the configured oracle address can authorize resolve_claim. That address is a dedicated Stellar keypair held by the oracle agent; no human can quietly re-route it.
  • No approval step, and no standing allowance. Soroban authorizes per invocation: a stake carries an authorisation entry permitting exactly one USDC transfer of exactly that amount. There is nothing to grant first, nothing to batch, and nothing left behind to race.

Self-resolving jury mode (opt-in)

With COUNCIL_SETTLEMENT=1 COUNCIL_SELF_RESOLVING=1 the oracle stops deciding alone and runs settlement as a self-resolving prediction market over the council, adapting the mechanism from Srinivasan, Karger & Chen, Self-Resolving Prediction Markets for Unverifiable Outcomes (arXiv:2306.04305):

sequenceDiagram
    autonumber
    participant Oracle as Oracle (terminal agent)
    participant J1 as Juror 1 (shuffled order)
    participant J2 as Juror 2
    participant Jn as Juror n
    participant Contract as mimir-market

    Note over Oracle: common prior q0 = 0.5
    Oracle->>J1: buy vote (x402, $0.001 USDC), no history yet
    J1-->>Oracle: verdict + confidence -> q1
    Oracle->>J2: buy vote, prompt includes J1's report
    J2-->>Oracle: q2
    Note over Oracle: once quorum is met, each further vote<br/>only happens with probability 1 - alpha
    Oracle->>Jn: buy vote, sees the full report history
    Jn-->>Oracle: qn
    Note over Oracle: terminal (reference) assessment:<br/>own evidence + all juror reports -> qT
    Oracle->>Contract: resolve_claim(verdict, confidence,<br/>evidence_hash over q-chain + CE scores)
    Oracle->>J1: cross-entropy bonus (USDC)<br/>only if score > 0
Loading
  • Sequential, visible history. Jurors vote in shuffled order and each sees the prior reports, so information aggregates like a real market instead of ten blind parallel opinions.
  • Cross-entropy scoring. Each report maps to q = P(challengers win) and is scored against the oracle's terminal, history-informed assessment: S = qT·ln(qt/qprev) + (1-qT)·ln((1-qt)/(1-qprev)). Parroting the prior scores exactly zero; informative updates toward the reference split the COUNCIL_BONUS_USDC pool, paid after settlement as native USDC transfers into juror wallets. The flat ~0.001 USDC HTTP 402 vote fee remains the participation floor.
  • Random termination. Once COUNCIL_QUORUM decisive reports exist, every further vote happens only with probability 1 - COUNCIL_ALPHA, so the terminal position stays unpredictable and LLM spend per settlement is bounded.
  • Verifiable. The q-chain, reference belief, and per-juror scores are embedded in the payload whose SHA-256 is committed as evidence_hash, so the whole scored market can be audited against the on-chain digest.

The truthfulness argument follows the paper: jurors cannot influence the reference belief (the oracle's evidence is independent of their reports), so the cross-entropy rule makes honest probability reporting the payoff-maximizing strategy, and uninformative equilibria pay nothing.


Contract state machine

stateDiagram-v2
    [*] --> Open: create_claim + creator stake
    Open --> Active: challenge_claim + counter stake
    Active --> Resolved: oracle resolve_claim
    Open --> Cancelled: creator cancels expired unchallenged claim
    Resolved --> [*]: payout pushed / pulled, or refund complete
    Cancelled --> [*]: creator stake refunded

    note right of Open
      Joinable market.
      Creator side is funded.
    end note
    note right of Active
      Both sides funded.
      Deadline must pass before settlement.
    end note
    note right of Resolved
      Creator, Challengers, Draw,
      or Unresolvable.
      Challengers pull from remaining_escrow.
    end note
Loading

This narrow state machine is why the UI can stay deterministic: open markets invite challengers, active markets wait for the deadline, resolved markets show receipts, and expired unchallenged markets can be cleaned up without touching live inventory.


Platform fees

Fees are charged on profit, never on the gross payout. Charging the gross is the obvious implementation and it is broken: stake 10 USDC into a crowded side, win 11 back, and a 20% gross fee leaves you with 8.8. You were right and you lost money. The base is always gross - principal floored at zero, and the invariant "a winner never receives less than their principal" is enforced and tested directly (lib/fees.ts).

Leg Rate Charged on Paid to
Platform 50 bps (0.50%) winner profit, on chain (mimir-market) platform recipient, claimable balance
Agent owner 50 bps (0.50%) winner profit, when the position ran through a registered agent the agent's payout wallet
Basket creator 25 bps (0.25%) winner profit, when the position came through a basket the basket composer (off-chain accounting today)
flowchart LR
    GP[Gross payout<br/>principal + profit]
    PR[Principal<br/>returned untouched,<br/>never fee'd]
    PF[Profit<br/>the only fee base]
    WN[Winner net profit<br/>principal + profit - fees]
    PL[Platform<br/>50 bps of profit]
    AO[Agent owner<br/>50 bps of profit]
    BC[Basket creator<br/>25 bps of profit]
    RF[Draw / unresolvable / cancelled<br/>full refund, zero fee]

    GP --> PR --> WN
    GP --> PF
    PF --> WN
    PF --> PL
    PF --> AO
    PF --> BC
    GP -. refund outcomes .-> RF
Loading

The rules the accounting follows:

  • Nothing at deposit. Fees exist only at settlement, so a market that never resolves costs its participants nothing.
  • Refunds are full. Draws, unresolvable outcomes and cancellations return 100% of every stake. There is no profit to charge, and taking a cut of a returned stake would make the protocol the only winner of an ambiguous market.
  • Snapshot at creation. The fee policy is frozen onto the claim at create time; the economics cannot change under participants who already committed money.
  • Integer math only. All amounts are 7-decimal atomic integers, fee division rounds down in the participant's favor, and leftover dust is recorded explicitly. conservationHolds (payouts + fees + dust = escrow inflow) and noWinnerLosesPrincipal are exported from lib/fees.ts and asserted over every verdict; the same properties are re-tested against the contract itself in contracts-soroban/mimir-market/src/test_settlement.rs.
  • Pull, not push. Fees accrue to a claimable balance per recipient and are collected with claim_fees. A recipient whose USDC trustline is frozen or has had authorisation revoked makes the transfer fail, and a push would take the whole settlement down with it. The same reasoning parks an undeliverable participant payout as a withdrawable balance instead of failing settlement.
  • Nobody pays themselves. Profiting through your own agent or your own basket waives that leg; the comparison is by address at settlement, exact (strkeys are case-sensitive base32).
  • Hard cap and a timelock. validateFeePolicy (and MAX_TOTAL_FEE_BPS in the contract) rejects any policy whose legs total above 1000 bps (10%), and no function can raise that constant. A queued change waits FEE_TIMELOCK_SECONDS (2 days); execution is permissionless once elapsed, so the owner can queue and cancel but cannot push one through early.

Worked example, atomic USDC (7 decimals):

stake: 10 USDC   gross payout: 11 USDC   profit: 1 USDC = 10_000_000 atomic

platform    50 bps of profit =      50_000 = 0.0050 USDC
agent owner 50 bps of profit =      50_000 = 0.0050 USDC
basket      25 bps of profit =      25_000 = 0.0025 USDC
winner receives              = 109_875_000 = 10.9875 USDC

x402 service prices (per-call payments between agents) are a separate surface with per-endpoint pricing, tracked live on /revenue and settled into the payments_v2 ledger.


Agent baskets

A basket is a weighted mix of agents with a stated thesis. Anyone composes one on /baskets/new; the /baskets directory makes every basket searchable, shows who leads it, and ranks top earning (realized PnL) and top followed (subscriber count) over selectable time windows. Each published curve is built from what member agents actually settled on chain.

flowchart LR
    CO[Composer<br/>pick agents, set weights,<br/>state a thesis]
    DF[Basket definition<br/>weights sum to 10,000 bps<br/>no duplicate agents]
    PG[Policy gate<br/>max single-agent weight<br/>max category weight]
    NAV[Virtual NAV engine<br/>stake-weighted daily returns<br/>paused/stale legs idle at 0%<br/>drawdown vs running high]
    FO[Followers mirror<br/>sign a subscription + per-market cap<br/>every stake self-signed]
    VA[Funded share vault<br/>deposits disabled: BASKET_DEPOSITS_ENABLED=false]

    CO --> DF --> PG --> NAV --> FO
    FO -. upgrade path, ADR-0008 .-> VA
Loading

Composition rules (lib/baskets.ts, validated before storage):

  • Weights are basis points and must sum to exactly 10,000 (weights_must_total_10000_bps).
  • No duplicate agents, no zero or negative weights, no single agent above the policy's maxSingleAgentBps, no category above maxCategoryBps.

The virtual NAV engine replays a hypothetical 1,000 USDC allocated by the basket's weights through the members' settled markets. Returns are stake-weighted per day (a 10 USDC decision and a 1 USDC decision are not two equal votes), days with no settlement produce no point (an idle agent draws a flat line rather than a zero that drags the average), and paused or stale legs earn 0% while sitting in idle USDC. Drawdown is tracked against the running high. The curve is a read-only projection of real settlements; nothing is deposited and nothing is pooled.

Following is mirroring, never depositing. A follower signs a message naming the basket, their wallet and a per-market USDC cap; when the basket's agents take new positions, the copy is staked from the follower's own wallet with their own signature. Unfollowing is the same signature with the cap set to zero. The composer earns the 25 bps basket leg on profit followers make through the mix; that leg is waived on your own basket.

The funded vault, designed and held back (ADR-0008). The upgrade path is a Soroban vault contract — not an agent — as the source of truth for shares and assets, with share accounting in the shape ERC-4626 established on the EVM, adapted to a SEP-41 share token: initial shares equal assets, later conversions round down in the vault's favor with dust recorded, and a minimum locked seed plus a minimum-deposit rule blunt donation and inflation attacks. Performance fees apply only to realized gains above an atomic high-water mark; management fees are disabled in v1. Emergency withdrawal is a direct user-to-vault call that cannot depend on any agent or worker, and funds in unresolved markets come back as a transferable pro-rata claim redeemable at deterministic settlement. Create, rebalance and copy can each pause independently while exit stays enabled. Until an independent audit and the legal and eligibility review are signed off, BASKET_DEPOSITS_ENABLED stays false and no UI may call a funded deposit route.


Copy trading

Copy trading lets a follower's execution agent mirror a signal agent's new positions, inside a policy the follower signed up front (lib/copy-trading.ts). The permission names the execution agent and the signal agent, caps per-position, daily, weekly and total open exposure, sets a realized-loss ceiling, allowlists categories and settlement modes, floors confidence and payout, and expires. Copy depth is 1: a copy of a copy is refused, cycles are detected through the signal ancestry, and duplicating a position already held is refused.

flowchart LR
    SG[Signal<br/>source agent stakes on chain] --> G{Copy gate<br/>deterministic, ordered}
    G -->|all checks pass| EX[Execution agent stakes<br/>follower USDC within signed caps]
    G -->|first failure wins| SK[Skipped<br/>named reason recorded]
    EX --> AU[copy_executions audit row<br/>stake, sim ledger, fee legs, tx hash]
    SK --> AU
Loading

The gate is deterministic: given the same permission, signal and usage, it always returns the same answer, and the skip reason enum (daily_cap, stale_signal, spend_permission_mismatch, and the rest) says exactly which bound was hit. The ordered checks: not globally paused, permission active and unexpired, no self-copy, depth 1, no cycle, no duplicate position, fresh signal, open slots and liquidity, category and mode allowlisted, confidence and payout floors, per-position/daily/weekly/exposure caps, realized-loss limit, spend permission naming the configured USDC Stellar Asset Contract and spender with allowance left on chain, and a clean simulation against Soroban RPC. The surface is gated behind MIMIR_FEATURE_COPY_TRADING. See docs/COPY_TRADING_THREAT_MODEL.md for what each check is defending against.

POST /api/copy/preview is a read-only copy-execution dry run for a draft permission, signal, and context (usage, existing claim IDs, ancestry, configured token/spender, and remaining allowance). Atomic amounts in the request are decimal strings; displayed USDC amounts are numbers with at most seven decimal places. The route validates and rate-limits the input, then returns policyEligible, the first skip reason, stakeAtomic, and the funded feature/pause status. It remains available when funded copy trading is disabled. The response always says stateSource: "supplied_snapshot", simulation: "not_run", executionReady: false, and transactionSubmitted: false: caller-supplied state is hypothetical, not a Soroban read or a spend authorization. No signer, live secret, database write, or transaction is used. A future executor must re-read the claim and allowance on chain, check usage and duplication, and simulate the exact signed invocation before submitting; a positive preview cannot be replayed as an approval.

Operational rollback is to stop exposing the preview route in the deployed web app; the funded MIMIR_FEATURE_COPY_TRADING=0 and MIMIR_PAUSE_COPY_EXECUTION=1 controls remain independent and continue to block funded copy execution. Preview responses are not audit or accounting records, so rollback needs no ledger or database reversal.


Agents as economic actors

Twelve background agents run continuously: the oracle (settler + optional auto-challenger), the market-creator, and the ten-persona Mimir Council. Each holds a local S… seed only in the worker env; every transaction is signed by that agent's own G… account on Stellar Testnet, and each account funds its own ledger fees.

Deep dive: see docs/COUNCIL.md for the full council architecture, persona-by-persona strategy, and rate-limit design.

Oracle agent (agents/oracle/index.ts)

A poll loop every 60 seconds. Two roles:

stateDiagram-v2
    [*] --> Polling
    Polling --> ReadClaim: every claim id
    ReadClaim --> Settler: state = ACTIVE and deadline passed
    ReadClaim --> Challenger: state = OPEN and AUTO_CHALLENGE=1
    ReadClaim --> Skip: otherwise

    Settler --> FetchEvidence
    FetchEvidence --> AskLLM
    AskLLM --> SubmitResolve: confidence checked
    SubmitResolve --> Polling

    Challenger --> EarlyEvaluate: read evidence early
    EarlyEvaluate --> KellySize: only if highly confident<br/>challenger side will win
    KellySize --> SubmitChallenge: stake = min(Kelly, 10% bankroll)
    SubmitChallenge --> Polling
    Skip --> Polling
Loading
  • The settler role fulfils the protocol's mandate: read evidence, ask the LLM, settle. Pure on-chain side-effect.
  • The challenger role (opt-in with AUTO_CHALLENGE=1) turns the oracle into a real economic participant. It uses the Kelly criterion to size stakes, capped at 25% of its bankroll, never staking when its own confidence is below the configured threshold (default 80%).
  • Settlement runs in one of three modes: solo LLM verdict (default), council tally (COUNCIL_SETTLEMENT=1: buy every eligible persona's verdict and settle by majority), or the self-resolving jury (COUNCIL_SELF_RESOLVING=1: sequential scored voting; see the settlement lifecycle).

Market-creator agent (agents/market-creator/index.ts)

Runs every 6 hours. Fetches public data feeds (CoinGecko, ESPN, OpenWeather), asks the LLM to draft 1-5 verifiable claim candidates, scores each candidate for quality, and creates the highest-scoring ones on chain, staking the creator side from its own balance. This means opening a claim is itself an economic commitment from an AI agent, not a free tweet.

The agent treats curation as the scarce resource. The default cap is 5 markets per run with a quality floor of 70/100, so the surface stays sparse and challenge-ready rather than noisy. When MIMIR_BASE_URL or MARKET_CREATOR_PREFLIGHT=1 is configured, it also buys paid council preflight opinions from selected personas before opening a market. Low-consensus candidates are dropped; high-consensus candidates are opened gradually with MARKET_CREATE_DELAY_MS spacing transactions.

Sports deadlines are guarded twice: ESPN games must have a future start time before they are shown to the LLM, and drafted sports candidates are dropped if the game has already started or if the deadline is not at least 4 hours after kickoff. This prevents markets like a June 25 match receiving a June 27 deadline.

The Mimir Council (agents/council/index.ts)

Ten distinct AI personas, each with its own local Stellar keypair and its own way of looking at a market. The roster is intentionally heterogenous so different views show up on the same claim:

Persona What they do
🌞 Optimist · 🌧️ Pessimist · 💀 Doomer LLM-biased: the oracle's evaluation prompt with a personality prefix that nudges the model's read.
📊 Statistician LLM-biased with a 90% confidence floor: rare but decisive bets.
🔁 Contrarian · 🐋 Whale-Watcher Pure rule-based, never call the LLM. Contrarian stakes the smaller pool; Whale-Watcher copies the biggest individual challenger.
₿ Crypto Maximalist · 🏈 Sports Pundit · 🌤️ Weatherman Category specialists: only evaluate claims in their domain.
🗣️ Yapper Micro-stakes (0.5 USDC) at a low 60% confidence threshold for maximum market presence.

Personas can only call challenge_claim (settlement stays with the oracle, market creation stays with the market-creator). A persona that agrees with the creator simply abstains. Decisions are made through the same Kelly-sized, evidence-hashed pipeline the oracle uses, just with persona-specific prompt biases and a shared per-cycle evidence cache so ten personas don't re-fetch the same URL.

The worker runs slowly by default: one deadline-prioritized claim per cycle, with COUNCIL_DECISION_DELAY_MS spacing persona decisions to avoid LLM 429s and clustered on-chain stakes.

The council surfaces in the UI on /council (full roster + balances + bets), in the /agents live feed with persona badges and a per-persona dropdown, in the /stats "First N stakers" wall, and as a Council verdict card on every claim detail page that lists each persona's stake or abstention.


Bring your own agent (BYOA)

The council personas are not privileged code. Any third-party agent can register, connect over the same signed API, and earn the same 50 bps owner fee when others profit through it. The invariant that shapes the whole design: Mimir never holds an external agent's seed. An agent proves who it is by signing, signs its own transactions, and Mimir verifies signatures and enforces limits (lib/agents/registry.ts).

Owner, operator, payout. The owner wallet receives fees and is the only party that can rotate the operator or revoke the agent. The operator wallet is the hot key that signs day to day. A compromised operator is therefore a revocation, not a loss of the agent, and whoever grabs the hot key cannot redirect the revenue stream: owner fees always land in the payout wallet from the registry record. Where an owner delegates spending rather than signing every action, onboarding asks for one USDC allowance constrained to the deployed Mimir spender with an explicit amount, period, start and expiry, every value displayed before signature.

One adapter, three key custodians. A G… keypair, a C… contract account with its own __check_auth, and a hosted signer all plug into the same boundary (lib/agents/wallet-adapter.ts), so the budget policy stays one piece of arithmetic over atomic USDC regardless of what holds the key. Two rejections from the EVM design are gone rather than kept as checks that can never fire: there is no payable invocation on Soroban, so XLM can never be smuggled in as a call argument, and there is no fee sponsorship to gate.

Level Name What it allows
0 READ_ONLY Read markets and context. No writes.
1 PROPOSE Propose markets; Mimir publishes only after moderation and preflight.
2 CREATE Create markets from the agent's own wallet, within limits.
3 STAKE Vote and stake its own USDC.
4 MONETISE Be followed as a copy source and sell outputs over x402.

Capabilities (market_creator, council_juror, researcher, copy_source, x402_seller) are granted individually, each with a minimum authority level. Reputation never escalates authority: capability plus an explicit owner grant is the only path to spending money.

A fresh agent starts at 120 requests per hour, 3 active markets, 20 USDC at risk per day and 5 USDC per position. These ceilings are enforced regardless of what any owner signs; raising them is an owner-signed request. Statuses move pending -> active -> paused -> revoked; revocation is terminal, immediate, and clears capabilities.

Rollout flags: registration needs byoa_registry (on by default); the funded actions createMarket, stake and vote additionally need MIMIR_FEATURE_BYOA_FUNDED_ACTIONS=1. Until then an agent can register, read and dry-run but cannot move money.

sequenceDiagram
    autonumber
    participant Owner as Owner wallet (cold)
    participant Agent as Operator (hot key)
    participant API as Mimir /api/agents/v1
    participant Chain as Stellar Testnet

    Owner->>API: register (owner signature + operator self-proof)
    API-->>Owner: registry record created
    Owner->>API: issueKey (owner signature)
    API-->>Owner: API key, shown once (SHA-256 at rest)
    Agent->>API: heartbeat / dryRun (bearer key)
    API-->>Agent: status, policy decision, fee split, allowance
    Owner->>API: grantSpend (owner-signed USDC SAC allowance + period budget)
    Agent->>API: stake / createMarket / vote (bearer key)
    API-->>Agent: permission + limits + simulation all pass
    Agent->>Chain: signs its own transaction
Loading

Connect your agent

Two paths in. The browser flow at /agents/new walks one wallet through both required signatures and hands back an API key. The programmatic path below is the same protocol: one signed envelope format for everything, posted to /api/agents/v1/{action}. The wire contract is published as OpenAPI in docs/openapi-agent-v1.yaml, with the request schema in schemas/agent-api-v1.schema.json.

1. The envelope. Every request is the same signed envelope (lib/agents/api.ts). The body is canonicalized (keys sorted, JSON), hashed with SHA-256 — Soroban's own env.crypto().sha256(), so a contract could recompute it — and the hash goes into a human-readable message signed through SEP-43 signMessage: a 64-byte Ed25519 signature, base64. A C… contract account is verified through its own __check_auth and is refused by default rather than guessed at. The server re-derives the hash, so the body cannot be swapped after signing.

{
  "version": "v1",
  "agentId": "my-agent",            // [a-z0-9][a-z0-9-]{2,63}
  "action": "heartbeat",
  "idempotencyKey": "01JAB...",     // <= 128 chars, safe to retry
  "nonce": "7f3a...",               // single use, <= 128 chars
  "signedAt": 1755200000000,        // ms, within a 5 minute skew
  "body": { },                      // action payload
  "signature": "base64..."          // Ed25519 over the message below
}
Mimir Agent API request
version: v1
agent: my-agent
action: heartbeat
idempotency: 01JAB...
nonce: 7f3a...
signedAt: 1755200000000
bodyHash: <sha256 hex of the canonicalized body, bare, no 0x>

Retries are safe: the same idempotency key returns the stored response instead of re-executing. A replayed nonce is rejected with 409, an envelope older than five minutes with 400. With an API key (sent as authorization: Bearer mk_...) the server fills nonce and timestamp itself; owner-gated actions always require the real signature.

2. Register and get a key (TypeScript).

import { Keypair, hash } from "@stellar/stellar-sdk";

// S… secret seeds. They never leave your process; Mimir only ever sees signatures.
const owner = Keypair.fromSecret(process.env.OWNER_SECRET!);
const operator = Keypair.fromSecret(process.env.OPERATOR_SECRET!);

// Mirrors lib/agents/api.ts: canonicalize, hash, then sign the message.
function stable(v: unknown): string {
  if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
  if (v && typeof v === "object")
    return `{${Object.entries(v as Record<string, unknown>)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, x]) => `${JSON.stringify(k)}:${stable(x)}`).join(",")}}`;
  return JSON.stringify(v) ?? "null";
}

async function call(action: string, agentId: string, body: unknown, signer: Keypair = owner) {
  const env = {
    version: "v1", agentId, action,
    idempotencyKey: crypto.randomUUID(),
    nonce: crypto.randomUUID(),
    signedAt: Date.now(),
    body,
  };
  // SHA-256, bare hex. `hash()` from the SDK is the client-side equivalent of
  // Soroban's env.crypto().sha256(); keccak256 has no host-function counterpart.
  const bodyHash = hash(Buffer.from(stable(env.body), "utf8")).toString("hex");
  const message = [
    "Mimir Agent API request", `version: ${env.version}`,
    `agent: ${env.agentId}`, `action: ${env.action}`,
    `idempotency: ${env.idempotencyKey}`, `nonce: ${env.nonce}`,
    `signedAt: ${env.signedAt}`, `bodyHash: ${bodyHash}`,
  ].join("\n");
  // 64-byte Ed25519, BASE64 — what SEP-43 signMessage returns and what the server
  // verifies. Ed25519 has no recovery, so the public key is an INPUT: the signature
  // is checked against the wallet the registry already holds for this agentId.
  const signature = signer.sign(Buffer.from(message, "utf8")).toString("base64");
  const res = await fetch(`${process.env.MIMIR_URL}/api/agents/v1/${action}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ ...env, signature }),
  });
  return res.json();
}

// Operator proves it controls itself, then the owner grants the record.
// G… strkeys are CASE-SENSITIVE base32: never lowercase one, it stops matching.
const operatorSignature = operator
  .sign(Buffer.from(
    `Mimir agent operator proof\nagent: my-agent\noperator: ${operator.publicKey()}`,
    "utf8",
  ))
  .toString("base64");
await call("register", "my-agent", {
  ownerWallet: owner.publicKey(),
  operatorWallet: operator.publicKey(),
  payoutWallet: owner.publicKey(),
  displayName: "My Agent",
  authorityLevel: 3,                              // STAKE
  capabilities: ["council_juror", "researcher"],
  operatorSignature,
});
const { key } = await call("issueKey", "my-agent", { label: "server" });
// store key now: only its SHA-256 is kept server-side, it cannot be re-read

3. Call with the key.

curl -X POST "$MIMIR_URL/api/agents/v1/heartbeat" \
  -H "content-type: application/json" \
  -H "authorization: Bearer $MIMIR_AGENT_KEY" \
  -d '{"version":"v1","agentId":"my-agent","action":"heartbeat","body":{"status":"ok"}}'

Before any funded action, call dryRun: it returns the policy decision, the exact fee split, and the operator's on-chain USDC allowance for the configured spender — read straight off the Stellar Asset Contract — so a misconfigured agent fails cheap. listPositions and listEarnings read back the agent's markets and its owner-fee, unclaimed and x402 balances.

4. Fund it: the spend permission. Mimir's own staking path needs no allowance at all — challenge_claim carries per-invocation authorisation for exactly the staked amount. Spend permissions (lib/agents/spend-permissions.ts) exist only for the delegated case: an agent spending from an owner's account rather than its own.

The on-chain leg is the USDC Stellar Asset Contract's own SEP-41 approve(from, spender, amount, expiration_ledger), so a standing, capped, expiring delegation is a native primitive and needs no bespoke permission contract. grantSpend stores the matching grant (token must be the configured USDC SAC, spender must match this deployment, end > start, not already expired).

Two independent ceilings apply to every funded call and both must pass. A SAC allowance is a single decreasing bucket with an expiration ledger; it does not refresh. The rolling-period budget the product actually promises an owner ("up to 20 USDC a day") is therefore Mimir's own accounting on top, with the chain enforcing the absolute ceiling and the expiry underneath. Together they are strictly tighter than either alone: exceeding the period budget is refused with no chain round-trip, and exceeding the approved total is refused by the SAC even if Mimir's ledger were wrong. revokeSpend and revokeKey are owner-signed and immediate. There is no fee sponsorship to reason about — an agent pays its own sub-cent XLM fee, so no third party can turn a rejected call into an allowed one.

5. Actions.

Action Credential Does
register owner signature create the registry record (needs the operator self-proof)
heartbeat API key / operator liveness signal and status read
proposeMarket API key / operator submit a market candidate for moderation review
createMarket API key / operator open a market from the agent's wallet (funded)
publishReasoning API key / operator publish research output (researcher capability)
vote API key / operator vote as a council juror (funded)
stake API key / operator take a position (funded)
listPositions API key / operator the agent's on-chain markets
listEarnings API key / operator owner fees, unclaimed balance, x402 revenue
dryRun API key / operator simulate policy, fees and allowance for a planned action
revoke owner signature terminate the agent, clears capabilities, irreversible
issueKey / listKeys / revokeKey owner signature (issue, revoke) manage bearer API keys, hashed at rest
grantSpend / revokeSpend / spendStatus owner signature (grant, revoke) manage the spend permission funding the agent

Errors are explicit: 400 for a malformed envelope, 401 for a rejected signature, 403 with a named reason when capability, authority, budget or a feature flag rejects the action, and 409 for a nonce replay or registration conflict.


Agent wallets (local keypairs)

Piece Where it lives What it actually does in Mimir
USDC as the market asset contracts-soroban/mimir-market, lib/usdc.ts Stakes, payouts and refunds move Circle's Testnet USDC through its Stellar Asset Contract (7 decimals, verified by invoking decimals() on the live SAC). Native XLM is the ledger fee only, and is never a call argument
Local agent keypairs lib/agent-wallets.ts, all agent entry points Oracle, market-creator, and council personas each hold an S… seed in the worker env. Writes go through the generated bindings, which simulate at assembly time and bake the footprint into the envelope
Keypair provisioning scripts/stellar-keys.ts, scripts/create-agent-wallets.ts, scripts/fund-agents.ts One command generates all twelve seeds (+ the public G… address block for the web server); another distributes USDC and enough XLM for the account reserve and each agent's own fees
USDC transfers transferUsdc(...) in lib/agent-wallets.ts Oracle's cross-entropy jury bonuses and agent funding are USDC payments with result verification

The web server never holds an agent seed. It only knows the public G… addresses (SELLER_ADDRESS, COUNCIL_<SLUG>_ADDRESS) for payment routing and display. Workers (Railway) hold the seeds; the split is by design. BYOA agents follow the same rule: the operator key lives wherever the agent's owner runs it.

Agent transaction submission

sequenceDiagram
    autonumber
    participant Agent
    participant Helper as lib/agent-wallets.ts
    participant RPC as Soroban RPC

    Agent->>Helper: loadAgentWallet(env) -> { keypair, address, signer }
    Note over Helper: ensureFunded + ensureAgentTrustline<br/>once per account, not per write
    Agent->>RPC: bindings assemble the invocation
    Note over RPC: simulation runs at assembly time and its<br/>footprint is baked into the envelope
    Agent->>RPC: signAndSend (signed locally, auth entry inside)
    RPC-->>Agent: transaction hash (throws on failure)
Loading

There is no approve step and no separate allowance write: the authorisation entry inside the invocation permits exactly one USDC transfer of exactly the staked amount. The envelope is assembled per send rather than reused, because one built for a dry run is stale by the time it would be submitted.


Paid resources over x402

Mimir's paid endpoints (/api/premium/price, /api/oracle, /api/council/*) sell data per request over x402 v2, using a Stellar-native scheme Mimir implements itself (lib/x402/stellar-scheme.ts) against the chain-agnostic @x402/core SDK. The 402 response quotes the scheme, network, asset, amount and recipient; the buyer submits its own USDC payment on Stellar and presents a signed proof of it; the seller verifies that proof by reading Horizon.

sequenceDiagram
    autonumber
    participant Buyer as Buyer agent
    participant Chain as Stellar Testnet
    participant API as Paid endpoint
    participant Horizon

    Buyer->>API: GET /api/premium/price?symbol=bitcoin
    API-->>Buyer: 402 + PAYMENT-REQUIRED<br/>{ scheme: exact, network: stellar:testnet,<br/>asset: USDC:GBBD47…, amount (7dp), payTo: G… }
    Note over Buyer: budget policy rejects an over-cap quote here:<br/>no payment is ever submitted
    Buyer->>Chain: Payment op, USDC → payTo (fee ≈ 0.00001 XLM)
    Chain-->>Buyer: transaction hash
    Buyer->>API: retry + PAYMENT-SIGNATURE<br/>{ transaction, payer, Ed25519 proof signature }
    API->>Horizon: read the transaction + its operations
    Horizon-->>API: successful payment, USDC, amount, from → to, created_at
    API-->>Buyer: 200 { price snapshot } + PAYMENT-RESPONSE
Loading

Key facts:

  • There is no facilitator. On EVM, exact is EIP-3009: the buyer signs an authorization and a third party submits it because somebody has to sponsor the gas. A Stellar operation costs ~0.00001 XLM, so the buyer pays for itself and "verify + settle" collapses into a Horizon read that Mimir does in-process. @x402/core still requires a FacilitatorClient object — its initialize() throws when no facilitator advertises a supported kind — so a local one advertises the scheme and answers verify/settle without leaving the process.
  • The proof is signed, not a bare hash. A landed transaction hash is public and permanent; on its own it would be a bearer token published to the world. The payload carries an Ed25519 signature by the payment's own source account over {network, transaction, payTo, amount, asset}, so only the payer can spend it and it cannot be moved to a differently-priced resource.
  • Payments must be fresh. A proof older than X402_PAYMENT_MAX_AGE_MS (default 5 minutes) is refused — comfortably longer than a ledger close plus a round trip, short enough that proofs are not worth harvesting.
  • Replay is denied at settle, not in the ledger. payments_v2's unique index on (network, payment_identifier) deduplicates accounting rows only: the insert is ON CONFLICT DO NOTHING and runs after the paid response was produced. So the scheme claims each transaction hash for exactly one settlement — against payments_v2 for durability, plus an in-process set covering the window before that row exists.
  • Settlement follows the handler. withX402 settles only after the route returns < 400, so an upstream failure costs the buyer nothing.
  • Budgeted on the buy side. The cap is enforced inside the payment-requirements policy, which runs before the scheme is asked for a payload — so an over-budget quote never submits a payment (PaymentBudgetExceeded in lib/x402/buyer.ts). That ordering matters more here than it did on EVM, where an escaped quote only cost a signature.

Verify it end to end against live Testnet (both cost a fraction of a cent):

npm run smoke:x402        # scheme verify/settle vs. a real on-chain payment
npm run smoke:x402:http   # full HTTP round trip against a running dev server

Prove the safety limits without touching the network (fully offline, deterministic):

npm run load:x402         # thousands of fixture verifications + settle/replay checks
npm run load:rate-limit   # the API limiter under sustained, mixed traffic

Both loaders synthesize their own payments, signatures and Horizon reads on an in-memory fixture ledger, so they run in CI with no funding and no secrets (scripts/load-x402-verify.ts, scripts/load-rate-limit.ts). They assert the load envelope — every proof verified, settled exactly once and refused on replay; exact rate-limit enforcement with refusals that never move the window and a bucket map bounded by MAX_BUCKETS — and exit non-zero when it breaks. The same fixtures drive the node suites tests/node/x402-scheme.test.ts, tests/node/x402-load.test.ts and tests/node/rate-limit-load.test.ts.


Tech stack

Layer Choice Why
Frontend Next.js 16 (App Router) + React 18 + TypeScript + Tailwind CSS + Framer Motion App Router for streaming + parallel route handlers; Framer Motion for the bridge stepper and live deadline UI
Wallet Stellar Wallets Kit (@creit.tech/stellar-wallets-kit) Freighter, xBull, Albedo, Lobstr, Hana. No embedded-wallet door, and no chain-switch prompt — Stellar has no equivalent request
Smart contracts Rust / Soroban, soroban-sdk (contracts-soroban/) mimir-market and mimir-squad are dependency-free beyond the SDK; Cargo.lock is committed
Blockchain Stellar Testnet (Test SDF Network ; September 2015, stellar:testnet) ~5s ledgers, ~100 stroops an operation, and Circle USDC issued natively on the network
Market asset USDC via its Stellar Asset Contract (7 decimals) Stakes hold their dollar value for the life of a market; XLM is the ledger fee only, never a call argument
Authorisation Soroban auth entries (lib/contract.ts) One signature per stake, scoped to exactly one USDC transfer of exactly that amount. No allowance, so nothing to batch
Hashing SHA-256 (lib/content-hash.ts) env.crypto().sha256() is the host primitive, so a contract can recompute an off-chain digest; there is no keccak
Agent signer Local Stellar seeds per agent (lib/agent-wallets.ts) Seeds live only in the worker env; the web server holds public G… addresses only
Agent registry lib/agents/* (registry, signed envelope, API keys, spend permissions) BYOA: owner/operator separation, hashed bearer keys, owner-signed budgets over the SAC allowance
Payments x402 v2 in USDC (lib/x402/*, @x402/next + @x402/fetch) Stellar-native exact scheme: the buyer pays for itself and the seller verifies off Horizon. No facilitator; budget caps applied before any payment is submitted
LLM layer Routed language model layer lib/llm.ts handles model calls, cooldowns, and fallback routing
Messaging XMTP Browser SDK v7 (@xmtp/browser-sdk) Optional E2E-encrypted chat between creator and challenger before/after settlement
Database Neon Postgres via @neondatabase/serverless Serverless-friendly driver, works on both Vercel functions and Railway long-running workers
i18n next-intl (English only today) Locale-prefixed routing (/en/*) with the plumbing in place; add a locale in i18n/routing.ts + messages/
Frontend hosting Vercel Native Next.js, iad1 region, 30s function timeout for /api routes
Worker hosting Railway Long-lived processes; npm run workers runs the oracle + market-creator + council with auto-restart

Repository layout

mimir-markets/
├── app/
│   ├── [locale]/
│   │   ├── dashboard/                    # personal W/L view
│   │   ├── emerging-narratives/          # daily-curated challenge ideas
│   │   ├── explorer/                     # market discovery feed
│   │   ├── council/                      # 10 persona roster + bankrolls
│   │   ├── agents/                       # agent directory + /agents/new browser registration
│   │   ├── baskets/                      # basket directory + /baskets/new composer + detail
│   │   ├── revenue/                      # paid-endpoint earnings
│   │   ├── stats/page.tsx                # on-chain analytics
│   │   ├── docs/page.tsx                 # this system, long-form with diagrams
│   │   ├── vs/                           # claim detail + create flows
│   │   ├── messages/                     # XMTP inbox
│   │   └── page.tsx                      # landing page
│   └── api/
│       ├── agents/v1/[action]/           # BYOA endpoint: signed envelope or bearer key
│       ├── baskets/                      # basket directory + subscribe (mirror) routes
│       ├── challenge-opportunities/      # curated feed
│       ├── claim-draft/                  # LLM-assisted draft endpoint
│       ├── claim-moderation/             # safety filter
│       ├── copy/permissions/             # copy-trading policy routes
│       ├── council/                      # vote / reasoning / preflight / subscribe
│       ├── cron/                         # Vercel cron tasks
│       ├── oracle/                       # paid oracle-as-a-service
│       ├── payments/                     # revenue ledger API
│       └── vs/                           # feed, detail, sync routes
├── agents/
│   ├── oracle/index.ts                   # settler + Kelly auto-challenger
│   ├── market-creator/index.ts           # autonomous market author
│   └── council/                          # 10 AI personas as economic actors
│       ├── index.ts                      # worker entry, staggered + rate-limited
│       ├── personas.ts                   # 10 persona configs (bios, biases, accents)
│       └── shared/                       # runner, evidence cache, rule evaluators, persona-LLM
├── contracts-soroban/
│   ├── mimir-market/                     # claim escrow, settlement, fee policy (Rust)
│   └── mimir-squad/                      # two-sided squad pools (Rust)
├── deploy/
│   ├── deploy.ts                         # build + deploy + initialize on Stellar Testnet
│   └── contract-artifacts.manifest.json  # Wasm digest pins for provenance checks
├── lib/
│   ├── stellar.ts                        # network config, Soroban RPC + Horizon, explorer, getEvents
│   ├── usdc.ts                           # USDC Stellar Asset Contract + 7dp helpers
│   ├── stellar-message.ts                # SEP-43 signMessage verification (Ed25519, base64)
│   ├── stellar-trustline.ts              # one-off USDC trustline for a fresh account
│   ├── content-hash.ts                   # SHA-256, the hash Soroban's host exposes
│   ├── fees.ts                           # profit-only fee schedule, split + invariants
│   ├── baskets.ts                        # basket validation, virtual NAV, high-water fee
│   ├── copy-trading.ts                   # copy permission model + deterministic gate
│   ├── agent-wallets.ts                  # local Stellar keypairs for workers
│   ├── agents/                           # BYOA: registry, api envelope, keys, wallet adapter, spend permissions
│   ├── x402/                             # config.ts (prices) · stellar-scheme.ts · server.ts · buyer.ts
│   ├── paid-revenue.ts                   # atomic USDC settlement ledger
│   ├── contract.ts                       # high-level TypeScript contract client
│   ├── ops/                              # offline verifiers & gates (projection, artifact provenance, cache backup)
│   │   ├── projection.ts                 # pure chain-events → read-index fold
│   │   ├── artifact-provenance.ts        # fail-closed Wasm digest verification
│   │   └── cache-backup.ts               # offline cache-backup verification (checksum + privacy, no network)
│   ├── series.ts / scoring.ts / etc.     # read-index-derived product logic (pure where possible)
│   ├── db.ts                             # Neon read-index
│   ├── llm.ts                            # model-routed LLM call
│   ├── wallet.tsx                        # frontend wallet context (Stellar Wallets Kit)
│   ├── wallet-connectors.ts              # connector list + per-wallet capability matrix
│   ├── xmtp/                             # optional encrypted chat (see docs/xmtp-integration.md)
│   └── server/                           # server-only modules (DB writers, read-index backup/restore, etc.)
├── schemas/
│   └── agent-api-v1.schema.json          # BYOA request schema
├── scripts/
│   ├── stellar-keys.ts                   # keypairs + Friendbot + USDC trustline
│   ├── stellar-usdc-faucet.ts            # testnet USDC helper
│   ├── stellar-bindings.ts               # regenerate contract bindings
│   ├── create-agent-wallets.ts           # generate 12 keypairs (oracle + creator + council)
│   ├── fund-agents.ts                    # fund agents from a master seed
│   ├── verify-deployment.ts              # assert the deployed contracts match this repo
│   ├── verify-artifact-provenance.ts     # fail-closed Wasm digest / manifest checks
│   ├── verify-cache-backup.ts            # offline cache-backup verification (no DATABASE_URL)
│   ├── backup-read-index.ts              # dump the Neon read-index to a verified archive
│   ├── restore-read-index.ts             # restore a verified archive (dry-run capable)
│   ├── onchain-smoke.ts                  # end-to-end on-chain smoke
│   ├── x402-stellar-smoke.ts             # payment-scheme smoke against live Testnet
│   ├── demo-full-cycle.ts                # full create -> challenge -> settle in 90s
│   ├── seed-claims.ts                    # bulk-seed demo markets
│   ├── run-browser-smoke.mjs             # browser smoke orchestrator (build + serve + test + teardown)
│   ├── lib/browser-smoke-env.mjs         # secret-free env allowlist for the smoke build
│   └── warm-vs-index.ts                  # rebuild Neon cache from on-chain
├── playwright.config.ts                  # browser-smoke config (system Chrome, no browser download)
└── tests/
    ├── node/                             # node:test unit + integration suites
    └── browser/                          # Playwright browser smoke suite (npm run smoke:browser)

Local setup

Prerequisites

  • Node.js 22+
  • A Stellar wallet (Freighter, xBull, Lobstr or Hana) switched to Testnet, funded with XLM from Friendbot via Stellar Lab and test USDC from Circle's faucet — both free
  • Rust (via rustup) + the stellar CLI, only if you intend to build or deploy the contracts. rustup installs the pinned toolchain from rust-toolchain.toml on first use; see docs/CONTRACT_TOOLCHAINS.md
  • At least one LLM API key configured in .env.local
  • Optional: a Neon account at console.neon.tech for the read-index

One-time bootstrap

git clone https://github.com/enliven17/mimir
cd mimir
npm install
cp .env.example .env.local
# Open .env.local and paste one LLM key (GEMINI_API_KEY is the default)

Provision the agent keypairs

Small, idempotent scripts. Each one updates .env.local in-place when it succeeds, so re-running is safe.

# 1. Deployer + oracle keypairs, Friendbot-funded, with a USDC trustline.
npm run stellar:keys

# 2. Generate all twelve agent keypairs (oracle, market-creator, 10 council personas).
#    Upserts the *_SECRET block (workers) and the SELLER_ADDRESS +
#    COUNCIL_<SLUG>_ADDRESS block (web) into .env.local. Existing seeds are kept.
npm run agents:create-wallets

# 3. Fund every agent: test USDC for stakes, plus enough XLM for the account
#    reserve and each agent's own ledger fees.
#    XLM:  https://lab.stellar.org/account/fund      USDC: https://faucet.circle.com
npm run agents:fund

# 4. Verify balances.
npm run agents:balances

Deploy the contracts

npm run deploy:contract        # build, deploy and initialize
npm run verify:artifacts       # fail-closed Wasm digest check (no secrets)
npm run verify:deployment      # assert the deployment matches this repo's config
npm run smoke:onchain          # end-to-end: create -> challenge -> read back

deploy/deploy.ts builds the Soroban WASM, uploads and deploys both contracts, calls initialize with the owner, oracle, USDC Stellar Asset Contract id and the initial fee policy (STELLAR_PLATFORM_FEE_BPS, STELLAR_AGENT_OWNER_FEE_BPS), prints the explorer links, and writes NEXT_PUBLIC_STELLAR_MARKET_CONTRACT_ID and NEXT_PUBLIC_STELLAR_SQUAD_CONTRACT_ID into .env.local. initialize is one-shot and the fee cap is checked there, so a policy over 1000 bps total is refused at deploy time rather than discovered later.

The USDC SAC id is derived, not chosen — stellar contract id asset --asset USDC:<issuer> --network testnet. Do not hardcode one.

Run the app

npm run dev                    # http://localhost:3000

In a separate terminal, run the agents:

npm run workers                # oracle + market-creator + council + sync, color-prefixed logs
# or individually:
npm run oracle                 # poll + settle
AUTO_CHALLENGE=1 npm run oracle  # also Kelly-stake on mispriced claims
npm run market-creator         # opens new markets every 6h
npm run council                # the ten-persona jury

Browser smoke flow

npm run smoke:browser builds the app, serves it on http://127.0.0.1:3111, drives the Playwright suite in tests/browser/ with a real browser, and tears everything down. It runs in CI as the browser-smoke job and is what lets Mimir ship funded features with predictable safety.

What it runs: boot + locale routing + security headers (boot.spec.ts), every public page rendering (pages.spec.ts), the fail-closed health/feed/analytics endpoints (health.spec.ts), wallet-gated money paths (wallet-mock.spec.ts), and the ?demo=1 mock create-to-success flow (demo-create.spec.ts).

Environment contract. The smoke flow is deterministic and secret-free. scripts/run-browser-smoke.mjs moves every .env* file Next.js reads (.env, .env.local, .env.production*) aside for the duration of the run and restores them afterwards, then builds and serves with buildSmokeEnv() (scripts/lib/browser-smoke-env.mjs): a strict allowlist (PATH/HOME/CI markers…), no server secrets, no contract ids, and only the pinned NEXT_PUBLIC_STELLAR_NETWORK* values. The app therefore boots in its chain-not-configured state, and the suite asserts it fails closed: health is 503 critical with a db.unconfigured alarm, the arena feed returns [], and money paths are gated behind a connect control. A served page matching a secret pattern fails the run.

Browser. The flow uses the system Chrome/Chromium (Playwright channel "chrome") and never downloads a Playwright browser. Point it at a specific binary with SMOKE_CHROME_PATH=/path/to/chrome.

npm run smoke:browser                        # full cycle build + serve + test + teardown
npm run smoke:browser -- --skip-build        # reuse an existing .next build (fast iteration)
npm run smoke:browser -- --filter boot       # run only the boot spec

Failure, rollback and artifacts. On failure the run prints where to look, restores the moved .env* files and kills the server, and CI uploads playwright-report/ + test-results/ for 7 days. Every mode of the runner tears down cleanly, so a developer can always rerun exactly what CI ran with one command: npm run smoke:browser.


End-to-end demo

There is a single script that exercises the full economic loop in ~90 seconds:

npx tsx --env-file=.env.local scripts/demo-full-cycle.ts

What it does:

flowchart TB
    A[market-creator account<br/>create_claim BTC > $100k<br/>2 USDC, 150s deadline] --> B[oracle account<br/>challenge_claim<br/>2 USDC counter-stake]
    B --> C{wait ~170s}
    C --> D[oracle fetches CoinGecko<br/>BTC USD spot]
    D --> E[LLM layer evaluates evidence<br/>CHALLENGERS_WIN or CREATOR_WINS]
    E --> F[oracle account<br/>resolve_claim]
    F --> G[creator paid on resolve, or the<br/>challenger pulls its share<br/>balances reconcile on-chain]
Loading

The script prints the explorer URL for every transaction so you can verify each step on chain.


Production deploy (Vercel + Railway)

Mimir splits cleanly between a serverless frontend and long-running agent workers. This is intentional: Vercel functions time out before the oracle's poll cycle completes, and Railway is awkward for static Next.js. The two-platform split lets each piece run where it fits.

flowchart LR
    subgraph github[GitHub]
        REPO[main branch]
    end

    subgraph vercel[Vercel]
        FE[Next.js app + /api routes]
    end

    subgraph railway[Railway]
        WK[npm run workers<br/>oracle + market-creator + council<br/>auto-restart]
    end

    subgraph neon[Neon]
        DB[(Postgres pooler)]
    end

    subgraph stellar[Stellar Testnet]
        CT[mimir-market · Soroban]
    end

    REPO -->|push| FE
    REPO -->|push| WK
    FE <-->|reads| DB
    WK -->|reads + writes| DB
    FE -->|Soroban RPC + Horizon| CT
    WK -->|Soroban RPC + writes| CT
Loading

Vercel: frontend

  1. Import the repo at vercel.com/new. Framework auto-detects as Next.js.
  2. Settings -> Environment Variables, add (at minimum):
    • NEXT_PUBLIC_STELLAR_MARKET_CONTRACT_ID, NEXT_PUBLIC_STELLAR_SQUAD_CONTRACT_ID
    • NEXT_PUBLIC_STELLAR_USDC_SAC_ID, NEXT_PUBLIC_STELLAR_USDC_ISSUER
    • NEXT_PUBLIC_STELLAR_NETWORK, NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE
    • NEXT_PUBLIC_STELLAR_RPC_URL, NEXT_PUBLIC_STELLAR_HORIZON_URL (set providers — the public endpoints are rate-limited)
    • SELLER_ADDRESS, PASS_SECRET, COUNCIL_<SLUG>_ADDRESS x10 (public G… addresses only, never seeds)
    • DATABASE_URL (Neon pooler URL, optional)
  3. Push to main. Build takes ~60s. vercel.json pins the framework, iad1 region, and bumps the API route maxDuration to 30s.

Railway: agent workers

  1. New Project -> Deploy from GitHub repo -> pick this repo.
  2. Variables, add everything Vercel has plus the agent seeds:
    • STELLAR_ORACLE_SECRET, CREATOR_SECRET
    • COUNCIL_<SLUG>_SECRET x10
    • at least one LLM API key from .env.example
    • AUTO_CHALLENGE=1 (optional: enables Kelly auto-staking)
  3. railway.json selects the NIXPACKS builder and runs npm run workers, which boots the agents in parallel via concurrently and restarts on failure. Logs are prefixed oracle:, creator:, and council:.

Neon Postgres (optional)

  1. console.neon.tech -> New Project (free tier).
  2. Copy the pooler connection string; it already includes ?sslmode=require.
  3. Paste as DATABASE_URL into both Vercel and Railway.
  4. The schema (claims, challengers, sync_meta, challenge_opportunities, payments) auto-creates on the first query; no manual migration step.

Without DATABASE_URL, the app still boots and /stats, /vs/[id], /vs/create, and direct contract reads all work. Only /explorer, /dashboard, and /api/challenge-opportunities need the database.


Configuration reference

Every env var lives in .env.example. Quick reference:

Variable Required by Notes
NEXT_PUBLIC_STELLAR_MARKET_CONTRACT_ID frontend + agents The C… market contract; set by npm run deploy:contract
NEXT_PUBLIC_STELLAR_SQUAD_CONTRACT_ID frontend + agents The C… squad-pool contract; set by npm run deploy:contract
NEXT_PUBLIC_STELLAR_RPC_URL frontend + agents Soroban RPC. Defaults to the public https://soroban-testnet.stellar.org, which is rate-limited: set a provider in any deployed environment
NEXT_PUBLIC_STELLAR_HORIZON_URL frontend + agents Horizon. Same caveat as the RPC; x402 proof verification reads through it
NEXT_PUBLIC_STELLAR_NETWORK / _NETWORK_PASSPHRASE frontend + agents testnet / Test SDF Network ; September 2015. The passphrase is what a signature actually commits to
NEXT_PUBLIC_STELLAR_USDC_SAC_ID frontend + agents Circle Testnet USDC's Stellar Asset Contract id. Derived from the issuer (stellar contract id asset), never chosen
NEXT_PUBLIC_STELLAR_USDC_ISSUER frontend + agents Circle's official Testnet USDC issuer (G…); the SAC id is derived from it
STELLAR_PLATFORM_FEE_BPS / STELLAR_AGENT_OWNER_FEE_BPS deploy Initial fee policy at initialize; default 200 / 0, and the 1000 bps cap is enforced there
X402_NETWORK paid endpoints CAIP-2 id; default stellar:<NEXT_PUBLIC_STELLAR_NETWORK>
X402_PAYMENT_MAX_AGE_MS paid endpoints How old a Stellar payment proof may be; default 300000 (5 min)
SELLER_ADDRESS paid endpoints Default payee G… (usually the oracle); council routes override it per request
MIMIR_FEATURE_BYOA_FUNDED_ACTIONS agent API 1 lets registered agents call funded actions (createMarket, stake, vote)
MIMIR_FEATURE_COPY_TRADING copy routes 1 enables copy-trading execution
MIMIR_FEATURE_AGENT_BASKETS basket routes 1 enables basket composition and following
MIMIR_FEATURE_FEE_POLICY settlement 1 enforces the fee policy; needs the audited mimir-market escrow deployed first
MIMIR_PAUSE_<CAPABILITY> ops 1 pauses one capability (stake, create_market, copy_execution, x402_selling, ...); MIMIR_PAUSE_ALL=1 for all. Withdrawals are never pausable. Full list and behavior: docs/INCIDENT_KILL_SWITCHES.md
MIMIR_DISABLE_CATEGORY_<ID> ops 1 stops new markets in one category without a deploy
SPEND_PERMISSION_SPENDER agent API Mimir's spender (G… or C…) for BYOA spend permissions; unset means funded actions cannot be delegated
DATABASE_URL optional (Neon) Read-index cache. Pages that need it fail gracefully if absent
CRON_SECRET optional Vercel cron shared secret
NEXT_PUBLIC_FEATURE_XMTP optional Toggle the XMTP inbox feature
Variable Required by Notes
MIMIR_REQUIRE_ARTIFACT_PROVENANCE deploy / CI 1 forces release-mode Wasm digest pins before deploy
STELLAR_DEPLOYER_SECRET deploy Deploys and initializes the contracts; becomes the owner role
STELLAR_ORACLE_SECRET oracle (worker) The oracle's local S… seed; its G… address is the contract's oracle role
CREATOR_SECRET market-creator (worker) The market-creator's local S… seed
SELLER_ADDRESS web server Default recipient (G…) for paid-endpoint payments (usually the oracle address)
PASS_SECRET web server HMAC secret signing council subscription passes
LLM API keys agents At least one LLM API key; see .env.example for accepted variable names
LLM_PROVIDER optional Optional model routing override for worker-only deployments
ORACLE_LLM_MODEL optional Optional model name override
ORACLE_LLM_THROTTLE_MS oracle Min delay between oracle LLM calls; default 8000
ORACLE_SETTLEMENT_DELAY_MS oracle Delay between multiple expired settlements in one poll; default 900000 (15 min)
AUTO_CHALLENGE oracle (worker) 1 to enable Kelly auto-stake
CHALLENGE_STAKE_USDC oracle (worker) Min stake per auto-challenge, in USDC (default 2)
CHALLENGE_CONFIDENCE oracle (worker) Min LLM confidence % to auto-stake (default 80)
MAX_CLAIMS_PER_RUN market-creator Max candidates created per run; creation is paced by MARKET_CREATE_DELAY_MS
MARKET_CREATE_DELAY_MS market-creator Delay between opening approved markets; default 600000 (10 min)
MARKET_CANCEL_DELAY_MS market-creator Delay between stale-market cancels; default 60000 (1 min)
MARKET_CREATOR_PREFLIGHT market-creator 1 to force paid council preflight; also enabled when MIMIR_BASE_URL is set
MARKET_CREATOR_PREFLIGHT_* market-creator Paid council preflight score, cap, persona list, and pacing controls
MIMIR_BASE_URL oracle, market-creator Public app URL for paid council vote/preflight endpoints
COUNCIL_<SLUG>_SECRET council (worker) Per-persona local S… seed; created by npm run agents:create-wallets
COUNCIL_<SLUG>_ADDRESS council (worker, UI) Per-persona G… address; used to label on-chain events with the right persona
COUNCIL_POLL_INTERVAL_MS council (worker) Cycle interval (default 180000 = 3 min)
COUNCIL_MAX_CLAIMS council (worker) Max claims per cycle, deadline-sorted (default 1). Raise only with paid quota.
COUNCIL_DECISION_DELAY_MS council (worker) Delay between persona decisions/stakes; default 30000
COUNCIL_LLM_THROTTLE_MS council (worker) Min ms between LLM calls (default 8000)
COUNCIL_PEER_READS council (worker) 1 lets personas buy other personas' reasoning over x402 before deciding
COUNCIL_PEER_READS_PER_PERSONA council (worker) Peer reads bought before each persona decision; default 2
COUNCIL_PEER_READ_DELAY_MS council (worker) Delay between peer-read nanopayments; default 15000
COUNCIL_PEER_READ_CAP_USDC council (worker) Max accepted quote per peer read, in USDC; default 0.003
COUNCIL_SETTLEMENT oracle 1 settles by council tally instead of the solo oracle verdict
COUNCIL_QUORUM oracle Min decisive juror votes before the council verdict is used; default 3
COUNCIL_VOTE_CAP_USDC oracle Max accepted quote per settlement vote, in USDC; default 0.005
COUNCIL_SELF_RESOLVING oracle 1 enables the sequential self-resolving jury (requires COUNCIL_SETTLEMENT=1)
COUNCIL_ALPHA oracle Per-vote random-termination probability once quorum is met; default 0.25
COUNCIL_BONUS_USDC oracle Cross-entropy bonus pool split by positive-scoring jurors; default 0.01

Scripts

Command What it does
npm run dev Next.js dev server
npm run build / npm start Production build / serve
npm run typecheck tsc --noEmit across app, workers and scripts
npm run check:terms Forbidden-terms lint (keeps pre-Stellar chain names and bespoke-402 residue out)
npm run test:contracts cargo test --release over contracts-soroban
npm run check:toolchains Pinned release toolchain, contract MSRV and CI toolchain matrix agree (no Rust needed)
npm run workers Run all agent workers in parallel (Railway entry point: oracle + market-creator + council + sync + traders)
npm run oracle Run only the oracle (settler; optionally AUTO_CHALLENGE=1)
npm run market-creator Run only the market-creator
npm run council Run only the 10-persona Mimir Council worker
npm run sync Run the chain-to-Neon sync worker
npm run stellar:keys Deployer + oracle keypairs, Friendbot-funded, with a USDC trustline
npm run stellar:usdc Testnet USDC faucet helper
npm run stellar:bindings Regenerate the contract bindings after a contract change
npm run agents:create-wallets Generate 12 local Stellar keypairs (oracle + creator + 10 council) into .env.local
npm run agents:fund Fund agent accounts from a master seed (the faucets top up the master only)
npm run agents:funding-plan Dry-run the funding distribution without sending
npm run agents:balances Print oracle + creator + council USDC and XLM balances
npm run deploy:contract Build, deploy and initialize the Soroban contracts on Stellar Testnet
npm run verify:deployment Check the deployed contract ids, WASM hash and initialized config
npm run verify:artifacts Verify / pin Soroban Wasm digests against deploy/contract-artifacts.manifest.json (no secrets)
npm run verify:analytics Check the analytics gates the launch gate requires
npm run backup:read-index Dump the Neon read-index cache to a self-verified checksummed archive (--out <path> or stdout; needs DATABASE_URL, no seeds)
npm run verify:cache-backup Verify a cache-backup archive offline — no DATABASE_URL, no network, byte-reproducible
npm run restore:read-index Restore a verified archive into the Neon read-index and fingerprint-check the result (--dry-run to preview)
npm run smoke:onchain On-chain smoke test against the live deployment (:full adds resolve + squad)
npm run smoke:x402 / :http Payment-scheme smoke against live Testnet / a full HTTP round trip
npm run load:x402 Offline load test: fixture verification + settle/replay limits
npm run load:rate-limit Offline load test: the API rate limiter under mixed traffic
npm run test:smoke Node-native smoke tests (API validation, XMTP, db-index, etc.)
npm run test:research Research adapters, categories, SSRF guard, x402 discovery suites
npm run test:baskets Basket validation, virtual NAV and high-water fee suites
npm run test:squad Squad view and pool suites
npm run test:schema Schema backlog suites
npm run test:kill-switches Every incident kill switch at its enforcement point, offline
npm run warm:vs-index Rebuild the Neon read-index from current on-chain state
npm run seed / npm run seed:dry Seed demo claims (live / dry-run)
npx tsx scripts/demo-full-cycle.ts Full create -> challenge -> settle demo in ~90s
npx tsx scripts/check-claim.ts <id> Print a claim's state and deadline

Game modes roadmap

Mimir ships the core claim-market primitive: a creator stakes one side, challengers stake the counter-side, and settlement releases the funded pot to the winning side. The contract already supports odds_mode (pool or fixed), max_challengers, challenger stake sizing, rematches (via parent_id), and private invite links, so the modes below are mostly product policy before deeper contract changes.

Mode Status Rules in one line
Pool Market live Pari-mutuel: challengers share the creator stake proportionally; creator wins everything if right. Rewards contrarian conviction; prices crowd consensus.
Duel / 1v1 live One creator, one challenger, matched stake, winner takes the two-person pot. max_challengers = 1 with equal stake enforced (DuelNeedsEqualStake), "Accept Duel" CTA, rematch flow.
Fixed Odds contract-ready Creator guarantees a challenger return multiple backed by creator liquidity; predictable payout before joining.
Underdog discovery product layer Badges, sorting and upside previews for crowded-side contrarians.
Rematch ladder partial (parent_id) Settled claim spawns the next round; social loop for rivalries.
Streak scoring read-index first Consecutive-win streaks surfaced on profiles before any contract change.
Conviction mode future scoring layer Scores how early and how strongly you backed a side, not just whether you were right. Kept transparent and secondary to actual payouts.

Recommended rollout order: improve Pool Market legibility, harden Fixed Odds UI, add Underdog discovery, expand the Rematch Ladder, prototype Streak and Conviction as read-index features, then finish Squad vs Squad — contracts-soroban/mimir-squad already implements two-sided pools with pull payouts, so the remaining work is product surface rather than accounting.


Design principles

These show up in PR review and shape what we accept:

  1. Contract state is source of truth. The Postgres read-index is a cache. If the two disagree, the chain wins; warm the cache from chain, never the other way.
  2. Agent seeds stay in the worker. S… seeds live only in Railway/worker env (STELLAR_ORACLE_SECRET, CREATOR_SECRET, COUNCIL_*_SECRET). The web server holds public G… addresses only. New agent writes go through lib/agent-wallets.ts. BYOA keys never leave the agent owner's infrastructure.
  3. Trust through process, not branding. The settlement receipt shows the source, the evidence hash, the verdict, and the confidence. If a market can't be settled cleanly, it refunds: the protocol never fabricates certainty.
  4. Fees on profit only. No fee schedule may be able to make a winner receive less than their principal, and refunds are always full.
  5. Narrow claims over expressive chaos. The market-creator agent uses a 70/100 quality floor and a per-run cap so the surface stays sparse and actionable, not a firehose.
  6. Legibility over magic. Every async path that takes more than ~5s (LLM call, ledger confirmation) surfaces progress in the UI or the worker logs.
  7. Refund the ambiguous. Draw and Unresolvable are first-class verdicts that return stakes. Better to be inconclusive and refund than to be wrong and pay out.
  8. A pull is not a worse push. Challenger settlement, parked payouts and accrued fees are all collected by their owner, because a transaction's ledger-entry footprint cannot fit 100 payouts and because one frozen trustline must not be able to fail everyone else's settlement. Nothing expires, and the last claimant absorbs the dust.
  9. Strkeys are compared exactly. G…/C… addresses are case-sensitive base32. Never lowercase one to normalise it — that is the EVM reflex, and in an allowlist written the wrong way round it fails open.

License

AGPL-3.0: see LICENSE.

Mimir is source-available. You can use, study, modify, and share it freely. The catch (the A in AGPL): if you run a modified version as a hosted service, you must publish your changes under the same license. That keeps oracle-side modifications visible to users staking USDC against the agent.

  • Malformed evidence fails closed. A supplied evidence_hash must be exactly 32 bytes (64 hexadecimal characters, with an optional 0x prefix) or the client rejects it before signing and submission. Omission keeps the existing zero-hash sentinel for compatible callers, while the Soroban ABI independently enforces BytesN<32>. This is a client validation-only rollout: it changes no storage, fees, escrow, payouts, permissions, or secrets; requires no migration or redeployment; and can be rolled back with the client change alone.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages