diff --git a/docs/swaps-bridge-faq.md b/docs/swaps-bridge-faq.md new file mode 100644 index 00000000000..d83e846db4d --- /dev/null +++ b/docs/swaps-bridge-faq.md @@ -0,0 +1,81 @@ +## MetaMask Swaps & Bridge – FAQ + +Quick links +- PM Guide: `docs/swaps-bridge-pm-guide.md` +- Bridge Controller: `packages/bridge-controller/src/bridge-controller.ts` +- Bridge Status Controller: `packages/bridge-status-controller/src/bridge-status-controller.ts` +- Fetch (quotes/tokens/prices): `packages/bridge-controller/src/utils/fetch.ts` +- Status fetch/backoff: `packages/bridge-status-controller/src/utils/bridge-status.ts` +- Feature flags: `packages/bridge-controller/src/utils/feature-flags.ts` +- Selectors (sorting/metadata): `packages/bridge-controller/src/selectors.ts` +- Balance/allowance: `packages/bridge-controller/src/utils/balance.ts` +- Chain/token utils & constants: `packages/bridge-controller/src/utils/bridge.ts`, `packages/bridge-controller/src/constants/bridge.ts`, `packages/bridge-controller/src/constants/swaps.ts` + +1) Where do quotes come from? +- Bridge API `GET /getQuote` (built in `fetchBridgeQuotes` in `utils/fetch.ts`). + +2) Are API calls proxied or direct? +- Quotes/Tokens/Status: via Bridge API (`https://bridge.api.cx.metamask.io`). +- Prices: direct to Price API (`https://price.api.cx.metamask.io`). + +3) How often do quotes refresh? Can we override per chain? +- Default 30s (`REFRESH_INTERVAL_MS` in `constants/bridge.ts`). +- Per-chain override via feature flags (`utils/feature-flags.ts` + `selectors.ts`). + +4) When does quote polling stop? +- After `maxRefreshCount` (default 5) or when `insufficientBal` is true (`bridge-controller.ts`). + +5) Where are feature flags stored and which keys are used? +- Source: `RemoteFeatureFlagController` state. +- Mobile uses `bridgeConfigV2`; Extension uses `bridgeConfig` (`utils/feature-flags.ts`). + +6) What’s cached and for how long? +- Tokens: 10 minutes (`fetchBridgeTokens`, `cacheOptions`). +- Quotes: no cache (fresh; `cacheRefreshTime: 0`). +- Prices: 30 seconds per currency (`fetchAssetPrices`). + +7) Which RPC methods do we use? +- Native balance: `eth_getBalance` via ethers `provider.getBalance`. +- ERC-20 balance: `eth_call` to `balanceOf` via ethers `Contract` (`utils/balance.ts`). +- ERC-20 allowance (USDT reset logic): `allowance(owner, spender)` (`bridge-controller.ts` + `bridge-status-controller/utils/transaction.ts`). + +8) How is L1 gas fee (OP/Base) added to quotes? +- After fetching quotes, we call `TransactionController.getLayer1GasFee` for approval/trade and append `l1GasFeesInHexWei` when all quotes are on OP/Base (`bridge-controller.ts`). + +9) How is status polled and retried? +- Status endpoint: Bridge API `GET /getTxStatus` (`bridge-status/utils/bridge-status.ts`). +- Exponential backoff: base interval * 2^(attempts-1), using `REFRESH_INTERVAL_MS` as base. + +10) How do non‑EVM flows work (Solana, BTC, Tron)? +- Via Snaps using `SnapController:handleRequest`. +- Fee computation: unified `computeFee` (`bridge-controller/src/utils/snaps.ts`). +- Rent exemption (Solana): `getMinimumBalanceForRentExemption` (`utils/snaps.ts`). +- Non‑EVM tx submission handled in `bridge-status-controller.ts` and stored in history. + +11) Mobile vs Extension differences to be aware of +- `X-Client-Id` header: `mobile` vs `extension` (`constants/bridge.ts`). +- Feature flags field: Mobile `bridgeConfigV2`, Extension `bridgeConfig`. +- Mobile hardware wallets: require approval gating and a small delay (`bridge-status-controller/utils/transaction.ts`). + +12) Where are the base URLs defined? +- `BRIDGE_PROD_API_BASE_URL`, `BRIDGE_DEV_API_BASE_URL` in `constants/bridge.ts`. +- SWAPS v2 base (reference): `constants/swaps.ts`. + +13) How do we fetch tokens for a destination network the user hasn’t imported? +- `fetchBridgeTokens` hits Bridge API `GET /getTokens?chainId=…` and caches for 10 minutes (`utils/fetch.ts`). + +14) How are asset exchange rates sourced? +- Prefer existing controller state: `MultichainAssetsRatesController`, `CurrencyRateController`, `TokenRatesController`. +- If missing, fetch from Price API and store in `assetExchangeRates` (`bridge-controller.ts`). + +15) How do we detect quote expiration on the client? +- Selector `selectIsQuoteExpired` compares `quotesLastFetched` vs refresh interval and whether another refresh is expected (`selectors.ts`). + +16) How are quotes sorted and which is recommended? +- Default sorted by cost ascending; alternative ETA ascending. +- `selectRecommendedQuote` returns the top item (`selectors.ts`). + +17) How do we validate responses and surface issues? +- Quotes and status responses validated with schemas. +- Validation failures are recorded and can be tracked/inspected (see `utils/fetch.ts` and `bridge-status/utils/bridge-status.ts`). + diff --git a/docs/swaps-bridge-pm-guide.md b/docs/swaps-bridge-pm-guide.md new file mode 100644 index 00000000000..919a156cc3f --- /dev/null +++ b/docs/swaps-bridge-pm-guide.md @@ -0,0 +1,114 @@ +## MetaMask Swaps & Bridge: PM Guide + +### Table of contents +- [Executive summary](#executive-summary) +- [Where core logic lives](#where-core-logic-lives) +- [APIs called (direct vs via Bridge API)](#apis-called-direct-vs-via-bridge-api) +- [Feature flags (LaunchDarkly via RemoteFeatureFlagController)](#feature-flags-launchdarkly-via-remotefeatureflagcontroller) +- [Caching, refresh rates, and timing](#caching-refresh-rates-and-timing) +- [RPC usage (what providers/methods we call)](#rpc-usage-what-providersmethods-we-call) +- [Non‑EVM (Snaps) integration](#non-evm-snaps-integration) +- [Mobile vs Extension differences](#mobile-vs-extension-differences) +- [FAQ (quick answers)](#faq-quick-answers) + +### Executive summary +- The user-facing cross-chain Swaps experience is implemented by the Bridge Controller (quotes, UX timing/metrics) and the Bridge Status Controller (tx submission + tracking). +- Quotes/tokens/status use the Bridge API; price data uses the Price API directly. Caching: tokens 10m; prices 30s; quotes no cache. +- Feature flags come from the Remote Feature Flag Controller (LaunchDarkly-backed) and control refresh rates, overrides, etc. +- EVM operations use the selected provider (eth_getBalance, ERC-20 balanceOf, contract allowance); non-EVM use Snaps via SnapController. + +### Where core logic lives +- Bridge Controller (quotes, metrics, polling, exchange rates, ERC20 allowance): + - `packages/bridge-controller/src/bridge-controller.ts` + - Selectors (quote sorting/metadata): `packages/bridge-controller/src/selectors.ts` + - Fetch helpers (quotes/tokens/prices): `packages/bridge-controller/src/utils/fetch.ts` + - Feature flags helpers: `packages/bridge-controller/src/utils/feature-flags.ts` + - Balance/allowance helpers: `packages/bridge-controller/src/utils/balance.ts` + - Chain/token utilities: `packages/bridge-controller/src/utils/bridge.ts`, `.../constants/bridge.ts`, `.../constants/swaps.ts` + +- Bridge Status Controller (submit txs, poll status, build history, metrics): + - `packages/bridge-status-controller/src/bridge-status-controller.ts` + - Status fetch + backoff: `packages/bridge-status-controller/src/utils/bridge-status.ts` + - Tx helpers (EVM batching/7702, non‑EVM handling): `packages/bridge-status-controller/src/utils/transaction.ts` + - Received amount calc: `packages/bridge-status-controller/src/utils/swap-received-amount.ts` + +### APIs called (direct vs via Bridge API) +- Bridge API (Swaps backend): + - Quotes: `GET {BRIDGE_API}/getQuote?…` + - Built in `packages/bridge-controller/src/utils/fetch.ts` (function `fetchBridgeQuotes`) + - Tokens: `GET {BRIDGE_API}/getTokens?chainId=…` + - Built in `fetchBridgeTokens` + - Status: `GET {BRIDGE_API}/getTxStatus?…` + - Built in `packages/bridge-status-controller/src/utils/bridge-status.ts` (function `fetchBridgeTxStatus`) + +- Price API (direct, not proxied): + - Spot prices: `GET https://price.api.cx.metamask.io/v3/spot-prices?assetIds=…&vsCurrency=…` + - Built in `fetchAssetPrices` + +- SWAPS v2 base (reference only): + - `packages/bridge-controller/src/constants/swaps.ts` → `https://swap.api.cx.metamask.io` + +Constants (env): +- `BRIDGE_PROD_API_BASE_URL` = `https://bridge.api.cx.metamask.io` +- `BRIDGE_DEV_API_BASE_URL` = `https://bridge.dev-api.cx.metamask.io` +- Client identity header: `X-Client-Id` set to `extension` or `mobile` + +### Feature flags (LaunchDarkly via RemoteFeatureFlagController) +- Source: `RemoteFeatureFlagController` state + - Mobile reads `bridgeConfigV2`; Extension reads `bridgeConfig` + - Normalized by `processFeatureFlags` (`packages/bridge-controller/src/utils/feature-flags.ts`) +- Usage examples: + - Refresh interval override per chain and defaults + - Quote request overrides by feature (e.g., `FeatureId.PERPS` sorting tweak) + +### Caching, refresh rates, and timing +- Quotes polling: + - Default refresh: 30s (`REFRESH_INTERVAL_MS`), overridable by flags per chain + - Stop after `maxRefreshCount` (default 5) or if `insufficientBal=true` + - State tracks: `quotesRefreshCount`, `quotesLastFetched`, `quotesInitialLoadTime` +- Cache policy per endpoint: + - Tokens: 10 minutes (via `cacheOptions`) + - Quotes: no cache (always fresh) + - Prices: 30 seconds per currency (merged across currencies) +- Status polling: + - Interval base equals `REFRESH_INTERVAL_MS`, with exponential backoff on failures: `base * 2^(attempts-1)` + +### RPC usage (what providers/methods we call) +- Balances: + - Native: `eth_getBalance` via ethers `provider.getBalance` + - ERC-20: `eth_call` to `balanceOf` via ethers `Contract` +- Allowance: + - `erc20.allowance(owner, spender)` to check if USDT reset is needed +- L1 gas fees (OP/Base): + - Uses `TransactionController.getLayer1GasFee` per approval/trade tx + +### Non‑EVM (Snaps) integration +- Fee estimation and rent exemption: + - `SnapController:handleRequest` with `computeFee` (unified) and Solana rent exemption call +- Tx submission for non‑EVM: + - Unified `signAndSend` style flow through Snaps; response may include `transactionId`, `signature`, or structured result +- Stored in history with `isBridgeTx` and non‑EVM hints for UI/metrics + +### Mobile vs Extension differences +- Client ID header: `mobile` vs `extension` +- Feature flags field: `bridgeConfigV2` (Mobile) vs `bridgeConfig` (Extension) +- Mobile hardware wallets: require pre-approval gating and a small delay between approval/trade to improve UX + +### FAQ (quick answers) +- Where do we fetch quotes from? + - Bridge API `/getQuote` (proxied Swaps backend). +- Do we hit token/price APIs directly? + - Tokens: via Bridge API. Prices: direct Price API. +- How often do quotes refresh? Can I change it per chain? + - Default 30s; per-chain overrides via feature flags. +- When do we stop refreshing quotes? + - After max refreshes (default 5) or when balance is insufficient. +- How is balance checked? + - Native via `eth_getBalance`; tokens via `balanceOf` contract call on the selected provider. +- How is L1 gas for OP/Base added? + - Post-quote, we append L1 fees via `TransactionController.getLayer1GasFee`. +- Where do status updates come from? + - Bridge API `/getTxStatus`, with exponential backoff on failures. +- How do non‑EVM flows work? + - Through Snaps: fee computation, tx submission, signatures; tracked in history and polled when needed. +