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: Pretium | Kenya-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 Bridge | Aggregates 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 Search | Three 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 table | Stores 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 table | Tracks 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 table | Separate 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: WalletConfig | Added 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: TelegramProfile | Added auto_trade_enabled, auto_trade_limit, and preferred_language fields. |
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
pending | Record created in DB, STK push not yet sent |
processing | STK push sent to user's phone — awaiting PIN entry |
payment_confirmed | MPesa payment received by Pretium (first webhook). USDC not yet released. |
completed | USDC released to wallet (second webhook with is_released: true). real_balance_usdc credited. |
failed | STK 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:
| Currency | Symbol | Country |
| KES | KSh | Kenya |
| NGN | ₦ | Nigeria |
| GHS | GH₵ | Ghana |
| UGX | USh | Uganda |
| TZS | TSh | Tanzania |
| ZAR | R | South 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.
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 Protocol | Most 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 & others | Fallback routes LI.FI may select if Across is congested. Always delivers native USDC. |
BridgeTransaction statuses
pending | Record created, tx not yet submitted by user |
bridging | Tx confirmed on Base, waiting for Polygon arrival |
done | USDC arrived on Polygon ✓ — linked real trade fires |
failed | Bridge timed out or LI.FI returned FAILED |
refunded | LI.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.
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 attack | The tx_hash field is UNIQUE in the Payment table. A second submission of the same hash is immediately rejected. |
| Failed transaction | The verifier checks receipt.status == 1. A failed on-chain tx still has a hash — it is rejected. |
| Wrong recipient | The USDC Transfer event is parsed and the to address is checked against NORT_TREASURY_ADDRESS. Payments sent elsewhere are rejected. |
| Underpayment | The transferred amount is checked against the required amount. Underpayments are rejected. |
| Auth requirement | POST /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. |
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 type | Query template | Results |
| News | "{market question}" odds OR prediction OR analyst 2026 | 6 results |
| Social / Sentiment | "{market question}" reddit OR twitter OR sentiment OR community opinion | 5 results |
| Context / Background | "{market question}" explained OR background OR history OR resolution | 4 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.
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 trade | MIN_REAL_TRADE_USDC (default: $1.00 USDC) |
| Maximum trade | MAX_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_bridge | Trade created, waiting for USDC to arrive on Polygon via bridge |
pending_execution | Bridge done, CLOB order submission in progress |
open | CLOB order placed on Polymarket — market not yet resolved |
closed | Market resolved — P&L calculated and credited/debited |
failed | Bridge 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.
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.
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.