SurgeFriction

monitoring smart-wallet activity. scoring conviction 0-100. sizing response to signal strength. execution routed via Jupiter. LP Agent checks Meteora ranges. Torque reward event queued

Live signal tape

ISSUE 05 — FIELD NOTES

Building on Torque MCP

BUILD WINDOW · 2026-04-12 → 2026-04-27 · 194 LINES OF NOTES

MCP TOOLS USED

7

CUSTOM EVENTS EMITTED

5

LEADERBOARD UPDATES

live + fallback

TIME TO FIRST EVENT

18m

── Friction log · 6 findings

SPEC GAPT-01

REST API · Discoverability

Three hostnames (`ingest.torque.so`, `server.torque.so`, `platform.torque.so`) and the `x-api-key` header convention are documented only in the bundled MCP CLI source.

Reproduction

  1. Read MCP setup tools — only mention `mcp.json`, no REST.
  2. Try emitting a custom event over HTTP — no docs for the body shape.
  3. Inspect the MCP CLI source to discover `{ eventName, userPubkey, timestamp, data }`.

Fix

Publish a REST quickstart on docs.torque.so with the three hostnames, the auth header, and the canonical event body shape.

MISSINGT-02

MCP · Sandbox / fixture mode

MCP tools require a live API key for any meaningful response — no fixture/mock mode for local development.

Reproduction

  1. Try `create_custom_event` without an API key.
  2. Call fails — no fixture path to keep building offline.

Fix

Return deterministic fixture responses when the API key is missing or `TORQUE_FIXTURE=1` is set.

FRICTIONT-03

MCP · Schema validation on event create

`create_custom_event` accepts any field names without warning on collisions with reserved fields, leading to silent rebate failures.

Reproduction

  1. Create an event with a field named `timestamp`.
  2. Tool succeeds.
  3. Live emits silently produce $0 rebates because `timestamp` is reserved.

Fix

Validate field names against the reserved set at create time and surface a clear error.

FRICTIONT-04

Incentive DSL · Consistency

Raffle, rebate, and leaderboard incentives use three subtly different query DSLs.

Reproduction

  1. Write a leaderboard query referencing `data.amountUsd`.
  2. Translate to a rebate query — same field, different syntax.
  3. Translate to a raffle entry — third syntax.

Fix

Pick one DSL and apply it across all incentive primitives.

FRICTIONT-05

MCP · Schema ID stability

`attach_custom_event` resolves schemas by string name; renaming an event breaks all attached incentives without warning.

Reproduction

  1. Create event `kairos_trade_executed`.
  2. Attach a rebate by name.
  3. Rename the event to `surge_trade_executed` — attached rebates orphan silently.

Fix

Return a stable schema ID from `create_custom_event` and use it for attachment instead of the human-readable name.

FRICTIONT-06

Live integration · 404 detection

When the inferred REST endpoint shape is wrong, fetches return 404 silently and the leaderboard panel just shows fixture data — no signal that the live integration is broken.

Reproduction

  1. Set `TORQUE_API_KEY` and watch the leaderboard.
  2. Endpoint returns 404; UI silently falls back to fixture.
  3. Only catch is reading server logs.

Fix

Surface a one-line `degraded` status pill when the live call 404s, distinct from `fixture`.

Full report · TORQUE-FRICTION.md (194 lines)

Torque MCP — Friction Log

Project: Surge (Signal-to-execution trading intelligence) Developer: @surge-team Date: 2026-04-16 Track: Torque MCP ($3,000 prize)


Setup

MCP Server Installation

claude mcp add torque -- npx @torque-labs/mcp

What worked: The npx @torque-labs/mcp command launches correctly. MCP tools are available in Claude Code after restart.

Friction: The MCP server requires an active Torque API key to do anything meaningful. Without TORQUE_API_KEY set, all tool calls return authentication errors rather than fixture/demo responses. For hackathon development, a sandbox mode that returns mock responses would let builders wire events before obtaining a key.


Event Schema Creation

