git info NORT v2 — Full Documentation

What's New in v2 NORT v2.0

NORT v2 moves beyond paper trading. The core signals engine, AI advice, and dashboard from v1 remain intact — v2 adds a real-money layer on top. Users can fund their account with KES via MPesa through Pretium, bridge USDC from Base to Polygon via LI.FI, and execute real Polymarket CLOB trades — all from the same Telegram bot and dashboard.

v2 Overview

Everything in v1 still works unchanged. v2 adds these capabilities on top:

  • Pretium On-Ramp / Off-Ramp — Users in Kenya can deposit KES via MPesa STK push and receive USDC on Base. They can also withdraw USDC back to KES via MPesa. Pretium handles the KES←”USDC conversion and sends webhooks to NORT when funds arrive.
  • USDC Bridge via LI.FI — Polymarket requires USDC on Polygon. NORT uses LI.FI to move USDC from Base to Polygon before each real trade. The bridge polls for completion and auto-triggers the trade once funds arrive.
  • x402 Micro-Payments — Premium AI advice is gated behind an x402 payment. Users submit a transaction hash as proof of payment; the backend verifies it on-chain before generating premium advice.
  • Real On-Chain Trading — A server-controlled Polygon wallet places real Polymarket CLOB orders on behalf of users. Real balance is tracked per user in WalletConfig.real_balance_usdc. Users switch between paper and real mode explicitly.
  • Enriched AI Advice (OpenClaw) — The advice agent now runs three parallel Tavily web searches (news, social sentiment, background context) before building the prompt — giving the AI grounded, current information to reason from.
  • FX Rate Layer — All USDC balances are displayed in the user's local currency (KES, NGN, GHS, UGX, TZS, ZAR, EUR, GBP) using a server-side rate cache that refreshes every 60 seconds.
  • Dual Leaderboard — The leaderboard now has separate paper and real modes (?mode=paper or ?mode=real). XP, levels, and achievements are tracked independently.
  • Auto-trade setting — Users can enable auto_trade_enabled and set an auto_trade_limit in their TelegramProfile. This stores their preference for automatic trade execution within a set limit.

Paper Mode Is Still the Default

v2 does not remove paper trading. Users switch between paper and real mode via POST /wallet/mode with mode=real and confirmed=true. Paper mode is the default for all new users — nothing about the existing experience changes unless the user actively switches.

The Money Flow

In v2, two separate money flows exist. They are completely independent:

Deposit Flow (KES ←’ USDC) User initiates via Pretium. MPesa STK push fires. KES is collected. Pretium converts to USDC and deposits to the user's Base wallet. Pretium sends a webhook to NORT. NORT credits real_balance_usdc in the DB once Pretium confirms asset release.
Trade Execution Flow (Base ←’ Polygon ←’ Polymarket) User places a real trade. Backend calls LI.FI to bridge USDC from Base to Polygon (~1–2 minutes). Once bridge status is done, the backend places a CLOB order on Polymarket using the server wallet. Returns a Polymarket order ID and Polygon tx hash.
x402 Payment Flow (Base ←’ NORT Treasury) User sends USDC on Base to the NORT treasury address. Submits the transaction hash via POST /x402/verify. Backend checks the hash on-chain and unlocks premium advice. Stays entirely on Base — never crosses to Polygon.
Withdrawal Flow (USDC ←’ KES) User initiates an off-ramp via Pretium. USDC is deducted from real_balance_usdc upfront. Pretium converts and sends KES via MPesa. On webhook confirmation, the deduction is final. On failure, USDC is refunded.

Architecture Changes

The three-layer architecture from v1 is unchanged. v2 adds three external integrations and several new database tables:

