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.
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.
- The SPA's CSP
connect-src contains a bare https: grant — frontend/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.
- 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-key → ANTHROPIC_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 OFF — ASSISTANT_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 / _ADDRESS → gateway.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_KEY → gateway.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 depositor — depositV3 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 only — 0x40C5…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_API → ALTO_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-key — workstation 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 only — 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, 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.io — in 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.json → native.{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-configured — OPENSEA_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 |
Hard dependencies (absence = outage, not degradation):
- 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.
- 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.
- 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.
- 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).
- Public RPC floor (publicnode + ETC Cooperative/rivet) — the build default behind every member override. ETC 61 / Mordor 63 have no commercial provider at all.
- 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.
- 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).