Used create_custom_event to create 5 event schemas:

  1. surge_trade_executed — fired after every swap, with tokenIn, tokenOut, amountUsd, strategy, signalConviction, txSignature
  2. surge_limit_order_created — fired when a Jupiter limit order is created
  3. surge_dca_created — fired when a Jupiter DCA is started
  4. surge_lp_zap_in — fired after LP Agent zap-in via landing endpoint
  5. surge_lp_zap_out — fired after LP Agent zap-out via landing endpoint

Friction: There is no schema validation feedback at creation time — the event schema is accepted even if field names conflict with reserved Torque fields. Only discovered this after emitting events and seeing unexpected field merging in the dashboard.

Friction: The MCP tool create_custom_event doesn't return the schema ID in a stable format. Subsequent attach_custom_event calls require the schema name string, not an ID, but the docs describe an ID-based approach. Spent time reconciling the two patterns.


Incentive Primitives

Leaderboard

Created via generate_incentive_query:

  • Event: surge_trade_executed
  • Rank by: event count
  • Period: weekly (Mon 00:00 UTC → Sun 23:59 UTC)
  • Top 10 wallets displayed in Surge Rewards panel

Friction: The generate_incentive_query tool produces a SQL-like query fragment that must be submitted separately to create the actual leaderboard. The two-step process (generate → create) is not documented in the MCP tool descriptions — had to discover the create_recurring_incentive tool exists via tab-completion.

Rebate

Created via create_recurring_incentive:

  • Type: rebate
  • Event: surge_trade_executed
  • Rate: 50 bps of amountUsd field per event
  • Settlement: weekly

Friction: The amountUsd field in the rebate formula must match exactly the field name in the event data object. There is no validation that the referenced field exists in the event schema — a typo silently results in $0 rebates.

Raffle

Created via create_recurring_incentive:

  • Type: raffle
  • Event: surge_trade_executed
  • Eligibility: wallets with ≥3 events in the current period
  • Draw: weekly

Friction: Raffle eligibility conditions use a different DSL than the leaderboard query. The documentation conflates the two. Found the correct syntax only by reading the MCP tool's schema definition in the returned JSON.


API Endpoint Discovery

The MCP tools cover setup (event schema creation, incentive creation). For emitting events from the application, a REST API is needed.

Friction (critical): The Torque MCP documentation does not specify the REST API endpoint for event emission. The tools assume events are emitted via another Torque SDK, but for a Next.js app calling server-side routes, we need an HTTP endpoint.

First attempt (wrong): Based on the brand domain we guessed POST https://api.torque.so/v1/custom-events with Authorization: Bearer <key>. Every request returned HTTP 530 Cloudflareapi.torque.so resolves but has no live origin behind it. Forty minutes wasted before we suspected the path itself was wrong.