New: PretiumKenya-based KES←”USDC on-ramp/off-ramp. Sends webhooks to POST /api/pretium/webhook. Manages STK push for deposits and MPesa disbursement for withdrawals.
New: LI.FI BridgeAggregates cross-chain bridge routes (Across, CCTP, Stargate). Called by the bridge API before every real trade. Polled asynchronously in a background task until DONE/FAILED.
New: Tavily SearchThree parallel web searches run before every advice call — news, social sentiment, and background context. Results are injected into the OpenClaw prompt for grounded analysis.
New: PretiumTransaction tableStores every on-ramp and off-ramp transaction with full lifecycle — pending ←’ processing ←’ payment_confirmed ←’ completed (for on-ramp) or pending ←’ processing ←’ completed (for off-ramp).
New: BridgeTransaction tableTracks every LI.FI bridge from Base ←’ Polygon. Statuses: pending ←’ bridging ←’ done / failed / refunded. Linked to a RealTrade via real_trade_id so bridge completion auto-fires the trade.
New: RealTrade tableSeparate from PaperTrade — stores real on-chain trades with Polymarket order ID, Polygon tx hash, shares, price per share, P&L, and settlement status. Never mixed with paper history.
Updated: WalletConfigAdded real_balance_usdc (user's real USDC balance tracked in DB) and trading_mode ("paper" or "real"). paper_balance and real_balance_usdc are tracked independently.
Updated: TelegramProfileAdded auto_trade_enabled, auto_trade_limit, and preferred_language fields.

Pretium On-Ramp / Off-Ramp

Pretium is NORT's KES←”USDC conversion partner. It enables users in Kenya to deposit Kenyan Shillings via MPesa and receive USDC on Base, and to convert USDC profits back to KES sent directly to their phone. NORT never touches fiat — Pretium handles all KES processing.

What Pretium Is

Pretium is an African crypto on-ramp/off-ramp API. It accepts mobile money deposits (MPesa Safaricom in Kenya), converts them to USDC, and deposits directly to a provided wallet address on the configured chain (Base by default). For off-ramp, it accepts USDC and disburses KES via MPesa.

Key Pretium credentials configured in .env:

PRETIUM_API_KEY=...
PRETIUM_API_SECRET=...
PRETIUM_CHAIN=BASE
PRETIUM_ASSET=USDC
PRETIUM_FIAT_CURRENCY=KES
PRETIUM_MOBILE_NETWORK=Safaricom
PRETIUM_WEBHOOK_BASE_URL=https://your-backend-url

The backend wraps all Pretium calls in services/backend/core/pretium_service.py and pretium_client.py. The API routes are registered under services/backend/api/pretium.py.

On-Ramp (KES ←’ USDC)

An on-ramp converts KES to USDC and deposits to the user's Base wallet. The full flow:

  • User provides their phone number, amount in KES, and wallet address
  • Backend fetches the current KES/USDC exchange rate from Pretium to estimate how much USDC the user will receive
  • A PretiumTransaction record is created with status pending
  • Backend calls Pretium's on-ramp endpoint — this automatically triggers an MPesa STK push to the user's phone
  • The user enters their MPesa PIN on their phone to authorise the payment
  • Pretium sends two webhooks: first when payment is confirmed, then when USDC is released to the wallet
  • On the second webhook (is_released: true), NORT credits real_balance_usdc in the user's WalletConfig

Phone Number Normalisation

Phone numbers are normalised to E.164 format (+254XXXXXXXXX) before API calls. Pretium's on-ramp API expects local shortcode format (07XXXXXXXX), so NORT converts automatically. Inputs like 0712345678 or 254712345678 are both accepted.

On-ramp transaction statuses
pendingRecord created in DB, STK push not yet sent
processingSTK push sent to user's phone — awaiting PIN entry
payment_confirmedMPesa payment received by Pretium (first webhook). USDC not yet released.
completedUSDC released to wallet (second webhook with is_released: true). real_balance_usdc credited.
failedSTK push declined, timeout, or Pretium error. No balance change.

Off-Ramp (USDC ←’ KES)

An off-ramp converts the user's tracked USDC balance back to KES sent to their phone. The flow:

  • User provides amount in USDC, phone number, and the on-chain transaction hash proving they sent USDC to Pretium's settlement wallet
  • Backend checks the user's real_balance_usdc — if insufficient, the request is rejected immediately
  • The USDC amount is deducted upfront before the Pretium API call, to prevent double-spend
  • Backend calls Pretium's off-ramp (pay) endpoint, which initiates an MPesa disbursement to the user's phone
  • On success webhook, the deduction is confirmed final
  • On failure webhook, the USDC is refunded back to real_balance_usdc

Off-ramp requires an on-chain tx first

For the off-ramp, the user must first send USDC from their wallet to Pretium's settlement wallet address on the configured chain. The transaction_hash of that on-chain send is submitted as proof. Pretium will only disburse KES after verifying the on-chain transfer.

Webhooks & Balance Credit

Pretium sends POST webhooks to /api/pretium/webhook. The backend processes two distinct webhook types:

Payment Confirmation Webhook Sent when Pretium receives the MPesa payment. Contains status: "COMPLETE" and a receipt_number. For on-ramps, this moves status to payment_confirmed — USDC not yet released. For off-ramps, this is the final confirmation — status moves to completed.
Asset Release Webhook On-ramp only. Sent when USDC is deposited to the user's wallet. Contains is_released: true and transaction_hash. NORT uses this as the trigger to credit real_balance_usdc. Status moves to completed.
Failure / Cancellation Webhook status: "FAILED" or "CANCELLED". For off-ramps, the deducted USDC is refunded automatically. For on-ramps, the status is set to failed and no balance changes.

All webhooks are processed idempotently — if the same webhook arrives twice (Pretium retries on non-200 responses), the second call is a no-op because the status check guards against regressing from a terminal state.

FX Rates & Local Currency Display

All on-chain values are stored as USDC internally. The GET /fx/rates endpoint converts these to the user's local currency before display. The FX cache is refreshed every 60 seconds from ExchangeRate-API (with hardcoded fallbacks if the API is unreachable).

Supported currencies and their symbols:

CurrencySymbolCountry
KESKShKenya
NGN₦Nigeria
GHSGH₵Ghana
UGXUShUganda
TZSTShTanzania
ZARRSouth Africa
EUR€Eurozone
GBP£United Kingdom

A balance of 50 USDC shows as KSh 6,475 for a Kenyan user — immediately meaningful, not raw crypto figures. The Pretium on-ramp also uses the current buying rate from Pretium itself (fetched before each transaction) to show the user an accurate estimate of USDC received before they confirm the STK push.

USDC Bridge (LI.FI)

Polymarket runs on Polygon. User funds are on Base. Before any real trade can execute, USDC must move from Base to Polygon. NORT v2 uses LI.FI — a bridge aggregator that auto-selects the fastest, cheapest route across Across, CCTP, Stargate, and others.

Why We Bridge

Polymarket's CLOB requires native USDC on Polygon — the Circle-issued token at 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359. The USDC on Base is a different contract. You cannot send Base USDC to Polymarket directly — it uses the wrong token and the transaction will fail.

LI.FI is used because it abstracts away the complexity of choosing a bridge: one API call returns a ready-to-sign transaction that, when submitted, automatically moves USDC via the best available route and delivers native USDC on Polygon.

Never USDC.e

USDC.e is the old bridged version of USDC on Polygon. Polymarket only accepts native USDC. LI.FI routes are always configured to deliver the correct token.

Bridge Flow

The bridge is split across two API calls to keep the user-facing response fast:

# 1. Get a quote (cached 30s per wallet+amount)
GET /bridge/quote?wallet_address=0x...&amount_usdc=50
←’ Returns LI.FI quote + transactionRequest to sign

# 2. User signs the transaction via Privy and submits to Base.
#    Then notifies the backend with the tx hash:
POST /bridge/start
{ "wallet_address": "0x...", "amount_usdc": 50, "lifi_tx_hash": "0x..." }
←’ Creates BridgeTransaction record, starts background polling
←’ Returns: { "bridge_id": 42, "status": "bridging" }

# 3. Poll for completion (frontend polls every 10s)
GET /bridge/status/42
←’ Returns current status: pending | bridging | done | failed | refunded

# When status == "done" ←’ the linked real trade fires automatically

Background polling is handled by wait_for_bridge() in core/bridge.py, which runs as a FastAPI BackgroundTask so it never blocks the API response. It polls LI.FI every 15 seconds, with a 10-minute timeout.

Route Selection & Bridge Statuses

LI.FI evaluates all available routes and returns the best one. Common routes for Base ←’ Polygon:

Across ProtocolMost commonly selected. Relayer fronts funds; user receives native USDC on Polygon in ~1–2 minutes. Small relayer fee (~0.1–0.2%).
CCTP (Circle)Burns USDC on Base, mints native USDC on Polygon. Zero slippage, zero bridge fee, but 15–20 minute attestation time. Selected for low-urgency or large amounts.
Stargate & othersFallback routes LI.FI may select if Across is congested. Always delivers native USDC.
BridgeTransaction statuses
pendingRecord created, tx not yet submitted by user
bridgingTx confirmed on Base, waiting for Polygon arrival
doneUSDC arrived on Polygon ✓ — linked real trade fires
failedBridge timed out or LI.FI returned FAILED
refundedLI.FI issued a refund back to Base

Rate limit handling: LI.FI's public API allows ~200 requests per 2 hours. The backend applies a 30-second quote cache per wallet+amount pair and an asyncio.Semaphore(3) to cap concurrent LI.FI calls. A BridgeRateLimitError triggers a 60-second backoff before retrying.

Failure & Refund Handling

If a bridge transaction fails or is refunded by LI.FI, NORT handles both cases automatically:

  • The BridgeTransaction status is updated to failed or refunded
  • If the bridge was linked to a RealTrade (via real_trade_id), the trade is marked failed
  • The USDC amount is refunded back to the user's real_balance_usdc in the DB automatically

On-chain refund is handled by LI.FI

The actual USDC refund on-chain is handled by the bridge protocol (LI.FI / Across). NORT's DB refund only restores the user's tracked balance — it does not send any on-chain transaction.

x402 Payments

x402 is an HTTP-native micro-payment standard used to gate premium AI advice. Users pay per premium query by submitting a transaction hash as proof. The backend verifies the hash against the blockchain before generating the advice — no subscriptions, no API keys.

What x402 Is

The name comes from HTTP status code 402 ("Payment Required"). When a user requests a premium resource without a confirmed payment, the backend returns a 402 response with payment instructions. The user pays on-chain, then re-submits the request with the transaction hash as proof. The backend verifies and unlocks the resource.

In NORT, x402 gates /agent/advice when premium: true is set. The verification endpoint is POST /x402/verify. Payment must be in USDC sent to the NORT treasury wallet on Base.

x402 stays on Base

x402 payments never cross to Polygon. They settle entirely on Base — from the user's wallet to NORT's treasury. They are completely independent of the trade execution flow.

Verify Flow

The verification endpoint (POST /x402/verify) accepts a proof and user identifier, then runs on-chain checks. The request body:

POST /x402/verify
Authorization: Bearer <privy_jwt>
{
  "proof": "0xabc123...",           // transaction hash of the USDC payment
  "wallet_address": "0x...",        // user's wallet (falls back to JWT wallet)
  "market_id": "market-001"         // optional: locks the proof to a specific market
}

The verifier in core/x402_verifier.py checks:

  • Fetches the transaction receipt from Base using the provided hash
  • Confirms receipt.status == 1 (transaction succeeded, not reverted)
  • Parses the USDC Transfer event — checks the to address matches the NORT treasury
  • Checks the transferred amount meets the required minimum
  • Checks the hash has not been used before (unique constraint in the Payment table)

If all checks pass, a confirmed Payment record is stored and the advice call is allowed through. has_premium_access() is called inside the advice route to check whether the user has a valid payment on file before calling OpenClaw.

Security & Edge Cases

Replay attackThe tx_hash field is UNIQUE in the Payment table. A second submission of the same hash is immediately rejected.
Failed transactionThe verifier checks receipt.status == 1. A failed on-chain tx still has a hash — it is rejected.
Wrong recipientThe USDC Transfer event is parsed and the to address is checked against NORT_TREASURY_ADDRESS. Payments sent elsewhere are rejected.
UnderpaymentThe transferred amount is checked against the required amount. Underpayments are rejected.
Auth requirementPOST /x402/verify requires a valid Privy JWT (get_current_user). The JWT's wallet overrides the wallet_address param if they differ, preventing users from submitting proofs on behalf of others.

OpenClaw AI Agent

OpenClaw is NORT's AI advice layer. In v2 it is significantly upgraded with three parallel Tavily web searches that run before the LLM prompt is built — giving the model real news, social sentiment, and background context to reason from, rather than relying purely on its training data.

How OpenClaw Works

The advice pipeline (POST /agent/advice) runs in four stages:

  • Stage 1 — DB fetch: Market data and the latest AI signal for the market are read directly from the Neon database (not via localhost HTTP calls). This avoids any internal network dependency.
  • Stage 2 — Tavily search (parallel): Three searches fire simultaneously with a 15-second timeout: news query, social/sentiment query, and background/context query. Results are injected into the prompt as named sections.
  • Stage 3 — OpenClaw prompt: A single structured prompt is built from market data + signal + all three search results and sent to OpenRouter. The model is anthropic/claude-3-haiku by default.
  • Stage 4 — Parse: The LLM returns JSON. The parser extracts summary, why_trending, risk_factors, suggested_plan (BUY YES / BUY NO / WAIT), and confidence. A stale data warning is included if the model flagged it.

Premium vs Free Advice

Both free and premium advice calls use the same pipeline. The difference is that premium calls (gated by x402) use the full prompt with all search context and a higher max_tokens budget, while free advice uses a shorter prompt. The backend checks has_premium_access() before deciding which template to use.

Advice response format
{
  "market_id": "0xabc...",
  "summary": "This market has seen significant momentum...",
  "why_trending": "Large volume spike suggests informed traders...",
  "risk_factors": [
    "Market expires in 3 weeks — time decay risk",
    "BTC historically volatile around macro events"
  ],
  "suggested_plan": "BUY YES",   // "BUY YES" | "BUY NO" | "WAIT"
  "confidence": 0.72,
  "disclaimer": "This is not financial advice. Paper trading only.",
  "tool_calls_used": [
    "tavily_news: ...",
    "tavily_social: ...",
    "tavily_context: ..."
  ],
  "stale_data_warning": null      // non-null if AI flagged data as outdated
}

Tavily Search Integration

Tavily is a search API optimised for LLM use. Before each advice call, three searches run in parallel via asyncio.gather() with a combined 15-second timeout:

Search typeQuery templateResults
News"{market question}" odds OR prediction OR analyst 20266 results
Social / Sentiment"{market question}" reddit OR twitter OR sentiment OR community opinion5 results
Context / Background"{market question}" explained OR background OR history OR resolution4 results

If Tavily is unreachable or the timeout is hit, the search sections are set to "Search timed out." and the advice call proceeds anyway — OpenClaw will still analyse the market data and signal, just without real-time web context. The stale_data_warning field in the response flags this case.

Tavily is configured via TAVILY_API_KEY in .env. OpenRouter is configured via OPENCLAW_TOKEN.

Language Support

Each user's preferred language is stored in TelegramProfile.preferred_language (default: "en"). Users can switch to Kiswahili via /language sw and back with /language en. The language preference persists across sessions.

Language selection affects the bot's response formatting and the language in which advice output is delivered. The underlying market data, signal scores, and confidence numbers are always in English — only the user-facing text and AI output language changes.

Real Trading

Real trading in NORT v2 uses a custodial server wallet architecture. NORT operates a single Polygon wallet that holds pooled USDC and places CLOB orders on Polymarket on behalf of users. User balances are tracked individually in the database — the server wallet is the execution layer.

Server Wallet Architecture

Rather than each user having their own Polygon wallet, NORT uses a single server-controlled Polygon wallet (configured via SERVER_POLYGON_WALLET_PK and SERVER_POLYGON_WALLET_ADDRESS in .env). This wallet:

  • Holds pooled USDC bridged from users' Base deposits
  • Signs and submits all Polymarket CLOB orders
  • Polls open orders for settlement results
  • Credits/debits individual user balances in WalletConfig.real_balance_usdc based on trade outcomes

Custodial, not non-custodial

Unlike a non-custodial setup, NORT's server wallet holds and controls the funds used for trading. Users trust NORT to faithfully execute their trades and credit their individual balances correctly. The real balance is a DB record — the on-chain pool belongs to the server wallet.

Real trading is feature-flagged: REAL_TRADING_ENABLED=true must be set in the environment. A REAL_TRADING_BETA_ALLOWLIST (comma-separated wallet addresses) can restrict access during beta rollout. Users also need trading_mode == "real" in their WalletConfig and sufficient real_balance_usdc.

Trade amount limits
Minimum tradeMIN_REAL_TRADE_USDC (default: $1.00 USDC)
Maximum tradeMAX_REAL_TRADE_USDC (default: $50.00 USDC)

Placing a Real Trade

Real trades are placed via POST /real/trade. This requires a valid Privy JWT and the user must be in real mode:

POST /real/trade
Authorization: Bearer <privy_jwt>
{
  "market_id":       "0xabc...",
  "market_question": "Will BTC hit $100k by June?",
  "outcome":         "YES",
  "amount_usdc":     10.0
}

The backend checks in order: real trading enabled ←’ user in real mode ←’ sufficient balance ←’ allowlist (if set) ←’ validates amount bounds. If all pass, place_real_trade() constructs a Fill-or-Kill (FOK) market order and submits it to the Polymarket CLOB API. A FOK order either fills completely at or better than the market price, or is cancelled — no partial fills.

If the order is cancelled by Polymarket, the user's balance is refunded immediately. If it fills, the trade is stored in the RealTrade table with status open and the Polymarket order ID.

Trade Lifecycle & Settlement

Real trades go through these statuses:

pending_bridgeTrade created, waiting for USDC to arrive on Polygon via bridge
pending_executionBridge done, CLOB order submission in progress
openCLOB order placed on Polymarket — market not yet resolved
closedMarket resolved — P&L calculated and credited/debited
failedBridge failed, CLOB rejected, or execution error — balance refunded

Settlement runs automatically in the background. When a market resolves on Polymarket, NORT fetches the outcome and calculates P&L. For a WIN, the payout is credited to real_balance_usdc. For a LOSS, the staked amount was already deducted when the trade was placed — no further change. Users can also trigger a manual settlement check via POST /real/settle for their own open trades.

Mode Toggle

Users switch between paper and real mode via the mode API:

# Check current mode
GET /wallet/mode

# Switch to real mode (requires explicit confirmation)
POST /wallet/mode
{ "mode": "real", "confirmed": true, "wallet_address": "0x..." }

# Switch back to paper
POST /wallet/mode
{ "mode": "paper", "wallet_address": "0x..." }

Switching to real mode requires confirmed: true in the request body — this prevents accidental mode switches. The mode is stored in WalletConfig.trading_mode and persists between sessions. Paper balance and real balance are always tracked independently — switching modes never touches either balance.

Real mode uses real funds

When in real mode, any trade placed via POST /real/trade uses actual USDC from real_balance_usdc. This is not reversible. Ensure you have funded your account via Pretium before enabling real mode.

Leaderboard & Achievements

NORT v2 extends the leaderboard with a real trading mode. Users can now compete on paper P&L and real USDC performance. XP, levels, and achievements are tracked regardless of mode.

Paper vs Real Leaderboard

The leaderboard endpoint accepts a mode query parameter:

  • GET /leaderboard?mode=paper — ranked by paper portfolio value (default, existing v1 behaviour)
  • GET /leaderboard?mode=real — ranked by real USDC balance and real trade P&L

The same applies to personal rank: GET /leaderboard/me?wallet_address=0x...&mode=real. Each entry includes rank, badge, XP, trading streak, win rate, and net P&L for the selected mode.

Paper and real leaderboards are completely independent — a user who is #1 in paper does not automatically rank well in real, and vice versa.

XP, Levels & Achievements

User stats (XP, level, streak) are available at GET /user/stats. Achievements are listed at GET /user/achievements, which returns each achievement with its XP value and whether the user has earned it.

Achievements are earned through trading actions — placing trades, maintaining winning streaks, reaching portfolio milestones, and so on. The full achievement list and XP thresholds are defined in core/leaderboard.py. Total XP and earned count are summarised in the achievements response.

v2 API Reference

All v1 endpoints remain unchanged. v2 adds routes for Pretium on/off-ramp, LI.FI bridge, real trades, x402 verification, mode switching, and extended leaderboard. All endpoints are available both with and without the /api prefix.

Pretium Routes

POST /pretium/onrampInitiate a KES ←’ USDC on-ramp. Triggers MPesa STK push. Body: { telegram_user_id, amount (KES), phone_number, wallet_address }. Returns transaction ID and estimated USDC amount.
POST /pretium/offrampInitiate a USDC ←’ KES off-ramp. Body: { telegram_user_id, amount_crypto, phone_number, wallet_address, transaction_hash }. Deducts USDC immediately, refunds on failure.
GET /pretium/transaction/{id}Get a single Pretium transaction by ID. Returns full lifecycle status and all amounts.
GET /pretium/transactionsList transactions for a user. Query params: telegram_user_id, type (onramp/offramp), limit.
GET /pretium/status/{id}Force-check transaction status against Pretium API (bypasses cache). Updates DB status.
GET /pretium/rateGet current KES/USDC exchange rate from Pretium. Returns buying and selling rates.
POST /pretium/webhookPretium webhook receiver. Handles payment confirmation and asset release events. Called by Pretium, not by clients.

Bridge Routes

GET /bridge/quoteGet a LI.FI quote for bridging USDC from Base ←’ Polygon. Query params: wallet_address, amount_usdc. Cached 30s per wallet+amount. Returns route, estimated output, bridge tool, and estimated time.
POST /bridge/startRecord a bridge transaction hash and start background polling. Body: { wallet_address, amount_usdc, lifi_tx_hash }. Returns bridge_id to poll with.
GET /bridge/status/{id}Get current status of a bridge transaction. Returns status, both tx hashes (sending and receiving sides), elapsed time, and any error message.
GET /bridge/historyList all bridge transactions for a user. Query params: wallet_address or telegram_user_id.

Real Trade Routes

POST /real/tradePlace a real Polymarket CLOB trade. Requires Privy JWT + real mode. Body: { market_id, market_question, outcome, amount_usdc }. Returns the created trade with Polymarket order ID.
GET /real/tradesList all real trades for the authenticated user. Optional filter: ?status=open|closed|failed|pending_execution. Returns summary stats (wins, losses, net P&L) plus full trade list.
GET /real/trade/{id}Get a single real trade by ID. Returns full detail including Polygon tx hash, P&L, and settlement info.
POST /real/settleManually trigger settlement check for the user's open real trades. Useful when the user believes a market has just resolved. Returns count of settled trades.
GET /wallet/server-balanceOperator endpoint — returns the live USDC balance of the server wallet on Polygon. Useful for ensuring the pool is adequately funded.

x402 & Mode Routes

POST /x402/verifyVerify an x402 payment proof. Body: { proof, wallet_address, market_id? }. Fetches tx from Base, validates recipient and amount, checks replay. Stores confirmed Payment record if valid. Requires Privy JWT.
POST /agent/x402/verifyLegacy alias of /x402/verify for backward compatibility. Does not require Privy JWT.
GET /wallet/modeGet the user's current trading mode. Returns trading_mode, real_balance_usdc, and can_switch_to_real. Requires Privy JWT.
POST /wallet/modeSwitch trading mode. Body: { mode: "paper"|"real", confirmed: true, wallet_address }. Switching to real requires confirmed: true. Requires Privy JWT.
POST /wallet/connectRegister or upsert a wallet. No auth required — called on login before JWT is available. Idempotent. Returns paper wallet initialization status.
GET /wallet/summaryGet the user's full wallet summary — paper balance, real balance, open trades, P&L. Accepts wallet_address or telegram_user_id.

Leaderboard Routes

GET /leaderboardFull ranked leaderboard. Query params: limit (max 200), mode (paper|real). Returns ranked list with badge, XP, streak, win rate, and net P&L per entry.
GET /leaderboard/mePersonal rank card. Query params: wallet_address or telegram_user_id, mode. Returns the user's rank, badge, and stats in the selected leaderboard.
GET /user/statsXP, level, and trading streak for a user. Query params: wallet_address or telegram_user_id.
GET /user/achievementsEarned and locked achievements for a user. Returns total XP earned, count of achievements unlocked, and full achievement list with earned status per item.

FX Routes

GET /fx/ratesCurrent USD ←’ local currency exchange rates. Returns rates for KES, NGN, GHS, UGX, TZS, ZAR, EUR, GBP with their symbols. Cache refreshes every 60 seconds. Used by the dashboard to display balances in local currency.

FAQs

Common questions about NORT v2's real trading, Pretium on-ramp, x402 payments, and the bridge.

Real Trading

Is real trading the default in v2?

No. Paper mode is still the default for all users. Real mode must be explicitly enabled via POST /wallet/mode with mode=real and confirmed=true. Users who never change their mode continue on paper exactly as in v1.

Does NORT hold my funds?

Yes — NORT v2 uses a custodial server wallet on Polygon. When you deposit via Pretium, USDC is tracked in your real_balance_usdc in the NORT database. Real trades are executed by the server wallet on your behalf. This is different from a non-custodial setup — you are trusting NORT to faithfully manage your balance.

What happens if a real trade fails?

If the Polymarket CLOB rejects the order or if the bridge fails before the order is placed, the trade is marked failed and your real_balance_usdc is refunded automatically. You do not lose funds on failed orders.

How long does a real trade take end to end?

If using Across as the bridge route (~1–2 minutes), the full end-to-end time from placing the trade to having an open CLOB order on Polymarket is approximately 2–3 minutes. CCTP routes take 15–20 minutes due to Circle's attestation process.

What is the minimum trade size?

The minimum is configured by MIN_REAL_TRADE_USDC (default $1.00 USDC). The maximum is MAX_REAL_TRADE_USDC (default $50.00 USDC per trade). Trades outside these bounds are rejected with a 400 or 402 error before the bridge is called.

Pretium & MPesa

How do I fund my NORT account?

Call POST /pretium/onramp with your phone number, KES amount, and wallet address. Pretium will send an MPesa STK push to your phone. Enter your PIN to confirm. Once Pretium processes the payment and releases the USDC, your real_balance_usdc will be updated automatically via webhook.

How long does the on-ramp take?

The MPesa payment typically confirms within seconds. USDC release by Pretium usually happens within 1–5 minutes. The NORT balance update happens as soon as the asset release webhook arrives.

What if the MPesa STK push fails?

If the STK push times out or is declined, the PretiumTransaction is marked failed via webhook and no balance changes occur. You can initiate a new on-ramp transaction.

Can I withdraw my USDC back to KES?

Yes, via POST /pretium/offramp. You must first send USDC from your wallet to Pretium's settlement address on Base, then submit the transaction hash as proof. Pretium will convert and send KES to your phone via MPesa. The USDC is deducted from your balance upfront and refunded automatically if the off-ramp fails.

What currencies are supported for balance display?

KES, NGN, GHS, UGX, TZS, ZAR, EUR, and GBP. Balances are stored as USDC internally and converted for display using the GET /fx/rates cache, refreshed every 60 seconds.

x402 Payments

What does the x402 payment unlock?

One premium AI advice call. The payment is per-query. The premium tier unlocks a deeper analysis with more search context and extended reasoning compared to the free tier.

Can I reuse a transaction hash for multiple advice calls?

No. Transaction hashes are stored with a UNIQUE constraint in the Payment table (replay protection). The first submission of a hash consumes it — any repeat attempt is rejected with "Proof already used."

Where does the x402 payment go?

To NORT's treasury wallet on Base. x402 payments never leave Base — they are completely independent of the Polygon trading flow.

Is free advice still available in v2?

Yes. POST /agent/advice with premium: false still provides free AI analysis powered by the same OpenClaw pipeline with Tavily search. The x402 gate only applies when premium: true is set.