Skip to content

06 — Annex: External Vendors

Every third-party the running platform talks to: what it provides, who consumes it, the egress host, the auth model, where the credential lives, what member data reaches it, and which direction the money flows.

The degradation pattern is uniform — and that is the point

Across 55+ vendors, one pattern holds almost everywhere: a missing credential or a cleared flag yields 503 <feature>_unconfigured and the surface hides or degrades with its own reason in place. Almost nothing is load-bearing. The interesting entries are therefore the exceptions.

Hard dependencies — absence is an outage

Vendor Why absence is an outage Notes
GCP (chippr-bots-site-wp) All compute, secrets, KMS signing keys, images and the SPA Shared project — see 03 for why IAM is additive-only and enforced twice
Cloudflare DNS, TLS, the origin lock, and the HTTP 451 geo gate Losing it is a compliance event, not merely an availability one: the geo gate is a legal control under CODEOWNERS
QuickNode → alto The only hard-required third-party credential on a runtime node alto takes one URL, no failover, no default; without it the bundler aborts boot and the Polygon passkey gasless rail stops. The endpoint is capped at 50 req/s shared with the gateway and exporter
Pinata / IPFS Wager creation, open challenges and encrypted backup pin JSON with no fallback The sharpest edge in the estate — see below
Public RPC floor (publicnode, ETC Cooperative/rivet) ETC 61 and Mordor 63 have no commercial provider at all A public endpoint going dark takes those chains' reads with it

The Pinata trap, recorded because it has already fired. The member-facing credential is VITE_PINATA_JWT — a Terraform-unmanaged Cloud Run environment variable that is a second copy of the workstation secret. A JWT scoped for pinFileToIPFS but not pinJSONToIPFS authenticates correctly, passes testAuthentication, and breaks every member write. That is a real production incident (2026-08-30), and it is a class of failure a credential check cannot catch: the credential is valid, the scope is wrong. Note also that VITE_ variables are compiled into the client bundle and are public once shipped — they cannot be secured by relocating them.

Self-hosted single points (alto, OZ Relayer) stop the gasless product but never strand funds: every flow keeps a self-submit path.

What member data actually leaves the perimeter

A short list, and worth stating precisely because the platform's posture is self-custody: Merkl (address in the URL path), Morpho, OpenSea, the Polymarket CLOB, The Graph, mempool.space / stampchain (bare Bitcoin addresses and signed raw transactions only), the two assistant rails, and Pinata. No key material leaves the client anywhere — and a raw PM_SIGNER_PRIVATE_KEY in the gateway environment refuses boot rather than silently downgrading KMS signing to a hot key.

A note on the CSP as a vendor boundary

The SPA's connect-src carries a bare https: grant. This is deliberate: spec 069 lets members point the app at their own RPC node, and a static header cannot hold a per-member allowlist. The named hosts in connect-src document intent; they are not a boundary. script-src and frame-src are the enforced directives — which is why blob: in script-src is scoped to verified mini-app bytes and https: must never be added there.

Repo: /home/user/prediction-dao-research · compiled 2026-09-11 · READ-ONLY audit.

Method. Cross-referenced six independent evidence sources so no vendor rests on a single mention: (1) the gateway's boot config, which is the one place every upstream base URL is defaulted (services/relay-gateway/src/config/index.js); (2) the two committed VM compose files, which show what is actually switched on in production; (3) infra/vm/common/fetch-secrets.sh, which shows which credential reaches which container and whether it is required or optional; (4) scripts/secrets/registry.js + docs/runbooks/credential-rotation.md, the credential inventories; (5) the SPA CSP in frontend/nginx.conf, which is the enforced browser-egress allowlist; (6) packages/finops-catalogue/src/sources.js, the declared commercial-relationship registry. Contract-level (on-chain, no HTTP egress) vendors were read from deployments/*-v2.json and frontend/src/config/networks.js.

Two structural facts that shape the whole table.

  1. The SPA's CSP connect-src contains a bare https: grantfrontend/nginx.conf:96. It is there deliberately (spec 069: members may point the app at their own RPC node, and a per-member allowlist cannot exist in a static header). The named hosts in connect-src are therefore documentation of intent, not a boundary. The boundary that IS enforced is script-src ('self' 'unsafe-inline' blob: + Cloudflare only) and frame-src.
  2. Almost every vendor is optional and degrades to a stated honest state. The pattern is uniform: a missing credential or a disabled flag makes the route answer 503 <feature>_unconfigured and the SPA hides or degrades the surface. Only a handful of vendors are load-bearing; they are flagged REQUIRED in the tables and summarised in the last section.

