{
  "id": "m4-swap-metamask",
  "title": "The MetaMask Swap",
  "extends": "m1-send-metamask",
  "context": {
    "wallet": "MetaMask extension",
    "platform": "Chrome extension",
    "network": "mainnet",
    "settings": "defaults",
    "version": "web/5.157.3",
    "observed": "2026-08-24",
    "captures": [
      {
        "run": "20260824-222054-swap-metamask",
        "tx": "0x407247a9e2133843416136d9c42c6537ee174dc36a2283e71fed3cf4acc4196f"
      }
    ]
  },
  "_comment": "MetaMask's BUILT-IN Swap (not a dapp) — the swap most people actually do: in-wallet, no site connect. It extends the M1 send backbone (Infura, feature flags, prices, screening, broadcast). What the swap ADDS is one closed engine: bridge.api.cx.metamask.io/getQuoteStream computes the route, the minimum-out, AND the swap calldata you sign, in one call — the same one-company-computes-what-you-sign risk as U1, but the company is MetaMask itself. Two defaults observed live (run 20260824-214902): the receive token pre-fills to mUSD, MetaMask's OWN stablecoin, and the quote screen discloses a 0.875% MetaMask fee taken on top of network + slippage. The swap executes as a contract call to MetaMask's swap router (MetaSwap; the 'recipient' is that contract, not a person). REAL mainnet tx 0x407247a9…4196f (to MetaSwap, method swap, 0.002 ETH → mUSD), broadcast through MetaMask's Smart Transactions relay. FINDING (run 20260824-222054): the signed swap does NOT go to the public mempool — it is handed to MetaMask's private STX relay (transaction.api.cx.metamask.io/submitTransactions → 200, then batchStatus polling) which lands it via a private/MEV-protected path. It mined, but only after a delay (>2 min past submit), and it was never publicly visible in flight: a closed relay sits between you and inclusion, and while you wait you can only see the relay's own status, not the chain's.",
  "prepend_steps": [
    {
      "id": "swap-tokens",
      "phase": "discover",
      "name": "Token list · what you can swap",
      "host": "tokens.api.cx.metamask.io/v3/assets",
      "actor": "consensys-apis",
      "purpose": "The swappable-token list the Swap screen offers, with each token's price and metadata",
      "need": "R",
      "carries": [
        "ip",
        "selected_address"
      ],
      "returns": "which tokens you can pick, and the mUSD default it fills",
      "can_block": true,
      "on_failure": "degrade",
      "worst_lie": {
        "outcome": "false_belief",
        "note": "the list decides what you can swap into and what it's labelled; the receive field defaults to MetaMask's own mUSD"
      },
      "removable_by": "none",
      "provenance": {
        "status": "observed",
        "ref": "20260824-214902-swap-metamask · tokens.api.cx.metamask.io /v3/assets, token.api.cx.metamask.io"
      },
      "notes": "The receive token pre-fills to mUSD (MetaMask's own stablecoin) — a self-default the user did not choose.",
      "fixed_by": [
        "local-indexing"
      ]
    },
    {
      "id": "swap-quote",
      "phase": "discover",
      "name": "MetaMask swap engine · route + calldata + fee",
      "host": "bridge.api.cx.metamask.io/getQuoteStream",
      "actor": "consensys-apis",
      "purpose": "One streamed call returns the route, the minimum received, the 0.875% MetaMask fee, and the swap-router calldata the wallet will sign",
      "need": "RC",
      "carries": [
        "ip",
        "selected_address",
        "token_pair",
        "amount",
        "holdings"
      ],
      "births": [
        "unsigned_tx",
        "swap_route"
      ],
      "returns": "the price you act on and the exact bytes you sign",
      "can_block": true,
      "on_failure": "blocks",
      "failure_note": "no quote → no swap; there is no second quote source in the UI",
      "worst_lie": {
        "outcome": "funds_lost",
        "note": "one closed API sets the route, the minimum-out, and the calldata together — a bad minOut drains you within slippage and the wallet can't check the custom-encoded calldata locally"
      },
      "removable_by": "none",
      "tx_input": "field",
      "provenance": {
        "status": "observed",
        "ref": "20260824-214902-swap-metamask · GET bridge.api.cx.metamask.io/getQuoteStream"
      },
      "notes": "MetaMask merged Swaps + Bridge into one 'bridge' quote API. The quote screen discloses 'Includes 0.875% MetaMask fee' on top of network fee and 2% default slippage.",
      "fixed_by": [
        "verifiable-rpc",
        "indie-frontends"
      ]
    }
  ],
  "own_node": {
    "node": "192.168.68.54",
    "runs": [
      "20260924-213710-swap-metamask-localnode"
    ],
    "steps": {
      "swap-tokens": {
        "state": "out",
        "ref": "tokens.api.cx.metamask.io · 1 req after switch"
      },
      "swap-quote": {
        "state": "out",
        "ref": "bridge.api.cx.metamask.io · 2 req after switch · /getquotestream"
      },
      "ext-update": {
        "state": "unseen",
        "ref": "clients2.google.com seen before the switch only, not after"
      },
      "feature-flags": {
        "state": "out",
        "ref": "client-config.api.cx.metamask.io · 1 req after switch"
      },
      "phishing-list": {
        "state": "unseen",
        "ref": "phishing-detection.api.cx.metamask.io seen before the switch only, not after"
      },
      "c2-list": {
        "state": "unseen",
        "ref": "client-side-detection.api.cx.metamask.io seen before the switch only, not after"
      },
      "token-list": {
        "state": "out",
        "ref": "token.api.cx.metamask.io · 2 req after switch"
      },
      "spot-prices": {
        "state": "out",
        "ref": "price.api.cx.metamask.io · 5 req after switch · /v3/spot-prices"
      },
      "fiat-rates": {
        "state": "unseen",
        "ref": "price.api.cx.metamask.io still contacted (5 req) but this exact call wasn't singled out"
      },
      "accounts-balances": {
        "state": "out",
        "ref": "accounts.api.cx.metamask.io · 3 req after switch · /v4/multiaccount/balances"
      },
      "balance-fallback": {
        "state": "local",
        "ref": "192.168.68.54 · eth_call; mainnet.infura.io got 0 after switch"
      },
      "nft-detection": {
        "state": "unseen",
        "ref": "nft.api.cx.metamask.io seen before the switch only, not after"
      },
      "defi-positions": {
        "state": "unseen",
        "ref": "defiadapters.api.cx.metamask.io seen before the switch only, not after"
      },
      "icons": {
        "state": "out",
        "ref": "static.cx.metamask.io · 1 req after switch"
      },
      "block-poll": {
        "state": "local",
        "ref": "192.168.68.54 · eth_blockNumber; mainnet.infura.io got 0 after switch"
      },
      "segment": {
        "state": "out",
        "ref": "api.segment.io · 4 req after switch"
      },
      "sentry": {
        "state": "out",
        "ref": "sentry.io · 15 req after switch"
      },
      "screen-recipient": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "nonce": {
        "state": "unseen",
        "ref": "mainnet.infura.io seen before the switch only, not after"
      },
      "gas-fees": {
        "state": "out",
        "ref": "gas.api.cx.metamask.io · 2 req after switch · suggestedgasfees"
      },
      "estimate-gas": {
        "state": "unseen",
        "ref": "mainnet.infura.io seen before the switch only, not after"
      },
      "simulation": {
        "state": "out",
        "ref": "tx-sentinel-ethereum-mainnet.api.cx.metamask.io · 2 req after switch"
      },
      "security-alert": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "stx-broadcast": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "inclusion": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "stx-status": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "receipt-poll": {
        "state": "unseen",
        "ref": "mainnet.infura.io seen before the switch only, not after"
      },
      "etherscan-check": {
        "state": "unseen",
        "ref": "not in these runs"
      },
      "balance-refresh": {
        "state": "unseen",
        "ref": "accounts.api.cx.metamask.io still contacted (3 req) but this exact call wasn't singled out"
      }
    },
    "unmapped": [
      "tron-mainnet.infura.io",
      "solana-mainnet.infura.io",
      "bitcoin-mainnet.infura.io",
      "user-storage.api.cx.metamask.io",
      "monad-mainnet.infura.io",
      "subscription.api.cx.metamask.io",
      "base-mainnet.infura.io",
      "bsc-mainnet.infura.io",
      "carrot.megaeth.com",
      "api.merkl.xyz",
      "optimism-mainnet.infura.io",
      "polygon-mainnet.infura.io",
      "testnet-rpc.monad.xyz",
      "arbitrum-mainnet.infura.io",
      "linea-mainnet.infura.io",
      "linea-sepolia.infura.io",
      "sepolia.infura.io",
      "notification.api.cx.metamask.io",
      "geolocation.api.cx.metamask.io",
      "on-ramp-cache.api.cx.metamask.io",
      "authentication.api.cx.metamask.io",
      "metamask.github.io",
      "gateway.api.cx.metamask.io",
      "cdn.contentful.com"
    ]
  }
}
