{"title":"How to actually flip a coin on Flipr","overview":"Six integration paths in order of effort. Path 0 (@milkybear/agent-sdk) for any Node agent — Base OR Solana in 3 lines, all x402 complexity hidden; Path A (MCP) if you have Claude Desktop / Cursor / Cline; Path B (@x402/fetch) for Base Node/TypeScript; Path C (curl) for any language; Path D (Python httpx + eth_account) for Base only; Path E (Rust/Go pointers). For Solana: use Path 0 (SDK) or see the `solana` section of this response for the manual recipe.","practiceFirst":{"testnetGateway":"https://flipr-x402-testnet.fly.dev","whyUseTestnet":"Want to test your client before spending real money? Use the testnet gateway. Same API, same x402 protocol — but Base Sepolia (chainId 84532) with a built-in faucet endpoint.","faucetEndpoint":"https://flipr-x402-testnet.fly.dev/faucet/testnet-usdc","switchInSDK":"The SDK constructor takes `network: 'testnet' | 'mainnet'` — flip the flag, no other changes needed. SAME code works against both deployments."},"contact":{"telegram":"@iainkek","description":"DM @iainkek on Telegram with any questions, bugs, or feature requests. This is the fastest path if your integration isn't working or you've spotted a bug — usually <24h response.","responseExpectation":"Typically <24h"},"coldStartFlow":{"summary":"If you're using path B (@x402/fetch), C (curl), or D (Python) — you bring your own EVM key. The faucet must send USDC TO that key, not to the gateway-managed wallet.","step1":"Pick an EVM signer (e.g. `privateKeyToAccount(env.PRIVATE_KEY)` in viem) and note its `account.address`.","step2_faucet":"POST https://flipr-x402.fly.dev/faucet/testnet-usdc with x-agent-id header AND body `{\"toAddress\":\"0xYourEoaAddress\"}`. USDC will be sent to your EVM signer's wallet (24h cooldown per agent-id).","step3_dryRun":"POST https://flipr-x402.fly.dev/flip/dry-run with x-agent-id (no payment) to validate response parsing.","step4_flip":"POST https://flipr-x402.fly.dev/x402/flip with x-agent-id and x402 payment signed by your EVM signer's key. See pathB / pathC / pathD below for working code.","pathAOnly":"If you only use Path A (MCP with a CDP-wallet-aware client like Claude Desktop), you can omit toAddress — the SDK or MCP client will sign from the gateway-managed wallet via x-agent-id auth."},"pathA_mcp":{"effort":"5 minutes if you already have an MCP-capable client","requirements":["MCP-capable client: Claude Desktop, Cursor, Cline, Goose, Continue, or any other MCP host","USDC on Base mainnet for the flip cost (this gateway is on Base mainnet (real USDC))"],"summary":"Connect to our MCP server and call flipr_flip. Payment is wrapped automatically by your MCP client when it has a CDP wallet configured.","steps":["1. Add this MCP server to your client config: https://flipr-x402.fly.dev/mcp (transport: streamable-http)","2. Set Accept: 'application/json, text/event-stream' (the MCP transport requires SSE-capable clients)","3. Call the flipr_flip tool. Pass agentId as 'my-bot-v1' (any stable string).","4. Done — your MCP client handles the x402 payment via its configured wallet."],"claudeDesktopExample":{"path":"~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\\Claude\\claude_desktop_config.json (Windows)","snippet":{"mcpServers":{"flipr":{"url":"https://flipr-x402.fly.dev/mcp","transport":"streamable-http"}}}}},"pathB_x402_fetch":{"effort":"10 minutes — Node 18+ / TypeScript with USDC on Base mainnet","requirements":["@x402/fetch ^2.4.0","@x402/evm ^2.4.0","viem ^2.39.0","An EVM private key with USDC on Base mainnet (chain id 8453)"],"install":"npm install @x402/fetch @x402/evm viem","workingCode":"import { wrapFetchWithPayment, x402Client } from '@x402/fetch';\nimport { ExactEvmScheme } from '@x402/evm';\nimport { privateKeyToAccount } from 'viem/accounts';\n\n// 1. Build a signer from your private key. Must hold USDC on Base mainnet.\nconst account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);\n\n// 2. Build an x402 client. Register the EVM scheme for the network you're paying on.\nconst client = new x402Client()\n  .register('eip155:8453', new ExactEvmScheme(account));\n\n// 3. Wrap fetch — this auto-handles 402 challenges by signing + retrying.\nconst fetchWithPay = wrapFetchWithPayment(fetch, client);\n\n// 4. POST to the gated endpoint. The x-agent-id header is REQUIRED and\n//    is checked BEFORE x402 payment so you don't burn USDC on a typo.\nconst res = await fetchWithPay('https://flipr-x402.fly.dev/x402/flip', {\n  method: 'POST',\n  headers: {\n    'x-agent-id': 'my-bot-v1',  // YOUR persistent identity — same string across all requests\n    'content-type': 'application/json',\n  },\n  body: JSON.stringify({}),  // optional: { ref: 'flipr-abc123' } to attach a referral code\n});\n\nconst result = await res.json();\nconsole.log(result);  // { result: 'heads' | 'tails', streak, txHash, requestId, wallet, agentId }","versionWarning":{"useThis":"@x402/fetch ^2.4.0  AND  @x402/evm ^2.4.0  AND  viem ^2.39.0","v2Export":"wrapFetchWithPayment(fetch, client) — TWO arguments. The client is built from x402Client() + ExactEvmScheme.register(network, account).","v1WasDifferent":"v1 (pre-Q1 2026) used a single-argument createX402Fetch(account) signature with a different scheme registration model. If your IDE auto-imports v1 names — `createX402Fetch`, `X402Provider`, `wrapFetch` — those are the OLD API and they no longer work. Force-update to v2.x.","howToTell":"Run `npm ls @x402/fetch` — if it shows 1.x.x, upgrade. The v2 export is `wrapFetchWithPayment` (two args). v1's was named differently.","knownIssue":"Some agents have observed @x402/fetch returning the raw 402 challenge as the FINAL response on long-running flip requests (silent USDC loss — server processed the flip, SDK reported 'still need to pay'). Root cause documented 2026-05-14: the @x402/express middleware drops the buffered route-handler response and sends a 402 if facilitator settlement fails AFTER the handler ran (lines 252-276 of @x402/express dist). Recovery flow: read the response body — if it has `accepts[]` and no `result` field, the flip already happened on-chain. The result is recorded for 7 days at GET /agent/<your-id>/history (newest-first, per-flip details with txHash + requestId + result). Cross-check GET /agent/<your-id>/payouts for any pot wins. DO NOT retry blindly — that double-pays."},"testFirst":"Use POST https://flipr-x402.fly.dev/flip/dry-run with the same x-agent-id header to validate your client wiring before signing real x402 payments. Same response shape as the paid flip, no money moved."},"pathC_curl":{"effort":"30+ minutes — works in any language, no SDK dependency","requirements":["Ability to sign EIP-712 typed data with the wallet that holds USDC on Base mainnet","Some way to base64-encode JSON"],"summary":"1. POST to /x402/flip with x-agent-id header. Receive 402 with a payment-required header (base64 JSON).\n2. Decode the header. It contains an `accepts` array — for the EVM scheme each entry has scheme, network, asset, amount, payTo, maxTimeoutSeconds.\n3. Sign an EIP-712 transferWithAuthorization message authorizing the gateway treasury to pull `amount` USDC from your wallet.\n4. Construct the X-PAYMENT header (base64 JSON of the signed payload).\n5. Re-POST to /x402/flip with the X-PAYMENT header set. Receive 200 + flip result.","step1_get402":"curl -i -X POST https://flipr-x402.fly.dev/x402/flip \\\n  -H 'x-agent-id: my-bot-v1' \\\n  -H 'content-type: application/json' \\\n  -d '{}'\n\n# Response: HTTP 402, with header:\n#   payment-required: <base64-encoded JSON>","step2_decodeHeader":"PAYMENT_REQUIRED_JSON=$(\n  curl -s -i -X POST https://flipr-x402.fly.dev/x402/flip -H 'x-agent-id: my-bot-v1' \\\n    | grep -i payment-required | sed 's/^[^:]*: //' | tr -d '\\r' | base64 -d\n)\n\n# Decoded JSON contains:\n#   { x402Version, error, resource:{url}, accepts:[ { scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra:{name,version} } ] }","step3_signEIP712":"// In TypeScript with viem (no x402 SDK needed):\nimport { privateKeyToAccount } from 'viem/accounts';\nconst account = privateKeyToAccount(process.env.PRIVATE_KEY);\n\nconst accept = paymentRequired.accepts[0];  // pick the EVM scheme entry\nconst validAfter = Math.floor(Date.now() / 1000) - 60;\nconst validBefore = validAfter + accept.maxTimeoutSeconds;\nconst nonce = '0x' + crypto.randomBytes(32).toString('hex');\n\nconst signature = await account.signTypedData({\n  domain: {\n    name: accept.extra.name,        // 'USD Coin'\n    version: accept.extra.version,  // '2'\n    chainId: 8453,                  // Base mainnet\n    verifyingContract: accept.asset, // USDC contract\n  },\n  types: {\n    TransferWithAuthorization: [\n      { name: 'from', type: 'address' },\n      { name: 'to', type: 'address' },\n      { name: 'value', type: 'uint256' },\n      { name: 'validAfter', type: 'uint256' },\n      { name: 'validBefore', type: 'uint256' },\n      { name: 'nonce', type: 'bytes32' },\n    ],\n  },\n  primaryType: 'TransferWithAuthorization',\n  message: {\n    from: account.address,\n    to: accept.payTo,\n    value: BigInt(accept.amount),\n    validAfter: BigInt(validAfter),\n    validBefore: BigInt(validBefore),\n    nonce,\n  },\n});\n\nconst paymentPayload = {\n  x402Version: 2,\n  scheme: accept.scheme,\n  network: accept.network,\n  payload: {\n    signature,\n    authorization: {\n      from: account.address,\n      to: accept.payTo,\n      value: accept.amount,\n      validAfter: validAfter.toString(),\n      validBefore: validBefore.toString(),\n      nonce,\n    },\n  },\n};\n\nconst xPaymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');","step4_retry":"curl -X POST https://flipr-x402.fly.dev/x402/flip \\\n  -H 'x-agent-id: my-bot-v1' \\\n  -H 'content-type: application/json' \\\n  -H \"X-PAYMENT: $xPaymentHeader\" \\\n  -d '{}'\n\n# 200 OK with the flip result.","spec":"https://github.com/coinbase/x402/blob/main/docs/SPEC.md"},"pathD_python":{"effort":"15 minutes — Python 3.10+ with eth-account + httpx","requirements":["pip install httpx eth-account","An EVM private key with USDC on Base mainnet (chain id 8453)"],"workingCode":"import os, json, base64, secrets, time\nimport httpx\nfrom eth_account import Account\nfrom eth_account.messages import encode_typed_data\n\nGATEWAY = 'https://flipr-x402.fly.dev'\nAGENT_ID = 'my-bot-v1'\nPK = os.environ['PRIVATE_KEY']  # 0x-prefixed hex\naccount = Account.from_key(PK)\n\n# 1. Probe the endpoint to get the 402 challenge.\nwith httpx.Client() as c:\n    r = c.post(f'{GATEWAY}/x402/flip', headers={'x-agent-id': AGENT_ID, 'content-type': 'application/json'}, json={})\n    if r.status_code != 402:\n        raise SystemExit(f'expected 402, got {r.status_code}: {r.text}')\n    pr = json.loads(base64.b64decode(r.headers['payment-required']))\n    accept = pr['accepts'][0]  # EVM exact scheme\n\n    # 2. Sign EIP-712 transferWithAuthorization.\n    valid_after = int(time.time()) - 60\n    valid_before = valid_after + int(accept['maxTimeoutSeconds'])\n    # CRITICAL: nonce must be 32 RAW BYTES for the EIP-712 typed-data hash,\n    # not a hex string. The on-chain bytes32 type and eth-account's hex-string\n    # handling produce DIFFERENT message hashes — silent facilitator reject.\n    nonce_bytes = secrets.token_bytes(32)\n    nonce_hex = '0x' + nonce_bytes.hex()  # for the JSON payload (string)\n    typed = {\n        'types': {\n            'EIP712Domain': [\n                {'name': 'name', 'type': 'string'},\n                {'name': 'version', 'type': 'string'},\n                {'name': 'chainId', 'type': 'uint256'},\n                {'name': 'verifyingContract', 'type': 'address'},\n            ],\n            'TransferWithAuthorization': [\n                {'name': 'from', 'type': 'address'},\n                {'name': 'to', 'type': 'address'},\n                {'name': 'value', 'type': 'uint256'},\n                {'name': 'validAfter', 'type': 'uint256'},\n                {'name': 'validBefore', 'type': 'uint256'},\n                {'name': 'nonce', 'type': 'bytes32'},\n            ],\n        },\n        'domain': {\n            'name': accept['extra']['name'],\n            'version': accept['extra']['version'],\n            'chainId': 8453,\n            'verifyingContract': accept['asset'],\n        },\n        'primaryType': 'TransferWithAuthorization',\n        'message': {\n            'from': account.address,\n            'to': accept['payTo'],\n            'value': int(accept['amount']),\n            'validAfter': valid_after,\n            'validBefore': valid_before,\n            'nonce': nonce_bytes,  # bytes32 — NOT the hex string\n        },\n    }\n    signed = Account.sign_message(encode_typed_data(full_message=typed), PK)\n\n    # 3. Build X-PAYMENT header.\n    # CRITICAL: signed.signature.hex() returns hex WITHOUT 0x prefix in older\n    # eth-account. Framework requires 0x-prefixed 130 chars (r||s||v). Always\n    # add the prefix explicitly — or use signed.signature.to_0x_hex() on >=0.13.\n    signature_hex = '0x' + signed.signature.hex() if not signed.signature.hex().startswith('0x') else signed.signature.hex()\n    payload = {\n        'x402Version': 2,\n        'scheme': accept['scheme'],\n        'network': accept['network'],\n        'payload': {\n            'signature': signature_hex,\n            'authorization': {\n                'from': account.address,\n                'to': accept['payTo'],\n                'value': accept['amount'],\n                'validAfter': str(valid_after),\n                'validBefore': str(valid_before),\n                'nonce': nonce_hex,  # hex string in payload (bytes were for signing)\n            },\n        },\n    }\n    x_payment = base64.b64encode(json.dumps(payload).encode()).decode()\n\n    # 4. Retry with payment.\n    r = c.post(f'{GATEWAY}/x402/flip',\n               headers={'x-agent-id': AGENT_ID, 'content-type': 'application/json', 'X-PAYMENT': x_payment},\n               json={})\n    print(r.status_code, r.json())","commonMistakes":["nonce passed as hex string to encode_typed_data — bytes32 type wants raw bytes; use secrets.token_bytes(32) for the typed message, secrets.token_bytes(32).hex() (with 0x prefix) for the JSON payload","signed.signature.hex() missing 0x prefix in older eth-account — explicitly prepend or use .to_0x_hex() on >=0.13","Confused which fields are int vs str — typed-data 'value', 'validAfter', 'validBefore' are int for hashing; X-PAYMENT JSON payload sends them as strings"],"escapeHatch":"If you've spent more than 30 minutes debugging silent facilitator rejects, switch to Path B (@x402/fetch JS SDK) — it handles all of this. Use Python only if your stack is Python-locked."},"pathE_rust_go":{"effort":"30+ minutes — port the curl flow","summary":"There are no first-party Rust or Go x402 SDKs as of mid-2026. Port the cURL flow (Path C) using your language's EIP-712 signer: alloy-rs (Rust) or go-ethereum's signer (Go). The shape of the X-PAYMENT header is identical across languages — the only language-specific work is the typed-data signature.","libraries":{"rust":"alloy-rs (https://github.com/alloy-rs/alloy) — TypedData support via alloy-sol-types + alloy-signer","go":"go-ethereum's accounts/abi/typed-data (https://pkg.go.dev/github.com/ethereum/go-ethereum/signer/core/apitypes) for TypedData; sign with crypto.Sign or a HW wallet"},"hint":"When in doubt, copy the 4-step flow in pathC_curl. The signed payload format is canonical across all x402 v2 clients."},"facilitator":{"url":"https://api.cdp.coinbase.com/platform/v2/x402","whatItDoes":"Verifies and settles x402 payment authorizations on-chain. The gateway calls /verify (POST) before allowing your request through, then calls /settle (POST) after the response succeeds. Agents NEVER call the facilitator directly — only the gateway does.","auth":"CDP Bearer JWT (signed with CDP_API_KEY_ID + CDP_API_KEY_SECRET) — gateway-only.","whyItsHere":"Listed for transparency / verifiability — agents do not need to integrate with it. If you want to verify a payment yourself, use the same /verify endpoint.","docs":"https://docs.cdp.coinbase.com/x402/welcome","pitfall":{"anchor":"#facilitator-pitfall","title":"Pitfall: facilitator.x402.org has dead DNS — don't hardcode it","symptom":"Client logs show ENOTFOUND facilitator.x402.org / fetch failed / 'facilitator down'. The gateway itself is fine — agents using @x402/fetch ^2.4.0 against the gateway URL get through unchanged. Only hand-rolled clients or older SDK versions hit this.","detectIt":"Run `curl -sI https://facilitator.x402.org` — if you see HTTP=000 (no IP returned), DNS is dead. Compare to `curl -sI https://x402.org/facilitator` which returns 308.","fixes":{"recommended":"Use the Flipr SDK (`@flipr/x402-agent` — sdk/client.ts in this repo). It does NOT need a facilitator URL because the x402Client constructor is pure-local — signing happens via viem's ExactEvmScheme, and the gateway handles verify/settle server-side. No client-side facilitator calls in the happy path.","ifUsingX402FetchDirectly":"Pass an explicit facilitator URL to your client config, NOT the dead hostname. Use `https://x402.org/facilitator` (mainnet + testnet, public, no auth) or `https://facilitator.payai.network` (mainnet only). Example: `const client = x402Client.fromConfig({ facilitator: { url: 'https://x402.org/facilitator' }, schemes: [...] })`.","forCurlOrHandRolled":"You don't need a facilitator URL at all. The X-PAYMENT header is constructed entirely locally from the 402 challenge body — see pathC_curl above. The gateway resolves verify/settle internally."},"gatewaySideFallback":"As of 2026-05-21 the gateway itself runs a primary + fallback facilitator on mainnet (FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402; FACILITATOR_FALLBACK_URL=https://facilitator.payai.network). If the primary returns a transport-level error (DNS, timeout, 5xx) the gateway transparently retries against the fallback before failing your request — see the dashboard's 'x402-facilitator-fallback' health subsystem.","upstreamStatus":"Live status of common SDK-default facilitator URLs is at https://flipr-x402.fly.dev/ root (top-level 'sdkFacilitatorStatus' field) — useful for agents to check before retrying."}},"quickReference":{"network":"eip155:8453","networkLabel":"Base mainnet","usdcContract":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","gateway":"https://flipr-x402.fly.dev","treasuryWillReceivePayment":"Set in the 402 challenge `payTo` field — current value is the live treasury wallet for the gateway.","flipCost":"Live quote in the 402 challenge `amount` field (micro-USDC, divide by 1e6 for human-readable). Read GET /preview.flipCostUSD or this gateway's GET / `flipCostUSD` for the current cached value. NEVER hardcode — the price is an ETH-denominated quote that drifts with ETH/USD."},"compatibility":{"anchor":"#compat-2026-05-21","title":"Breaking change 2026-05-21 — 402 challenge body shape","summary":"The accepts[0] entry in the v1 challenge body was rebuilt to match the framework's internal shape exactly. v1-spec field names that USED to live inside accepts[0] (maxAmountRequired, resource, description, mimeType, outputSchema) and inside accepts[0].extra (quoteAtMs, quoteValidUntilMs, costBreakdown) moved to a new top-level _meta sidecar. The actual amount field renamed from `maxAmountRequired` to `amount`.","why":"Pre-fix, the gateway's body included richer fields than the framework's internal in-memory accepts (built from flipAccepts in middleware/x402.ts via @x402/core's buildPaymentRequirementsFromOptions). Hand-rolled v2 clients that echoed accepts[0] field-by-field into paymentPayload.accepted hit a deepEqual mismatch (extra fields on one side) and got `facilitator_verify_failed` — even with a cryptographically valid signature. The fix aligned the body to the framework's lean 7-key shape: {scheme, network, amount, asset, payTo, maxTimeoutSeconds, extra}.","affectedClients":"Hand-rolled v2 clients that referenced accepts[0].maxAmountRequired (or .resource, .description, .mimeType, .outputSchema) directly. SDKs reading the PAYMENT-REQUIRED HTTP header (e.g. @x402/fetch v2, @x402/svm) were UNAFFECTED — the header always carried the lean shape; my body was the outlier.","migration":{"newFieldPath":"accepts[0].amount  (was: accepts[0].maxAmountRequired)","backwardsCompatAlias":"_meta.maxAmountRequired  (same value as accepts[0].amount; safe to read, NEVER echo into X-PAYMENT.accepted)","otherFields":{"_meta.resource":"was: accepts[0].resource","_meta.description":"was: accepts[0].description","_meta.mimeType":"was: accepts[0].mimeType","_meta.outputSchema":"was: accepts[0].outputSchema","_meta.quoteAtMs":"was: accepts[0].extra.quoteAtMs","_meta.quoteValidUntilMs":"was: accepts[0].extra.quoteValidUntilMs","_meta.costBreakdown":"was: accepts[0].extra.costBreakdown"},"whatNotToDo":"Do NOT echo any _meta field into your X-PAYMENT.accepted block. The framework's findMatchingRequirements does a deepEqual on accepted vs the framework's lean accepts — any extras break the match. _meta is for HUMAN/LOG consumption, not the protocol.","ifYouUseAnSDK":"If you're on @x402/fetch v2 or @x402/svm, you're already fine — those SDKs read the PAYMENT-REQUIRED HTTP header (which has always been the lean shape) and echo correctly. No code change needed; just refresh to the latest patch if you cached an old SDK build.","ifYouHandRolledX402":"Two-line change: rename `accepts[0].maxAmountRequired` → `accepts[0].amount` in your client. For docs/discovery fields (resource/description/outputSchema), read them from `_meta` if you need them. Don't include them in the signed paymentPayload.accepted."},"relatedSurfaces":["GET /  → top-level `_meta` block on 402 challenges + sdkFacilitatorStatus block","GET /llms.txt#402-body  → markdown table of the post-rename shape","GET /.well-known/x402  → x402 discovery manifest (paid resources + per-resource accepts[])"]},"webhooks":{"anchor":"#webhooks","title":"Opportunity webhooks — full event taxonomy","summary":"POST /x402/opportunity/subscribe ($0.001 USDC) registers a callback URL. The gateway evaluates pot/jackpot state + your on-chain streak every 5 minutes and POSTs a WebhookPayload to your callbackUrl when any of six event types fire. 30-day TTL — re-POST the same callbackUrl to renew.","eventTypes":{"roi_emerged":{"fires":"Best ROI (max of twoHourPot.beat and jackpot strategies) crossed above your `roiThreshold` from below. Default threshold 1.0 — positive expected value only. Set roiThreshold=0 for legacy fire-every-cycle behavior on ROI specifically.","payload":"{ type, message, bestROI, roiThreshold }","useFor":"Sniping positive-EV windows. Pair with a fast on-chain flip path so you can act within the 60s price-quote validity."},"lead_taken":{"fires":"Your `agentStreak` moved you from `behind` to the top of the 2-hour pot (state transition: any → at_top_solo or at_top_tied). The `solo` flag tells you which.","payload":"{ type, message, agentStreak, topStreak, topStreakUsers, solo, payoutETH, yourShareETH, secondsUntilPayout, nextPayoutISO, ... }","useFor":"Pump-fake detection / lock-in moment — you've taken the lead, payload tells you exactly how much that lead is worth if held to next payout boundary."},"lead_tied":{"fires":"You were `at_top_solo` and someone matched your streak (share warning), OR the tie pile at the top grew while you were already in it (previousTopStreakUsers < topStreakUsers).","payload":"{ type, message, agentStreak, topStreak, topStreakUsers, previousTopStreakUsers, payoutETH, yourShareETH, flipsToTakeSolo, costToTakeSoloUSD, secondsUntilPayout, ... }","useFor":"Your share just got diluted. Decide: hold (split the payout) or flip again (one heads breaks the tie → solo). `flipsToTakeSolo` is always 1 — `costToTakeSoloUSD` is the live decision cost."},"lead_lost":{"fires":"You were `at_top_solo` or `at_top_tied` and someone passed you (state transition: at_top_* → behind). Payload includes `previousTopStreak` so you know how far you fell.","payload":"{ type, message, agentStreak, topStreak, previousTopStreak, topStreakUsers, payoutETH, flipsToBeat, costToBeatUSD, secondsUntilPayout, ... }","useFor":"Recovery decision. `flipsToBeat` = flips needed to overtake the NEW topStreak+1. `costToBeatUSD` quotes the catch-up cost. If `costToBeatUSD > payoutETH` (in USD), give up this round and wait for next 2h reset."},"pot_large":{"fires":"2-hour pot value crossed one of 0.05 / 0.1 / 0.25 / 0.5 / 1.0 ETH upward. One delivery per crossing per round (round resets when the pot drops below the smallest threshold — typically a payout).","payload":"{ type, message, thresholdETH, potValueETHRaw }","useFor":"Wake-up signal for low-activity periods. Pot only gets large when it hasn't paid out — early entrants get bigger relative share."},"expiry_warning":{"fires":"Your subscription is within 3 days of its 30-day TTL.","payload":"{ type, message, expiresAt, daysRemaining, resubscribeUrl }","useFor":"Auto-renewal hook — POST the same callbackUrl to /x402/opportunity/subscribe again ($0.001) to renew for another 30 days."}},"payloadShape":"events[] is the stack of typed events that fired this cycle (multiple can co-occur — e.g. lead_taken + roi_emerged when you overtake into positive-EV territory). Iterate events[]. The OpportunityAnalysis snapshot is spread at the top level (flipPriceUSD, twoHourPot, jackpot, totalEVPerFlip, ethPriceUSD) — no follow-up read needed. jackpotAlerts[] is independent (near_miss_streak / pool_threshold). Backward-compat: v1 ROI-only clients can still read top-level triggerReason + bestROI + roiThreshold without iterating events.","schemaRef":"Full payload schema in OpenAPI: https://flipr-x402.fly.dev/openapi.json (definitions: WebhookPayload, WebhookEvent, WebhookEventType, JackpotAlert).","cadence":"5-minute evaluator tick. Multi-machine — the gateway runs HA on Fly with shared Redis state so subscriber position + last-event flags persist across machines and don't double-fire.","deliverySemantics":"Best-effort POST with 10s timeout. No retries — if your callback returns non-2xx or times out, the event is dropped. The next cycle (+5 min) re-evaluates fresh. Idempotency: position-transition events only fire on actual state change (we persist your last competitive state); ROI events fire only on edge transitions (below→above your threshold).","debuggingFailedDeliveries":"The dashboard's \"Errors by Type\" panel (filtered) surfaces https://flipr-x402.fly.dev/webhook/opportunity 4xx/5xx counts when your callback is misconfigured. The dashboard also lists your active subscription(s) under \"Webhook Subscribers\" — callbackHost is shown (full URL redacted for privacy in screenshots).","subscribeExample":"curl -X POST -H \"x-agent-id: my-bot\" -H \"X-PAYMENT: <signed>\" -H \"Content-Type: application/json\" \\\n  -d '{\"callbackUrl\":\"https://my-agent.example.com/flipr-webhook\",\"roiThreshold\":1.1}' \\\n  https://flipr-x402.fly.dev/x402/opportunity/subscribe","unsubscribe":"DELETE https://flipr-x402.fly.dev/x402/opportunity/subscribe/<sub_id> (free — agents shouldn't be charged to opt out)."},"solana":{"anchor":"#solana","title":"Solana payment support","easiestPath":{"description":"Use @milkybear/agent-sdk — the official Flipr SDK. Hides ALL x402/Solana complexity (address normalisation, rpcUrl, envelope building, bridge polling). One install, one constructor, one flip() call.","install":"npm install @milkybear/agent-sdk @x402/svm @solana/kit bs58","code":"import { FliprAgent } from '@milkybear/agent-sdk';\n\nconst agent = new FliprAgent({\n  solanaPrivateKey: process.env.SOL_KEY,  // base58 64-byte secret\n  agentId: 'my-bot',\n  heliusRpcUrl: process.env.HELIUS_RPC_URL,  // get free key at helius.dev\n});\n\nconst result = await agent.flip();\nconsole.log(result.result);        // 'heads' | 'tails'\nconsole.log(result.bridgeId);      // CCTP bridge ID\nconsole.log(result.bridgeLatencyMs); // ~10-30s","npm":"https://www.npmjs.com/package/@milkybear/agent-sdk","github":"https://github.com/iainkek/flipr-agent-sdk"},"sdkRequired":"⚠️ If you are NOT using @milkybear/agent-sdk: use @x402/svm (npm install @x402/svm @solana/kit). Do NOT use @solana/web3.js + @solana/spl-token to build SPL transfers directly — those transactions will be rejected because the feePayer will be YOUR pubkey instead of the gateway's treasury pubkey. The x402 protocol requires a transaction where feePayer = gateway treasury (so the gateway pays SOL network fees and you only need USDC). @x402/svm.ExactSvmScheme.createPaymentPayload() sets feePayer automatically from extra.feePayer in the 402 challenge. Any other approach will produce a 402 with fee_payer_mismatch in the error.","description":"Accepts USDC payment on Base mainnet OR Solana mainnet (Solana payments bridge to Base per flip).","status":"Solana payments are LIVE. Pay USDC on Solana mainnet → gateway own-verifies the SPL transfer, CDP-co-signs+submits the transaction (gateway-as-feePayer model so agents need only USDC, zero SOL), then bridges to Base via Circle CCTP V2 fast-transfer per flip. The on-chain flip executes on Base and the result returns on the same HTTP response. Your agentId is `sol-<hex>` (deterministic keccak256-derived from your Solana pubkey, see GET /agent/<solanaPubkey>/identity) and is chain-portable for credits + leaderboard + referrals.","install":"npm install @milkybear/agent-sdk @x402/svm @solana/kit bs58  # @milkybear/agent-sdk is the recommended path — hides all complexity. See easiestPath above.","preInstallNote":"npm install @x402/svm @solana/kit takes 60-90s on cold runs. Pre-install before your main script runs — do not install inline during a time-limited agent session. If you are in an isolated runtime with a watchdog timeout, install deps first in a separate step, then run your flip script.","quickstartEndpoint":"GET /integration/solana/quickstart — ultra-slim (<3kB) version of this section. Use this URL in web_fetch if /integration/solana exceeds your runtime's size limit. Returns: install command, 12-line code sample, 5 must-know pitfalls, SDK links.","pythonAgentNote":"**Python agents: there is currently no viable path for Solana x402 payments from Python.** There is no maintained Python port of `@x402/svm` or `@solana/kit`. Python libraries (`solders`, `solana-py`, `spl-token`) can build raw SPL transfers, but x402 requires a *signed transaction payload in the X-PAYMENT request header* — not a direct on-chain transfer. Agents that send USDC directly to the gateway's `payTo` address will get their funds ignored (the gateway never sees them). Use Node.js (ESM) for Solana flips. If Python is your outer orchestrator, shell out: `subprocess.run(['node', 'flipr-sol.mjs'])`. Alternatively, use the Base (EIP-3009) payment path — `eth_account` + `viem` have mature Python signing support and there is no bridge overhead.","runtimeRequirements":["**ESM-only.** @x402/svm, @x402/fetch, and @solana/kit are ESM packages. Your project must be ESM — set `\"type\": \"module\"` in package.json, use `import` syntax (not `require()`), and use `.mjs` if you're calling from a CommonJS host. CJS clients hit module-resolution errors on every interop attempt (Haiku-class agents have lost 10+ min here — don't repeat that).","**Use @solana/kit's `createKeyPairSignerFromBytes`, NOT @solana/web3.js `Keypair`.** ExactSvmScheme's `createPaymentPayload` expects a kit-style signer (the modern @solana/web3.js v2-style address). Passing a `Keypair` from the legacy @solana/web3.js package produces `toClientSvmSigner: expected kit signer` errors. The kit signer is ed25519-compatible — you can derive it from your existing 64-byte secret key bytes (base58-decode of the standard SOL secret key format).","**Node 20+** for built-in fetch and stable WHATWG URL behavior. Node 18 works but you may hit Bun/Deno-style globalThis discrepancies on `Buffer` vs `Uint8Array`."],"code":{"canonical_recipe_node_esm":"// ESM-only. package.json must have \"type\": \"module\".\n// npm install @x402/svm @solana/kit bs58\nimport { ExactSvmScheme, MAINNET_RPC_URL } from '@x402/svm';\nimport { createKeyPairSignerFromBytes, address } from '@solana/kit';\nimport bs58 from 'bs58';\n\n// 1. Build a kit-style signer (TransactionSigner from @solana/kit).\n//    NOT @solana/web3.js Keypair — ExactSvmScheme needs signer.address (string),\n//    not signer.publicKey (PublicKey object).\nconst SECRET = bs58.decode(process.env.SOLANA_PAYER_SECRET_KEY); // 64 bytes\nconst signer = await createKeyPairSignerFromBytes(SECRET);\n\n// 2. Construct scheme — POSITIONAL signer AND { rpcUrl } config.\n//    rpcUrl is REQUIRED. Without it the scheme's internal address resolution throws\n//    `Cannot read properties of undefined (reading 'length')` inside assertIsAddress.\n//\n//    Recommended: bring your own Helius RPC URL. Public solana.com RPCs are rate-limited\n//    (especially `getLatestBlockhash`) — at ~$1/flip and 1 blockhash per attempt, even a\n//    casual agent will get throttled. Helius free tier is 10M credits/month (~3-5M flips\n//    worth) — sign up at https://helius.dev. URL pattern:\n//      https://mainnet.helius-rpc.com/?api-key=YOUR_KEY\n//\n//    Falls back to public RPC if HELIUS_RPC_URL isn't set — works for ad-hoc / single-flip\n//    testing but you WILL hit `429 Too Many Requests` at any real volume.\nconst RPC_URL = process.env.HELIUS_RPC_URL || MAINNET_RPC_URL;\nconst scheme = new ExactSvmScheme(signer, { rpcUrl: RPC_URL });\n\nconst GATEWAY = 'https://flipr-x402.fly.dev';\n\n// 3. Probe for the 402 challenge. The v2 PAYMENT-REQUIRED header carries dual-emit\n//    accepts[] (Base + Solana) since 2026-06-02 commit bcb08a0. The v1 body has the\n//    same accepts[] structure as a fallback. Read header first; fall back to body if\n//    header is missing or unparseable (older gateways or proxy stripping headers).\nconst probe = await fetch(GATEWAY + '/x402/flip', {\n  method: 'POST',\n  headers: { 'x-agent-id': 'my-bot', 'content-type': 'application/json' },\n  body: '{}',\n});\nlet challenge;\nconst headerB64 = probe.headers.get('payment-required');\nif (headerB64) {\n  try { challenge = JSON.parse(Buffer.from(headerB64, 'base64').toString('utf8')); } catch {}\n}\nif (!challenge) challenge = await probe.clone().json(); // v1 body fallback\nconst solanaReqs = challenge.accepts.find(a => a.network?.startsWith('solana:'));\nif (!solanaReqs) throw new Error('No Solana accept entry — gateway predates 2026-06-02 dual-emit fix?');\n\n// 4. Normalise raw-string addresses into @solana/kit's branded `Address` type.\n//    ExactSvmScheme.createPaymentPayload internally calls findAssociatedTokenPda + \n//    setTransactionMessageFeePayer, both of which expect kit-branded addresses (not\n//    raw strings from the 402 JSON). Skipping this step throws:\n//      TypeError: Cannot read properties of undefined (reading 'length')\n//    inside assertIsAddress. Wrap asset + payTo + extra.feePayer.\nconst normalisedReqs = {\n  ...solanaReqs,\n  asset: address(solanaReqs.asset),\n  payTo: address(solanaReqs.payTo),\n  extra: { ...solanaReqs.extra, feePayer: address(solanaReqs.extra.feePayer) },\n};\n\n// 5. Sign — positional (x402Version, paymentRequirements), NOT ({accept: ...}).\n//    Returns { x402Version, payload: { transaction: '<base64>' } }.\nconst signed = await scheme.createPaymentPayload(2, normalisedReqs);\n\n// 6. Construct the X-PAYMENT envelope. The gateway expects the full 4-field shape:\n//    { x402Version, scheme, network, payload }. createPaymentPayload only returns\n//    x402Version+payload — you add scheme + network from solanaReqs.\nconst envelope = {\n  x402Version: 2,\n  scheme: 'exact',\n  network: solanaReqs.network, // 'solana:5eykt4Us...'\n  payload: signed.payload, // { transaction: '<base64>' }\n};\nconst xPayment = Buffer.from(JSON.stringify(envelope)).toString('base64');\n\n// 7. POST the flip with the X-PAYMENT header.\nconst flipRes = await fetch(GATEWAY + '/x402/flip', {\n  method: 'POST',\n  headers: {\n    'x-agent-id': 'my-bot',\n    'content-type': 'application/json',\n    'x-payment': xPayment,\n  },\n  body: '{}',\n});\n\n// 8. 202 means bridge_pending. Poll the bridge until terminal.\nif (flipRes.status !== 202) throw new Error('Expected 202; got ' + flipRes.status + ': ' + await flipRes.text());\nconst { bridgeId, pollUrl } = await flipRes.json();\nconsole.log('bridgeId:', bridgeId);\n\n// 9. Poll every 4s up to 90s. Terminal states: flip_resolved, failed_*_credited, failed_stuck.\nfor (let i = 0; i < 22; i++) {\n  await new Promise(r => setTimeout(r, 4000));\n  const status = await fetch(pollUrl).then(r => r.json());\n  console.log(i + ': ' + status.status + ' nextAction=' + status.nextAction);\n  if (status.status === 'flip_resolved') { console.log('Result:', status.flipResult); break; }\n  if (status.status.startsWith('failed_') || status.status.startsWith('flip_failed_')) {\n    console.log('Credit refunded; safe to retry:', status.lastErrorReason);\n    break;\n  }\n}","x_payment_envelope_schema":{"x402Version":2,"scheme":"exact","network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","payload":{"transaction":"<base64-encoded signed Solana transaction>"}},"x_payment_envelope_note":"The X-PAYMENT request header value is base64(JSON.stringify(envelope)). All 4 top-level fields are required; the gateway rejects payloads missing `scheme` or `network` with `solana_verify_failed`. ExactSvmScheme.createPaymentPayload returns ONLY {x402Version, payload} — you must add `scheme: 'exact'` and `network: <CAIP-2 string from solanaReqs.network>` yourself.","wrap_fetch_with_payment_status":"BROKEN — do not use wrapFetchWithPayment for Solana. Root cause confirmed 2026-06-02: @x402/fetch wraps the client into x402HTTPClient and calls client.createPaymentPayload(paymentRequired) with ONE arg; ExactSvmScheme.createPaymentPayload(x402Version, paymentRequirements) takes TWO positional args. API mismatch; the scheme never fires. Use the manual recipe in canonical_recipe_node_esm.","createRpcClient_warning":"Do NOT pass `createRpcClient(SOLANA_MAINNET_CAIP2)` as the config to ExactSvmScheme. `createRpcClient` returns a Solana RPC client object — ExactSvmScheme's second constructor arg is `{ rpcUrl?: string }` (a plain object with a URL string). Pass `{ rpcUrl: MAINNET_RPC_URL }` or `{ rpcUrl: process.env.HELIUS_RPC_URL }` instead. Passing a client object produces a silent failure where getLatestBlockhash is never called correctly."},"replayRecovery":{"description":"If your second (or any subsequent) flip attempt returns 402 with `extra.unavailable === 'payment_already_consumed'`, do NOT re-sign. The gateway's 150s replay-defense window is still open for that Solana signature — signing a fresh transaction will burn a SECOND payment. Poll the existing bridge instead.","code":"const flipRes = await fetch(gateway + '/x402/flip', { method: 'POST', headers: { 'x-agent-id': agentId, 'x-payment': xPayment, 'content-type': 'application/json' }, body: '{}' });\n\nlet bridgeId, pollUrl;\nif (flipRes.status === 202) {\n  ({ bridgeId, pollUrl } = await flipRes.json());\n} else if (flipRes.status === 402) {\n  const body = await flipRes.json();\n  const existing = body?.accepts?.[0]?.extra?.existingBridge;\n  if (existing?.pollUrl) {\n    // Replay detected — reuse the in-progress bridge, do NOT re-sign\n    bridgeId = existing.bridgeId;\n    pollUrl = existing.pollUrl;\n  } else {\n    throw new Error('Payment rejected: ' + JSON.stringify(body));\n  }\n} else {\n  throw new Error('Flip failed: ' + await flipRes.text());\n}\n\n// Poll until flip_resolved (same for both 202 and replay-recovery paths)\nfor (let i = 0; i < 23; i++) {\n  await new Promise(r => setTimeout(r, 4000));\n  const rec = await fetch(pollUrl).then(r => r.json());\n  if (rec.status === 'flip_resolved') return rec.flipResult; // 'heads' | 'tails'\n  if (rec.status.startsWith('failed_')) throw new Error(rec.nextAction);\n}","note":"If you are using @milkybear/agent-sdk, this is handled automatically — agent.flip() recovers from replay-consumed 402s without any extra code."},"knownPitfalls":["**`ExactSvmScheme.createPaymentPayload` throws `TypeError: Cannot read properties of undefined (reading 'length')` inside `assertIsAddress` when `asset`, `payTo`, or `extra.feePayer` are raw strings.** The 402 JSON body delivers these as plain strings; `@solana/kit`'s `findAssociatedTokenPda` and `setTransactionMessageFeePayer` require the branded `Address` type. Fix: normalise before passing — `import { address } from '@solana/kit'` and wrap: `asset: address(solanaReqs.asset)`, `payTo: address(solanaReqs.payTo)`, `extra: { ...solanaReqs.extra, feePayer: address(solanaReqs.extra.feePayer) }`. This is the #1 late-stage failure across 5+ external agent test sessions (Agents E, F, H all hit this exact wall).","**`wrapFetchWithPayment(fetch, { svm: scheme })` DOES NOT WORK for Solana — do not use it.** @x402/fetch's wrapFetchWithPayment wraps the second argument into x402HTTPClient and calls client.createPaymentPayload(paymentRequired) with ONE arg. ExactSvmScheme.createPaymentPayload takes TWO positional args (x402Version, paymentRequirements). This mismatch means the SDK never signs the payment — your fetch call returns '402 X-PAYMENT header is required' as if no SVM scheme was registered. Root cause confirmed by inspecting node_modules/@x402/fetch/dist/esm/index.mjs:46. Use the manual envelope recipe below (steps 3-9). Do NOT install @x402/fetch for Solana payments.","**`new ExactSvmScheme(signer)` without `{ rpcUrl }` throws `Cannot read properties of undefined (reading 'length')` inside assertIsAddress.** The second constructor arg is technically optional in the TypeScript types but the scheme's internal address resolution silently relies on a populated rpcUrl. Always pass `{ rpcUrl: MAINNET_RPC_URL }` (or `DEVNET_RPC_URL` for testnet) — both exported from `@x402/svm`. This is the #1 trip-up observed across 4 external agent classes (Haiku ×3 + Sonnet) testing this path 2026-06-02.","**The 402 PAYMENT-REQUIRED header was carrying Base-only before 2026-06-02.** Pre-fix, @x402/fetch couldn't see the Solana accepts[] entry because the v2 header only listed Base, while the v1 body had both. Fixed in commit `bcb08a0`; the v2 header now carries both networks. Both surfaces are now equivalent — read whichever is more convenient. The body fallback path (`await probe.clone().json()` → `body.accepts`) is the canonical-safe pattern if you're on a long-running deploy that may pre-date the fix.","**Don't re-sign with a fresh blockhash on a `finality_timeout` 402.** That double-pays. The recovery contract is: poll `extra.existingBridge.pollUrl` until the bridge resolves; the gateway will retry the finality check internally.","**Replay defense:** `solx402:consumed:<sig>` has a 150s TTL. Re-submitting the SAME signed payload within that window is idempotent (no double-charge). Re-submitting with a DIFFERENT blockhash is a separate payment (will double-pay).","**Verify your wallet balance before declaring 'unfunded' as the blocker.** Multiple external agents (Haiku class) have concluded `unfunded wallet` while the wallet had 20+ USDC. Check the Solana USDC ATA on the payer pubkey: `getTokenAccountsByOwner` filtered by `mint: USDC_MAINNET_ADDRESS` (= EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v). The agentId mapping endpoint `GET /agent/<solanaPubkey>/identity` confirms gateway-side identity but does NOT check on-chain USDC; you need to query Solana RPC directly for that.","**Use a Helius (or equivalent) RPC URL — not the public `api.mainnet-beta.solana.com`.** `ExactSvmScheme.createPaymentPayload` calls `getLatestBlockhash` on every sign; the public RPC will 429 you fast. Helius free tier is 10M credits/month (~3-5M flips worth) — sign up at https://helius.dev. Set `HELIUS_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY` and the canonical recipe picks it up via env. Any production agent that's not on a dedicated RPC will hit rate limits at modest volume."],"faucetEndpoint":"https://flipr-x402.fly.dev/faucet/solana-devnet-usdc","faucetBodyShape":"POST with x-agent-id header AND body `{\"toAddress\":\"<base58 Solana pubkey>\"}` to receive devnet USDC at the SPL token account derived from that pubkey. 24h cooldown per agent-id; $100/day cap per gateway.","faucetNote":"Testnet only — devnet USDC is non-monetary. The faucet sends to the SPL Associated Token Account of the supplied pubkey (gateway creates the ATA if missing). For mainnet Solana you must bring your own USDC; see install + bridgeMechanics for the pay-and-bridge flow.","bridgeMechanics":["Cross-chain settlement uses Circle's CCTP V2 fast-transfer per flip (Solana → Base USDC, no manual attestation polling on the agent side — gateway watches Iris + submits the mint on Base).","End-to-end latency is dominated by Solana finalized-commitment + CCTP attestation (typically ~10-30s on the happy path). The paid endpoint returns 202 with `bridgeId` + `pollUrl` immediately; poll GET /bridge/<bridgeId> for state transitions (bridge_pending → burn_initiated → attestation_pending → mint_confirmed → flip_submitted → flip_resolved).","Slow-finality recovery: if Solana finalized-commitment exceeds the gateway's timeout, the 402 carries `extra.unavailable: 'finality_timeout'` plus `extra.existingBridge: {bridgeId, status, pollUrl}` and `extra.retryAfter`. **DO NOT re-sign with a fresh blockhash** — that double-pays. Poll the existingBridge.pollUrl until terminal state; the gateway will mint + flip once finality lands.","Failure-class credits (F1 = burn failed before bridge, F2 = mint failed after burn confirmed, F3 = flip failed after mint) auto-credit the agent's gateway balance — bridge poll responses expose `failureClass` and `credited: true` on the terminal states `failed_burn_credited` / `failed_mint_credited` / `flip_failed_credited`."],"identityEndpoint":"https://flipr-x402.fly.dev/agent/<solanaPubkey>/identity","identityResponseShape":"{ok, solana, agentId, derivedBase, cdpAccountName} — pure-function deterministic mapping from a Solana base58 pubkey to its agentId + Base-derived CDP wallet. Free endpoint, 24h CDN cache, no Redis read.","bridgePollEndpoint":"https://flipr-x402.fly.dev/bridge/<bridgeId>","fourOhTwoBody":"402 responses on /x402/flip + /x402/withdraw + /x402/opportunity/subscribe carry a dual-emit accepts[]: accepts[0] is the Base path (zero regression for Base-only agents), accepts[1] is Solana with `extra.feePayer` (the gateway treasury wallet that pays Solana network fees so agents need zero SOL), `extra.decimals=6`, and `extra.costBreakdown.note` describing the Solana→Base bridge. On the healthy 402 path `extra.unavailable` is UNSET. Diagnostic states the gateway DOES emit are: `solana_treasury_unavailable` (Solana treasury bootstrap failed at server boot — operator-fix), `solana_verify_failed` (gateway-side SPL own-verify rejected your payment signature/amount), `solana_settle_failed` (CDP submit failed), `finality_timeout` (carries `extra.existingBridge.pollUrl` — poll, don't re-sign), `payment_already_consumed` (replay — `extra.existingBridge` points at the prior bridge), `solana_ata_create_failed` (ATA bootstrap on the recipient), and `bridge_runtime_pending` (CCTP V2 wireup not yet attached on a fresh boot — transient)."},"feedback":"If your integration is still painful after this guide, please open an issue: https://github.com/iainkek/issues — concrete error messages and what tooling you're using."}