1. Model / AI vendors

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
Anthropic Messages API for the member assistant (FairWins-paid rail) relay-gateway (memberApi/assistant.js) api.anthropic.com HTTPS/443 API key (x-api-key + anthropic-version: 2023-06-01) Secret Manager anthropic-api-keyANTHROPIC_API_KEY in gateway.env, optional member's chat text + tool results. No key material. Member address is not in the prompt; the member's current app path rides as a trailing text block Optional, and currently OFFASSISTANT_ENABLED is commented out in the committed compose; route answers 503 assistant_unconfigured we pay them (per token). Catalogued planned/modelled — no billing credential is held, so any $ figure would be arithmetic services/relay-gateway/src/config/index.js:939; packages/assistant-contract/src/tools.js:61; infra/vm/common/fetch-secrets.sh:136-138; infra/vm/gateway/docker-compose.yml (# ASSISTANT_ENABLED: "true"); packages/finops-catalogue/src/sources.js (assistant-model-api)
GutterToken bring-your-own-key model credits (Anthropic-shaped API) — the member's own rail frontend only, browser-direct (lib/assistant/providers/guttertoken.js) api.guttertokens.com (API), app.guttertokens.com (signup/billing link-outs) HTTPS/443 member-held API key (sk-…) member-held: wallet-scoped userStorage key assistant_guttertoken_key_v1, device-only, deliberately absent from lib/backup/syncedObjects.js; redacted to sk-…+4 at every boundary member's chat text + tool results, sent from the member's browser. FairWins is not in the request path and never sees the key or the messages Optional (tenant feature assistant-byok); absent key ⇒ the rail is simply not offered they pay us (in kind) — referral credit on FairWins' own GutterToken account, 15% of referee's first deposit capped $50. Not cash, and no collector can read it frontend/src/lib/assistant/providers/guttertoken.js:1-41; tenants/fairwins/manifest.json (settings.features includes assistant-byok); packages/finops-catalogue/src/sources.js (referral-guttertoken, status: 'planned')

2. Market / trading venues (all read-proxied; the member is always the signer)

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
Polymarket CLOB Predict order book: market reads, fee reads, order post/cancel relay-gateway polymarket/ clob.polymarket.com HTTPS/443 L2 HMAC-SHA256 headers (POLY_API_KEY/POLY_PASSPHRASE/POLY_SIGNATURE/POLY_TIMESTAMP/POLY_ADDRESS). L1 EIP-712 wallet signature done offline at provisioning Secret Manager POLYMARKET_API_KEY / _SECRET / _PASSPHRASE / _ADDRESSgateway.env, all optional. Creds never leave the gateway process member addresses leave (user-specific reads are keyed by trader address); member-signed order payloads pass through. No key material Optional ⇒ 503 predict_unconfigured, SPA hides the Predict tab they pay us — builder-code programme: POLYMARKET_BUILDER_CODE=0x6e03…93a3, 50 bps taker / 0 maker (additive to the member's cost, must be disclosed as its own line) + weekly builder rewards services/relay-gateway/src/config/index.js:637; services/relay-gateway/src/polymarket/client.js:8-50; infra/vm/common/fetch-secrets.sh:129-132; infra/vm/gateway/docker-compose.yml (POLYMARKET_BUILDER_*)
Polymarket Gamma API market search / metadata for the wager oracle picker frontend, browser-direct gamma-api.polymarket.com HTTPS/443 keyless search terms + market ids only Optional (search degrades) none (read-only metadata) frontend/src/hooks/usePolymarketSearch.js:8; frontend/src/hooks/usePolymarketMarket.js:7; frontend/src/config/networks.js:621; CSP frontend/nginx.conf:96
Polymarket Data API (configured upstream) relay-gateway polymarket/ data-api.polymarket.com HTTPS/443 keyless / L2 as above as above as above Optional as above services/relay-gateway/src/config/index.js:642
OpenSea Collect: collectible reads + sell-side listing writes relay-gateway opensea/ api.opensea.io HTTPS/443 API key, x-api-key header Secret Manager OPENSEA_API_KEYgateway.env, optional; key never leaves the process member addresses leave (reads are by owner address / contract / slug) Optional ⇒ 503 collectibles_unconfigured, SPA hides Collect they would pay us — referral/affiliate, but OPENSEA_REFERRAL_ADDRESS: "" in the committed compose ⇒ unattributed today. Never a surcharge services/relay-gateway/src/config/index.js:588; services/relay-gateway/src/opensea/client.js:6,54,97; infra/vm/gateway/docker-compose.yml (OPENSEA_REFERRAL_ADDRESS: ""); packages/finops-catalogue/src/sources.js (referral-opensea, not-configured)
Gains Network Perps market data (read-only) relay-gateway perps/ backend-arbitrum.gains.trade, backend-base.gains.trade, backend-polygon.gains.trade, backend-pricing.eu.gains.trade HTTPS/443 keyless (public venue APIs) pair symbols; a member address only for their own positions read Optional (PERPS_ENABLED); per-venue failure isolation — a degraded venue is NAMED, never zeros they would pay us — venue-paid referral, but PERPS_GAINS_REFERRER deliberately unset ⇒ not-configured services/relay-gateway/src/config/index.js:784-789; infra/vm/gateway/docker-compose.yml (PERPS_ENABLED: "true", attribution ids unset); packages/finops-catalogue/src/sources.js (referral-gains)
GMX v2 Perps market data (read-only) relay-gateway perps/ arbitrum-api.gmxinfra.io HTTPS/443 keyless as above Optional they would pay us — GMX ref code (also discounts the trader); PERPS_GMX_REF_CODE unset. The GMX UI fee rides on-chain calldata (uiFeeReceiver) from the SPA, never a gateway request services/relay-gateway/src/config/index.js:790; infra/vm/gateway/docker-compose.yml; packages/finops-catalogue/src/sources.js (referral-gmx)
Hyperliquid Perps market data (read-only), incl. HIP-3 perp-dex fan-out relay-gateway perps/ api.hyperliquid.xyz HTTPS/443 keyless as above Optional platform-priced fee — FeeRouter service perps.hyperliquid.builder, hard-capped at 10 bps (Hyperliquid's own limit, not our 250); PERPS_HL_BUILDER_FEE_BPS: "0" today and HL trading is not enabled ⇒ nothing is charged services/relay-gateway/src/config/index.js:791; infra/vm/gateway/docker-compose.yml (PERPS_HL_BUILDER_FEE_BPS: "0"); packages/finops-catalogue/src/sources.js (fee-perps-hl-builder)
Uniswap (V3) swaps, and the LiquidityRouter supply path frontend (on-chain) + LiquidityRouter contract; @uniswap/sdk-core in the bundle no HTTP egress — canonical on-chain contracts (factory 0x1F98…F984, swapRouter 0x68b3…Fc45, quoter 0x61fF…B21e, positionManager 0xC364…FE88). app.uniswap.org is a link-out only on-chain (RPC) n/a n/a on-chain calldata; position NFTs mint to the member (no custody) Required for the Swap/Supply surfaces; unavailable = those surfaces only we charge a member fee — FeeRouter liquidity.deposit, ConfigOnly, cap 250 bps, rate 0 at launch frontend/src/config/networks.js:609-617,624-627; packages/finops-catalogue/src/sources.js (fee-liquidity-deposit); frontend/package.json:61
ETCswap DEX on Ethereum Classic / Mordor frontend (on-chain) on-chain; v3.etcswap.org / etcswap.org link-out on-chain n/a n/a on-chain calldata ETC-only surface none frontend/src/config/networks.js:432,523

3. DeFi yield / staking venues

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
Morpho Earn ▸ Lend: vault discovery, APY/TVL, position enrichment (GraphQL) frontend, browser-direct (lib/earn/morphoApi.js) api.morpho.org/graphql; rewards-legacy.morpho.org (legacy URD, deprecated); app.morpho.org link-out HTTPS/443 keyless member addresses leave (position enrichment is by address) Optional; the Earn surface degrades we charge a member fee — FeeRouter earn.lend (atomic depositToVaultWithFee); vault deposits are ERC-4626, no custody frontend/src/config/earn.js:13-15; frontend/src/lib/earn/morphoApi.js:61; frontend/src/config/networks.js:80; packages/finops-catalogue/src/sources.js (fee-earn-lend)
Merkl (Angle Labs) Earn reward claims / accrual (MIP-111 successor to the legacy URD) frontend, browser-direct (lib/earn/merkl.js, hooks/useEarnRewards.js) api.merkl.xyz/v4 HTTPS/443 keyless member address leaves — the request path is literally /users/<address>/rewards?chainId=… Optional; reward display degrades none (no fee, no referral) frontend/src/config/earn.js:19; frontend/src/lib/earn/merkl.js:64
Lido stETH APR for the liquid-staking card frontend, browser-direct eth-api.lido.fi/v1/protocol/steth/apr/sma; stake.lido.fi link-out HTTPS/443 keyless nothing member-specific (a global APR read) Optional we charge a member fee — FeeRouter stake.lido (ConfigOnly) frontend/src/config/staking.js:45,115; packages/finops-catalogue/src/sources.js (fee-stake-lido)
Polygon staking (Polygon Labs) validator list + sPOL LST frontend, browser-direct staking-api.polygon.technology/api/v2/validators; staking.polygon.technology link-out HTTPS/443 keyless nothing member-specific Optional we charge a member fee — FeeRouter stake.polygon frontend/src/config/staking.js:49,125,135; packages/finops-catalogue/src/sources.js (fee-stake-polygon)
Across Protocol Bridge quotes + deposit status (suggested-fees); on-chain SpokePool / HubPool relay-gateway bridge/ (HTTP) + BridgeRouter contract (on-chain) app.across.to/api HTTPS/443 keyless route (from/to chain, token, amount); the member is the depositordepositV3 is passed msg.sender, so an unfilled deposit refunds to the MEMBER and the router holds nothing Optional (BRIDGE_ENABLED) ⇒ 503 bridge_disabled, SPA hides the Bridge tab. A quote is never served stale — the route 503s we charge a member fee — FeeRouter bridge.transfer, ConfigOnly, cap 250 bps, rate 0 at launch services/relay-gateway/src/config/index.js:727; services/relay-gateway/src/bridge/quotes.js:2,22,83; frontend/src/config/networks.js:200-204,628-629; packages/finops-catalogue/src/sources.js (fee-bridge-transfer)

4. Compliance / screening / stablecoin issuers (all on-chain reads — no HTTP egress, no data sent)

vendor provides consumed by egress proto auth credential data sent optional? commercial evidence
Chainalysis Sanctions Oracle (isSanctioned) — the OFAC SDN list, published on-chain and maintained by Chainalysis frontend lib/screening/ + SanctionsGuard contract on-chain only0x40C5…c8fb on chains 1/10/137/42161, 0x3A91…739B on Base (different address) EVM RPC none (public view function) nothing leaves — an eth_call against the member's RPC. No off-chain KYT/risk provider is used (TRM, Chainalysis KYT, Elliptic all explicitly absent) Per-chain; a source that did not answer is unreadable, never "clear" free (Chainalysis publishes it) frontend/src/lib/screening/sources.js:13-31,63-69
Circle (USDC) native USDC token + issuer freeze list (isBlacklisted) frontend lib/screening/, every USDC value path, EIP-3009 (TransferWithAuthorization) for x402 + relayed intents on-chain only (per-chain USDC addresses) EVM RPC none nothing leaves Required for USDC value paths none (we use the token) frontend/src/lib/screening/sources.js:81-89; frontend/src/config/networks.js:600-608 (domainVersion: '2')
Tether (USDT) issuer freeze list (isBlackListed — note the different selector/capitalisation) frontend lib/screening/ on-chain only, Ethereum 1 0xdAC1…1ec7 EVM RPC none nothing leaves Optional per chain none frontend/src/lib/screening/sources.js:74,82
Cloudflare Turnstile human-tier CAPTCHA for the spec-106 caller-identity tiers frontend widget + relay-gateway identity/verifiers/challenge.js challenges.cloudflare.com (widget script and /turnstile/v0/siteverify) HTTPS/443 site key (public, VITE_CHALLENGE_SITEKEY) + secret at siteverify Secret Manager CHALLENGE_SECRET; boot refuses the published Turnstile test secrets in production the Turnstile token + client IP (Cloudflare sees the request) Optional ⇒ the human tier stops resolving; callers keep reading at the anonymous ceiling — throughput, never access free tier services/relay-gateway/src/config/index.js:486,491; frontend/src/lib/identity/challenge.js:32; docs/runbooks/credential-rotation.md:78

5. Blockchain RPC / indexing / node infrastructure

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
QuickNode keyed archive RPC (the only archive depth in the estate) + Admin API (credit usage, endpoint security state) + the spec-107 keyed-issuance endpoint alto bundler (REQUIRED), relay-gateway, finops-exporter, workstation (hardhat deploy/fork) *.quiknode.pro (small-white-general.{,optimism.,matic.,base-mainnet.,arbitrum-mainnet.}quiknode.pro); api.quicknode.com/v0 (admin) HTTPS/443 token in the URL path for RPC; API key for the Admin API; short-lived ES256 JWT for the member-facing issuance endpoint RPC URL: Secret Manager QUICKNODE_POLYGON_APIALTO_RPC_URL (bundler, required) and RPC_URL_PRIMARY_137 (gateway/finops, optional). Admin: finops-quicknode-key. JWT signing key: fairwins-rpc-access-signing-key (key material, rotated by succession). Workstation: fairwins-quicknode-{polygon,ethereum,optimism,base,arbitrum}-url chain reads/writes (addresses, calldata, tx). The URL is redacted to https://<host>/<redacted> at every log/status boundary REQUIRED for the bundler (alto takes ONE URL, no failover, no default — boot aborts). Optional elsewhere (gateway/finops fall back to public endpoints). Whole endpoint capped at 50 req/s shared by gateway + exporter + alto we pay them (modelled — the Admin API reports credits, never dollars; a Flat Rate RPS plan decouples spend from usage entirely) infra/vm/common/fetch-secrets.sh:227-237; infra/vm/gateway/docker-compose.yml (RPC_ACCESS_ENDPOINT_URL_*); services/relay-gateway/src/config/index.js:520; services/finops-exporter/src/config/index.js:143; scripts/secrets/registry.js (5 endpoint entries); docs/runbooks/credential-rotation.md:75,84-99,146
publicnode (Allnodes) public RPC — the build default for 7 networks and the gateway/engine failover frontend (default rpcUrl), relay-gateway, OZ Relayer engine, finops-exporter polygon-bor-rpc, ethereum-rpc, optimism-rpc, base-rpc, arbitrum-one-rpc, ethereum-sepolia-rpc, ethereum-hoodi-rpc, polygon-amoy-bor-rpc — all .publicnode.com HTTPS/443 keyless chain reads/writes. Member RPC overrides take precedence (spec 069) and the build default sits behind them as failover Effectively the floor of the read path — a member override or QuickNode sits in front free frontend/src/config/networks.js:566,666,716,773,829,907,984,268; services/relay-gateway/src/config/chains.js:33,44; services/oz-relayer/deploy/production/config.json:100-117; CSP frontend/nginx.conf:96
dRPC public RPC failover (Polygon) relay-gateway, engine, finops-exporter polygon.drpc.org (also eth/base/optimism/arbitrum.drpc.org referenced) HTTPS/443 keyless chain reads Failover only free services/relay-gateway/src/config/chains.js:33; infra/vm/gateway/docker-compose.yml (RPC_URLS_137)
ETC Cooperative / etc-network.info / Rivet / ETC Desktop Ethereum Classic + Mordor public RPC (ETC has no commercial provider in this estate) frontend, relay-gateway, engine rpc.mordor.etccooperative.org, geth-mordor.etc-network.info, etc.rivet.link, etc.etcdesktop.com HTTPS/443 keyless chain reads/writes The only option on 61/63 free services/relay-gateway/src/config/chains.js:54,64; frontend/src/config/networks.js:382,476
Polygon Labs RPC Amoy testnet public RPC frontend, gateway rpc-amoy.polygon.technology HTTPS/443 keyless chain reads Testnet only free services/relay-gateway/src/config/chains.js:44; CSP frontend/nginx.conf:96
The Graph (Studio / hosted gateway) WagerRegistry subgraph — wager history and reports frontend (subgraphUrl), relay-gateway member API (MEMBER_API_SUBGRAPH_<chainId>) api.studio.thegraph.com (query and /deploy/) HTTPS/443 keyless for the pinned Studio query URLs; GRAPH_API_KEY (query) / GRAPH_DEPLOY (deploy, not interchangeable) for gateway/CLI paths Secret Manager fairwins-graph-api-key, fairwins-graph-deploy-keyworkstation profile publish only, never on a VM GraphQL queries containing member addresses (wager participants) Optional — absence degrades to direct RPC reads via RegistrySource. Mordor deliberately has no subgraph and answers not-configured, which is NOT an empty list free tier TODAY (thegraph, modelled $0 — catalogued 2026-09-11): the pinned URLs are Studio, which consumes no GRT. Switching one to gateway.thegraph.com moves us to GRT query fees from a billing balance on Arbitrum One, and check:finops C6 fails that transition while the $0 is still asserted frontend/src/config/networks.js:274-276,585-587; infra/vm/gateway/docker-compose.yml (MEMBER_API_SUBGRAPH_137, pinned to v0.3.0, never /version/latest); scripts/secrets/registry.js; subgraph/package.json:11
Etherscan (V2 multichain) / Blockscout / Polygonscan / Arbiscan / Basescan / Solscan block-explorer link-outs, and contract verification in CI/ops frontend (link-out only), npm run verify:<net> etherscan.io, optimistic.etherscan.io, polygonscan.com, amoy.polygonscan.com, arbiscan.io, basescan.org, sepolia.etherscan.io, hoodi.etherscan.io, etc.blockscout.com, etc-mordor.blockscout.com, solscan.io HTTPS/443 API key for verification only Secret Manager fairwins-etherscan-api-key, workstation profile verify verification: contract source + bytecode. Link-outs send nothing (the member clicks) Optional free tier frontend/src/config/networks.js:269,383,483,575,670,720,777,833; scripts/secrets/registry.js
mempool.space Bitcoin: Esplora REST (UTXOs, fees, broadcast) — mainnet + testnet4 relay-gateway bitcoin/ mempool.space/api, mempool.space/testnet4/api HTTPS/443 keyless bare Bitcoin addresses and signed raw transactions only. Key material / xpubs never leave the client (derived from the passkey master seed) Optional (BTC_ENABLED, on today) ⇒ every Bitcoin surface hides/degrades. Deliberately not the QuickNode BTC endpoint (QuickNode serves Core JSON-RPC/Blockbook, not Esplora) free services/relay-gateway/src/config/index.js:681-682; services/relay-gateway/src/bitcoin/client.js:7; infra/vm/gateway/docker-compose.yml (BTC_ENABLED: "true", BTC_ESPLORA_URL)
stampchain.io Bitcoin Stamps indexer — tells a UTXO apart from an inscribed collectible relay-gateway bitcoin/ stampchain.io HTTPS/443 keyless bare Bitcoin addresses Optional, and FAIL-SAFE: unconfigured/down ⇒ /stamps answers degraded:true and the client protects unverified coins rather than spending them free services/relay-gateway/src/bitcoin/client.js:11,158,168; infra/vm/gateway/docker-compose.yml (BTC_STAMPS_URL)
Solana public clusters Solana RPC (spec 063) frontend config + useCrossChainDiscovery only api.mainnet-beta.solana.com, api.devnet.solana.com HTTPS/443 keyless addresses Declared, not live — no /v1/solana/* router exists in the gateway free frontend/src/config/solanaNetworks.js:1-31; no solana router in services/relay-gateway/src/server.js:39-61

6. ERC-4337 / gasless infrastructure

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
alto (Pimlico OSS bundler) self-hosted ERC-4337 bundler for passkey UserOps frontend (passkey rail) → bundler.fairwins.app our VM. Its own egress is QuickNode only (ALTO_RPC_URL, one URL, no failover) HTTPS/443 in; HTTPS/443 out to QuickNode inbound: Cloudflare origin-lock header; outbound: token-in-path alto-executor-key-137 (hot key holding real POL, executor and utility) + QUICKNODE_POLYGON_API — both required, else boot aborts UserOps, signed by the member's passkey REQUIRED for the passkey rail on Polygon. No bundler exists on ETC 61 / Mordor 63 — the write rail falls back to a native signer there self-hosted (image alto:v1.2.7); we pay gas (bundler-gas-polygon, modelled) infra/vm/bundler/docker-compose.yml:22,37,82; infra/vm/common/fetch-secrets.sh:225-237; packages/finops-catalogue/src/sources.js (bundler-gas-polygon)
OpenZeppelin Relayer ("engine") self-hosted relayer engine that broadcasts every relayed intent relay-gateway (ENGINE_URL=http://localhost:8080, shared network namespace) our VM. Its own RPC egress is the public list in config.json (a file in git — deliberately no keyed URL) loopback in; HTTPS/443 out bearer API_KEY + HMAC webhook WEBHOOK_SIGNING_KEY relay-engine-api-key (pinned v2), relay-webhook-secret (pinned v2), relay-engine-gcp-private-key — all required. Gas keys are GCP KMS, never raw; PM_SIGNER_PRIVATE_KEY present anywhere in the gateway env refuses boot signed transactions REQUIRED for all gasless relaying. Every gasless flow nonetheless keeps a self-submit fallback (never-stranded rule) self-hosted (image fairwins-relay-engine:multichain-v1.5.0); we pay gas (relayer-gas-polygon, relayer-gas-mordor) services/oz-relayer/deploy/production/config.json:45-78,100-117; infra/vm/gateway/docker-compose.yml; infra/vm/common/fetch-secrets.sh:166-176
EntryPoint v0.6 / ERC-4337 (Ethereum Foundation canonical) the account-abstraction singleton contracts + frontend on-chain only0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, recorded as entryPoint on all 8 EVM chains EVM RPC n/a n/a UserOps Required for the passkey rail free (canonical singleton) infra/vm/bundler/docker-compose.yml:37; deployments/*-v2.json (entryPoint)
FairWins VerifyingPaymaster sponsored gas — our own contract, signed by a KMS key contracts + gateway POST /v1/paymaster on-chain + GCP KMS EVM RPC / KMS ERC-7677 endpoint + KMS signature PM_SIGNER_KMS_KEY (Cloud KMS resource path; never a raw key) UserOp hashes Optional — passkey path falls back to self-funded UserOps; the confirm UI must disclose sponsored vs. user-pays honestly we pay gas (paymaster-gas, modelled; alerts on runway, never a balance floor) infra/vm/gateway/docker-compose.yml (PM_SIGNER_KMS_KEY, PAYMASTER_ADDRESS_137); packages/finops-catalogue/src/sources.js (paymaster-gas)

7. Cloud / hosting / observability

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
Google Cloud Platform (project chippr-bots-site-wp, shared with unrelated Chippr workloads) Compute Engine (2 VMs: fairwins-gateway, fairwins-bundler, + VPC/firewall/static IPs), Cloud Run (the SPA; MCP server module), Artifact Registry (all images), Cloud Build (CI/CD), Secret Manager (every credential), Cloud KMS (relayer gas keys + paymaster signer), BigQuery (billing export, the ONLY billed cost source), Cloud Storage, IAM / Workload Identity Federation, Cloud Monitoring (uptime checks + alert policies — this is the paging system), IAP (the only SSH path; :22 is open to the IAP range only) everything *.googleapis.com, us-central1-docker.pkg.dev, metadata.google.internal, token.actions.githubusercontent.com (WIF) HTTPS/443 workload identity on node/CI service accounts — no service-account key file, ever; operators impersonate Secret Manager itself; the workstation identity is declared Terraform (chippr-tf-modules//modules/ops-workstation) everything the platform holds REQUIRED — this is the platform. IAM is additive-only: google_project_iam_binding/_iam_policy are authoritative project-wide and would strip roles from the unrelated WordPress/ClearPath/Fukuii/Kings-Edge workloads; rejected by npm run check:iac and by the CI identity lacking projectIamAdmin we pay them — the only basis: 'billed' source in the catalogue (lags hours, never backfills) infra/terraform/environments/prod/main.tf:212; infra/terraform/environments/prod/imports.tf:28-85,237,252,332-347; cloudbuild.yaml:5,97,109,149; infra/vm/gateway/docker-compose.yml; packages/finops-catalogue/src/sources.js (gcp)
Cloudflare (zone fairwins.app, Free plan) DNS, TLS, WAF geo gate answering HTTP 451 — a legal control under CODEOWNERS, origin-lock transform rule, Analytics GraphQL, Turnstile (§4), cloudflareinsights.com beacon edge (all inbound) + finops-exporter (analytics read) api.cloudflare.com/client/v4/graphql, *.cloudflareinsights.com, challenges.cloudflare.com HTTPS/443 API token (Zone → Analytics → Read, exactly) + shared origin-lock header Secret Manager finops-cloudflare-token, origin-lock-secret (rotate at Cloudflare and here together) all inbound member traffic transits Cloudflare (IPs, request metadata) REQUIRED — the geo gate is compliance-critical and the origin lock is what stops a direct-to-origin bypass. Both rulesets are authoritative for their phase: an apply deletes any rule added at the dashboard we pay them ($0 on the free plan — an asserted zero via FINOPS_CLOUDFLARE_PLAN_USD=0, not an unset default). GraphQL returns requests/bytes but no dollar figure, so cost is modelled infra/cloudflare/waf-geo.md:1-30; infra/cloudflare/origin-lock.md:1-20; services/finops-exporter/src/config/index.js:129; infra/vm/gateway/docker-compose.yml (CLOUDFLARE_ZONE_ID, FINOPS_CLOUDFLARE_PLAN_USD: "0"); CSP frontend/nginx.conf:96
Grafana Cloud (chippr.grafana.net, Prom instance 3500268) metric sink for the FinOps exporter (via Grafana Alloy) + dashboards Alloy sidecar (push-only; we expose nothing) prometheus-prod-66-prod-us-east-3.grafana.net/api/prom/push HTTPS/443 two non-interchangeable tokens: glc_ Cloud Access Policy (metrics:write) ships metrics; glsa_ stack service account provisions dashboards Secret Manager finops-grafana-cloud-token (glc_) → alloy.env; GAFANA_SERVICE_ACCOUNT (misspelled, no R) is ops-only, never on a VM FinOps metric series — labels are bounded enumerations only, never a member address, wager id or tx hash Optional ⇒ dashboards go dark; the relay path is untouched. Alloy's WAL is a persisted volume so samples survive a Grafana outage we pay them (grafana-cloud, modelled; free tier is $0 but must be asserted) infra/vm/gateway/docker-compose.yml (alloy service, GRAFANA_CLOUD_PROM_URL/USER); docs/runbooks/credential-rotation.md:144-154; packages/finops-catalogue/src/sources.js (grafana-cloud)
Prometheus / Grafana (local, infra/observability/) read-only viewing surface bound to loopback on the operator workstation operator loopback HTTP none Explicitly NOT the paging system — Cloud Monitoring pages, and it runs when the workstation does not self-hosted infra/observability/README.md; project CLAUDE.md § workstation-secrets
Pinata / IPFS member-facing JSON pinning (wager creation, open challenges, encrypted data backup) and mini-app package publishing; gateway reads frontend via a same-origin /api/pinata/pinJSONToIPFS nginx proxy; ops CLI for file publishing api.pinata.cloud, gateway.pinata.cloud, ipfs.fairwins.app, ipfs.io, cloudflare-ipfs.com, dweb.link HTTPS/443 JWT bearer — injected by nginx, so the browser never holds it two copies: Secret Manager fairwins-pinata-jwt (workstation, publishing) and the Cloud Run runtime env VITE_PINATA_JWT on the SPA service (Terraform-unmanaged — appears in no plan). Rotating one leaves production on the other member-authored wager/challenge JSON and encrypted backup blobs (encrypted client-side; the plaintext never leaves) REQUIRED for member writes — wager creation and open challenges pin JSON with no fallback. A key valid for pinFileToIPFS but not pinJSONToIPFS authenticates fine and then breaks every member write with NO_SCOPES_FOUND (happened in production 2026-08-30); testAuthentication does not catch it we pay them (pinata, modelled — catalogued 2026-09-11; storage read via a SEPARATE read-scoped key, never the pinning JWT) frontend/src/constants/ipfs.js:14,25; frontend/nginx.conf.template:62-92; frontend/src/lib/miniapps/loader.js:57,242,319; docs/runbooks/credential-rotation.md:194-215; scripts/secrets/registry.js
Alphaday token news feed (spec 109) — the fourth read-proxy relay-gateway news/ api.alphaday.com (/items/news/?tags=<slug>) HTTPS/443 keyless — no credential exists for this module a curated slug only. No member data. The vendor has no CORS and no contract identity, which is why the proxy and our own (chainId,address)→slug table exist Optional (NEWS_ENABLED, on today). Advisory-only: no value path gates on it. Vendor image/icon/HTML are never forwarded. Cache TTL clamped ≥ 300 s free tier — catalogued modelled $0 purely so the dependency is visible if the vendor ever prices it services/relay-gateway/src/config/index.js:871; services/relay-gateway/src/news/normalize.js:6; infra/vm/gateway/docker-compose.yml (NEWS_ENABLED: "true"); packages/finops-catalogue/src/sources.js (alphaday-news-api)
CoinGecko POL/USD spot price for member-facing conversion frontend, browser-direct api.coingecko.com/api/v3/simple/price HTTPS/443 keyless nothing member-specific Optional (display only). Note the exporter deliberately does NOT use it — it reads a Chainlink feed instead, for zero credentials and a publishable updatedAt free tier frontend/src/hooks/usePriceConversion.js:34-35; services/finops-exporter/src/collectors/fx.js:1-12

8. Wallets, custody, hardware, and identity

vendor provides consumed by egress host(s) proto+port auth credential lives data sent optional? commercial evidence
WalletConnect / Reown mobile + hardware wallet connectivity (wagmi connector) frontend relay.walletconnect.{com,org} (WSS and HTTPS), rpc., explorer-api., verify., pulse., keys., notify., echo., push.walletconnect.{com,org}, api.web3modal.{com,org}, fonts.reown.com, cloud.walletconnect.com WSS/443 + HTTPS/443; verify.walletconnect.* in frame-src project id (public) VITE_WALLETCONNECT_PROJECT_ID, with a hardcoded fallback demo id e7a122e5…54f in wagmi.js:137 — a build-time public value, not a secret session metadata, member addresses, signing requests Optional per member (injected wallets work without it). Stubbed inert under Cypress because eagerly building the provider cost a full handshake on every page load free tier frontend/src/wagmi.js:4,135-147,307-312; CSP frontend/nginx.conf:96 (wss://relay.walletconnect.*, frame-src verify.walletconnect.*)
Safe (Gnosis) Safe v1.4.1 multisig contracts for Protect custody vaults; safe-singleton-factory for deterministic addresses contracts + frontend lib/custody/ on-chain only. Proposal discovery is our own safeProposalHub contract — no Safe Transaction Service is used EVM RPC n/a n/a on-chain calldata; approvedHashes reads Required for Protect. safeProposalHub needs a recorded deploy block per chain or proposal discovery is silently dead free (contracts). The policy guard singleton is deliberately NOT upgradeable — an upgrade key over a policy guard is a backdoor across every vault package.json:151-152; deployments/*-v2.json (safeProposalHub, safePolicyGuard, safePolicyGuardV2, policyGuardSetup); frontend/src/config/safeContracts.js
Ledger hardware-wallet signing (WebUSB / WebHID / BLE on native) frontend, behind the single lib/hardware/adapters.js#connectHardware seam no first-party egress in the app path (@ledgerhq/* SDKs; @ledgerhq/domain-service/evm-tools carry axios pins). Transport is USB/HID/BLE, local USB/HID/BLE physical device confirmation device-held. The store holds public metadata only{address, vendor, path, label, addedAt}; never key material, never an xpub tx to sign; every signature is a physical confirmation and is recover-and-verified before broadcast Optional free frontend/package.json:43-47; package.json:176-183; project CLAUDE.md § spec 085
Trezor hardware-wallet signing frontend, same seam connect.trezor.ioin frame-src (Trezor Connect runs in an iframe) HTTPS/443 iframe physical device confirmation device-held; same public-metadata-only store tx to sign Optional free frontend/package.json:60; CSP frontend/nginx.conf:96 (frame-src … https://connect.trezor.io)
Google Fonts webfont delivery (CSP-permitted) frontend fonts.googleapis.com (CSS), fonts.gstatic.com (files) HTTPS/443 keyless request metadata Permitted by CSP but should not be exercised — spec 090 requires the brand faces (Space Grotesk / Inter / JetBrains Mono) be self-hosted so the PWA works offline; "never add a Google Fonts link" free CSP frontend/nginx.conf:96 (style-src … fonts.googleapis.com, font-src … fonts.gstatic.com); project CLAUDE.md § spec 090
OpenStreetMap map tiles (CSP img-src grant) frontend *.tile.openstreetmap.org HTTPS/443 keyless tile coordinates Optional free CSP frontend/nginx.conf:96

9. Oracles (on-chain adapters — no HTTP egress from our code)

vendor provides consumed by egress auth data sent deployed where commercial evidence
Chainlink (a) Data Feed oracle adapter for wager resolution; (b) Functions adapter; © the POL/USD price feed the FinOps exporter reads (0xAB59…2dE0) instead of a price API contracts/oracles/Chainlink*Adapter.sol, WagerRegistry; services/finops-exporter/src/collectors/fx.js on-chain (latestRoundData) none nothing leaves chainlinkDataFeedAdapter + chainlinkFunctionsAdapter on Polygon 137 and Amoy 80002 Functions consumes LINK (subscription); feeds are free reads deployments/polygon-chain137-v2.json, deployments/amoy-chain80002-v2.json; services/finops-exporter/src/config/index.js:27,110; services/finops-exporter/src/collectors/fx.js:1-12. @chainlink/contracts is pinned EXACT 1.5.0 — a float 1.3.0→1.5.0 changed ChainlinkFunctionsOracleAdapter bytecode and only the byte-diff gate caught it (package.json:136)
UMA Optimistic Oracle V3 adapter for dispute-resolved wagers contracts/oracles/UMAOptimisticOracleV3Adapter.sol on-chain none assertion data on-chain umaAdapter on Polygon 137 and Amoy 80002 UMA bonds/fees paid on-chain per assertion. Note @uma/core is not an npm dependency — the interface is vendored (contracts/interfaces/IOptimisticOracleV3.sol) deployments/polygon-chain137-v2.json, deployments/amoy-chain80002-v2.json; contracts/oracles/UMAOptimisticOracleV3Adapter.sol; grep '"@uma/core"' package.json → no match
Polymarket CTF conditional-token resolution adapter (separate from the CLOB HTTP proxy) contracts/oracles/PolymarketOracleAdapter.sol on-chain (0x4D97…6045 on Polygon) none nothing leaves polymarketAdapter on 137, 80002, 1337 free read deployments/polygon-chain137-v2.json; frontend/src/config/networks.js:619-620

10. Developer / CI / build-chain vendors

vendor provides consumed by egress host(s) auth credential lives optional? commercial evidence
GitHub (+ Actions, Pages, CodeQL, Dependabot, release-drafter) source hosting, 27 CI workflows, CodeQL scanning, docs on Pages, automated dependency PRs CI, ops github.com, api.github.com, token.actions.githubusercontent.com GITHUB_TOKEN / WIF; TF_MODULES_TOKEN for the private modules repo repo secrets + WIF REQUIRED for the release train.infra-apply has never run — gated on unset repo variables WIF_PROVIDER / TF_APPLY_SERVICE_ACCOUNT, so every Terraform change merges and silently does nothing (verified 2026-08-16) we pay them .github/workflows/ (27 files); .github/dependabot.yml; third-party actions: dorny/paths-filter, reactivecircus/android-emulator-runner, release-drafter/release-drafter, hashicorp/setup-terraform, terraform-linters/setup-tflint, google-github-actions/auth; docs/runbooks/credential-rotation.md:196-198
chippr-robotics/chippr-tf-modules (private, sibling repo) the five shared Terraform modules, pinned by commit SHA (a tag can be repointed) Terraform github.com (private) TF_MODULES_TOKEN CI secret REQUIRED for terraform init. A missing token reads as repository not found, not as a permission error (GitHub 404s private repos) internal infra/terraform/modules/README.md (pointer only); project CLAUDE.md § spec 087 rule 4a
Trail of Bits — Slither, Medusa, Manticore static analysis, fuzzing, symbolic execution (security gates) CI PyPI, github.com/crytic/medusa (Go) none Gating in CI (check-slither-findings.js) free/OSS .github/workflows/security-testing.yml:129-131,295; slither.config.json; medusa.json
Google Lighthouse CI perf/a11y budgets on 6 routes × 2 viewport profiles CI npm (@lhci/cli@0.13.x) none Budgets report; an unmeasured route fails (scripts/e2e/check-lighthouse-coverage.js) — lhci assert only evaluates URLs it collected free .github/workflows/frontend-testing.yml:143,179-195
Cypress + axe-core E2E and accessibility scanning CI npm none cy.a11yScan uses the already-installed axe-core injected by the runner — adding cypress-axe would touch the lockfile (spec 075) free frontend/cypress/; project CLAUDE.md § spec 094
npm registry the single root lockfile, 10 workspace members everything registry.npmjs.org none REQUIRED to build. Load-bearing hazard: npm/cli#4828 silently drops the platform rolldown binary on an incremental npm install (and npm ci does not fix it — the entry is optional AND peer). Recover with npm run deps:reinstall; the current binary name is in REQUIRED_OPTIONAL (scripts/deps/check-dependency-hygiene.js), not in prose free package.json; scripts/deps/check-dependency-hygiene.js; project CLAUDE.md § spec 075
Docker Hub / Google container mirrors base images (redis:7-alpine, grafana/alloy:v1.10.2, gcr.io/cloud-builders/*) VMs, CI download.docker.com, dl.google.com, gcr.io, us-central1-docker.pkg.dev none / GCP Required at deploy time only (pull_policy: missing) free / GCP infra/vm/gateway/docker-compose.yml; cloudbuild.yaml:5,109
PyPI (mkdocs, mkdocs-material, ansible-core, ansible-lint) docs site build + config management CI, ops pypi.org none Docs/ops only free requirements.txt; .github/workflows/security-testing.yml:253
Unsplash stock images for seeded demo market categories ops script only images.unsplash.com keyless Ops/demo only — not a runtime dependency free scripts/operations/market-templates/upload-category-images.js:16-18

11. Native app channels

vendor provides consumed by egress host(s) auth credential lives optional? commercial evidence
Capacitor (Ionic) + community plugins the iOS/Android shells: @capacitor/core 8.5.0, @capacitor/{app,android,ios}, @capgo/capacitor-passkey 8.5.1, @capacitor-community/bluetooth-le 8.3.0 (Ledger over BLE) native shells only; all native logic behind lib/native/runtime.js build-time (npm), pinned EXACT under the spec-075 lockfile rules none Optional per tenant — absence of the manifest native block means no native channel, never a fallback free/OSS frontend/package.json:34-37,83-85; tenants/fairwins/manifest.jsonnative.{ios,android}.appId = app.fairwins.member; project CLAUDE.md § spec 103
Apple (App Store Connect / TestFlight / Xcode) iOS distribution + the Swift toolchain release workflow Apple operator-held signing identity not in the repo — iOS ships an UNSIGNED archive; the Apple identity is operator-held Optional; an unsigned build is recorded signed:false, loudly we pay them (developer programme) docs/runbooks/native-release-operations.md:27,68; .github/workflows/release.yml; .github/actions/native-prepare (selects the newest Xcode — an older compiler silently drops declarations from Capacitor 8's prebuilt XCFrameworks)
Google Play Console / Android SDK Android distribution + Java 21 toolchain release workflow Google service-account signing material env-delivered from Secret Manager, gated on ANDROID_SIGNING_SERVICE_ACCOUNT Optional; unset ⇒ recorded unsigned we pay them docs/runbooks/native-release-operations.md; .github/workflows/native-build.yml; project CLAUDE.md § spec 103 rule 4

12. Agent / API consumers (they pay us)

vendor/party provides egress host auth credential data commercial evidence
x402 agent payers (any EIP-3009-capable payer) pay-per-request access to priced member-API ops inbound only signed TransferWithAuthorization (not Receive…), verified before settlement payer-held payer address; the request is served AS THE PAYER they pay us — $0.01 read / $0.05 typed-data build / $0.10 assistant message, USDC to X402_PAY_TO = 0xcf76…0447 (the admin Safe). LIVE since 2026-08-28. 0 price = "not offered", never free. Contract-account payers are EOA-only refusals whose reason says so infra/vm/gateway/docker-compose.yml (X402_ENABLED: "true", X402_CHAIN_ID: "137", X402_PAY_TO); packages/finops-catalogue/src/sources.js (x402-agent-payments, status: 'live')
MCP clients (services/mcp-server) MCP surface over the member API outbound to our own gateway the member's own capability token member-held as the member none directly; holds no key and cannot pay — it surfaces a 402 whole and forwards X-PAYMENT byte-for-byte services/mcp-server/ (dependency-free, deliberately NOT a workspace member — lockfile hazard); docs/developer-guide/mcp-server.md

13. DECLARED BUT NOT LIVE

vendor / money path state evidence
Anthropic via the FairWins assistant rail Built, not offered. ASSISTANT_ENABLED is commented out in the committed compose; MEMBER_API_ENABLED above it is on. Catalogued planned with basis: 'modelled' — enabling it is a FinOps decision (rule B3 fails a planned money path a committed deployment switches on), not a flag flip infra/vm/gateway/docker-compose.yml (# ASSISTANT_ENABLED: "true"); packages/finops-catalogue/src/sources.js (assistant-model-api, status: 'planned')
GutterToken referral credit planned, metric: null, collector: 'none'. No referral code is registered, and more fundamentally nothing can ever read it — GutterToken publishes no balance/usage/referral endpoint. The code would live in the tenant manifest, where the C2b gate is structurally blind to it packages/finops-catalogue/src/sources.js (referral-guttertoken); tenants/fairwins/manifest.json has no settings.assistant block (verified: settings keys are features, chains, membership, fees, gateway, subgraph)
OpenSea referral live collector but not-configuredOPENSEA_REFERRAL_ADDRESS: "" in the committed compose. No code registered ⇒ nothing to earn. Reported as not-configured, never $0 infra/vm/gateway/docker-compose.yml; packages/finops-catalogue/src/sources.js (referral-opensea)
Gains referral / GMX ref code / Hyperliquid builder address All three attribution ids deliberately unset; link-outs go unattributed rather than carrying a code that earns nothing. PERPS_HL_BUILDER_FEE_BPS: "0" and HL trading is not enabled infra/vm/gateway/docker-compose.yml (# PERPS_GAINS_REFERRER / PERPS_GMX_REF_CODE / PERPS_HL_BUILDER_ADDRESS: unset on purpose); packages/finops-catalogue/src/sources.js
Perps in-app execution FR-018: no order controls ship. The proxy is read-only by construction (no write route). A separate build-time flag VITE_PERPS_MANAGE_ENABLED is deliberately absent from every cloudbuild infra/vm/gateway/docker-compose.yml (perps block); project CLAUDE.md § spec 082
Mini-app licenses / wager platform fee planned — they exist nowhere: MiniAppRegistry has no fee, price or payable function; WagerRegistry takes no platform cut packages/finops-catalogue/src/sources.js (miniapp-licenses, wager-platform-fee)
Solana (spec 063) Frontend network config + one consumer hook (useCrossChainDiscovery). No /v1/solana/* router exists in the gateway — the SPA would fall back to the public cluster frontend/src/config/solanaNetworks.js:1-31; services/relay-gateway/src/server.js:39-61 (routers: access, opensea, polymarket, bitcoin, perps, news, bridge, memberApi — no solana)
Semaphore / ZK anonymity Removed by design. Wager Pools are public-address-only; the ZK "claim code" design was dropped after testers rejected it. No Semaphore contract or dependency remains (poseidonT3 is a leftover on Mordor) contracts/pools/WagerPool.sol:36; project CLAUDE.md § spec 034 ("There is no Semaphore / anonymity")
QuickNode cost figure Container finops-quicknode-key exists with no enabled version, deliberately — that is the correct "not configured yet". FINOPS_QUICKNODE_PLAN_USD unset ⇒ not-configured, not $0 docs/runbooks/credential-rotation.md:146
QUICKNODE_POLYGON_WSS / QUICKNODE_AMOY_API / _WSS Declared but deliberately unread — nothing opens a WebSocket RPC and there is no Amoy-cohort node. Under Terraform with empty IAM policies on purpose (a recorded decision, not an orphan) infra/vm/common/fetch-secrets.sh:240+; docs/runbooks/credential-rotation.md:129-133
Terraform infra-apply Has never run. Gated on unset repo variables WIF_PROVIDER and TF_APPLY_SERVICE_ACCOUNT. Do not assume a merged infra PR was applied docs/runbooks/credential-rotation.md:196-198
Terraform manage_spa / manage_monitoring Live in GCP, declared in Terraform, gated OFF and not adopted. Monitoring in particular "fails QUIETLY": Cloud Monitoring accepts duplicates, so an apply would succeed and silently double every alert infra/terraform/environments/prod/imports.tf:271-352; variables.tf:156,178
Google Fonts Permitted by CSP but must not be used — brand faces are self-hosted for offline PWA CSP frontend/nginx.conf:96; project CLAUDE.md § spec 090
Funding Pools (spec 103) gasless rail Contracts are relayer-ready (…WithSig + EIP-3009 + factory …For) but the frontend ships self-submit + passkey only; FUNDING_POOL_TYPES is deliberately not in INTENT_TYPES/INTENT_ACTIONS. fundingPoolFactory is also not in deployments/polygon-chain137-v2.json deployments/polygon-chain137-v2.json (no fundingPoolFactory); project CLAUDE.md § spec 103

14. Criticality — what takes the platform down

Hard dependencies (absence = outage, not degradation):

  1. GCP — every container, every secret, every signing key (KMS), the SPA, the registry. It is the platform. Note the project is shared with unrelated Chippr workloads, which is why IAM is additive-only and enforced twice.
  2. Cloudflare — DNS/TLS for fairwins.app, and both the HTTP 451 geo gate (a legal control) and the origin lock. Bypassing or losing it is a compliance event, not just an availability one.
  3. QuickNode → alto — the only hard-required third-party credential on a runtime node. alto takes ONE URL with no failover and no default; without it the bundler aborts boot and the passkey gasless rail on Polygon stops. The whole endpoint is also capped at 50 req/s shared across gateway + exporter + alto.
  4. Pinata/IPFS — quietly the sharpest edge. Wager creation, open challenges and encrypted backup pin JSON with no fallback, and the member-facing credential is an unmanaged Cloud Run env var (VITE_PINATA_JWT) that is a second copy of the workstation secret. A key with the wrong scope authenticates fine and breaks every member write (production incident, 2026-08-30).
  5. Public RPC floor (publicnode + ETC Cooperative/rivet) — the build default behind every member override. ETC 61 / Mordor 63 have no commercial provider at all.
  6. Self-hosted, but single points of failure: the alto bundler and the OpenZeppelin Relayer engine. Every gasless flow keeps a self-submit fallback (never-stranded rule), so these degrade rather than strand funds — but the gasless product stops.
  7. GitHub / npm — build and release only, not runtime. Two named hazards: infra-apply has never executed, and npm/cli#4828 routinely drops the platform rolldown binary on Dependabot lockfile bumps.

Everything else degrades honestly (Polymarket, OpenSea, Morpho, Merkl, Lido, Polygon staking, Across, Gains, GMX, Hyperliquid, Alphaday, mempool.space, stampchain, The Graph, Grafana Cloud, Turnstile, Anthropic, GutterToken, Ledger, Trezor, WalletConnect) — each answers 503 <feature>_unconfigured or renders a stated reason, and the uniform rule is that an unreadable source is never a zero.

Where member data actually leaves the perimeter (the short list worth auditing): api.merkl.xyz (address in the URL path), api.morpho.org (address in the GraphQL body), api.opensea.io (owner address), clob.polymarket.com (trader address + signed orders), api.studio.thegraph.com (participant addresses), mempool.space/stampchain.io (bare BTC addresses and signed raw txs only — key material and xpubs never leave the client), api.anthropic.com / api.guttertokens.com (chat text; the GutterToken rail never transits FairWins), and Pinata (member-authored wager/challenge JSON; backup blobs are encrypted client-side).

No key material leaves the client anywhere. Bitcoin keys derive from the passkey master seed client-side; hardware wallets hold their own; the legacy-recovery vault stores only AES-GCM ciphertext; the GutterToken key is device-only and absent from backup; RPC credentials are device-only and absent from backup; the relayer's gas keys are Cloud KMS resources, never raw (and a raw PM_SIGNER_PRIVATE_KEY anywhere in the gateway env refuses boot).