How we found the truth: npm view @torque-labs/mcp revealed three CLI flags exposing the real hosts:

  • --apiUrl (default https://server.torque.so)
  • --platformUrl (default https://platform.torque.so)
  • --ingesterUrl (default https://ingest.torque.so) ← this is the one for events

We extracted the MCP package (npm pack @torque-labs/mcp), grepped the bundled JS, and found the route templates: /event/custom, /events, /offer/${id}/journey/leaderboard, etc. From there it took two probes to land the schema.

Confirmed contract (works as of 2026-04-29):

POST https://ingest.torque.so/events
Headers:
  x-api-key: <TORQUE_API_KEY>
  Content-Type: application/json
Body:
  { "eventName": "<schema_name>",
    "userPubkey": "<wallet_base58>",       // NOT "userWallet"
    "timestamp": "<ISO-8601>",             // required
    "data": { ... } }

Wrong-guess pitfalls we hit:

  • Host api.torque.so returns 530 (Cloudflare can't reach origin — domain isn't wired to a server).
  • Auth Authorization: Bearer … is rejected with 401 "Missing x-api-key header"; only x-api-key: works.
  • Body field userWallet is rejected with body/userPubkey Required — must be userPubkey.
  • Without timestamp, request is rejected with Invalid date.
  • A 400 "Event not found for this API key" after auth+schema pass means the event schema was never registered for that key — call MCP create_custom_event first.

Leaderboard endpoint (also undocumented for REST):

GET https://server.torque.so/offer/all
GET https://server.torque.so/offer/{offerId}/journey/leaderboard
Headers: x-api-key: <TORQUE_API_KEY>

Leaderboards are scoped to a recurring incentive (offer), not a project-wide event filter — you must create an offer via MCP create_recurring_incentive before there's anything to query. A fresh project responds 500 "Failed to parse offers" from /offer/all until at least one incentive exists.

Recommendation: Torque's MCP quickstart should include a one-page "emit from your app" reference covering: the three hosts, the x-api-key header, the userPubkey/timestamp/data body shape, and the prerequisite that the event schema exists for the API key. As written today, the only path to this information is decompiling the MCP CLI source.


What Worked Well

  • MCP tool discovery via Claude Code tab-completion was fast
  • create_custom_event with typed field schemas was clear
  • Fixture mode (console.log('[Torque]' ...)) worked perfectly for frontend demo development
  • Fire-and-forget pattern (void emitEvent(...)) keeps execution flows clean

What Needs Work (Prioritized)

  1. Sandbox mode: All MCP tools should work without a live API key, returning fixture/mock responses for development
  2. REST API docs: Document the three hostnames (ingest.torque.so, server.torque.so, platform.torque.so), the x-api-key header, and the canonical event body shape ({ eventName, userPubkey, timestamp, data }) alongside the MCP setup tools — today the only way to discover them is reading the bundled MCP CLI source
  3. Schema validation: create_custom_event should validate field names against reserved fields and warn on mismatch
  4. Incentive DSL consistency: Raffle, rebate, and leaderboard query syntax should use the same DSL
  5. Schema ID stability: Event schemas should return a stable ID for use in attach_custom_event instead of requiring string name matching

Demo Verification

All five event types fire correctly in fixture mode:

# Start dev server
npm run dev

# Execute a swap → triggers surge_trade_executed
# Console output:
[Torque] {
  "event": "surge_trade_executed",
  "userWallet": "<wallet>",
  "timestamp": "2026-04-16T...",
  "data": { "tokenIn": "USDC", "tokenOut": "SOL", "amountUsd": 50, "strategy": "swap" }
}

# Open LP position → triggers surge_lp_zap_in
[Torque] {
  "event": "surge_lp_zap_in",
  "userWallet": "<wallet>",
  "data": { "strategy": "lp-zap-in", "poolAddress": "HJPjoWUrhoZzkNfRpHuieeFk9WcZWjwy6PBjZ81ngndJ" }
}

Rewards panel loads top 5 leaderboard entries from fixture data and will switch to live Torque data when TORQUE_API_KEY is set.


Resolution (2026-04-27)

After this friction log was filed, the integration was hardened in three places:

  1. Endpoint is now env-overridable. lib/torque/client.ts reads TORQUE_API_BASE (default https://api.torque.so/v1) and TORQUE_EVENTS_PATH (default /custom-events). When @smicktrq confirms the canonical path, the deploy gets one env-var change — no code touched.
  2. Schema validation at emit time. validateSurgeEvent in lib/torque/events.ts checks every payload against the registered event schemas before fetch. Bad payloads log a warning and are dropped client-side instead of producing $0 rebates silently — friction item #2 closed.
  3. Live leaderboard / profile with local fallback. app/api/torque/leaderboard/route.ts and app/api/torque/profile/route.ts now try the live Torque API first and gracefully fall back to the local Surge event log on 404 / unreachable. Dashboard panels stay populated whether or not the inferred REST shape is correct — friction item #3 closed.
  4. Brand sweep. Five event names renamed kairos_* → surge_*. User-side action: re-run create_custom_event via MCP for each renamed event before firing live traffic.
  5. Gated live test. tests/torque-live.test.ts emits a synthetic surge_trade_executed and fails loudly with a recognisable message if the endpoint returns 404, so URL drift surfaces during CI rather than during demo.

THIS REPORT IS THE CANONICAL ARTIFACT · MIRRORED AT /TORQUE-FRICTION.MD IN THE REPO