rpc - #10014
Conversation
…taMask#9564) ## Explanation This PR add support for non-evm snap to ship `balance metadata` via - keyringApi.getAccountBalances - keyringEvent.AccountBalancesUpdated <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive, optional field on snap balance mapping with tests; no auth or breaking API changes. > > **Overview** > **Snap-backed balances can now carry chain-specific metadata** (e.g. Stellar spendable balance or trustline flags) through `AssetsController` state instead of dropping it after keyring responses. > > `SnapDataSource` copies optional `metadata` from snap keyring `Balance` objects into `assetsBalance` on both **`keyring_getAccountBalances` fetch** and **`AccountsController:accountBalancesUpdated`** handling. Entries without metadata stay `{ amount }` only. The balance-updated event payload type documents the optional `metadata` field. > > Tests cover fetch and event paths with Stellar-style metadata; the changelog records the behavior under Unreleased. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f9a02c2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…aMask#8338) ## Explanation `Messenger.delegate()` accepts a partial list of actions/events without compile-time enforcement that all required items are present. This means missing a delegation (e.g., a newly added `AllowedAction`) silently compiles and only fails at runtime. `delegateAll()` wraps `delegate()` with a `RequireExhaustive` branded intersection type that produces a clear TypeScript error showing exactly which actions or events are missing from the list. Own-namespace actions of the delegatee are automatically excluded from the check. Example error when an action is missing: ``` Property '__missing' is missing in type '[...]' but required in type '{ __missing: "KeyringController:signTypedMessage" }'. ``` ## References ## Changelog ### `@metamask/messenger` - **Added**: `delegateAll()` method — strict alternative to `delegate()` that requires all external actions and events to be listed - **Added**: `MessengerNamespace<M>` utility type — extracts the namespace string literal from a Messenger type - **Added**: `RequireExhaustive<Required, Provided>` utility type — validates tuple exhaustiveness at compile time ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > API addition only (`delegateAll` delegates to existing `delegate`); main risk is consumers adopting stricter typing, not runtime behavior changes. > > **Overview** > Adds **`Messenger.delegateAll`**, a typed wrapper around **`delegate`** that requires every **external** action and event on the delegatee messenger to be listed (own-namespace items are excluded). Missing entries fail at compile time via a **`RequireExhaustive`** branded type that surfaces **`__MISSING_DELEGATIONS__`**, and the source messenger must also type-check as able to provide those items. > > Also exports **`MessengerNamespace`** for extracting a messenger’s namespace literal from its type. > > **Testing/tooling:** Jest unit tests for runtime behavior, new **TSTyche** (`*.tst.ts`) compile-time tests, **`test`** split into **`test:unit`** + **`test:types`**, coverage/build/lint configs updated to exclude type-test files, and **yarn.config** allows the messenger package’s custom test script. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 22ed38b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…aMask#9673) ## Explanation `QuoteStatusManager.reportFinalised` surfaces a `QuoteStatusUpdateError` through the `onError` callback when it is asked to finalize a `txMetaId` that has no tracked quote-status entry. Those reports carried only an empty `quoteId`, the `txMetaId`, and `srcChainId`, so when one lands in Sentry there is nothing tying it to the on-chain source transaction — which is exactly what you need to tell whether the entry was never created, was created under a different source hash, or had already been evicted by TTL. `QuoteStatusUpdateErrorDetails` has carried an optional `srcTxHash` field since MetaMask#9596, but nothing on the finalization path populated it. This adds an optional `srcTxHash` parameter to `reportFinalised`, forwards it into the error details, and supplies it from every caller: | Caller | Source of the hash | | --- | --- | | `#onTransactionFailed` | `txMeta.hash` | | `#onTransactionConfirmed` (swap branch) | `txMeta.hash` | | `#handleOldHistoryItem` (polling permanently ends) | history item's `status.srcChain.txHash` | | `#fetchBridgeTxStatus` (final status) | the `settlementTxHash` already computed for `#reportSubmittedOnce` | | `SubmitStep.PublishCompletedEvent` | history item's `status.srcChain.txHash` | | `QuoteStatusManager.init()` reconciliation (both calls) | `entry.srcTxHash` | Things that may not be obvious: - The two `init()` call sites cannot actually reach the missing-entry branch, since that loop iterates entries that are by definition in the store. They pass `srcTxHash` purely so every call site is consistent. - In `#handleOldHistoryItem` and `SubmitStep.PublishCompletedEvent` the `txHistory` lookup is hoisted into a local rather than indexing state twice; the latter case is now wrapped in braces because it declares a `const`. - This is not a breaking change: `QuoteStatusManager` is not exported from `src/index.ts`, and the new parameter is optional. - One unrelated re-indent of an existing boolean expression inside `#processEntry`'s `retry()` closure, so the file passes the Prettier check. ## References https://consensyssoftware.atlassian.net/browse/SWAPS-4841 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Optional parameter and richer error metadata only; no change to successful finalization or backend sync behavior. > > **Overview** > **Quote status finalization errors** now include the source transaction hash when `reportFinalised` cannot find a tracked entry, so Sentry reports tie failures to the on-chain source tx (not just `txMetaId` and `srcChainId`). > > `QuoteStatusManager.reportFinalised` gains an optional `srcTxHash` argument that is forwarded into `QuoteStatusUpdateError` details. **Bridge status controller** call sites pass the hash from the relevant context (`txMeta.hash`, bridge history `status.srcChain.txHash`, or `settlementTxHash`); startup reconciliation passes `entry.srcTxHash`. Tests and the package changelog are updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4439794. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
) ## Explanation `SnapDataSource` previously delivered snap-sourced balance updates (from `AccountsController:accountBalancesUpdated`) by iterating `activeSubscriptions` and calling each subscription's own `onAssetsUpdate`. This meant updates were silently dropped whenever no active subscription was tracked for the chain at the moment the event arrived — which happens for networks like Tron, where a snap can report asset types (e.g. energy/bandwidth) that another data source (e.g. the WebSocket data source) owns the base chain for, but doesn't itself support. Since that other data source, not `SnapDataSource`, held the "active" subscription, the snap's updates never reached `AssetsController`. The fix has `SnapDataSource` accept a single `onAssetsUpdate` callback in its constructor and report all snap balance updates directly to `AssetsController` through that callback, instead of fanning them out through per-subscription state. This decouples delivery from subscription bookkeeping: since snaps push real-time updates and don't need polling, `subscribe()`/`activeSubscriptions` no longer need to store or invoke their own `onAssetsUpdate`, which also let the associated subscribe/unsubscribe/destroy logic be simplified (no more `isUpdate` branching or per-subscription cleanup for snap chains). `AssetsController` now passes `onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'SnapDataSource')` when constructing `SnapDataSource`, mirroring how the other data sources are wired up. ## References * Fixes an issue where Tron (and similar snap-sourced) asset updates could be silently dropped when no active `SnapDataSource` subscription existed for the chain. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them EOF <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how snap balance updates reach unified `assetsBalance` state; behavior is narrower but touches the multichain assets pipeline where incorrect delivery affects displayed balances. > > **Overview** > Fixes snap-sourced balances (e.g. Tron energy/bandwidth) being **silently dropped** when `SnapDataSource` had no matching entry in `activeSubscriptions`—common when another data source owns the chain subscription. > > **`SnapDataSource`** now takes a required **`onAssetsUpdate`** at construction. `AccountsController:accountBalancesUpdated` and subscribe-time initial fetches call that callback directly instead of iterating `activeSubscriptions` or invoking per-subscription `onAssetsUpdate`. Subscribe logic is slimmed down (no `isUpdate` / subscription bookkeeping for delivery; less destroy cleanup). > > **`AssetsController`** passes `onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'SnapDataSource')` when creating `SnapDataSource`. Tests and changelog updated; Jest coverage thresholds adjusted slightly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0745a3b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…#9526) ## Explanation The generic parameter has been removed from the `invalidateQueries` method, simplifying the type signature. In practice this was never used. It also created an odd inconsistency between the method type and the action type, which would be nice to avoid. ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them - I didn't test with a preview build, but I did search the entire organization for any references where the generic type param was used, and found none <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Narrow API/type-signature change with identical runtime behavior; breaking only for external callers that explicitly parameterized `invalidateQueries`, which the team reports were none. > > **Overview** > **Breaking (documented):** `BaseDataService.invalidateQueries` no longer accepts a `TPageData` type parameter; optional filters are now typed as `InvalidateQueryFilters<Json>` and still delegate to the query client unchanged. > > This aligns the public method signature with `DataServiceInvalidateQueriesAction`, which already used `Json` for filters. The unreleased changelog notes the break even though no in-org callers used the generic. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2faae35. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation Activity mappers in were using a hardcoded `nativeAssetsByCaipChainId` table. This doesn't scale and breaks on new chains for native metadata. This PR removes that table and replaces with an `assetType` field, setting its value to `native` when applicable so clients can resolve the correct native asset. ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Removes condition that prevents fetching token prices when cached values exist in the asset controller states. Token prices will now be fetched on each quote fetch to prevent displaying stale fiat amounts Testing - Get Swap quote with native dest asset, note fiat values - Switch the assets - The src fiat amount should roughly equal the previous dest fiat amount - Keep repeating and try other asset combinations ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Fixes https://consensyssoftware.atlassian.net/browse/SWAPS-4851 ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes quote-time pricing behavior and trims bridge-controller messenger dependencies, which can affect fiat display accuracy and client wiring but does not alter transaction or auth flows. > > **Overview** > Fixes stale fiat amounts in swap/bridge UI by **always** refreshing token prices when quotes are fetched, instead of skipping the price API when rates already exist in controller or assets state. > > `#fetchAssetExchangeRates` no longer filters asset IDs through `selectIsAssetExchangeRateInState` or builds exchange-rate sources from `MultichainAssetsRatesController`, `TokenRatesController`, and related messenger calls. Every quote fetch requests spot prices for the involved tokens (including native assets) and merges results into `assetExchangeRates`. Price requests now ask for both the user currency and **`usd`**. > > Messenger **`AllowedActions`** drops `TokenRatesController:getState` and `MultichainAssetsRatesController:getState`; currency for the fetch still comes from `CurrencyRateController` or `AssetsController:getExchangeRatesForBridge` when the assets-controller rates flag is on. Tests that asserted the old skip-fetch behavior are removed; selector tests add edge cases for missing rates and batch-sell `quoteRequestIndex` defaults. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit bb983c4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation Releases bridge-controller and bridge-status-controller <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Release-only version and changelog updates; consumer-facing risk is limited to the documented patch/minor bridge fixes, not large refactors in this diff. > > **Overview** > **Monorepo release 1153.0.0** that cuts and wires new versions of `@metamask/bridge-controller` and `@metamask/bridge-status-controller` (version fields, changelogs, `yarn.lock`). > > **`@metamask/bridge-controller@78.0.1`** (patch): documents dependency bumps and a fix to **always fetch and persist token prices** into `assetExchangeRates` ([MetaMask#9687]). > > **`@metamask/bridge-status-controller@74.6.0`**: when a finalization is reported for a transaction with **no tracked quote-status entry**, **`QuoteStatusUpdateError` now includes `srcTxHash`** in the error details ([MetaMask#9673]); depends on **`@metamask/bridge-controller@^78.0.1`**. > > No application source changes appear in this diff—only release/versioning artifacts. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7680037. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation Adds the user-facing `mm wallet send` command that sends a transaction end-to-end through the daemon-hosted `TransactionController`, closing MetaMask#9513. This is the CLI surface on top of the daemon's transaction capability (MetaMask#9512 / MetaMask#9612): it collects transaction parameters, dispatches them to the daemon, waits for the broadcast, and reports the resulting transaction hash. Send a transaction. `--value` is in ether; select the network with `--network-client-id` or `--chain-id`; the sender defaults to the selected account. The command previews the resolved plan and asks for confirmation before broadcasting, then prints the transaction hash: Because the daemon auto-approves the confirmation prompt — or your explicit `--yes` — is the only boundary before funds move; use `--dry-run` first if unsure. Gas is estimated automatically unless overridden with `--gas` / `--max-fee-per-gas` / `--max-priority-fee-per-gas` / `--gas-price` (each a `0x`-prefixed hex quantity). ### Dedicated `sendTransaction` RPC handler `TransactionController:addTransaction` returns a `Result` shaped like `{ transactionMeta, result }`, where `result` is a `Promise<hash>` that resolves once the transaction is signed and broadcast. That promise is **not JSON-serializable**, so it cannot travel back over the daemon's generic `call` dispatch. The daemon therefore exposes a dedicated `sendTransaction` handler (`src/daemon/send-transaction.ts`) that, server-side: 1. resolves the network client — from `networkClientId`, or from `chainId` via `NetworkController:findNetworkClientIdByChainId`; 2. resolves the sender — the provided `--from`, or the selected account; 3. calls `addTransaction(txParams, { networkClientId, origin: 'metamask', isInternal: true })` (internal, so it skips origin/permitted-account validation and is auto-approved by the headless daemon); 4. **awaits the broadcast** and re-reads the live record so the returned status reflects the post-broadcast state (`submitted`), not the `unapproved` creation snapshot; 5. returns a serializable `{ transactionHash, transactionId, status }`. Params are validated with superstruct at the daemon boundary (exactly one of `networkClientId` / `chainId`; `0x` address and hex quantities). ### The `mm wallet send` command A thin client over that handler (`src/commands/wallet/send.ts`): ```sh ➜ wallet-cli: yarn mm wallet send --help Send a transaction through the daemon-hosted TransactionController. Estimates gas automatically unless overridden, signs, broadcasts, and prints the resulting transaction hash. The daemon auto-approves, so the confirmation boundary is this command. USAGE $ mm wallet send --to <value> [--value <value>] [--from <value>] [--data <value>] [--network-client-id <value>] [--chain-id <value>] [--gas <value>] [--max-fee-per-gas <value>] [--max-priority-fee-per-gas <value>] [--gas-price <value>] [--dry-run] [-y] [-t <value>] FLAGS -t, --timeout=<value> Response timeout in milliseconds -y, --yes Skip the confirmation prompt and broadcast immediately. --chain-id=<value> Chain ID (0x-prefixed hex) to resolve to a network client. Provide this or --network-client-id, not both. --data=<value> Calldata as a 0x-prefixed hex string (for contract calls) --dry-run Resolve the network client and sender and validate params, but do not broadcast. --from=<value> Sender address (0x-prefixed). Defaults to the selected account. --gas=<value> Gas limit override, as a 0x-prefixed hex quantity --gas-price=<value> Legacy gasPrice override, as a 0x-prefixed hex wei quantity --max-fee-per-gas=<value> maxFeePerGas override, as a 0x-prefixed hex wei quantity --max-priority-fee-per-gas=<value> maxPriorityFeePerGas override, as a 0x-prefixed hex wei quantity --network-client-id=<value> Network client to send on. Provide this or --chain-id, not both. --to=<value> (required) Recipient address (0x-prefixed) --value=<value> [default: 0] Amount to send, in ether (e.g. 0.01). Defaults to 0. EXAMPLES $ mm wallet send --to 0xRecipient --value 0.01 --chain-id 0x1 $ mm wallet send --to 0xRecipient --value 0.01 --network-client-id mainnet --yes $ mm wallet send --to 0xContract --data 0xabcdef --value 0 --chain-id 0x1 --dry-run ``` ## Testing - Unit tests for the handler (network/sender resolution, internal submit, dry-run, broadcast + live status, param validation) and the command (arg parsing, preview/confirm/abort, `--yes`, `--dry-run`, error surfaces); **100% coverage maintained**. - **Real-chain e2e** (`tests/wallet-send.e2e.test.ts`): boots a local `anvil` node, adds it as a custom network, and drives the built `mm` CLI to sign, broadcast, and mine a real transaction (asserts receipt `status: 0x1` and a recipient balance increase). It is **skip-if-absent**; CI installs `anvil` for it only when `packages/wallet-cli/` changed. See `packages/wallet-cli/tests/README.md`. - `build`, package `test`, `test:e2e`, `yarn lint`, `changelog:validate` pass. ## References - Closes MetaMask#9513 - Builds on MetaMask#9512 / MetaMask#9611 (daemon transaction-capable) and MetaMask#9509 (the `TransactionController` slot); the mutating-RPC safety prerequisite MetaMask#9511 landed in MetaMask#9608. - `packages/wallet-cli/src/daemon/send-transaction.ts`, `src/commands/wallet/send.ts`, `tests/wallet-send.e2e.test.ts`. > [!NOTE] > Stacked on `sirtimid/wallet-cli-daemon-transaction-capable` (MetaMask#9612); this PR is based on that branch so the diff is scoped. Once MetaMask#9612 merges, this will be rebased onto `main` and retargeted. ## Checklist - [x] Tests cover the handler and command (unit) plus a real-chain broadcast (e2e); 100% coverage maintained. - [x] `build`, `test`, `test:e2e`, `yarn lint:fix`, `yarn lint`, `changelog:validate` pass. - [x] Confirmation prompt (`--yes` to skip) and `--dry-run` gate a real, irreversible send. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Adds an irreversible on-chain send path through a headless daemon that auto-approves transactions; mistakes in confirmation, `--yes`, or timeout/retry behavior could move real funds despite CLI safeguards. > > **Overview** > Introduces **`mm wallet send`** so users can broadcast through the daemon-hosted `TransactionController`, with ether `--value`, network selection via `--network-client-id` or `--chain-id`, optional gas overrides, **`--dry-run`**, and an interactive preview (or **`--yes`** to skip). > > Because `addTransaction`'s broadcast promise cannot cross JSON-RPC, the daemon gains a dedicated **`sendTransaction`** handler that resolves network/sender, submits as internal/auto-approved, awaits the hash, and returns `{ transactionHash, transactionId, status }` (with **`dryRun`** for preview-only). > > The CLI dry-runs before confirm, then pins the resolved **`from`** / **`networkClientId`** on broadcast; it validates RPC results, uses a longer default broadcast timeout with duplicate-send warnings on timeout, and documents that the daemon still auto-approves—so the command prompt is the main fund-moving gate. > > **CI/testing:** wallet-cli e2e installs **anvil** via `@metamask/foundryup`, sets **`MM_E2E_REQUIRE_ANVIL`**, adds a real-chain send e2e (anvil + custom network), shares daemon cleanup helpers, and updates knip/README/changelog accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0f0e34f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com>
## Explanation Releases `@metamask/client-utils` **1.2.1 → 1.3.0**: - Adds optional `assetType` on TokenAmount and Fee so clients can resolve icons when `assetId` is absent - Removes hardcoded `nativeAssetsByCaipChainId` lookup Related to MetaMask#9671. ## References * Related to MetaMask#9671 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> `V6_DEFI_POSITION_TYPES` in `@metamask/core-backend` did not match the Accounts API / Zerion wallet fungible position `position_types` filter values, so typed DeFi rows and downstream grouping could disagree with real API data. This PR updates the allowed position types to: `deposit`, `loan`, `locked`, `staked`, `reward`, `wallet`, `investment`. In `@metamask/assets-controllers`, `DEFI_POSITION_LIABILITY_TYPES` is updated from `lending` to `loan` so liability market-value handling stays aligned with the corrected type. Tested locally in both extension and mobile. <img width="828" height="556" alt="image" src="https://github.com/user-attachments/assets/4a4bde02-47a0-432c-9acb-88d65f0c9e56" /> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Related to https://consensyssoftware.atlassian.net/browse/ASSETS-3800 ## Checklist - [X] I've updated the test suite for new or updated code as appropriate - [X] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [X] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking type changes in `@metamask/core-backend` can fail compile-time checks for consumers using removed position types; incorrect liability handling before this fix could have mis-stated DeFi totals. > > **Overview** > **Aligns v6 DeFi `positionType` values with the Accounts API / Zerion fungible position types** so typed API rows and client grouping match live responses. > > In `@metamask/core-backend`, `V6_DEFI_POSITION_TYPES` (and `V6DeFiPositionType`) is replaced with `deposit`, `loan`, `locked`, `staked`, `reward`, `wallet`, and `investment`, dropping the previous set (e.g. `lending`, `yield`, `liquidity_pool`, `rewards`). A Zerion API doc link is added on the constant. > > In `@metamask/assets-controllers`, `DEFI_POSITION_LIABILITY_TYPES` now treats **`loan`** (not `lending`) as the liability type when subtracting borrow exposure from protocol `marketValue`. Tests for v6 grouping are updated for `loan` and `reward` position types. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f3eee31. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…etaMask#9672) Avoid Sentry rate limits by nesting pipeline timings as subspans and emitting them only on the first unlock-session fetch, plus a single AggregatedBalanceSelector span for all wallets. ## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to observability wiring and defensive error handling; asset fetch and state-update behavior is unchanged aside from when spans are emitted. > > **Overview** > Reduces Sentry span volume and improves Assets Health dashboard fidelity by restructuring how `AssetsController` emits traces. > > **Tracing helpers** — Inline `#emitTrace` is replaced by `utils/trace.ts` (`emitTrace` / `withTrace`). Omitting `trace` no-ops at call sites; numeric span data (especially `duration_ms`) is mirrored into tags and used to backdate `startTime` for p95 charts. `withTrace` treats Sentry/adapter rejections as best-effort so `getAssets` force updates and `handleAssetsUpdate` still complete. > > **Nested spans & session gating** — Fetch and update pipelines wrap work in parent spans (`AssetsFetchPipeline`, `AssetsBackgroundFetch`, `AssetsUpdateEnrichment`); per-source timings and summary spans (`AssetsFullFetch`, `AssetsControllerFirstInitFetch`, `AssetsUpdatePipeline`, etc.) nest via `parentContext`. Fast and background fetch lanes are sibling `withTrace` calls, not nested. Pipeline, data-source, and enrichment spans emit only until the first `forceUpdate` in an unlock session (`#firstInitFetchReported`); later polls, force refreshes, and subscription enrichment skip those spans (reset on lock/`#stop`). > > **Balance selector** — `calculateBalanceForAllWallets` emits one `AggregatedBalance` / `AggregatedBalanceSelector` pair for the full wallet pass instead of a root span per account group. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7001768. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary - Expands the incremental `lint:tsc` rollout from [MetaMask#9652](MetaMask#9652) by enabling typechecking for 11 more packages that already pass with no TypeScript errors. - Adds `tsconfig.lint.json` for: `analytics-controller`, `announcement-controller`, `app-metadata-controller`, `build-utils`, `client-controller`, `foundryup`, `local-node-utils`, `preferences-controller`, `rate-limit-controller`, `stellar-quickstart-up`, `storage-service`. - Updates root `tsconfig.lint.json` references accordingly (closed dependency set on packages already enabled in the parent PR). ## Test plan - [x] `yarn lint:tsc` passes with the expanded package set - [ ] CI lint job passes on this branch <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Config-only expansion of incremental typecheck coverage; no application or runtime code changes. > > **Overview** > Continues the incremental **`lint:tsc`** rollout by wiring **11 more packages** into the root TypeScript project build so `tsc --build tsconfig.lint.json` typechecks them in CI. > > Each package gets a new **`tsconfig.lint.json`** that extends its normal `tsconfig.json` plus **`tsconfig.packages.lint.json`**, writes cache under **`.tsc-lint-cache`**, and declares **`references`** only to dependencies already in the lint graph (controllers → `base-controller` / `messenger`; **`storage-service`** → `messenger`; standalone tools use empty references). Root **`tsconfig.lint.json`** adds matching **`references`** for: `analytics-controller`, `announcement-controller`, `app-metadata-controller`, `build-utils`, `client-controller`, `foundryup`, `local-node-utils`, `preferences-controller`, `rate-limit-controller`, `stellar-quickstart-up`, and `storage-service`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f55e4f4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
MetaMask#9681) …P-44 entry Arc (eip155:5042) was enabled before its native asset (USDC) had a SLIP-44 registry entry, so it used the erc20:0x0 zero-address placeholder in SPOT_PRICES_SUPPORT_INFO. The Arc team has since registered slip44:5042 for it. ## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single mapping correction for Arc in the CodeFi v2 spot-prices table; no auth, data migration, or broad logic changes. > > **Overview** > Updates Arc’s **native asset identifier** in `SPOT_PRICES_SUPPORT_INFO` (`codefi-v2.ts`) from the temporary `erc20:0x0` placeholder to **`slip44:5042`**, matching the registered SLIP-44 entry for Arc’s native USDC on chain `5042` / `0x13b2`. > > The **changelog** records this under Fixed for `@metamask/assets-controllers`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2e85266. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…Mask#9653) ## Explanation Removes old behaviour that was kept prior from WS issues around data in incorrect format, but now has been fixed. Also did some test spec cleaning... ## References MetaMask/metamask-extension#44786 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them ### Example Extension Test: <img width="534" height="658" alt="Screenshot 2026-07-29 at 12 37 58" src="https://github.com/user-attachments/assets/4b0e4417-bbe6-44ce-bcf7-433ac1305575" /> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core balance aggregation math used for wallet/group fiat totals; the fix aligns with the invariant that state amounts are human-readable, but any edge case that still stored raw base units would now be mispriced. > > **Overview** > Fixes **incorrect portfolio totals** when token balances are large human-readable amounts (e.g. billions of tokens with 9 decimals) by removing the legacy `scaleToHumanIfRaw` path in `balance.ts` aggregation. > > **`assetsBalance` amounts are always human-readable**, but the old heuristic treated values ≥ `10^decimals` as raw base units and divided by `10^decimals`, which **under-counted** those holdings in `getAggregatedBalanceForAccount`, `getAggregatedBalanceForAccountIds`, `calculateBalanceForAllWallets`, and `calculateBalanceChangeForAccountGroup` (metamask-extension#44786). > > Tests add a TangYuan regression case, shared `arrangeAssetsControllerState` helpers, and coverage for group aggregation when only `accountTreeState` is passed (no `accountsById`). Changelog documents the fix. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7056a0b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…bridges (MetaMask#9682) ## Summary Saved gas preferences were excluded from every transaction with `isInternal: true`. This also excluded wallet-initiated transfers, which are marked internal by the extension API even though they should use saved gas settings. This changes the exclusion to a dedicated transaction-type list containing swaps and bridge transactions. Wallet transfers can now apply saved gas preferences while swaps and bridges remain protected from underpriced saved fees. ## Related PRs - MetaMask#9401 - MetaMask/metamask-extension#43317 ## Testing - `yarn jest --config packages/transaction-controller/jest.config.js --runInBand packages/transaction-controller/src/utils/gas-fees.test.ts --coverage=false` - Added coverage for internal wallet transactions and swap/bridge exclusions. ## Changelog Added an Unreleased changelog entry for `@metamask/transaction-controller`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes which transactions receive saved gas fees at submit time; wallet sends gain user prefs while swap/bridge behavior stays guarded, but any misclassified `type` could get the wrong fee path. > > **Overview** > **Saved gas preferences** were skipped for every transaction with `isInternal: true`, which incorrectly blocked wallet-initiated transfers (often marked internal) from using the user’s advanced gas settings. > > `updateGasFees` now ignores saved gas only for **swap and bridge** transaction types (`SWAP_TRANSACTION_TYPES`, `bridge`, `bridgeApproval`) instead of all internal transactions. Internal `simpleSend` transfers can apply saved fees again; aggregator/relay-driven swaps and bridges still avoid user-saved fees that could underprice them. > > Tests cover internal wallet sends applying saved gas and parameterized cases for swap/bridge types still ignoring them. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 61d4074. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation
### Context
Updating the amount of a Money Account deposit currently fans one
logical user action out through multiple nested transaction updates,
parent calldata rebuilds, controller publications, gas estimations, and
quote generations. In the reference iOS simulator trace, Update/Done to
an executable quote took **8.503 seconds**:
| Segment | Baseline |
| --- | ---: |
| Update/Done → Relay request | 3,393.546 ms |
| Relay request | 4,107.940 ms |
| Relay response → executable quote | 1,001.045 ms |
| **Total** | **8,502.531 ms** |
Relay is a third-party request whose latency varies independently. This
proof of concept targets client-controlled work before and after that
request.
### What this PR changes
This PR adds two generic Transaction Controller primitives while
preserving Transaction Pay's existing reactive architecture. The final
Core diff is limited to `packages/transaction-controller`; Money
Account-specific orchestration lives in the companion Mobile PR.
#### TransactionController
- Adds `updateTransactionMetadata`, a synchronous messenger action that
atomically mutates the latest transaction metadata, with an optional
`skipResimulate` flag that defaults to normal automatic re-simulation.
- Callers opt out explicitly with `skipResimulate: true`; normal callers
retain automatic re-simulation by default.
- Normal transaction update APIs retain their existing re-simulation
behavior.
- Exports `updateEIP7702BatchData`, a pure synchronous utility that
validates indexed nested-transaction calldata updates, returns a new
nested-transaction list, and rebuilds parent EIP-7702 batch calldata
once.
- Keeps `updateAtomicBatchData` as the existing asynchronous API for
consumers that require gas estimation, while synchronously clearing
stale gas, gas-limit, gas-used, simulation, security-alert, and
gas-revert metadata when new batch calldata is published.
#### Transaction Pay architecture
Transaction Pay continues to use its existing state-driven quote
lifecycle:
1. The client asynchronously prepares transaction-specific calldata.
2. The client commits required assets, all nested calldata, and parent
EIP-7702 calldata in one `updateTransactionMetadata` call.
3. `TransactionPayController` observes the Transaction Controller state
change.
4. It derives required payment tokens from the updated transaction.
5. The existing latest-wins quote path starts one quote request and
aborts any older quote request for the same transaction.
The earlier proof-of-concept-specific
`TransactionPayController.updateAmount` action, constructor preparation
callback, explicit quote invocation, quote suppression set, and
duplicate quote-cancellation API have been removed. This leaves
transaction metadata as the integration boundary and the existing
listener as the single quote trigger.
### Flow
```text
Before
======
Update/Done
|
+--> update approval call --> rebuild parent --> publish
| `--> quote generation A (superseded)
|
`--> update deposit call --> rebuild parent --> publish
`--> quote generation B --> Relay
After
=====
Update/Done
|
`--> client prepares complete approval + deposit calldata
|
`--> one synchronous metadata update
- required assets
- all nested calldata
- parent EIP-7702 calldata
- clear stale derived metadata
- skip automatic re-simulation
|
`--> existing state listener
|
`--> one latest-wins quote --> Relay
```
### Mobile first adopter
The companion Mobile PR owns the Money Account-specific asynchronous
preparation because it has the required vault configuration, provider
access, and transaction builders.
Its coordinator:
- shares identical in-flight amount intents;
- prevents superseded asynchronous preparations from committing stale
results;
- validates the approval/deposit template again against current metadata
at commit time;
- derives `requiredAssets` from current metadata rather than replacing
it from a stale pre-request snapshot;
- applies both indexed calldata updates in one `updateEIP7702BatchData`
call;
- clears stale gas, simulation, security-alert, and simulation-revert
metadata;
- relies on the existing Transaction Pay listener to launch the quote.
The optimized path remains feature-gated, and unsupported Money Account
intents retain the legacy path.
### Configurable automatic re-simulation
Changing `txParams.data` through the normal transaction update path
triggers an asynchronous re-simulation check. For this MetaMask Pay
flow, calldata is produced by controlled builders using known providers,
and re-simulation is not required to construct the vendor quote.
`updateTransactionMetadata` lets controlled callers make this
performance and security policy explicit while leaving all other update
paths unchanged.
### Benchmark result
A follow-up React Native DevTools trace used the same inferred
Update/Done and executable-commit anchors:
| Segment | Baseline | PoC | Change |
| --- | ---: | ---: | ---: |
| Update/Done → Relay request | 3,393.546 ms | **1,512.891 ms** |
**-55.4%** |
| Relay request | 4,107.940 ms | 5,118.617 ms | +24.6% |
| Relay response → executable quote | 1,001.045 ms | **805.860 ms** |
**-19.5%** |
| **Observed total** | **8,502.531 ms** | **7,437.368 ms** | **-12.5%**
|
The Relay request regressed by 1.011 seconds in the second sample even
though this PR does not optimize Relay. Treating Relay as the same
4.108-second contribution in both traces gives a normalized PoC total of
**6,426.691 ms**, a **2,075.840 ms / 24.4% end-to-end improvement**.
Client-controlled time fell from **4,394.591 ms to 2,318.751 ms**, a
**47.2% reduction**.
Structural trace evidence:
- 29 → 19 requests in the measured window
- 18 → 11 requests before Relay
- two → one `eth_estimateGas` calls
- 1,640 ms → 483 ms of pre-request React scheduler time
- 1,091 ms → 313 ms of pre-request render time
These are two simulator traces, not production p50/p95 measurements.
Relay latency should continue to be reported separately, and old/new
runs should be alternated over multiple samples. The trace predates the
final review-driven architecture, so it is directional evidence rather
than a benchmark of the exact final code.
### Correctness and rollout constraints
- Indexed nested updates are validated before transaction state is
published.
- Required assets, nested calldata, and parent calldata are committed
together or not at all.
- The EIP-7702 utility is applied against current transaction metadata.
- Superseded Mobile preparations cannot commit stale transaction data.
- Transaction Pay's existing state listener remains the only quote
trigger.
- Existing quote generation remains latest-wins per transaction.
- Normal transaction update flows continue to use automatic
re-simulation.
- The Mobile integration is feature-gated and retains the legacy path.
- Security/privacy review is still required before rollout to confirm
whether Sentinel gates execution only or must also gate vendor
disclosure.
### Validation
Core:
- Focused Transaction Controller tests pass with coverage disabled;
Transaction Pay listener and quote integration tests also pass.
- `yarn build` passes for the complete monorepo.
- Transaction Controller messenger action-type checks pass.
- Transaction Controller changelog validation passes.
- Targeted ESLint and `git diff --check` pass.
- `yarn lint:misc:check` passes, including the final EIP-7702 utility
formatting fix.
- `yarn workspace @metamask/transaction-controller run test
--runTestsByPath src/TransactionController.test.ts --coverage=false`
passes.
Mobile companion:
- 33 focused tests pass for amount coordination, controller
initialization, and the confirmation hook.
- Targeted ESLint, Prettier, messenger action-type checks, and `git diff
--check` pass.
- TypeScript validation passes when checked against the locally built
Transaction Controller from this PR; the Mobile PR must consume the
released package version before merge.
## References
- Mobile first-adopter PR:
[MetaMask/metamask-mobile#33433](MetaMask/metamask-mobile#33433)
- Baseline trace SHA-256:
`28e517a73d33db3aa63470f9bc55bdbb47fc61060b09227dce3c5df2d4ae13a9`
- PoC trace SHA-256:
`e80c86da8913924a228627d851ab75965174a30c7607cee1416917aa2c34f`
## Checklist
- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by updating changelogs
for packages I've changed
- [ ] I've introduced breaking changes in this PR and have prepared
draft pull requests for clients and consumer packages to resolve them
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches core transaction state, gas estimation, and re-simulation
policy; incorrect metadata clearing or `skipResimulate` misuse could
affect quotes and safety checks for EIP-7702 batch flows.
>
> **Overview**
> Adds **`updateTransactionMetadata`** and the messenger action
**`TransactionController:updateTransactionMetadata`**, so callers can
mutate the latest transaction metadata in one synchronous update via a
callback, with optional **`skipResimulate`** to bypass automatic
re-simulation when calldata changes.
>
> Exports pure **`updateEIP7702BatchData`** (and
**`NestedTransactionUpdate`**) to apply indexed nested calldata updates,
rebuild parent EIP-7702 batch data without mutating the input, and
validate duplicate or missing indices.
>
> **`updateAtomicBatchData`** now uses that helper, validates the
transaction exists up front, clears stale gas, simulation,
security-alert, and gas-revert metadata when batch calldata changes
(while preserving receipt/simulation reverts where applicable), and
returns the regenerated batch calldata.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1344bed. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
) ## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Veda labels its linear annualized yield as APY, which underreports the compounded yield shown to Money account users. Convert all Veda vault APR fields to daily compounded APY. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Fixes: [MUSD-1207: [Feature] APY display – Convert APR to compounded APY in MetaMask UI](https://consensyssoftware.atlassian.net/browse/MUSD-1207) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes numeric yield values exposed to UI and downstream consumers of `getVaultApy`, which can affect displayed rates and any logic keyed on prior APR-like values. > > **Overview** > **Vault APY from `getVaultApy` is now daily-compounded APY**, not the linear APR values Veda returns under “APY” field names. > > A new `convertAprToApy` helper applies `(1 + apr/365)^365 - 1` during `normalizeVaultApyResponse`, covering top-level `apy`, `globalApyBreakdown` (`maturityApy`, `realApy`), and each `realApyBreakdown` entry (`apy`, `apyNet`). **Fees are unchanged.** Docs and types note that raw Veda fields are APR and that consumers receive compounded decimals. > > Tests and changelog are updated to match the new numbers (e.g. 5% APR → ~5.13% APY). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a13c518. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Major - core-backend Minor - assets-controller - transaction-controller Patch - assets-controllers - client-utils ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > The major `core-backend` DeFi position-type change can break consumers that relied on the old `V6_DEFI_POSITION_TYPES` union; widespread `transaction-controller` bumps also affect tx/gas behavior from 69.3.0. > > **Overview** > This is a **release train** PR: it bumps the root monorepo to **1155.0.0** and publishes aligned versions for the packages called out in the release notes, with **changelog sections** moved from `[Unreleased]` into the new tags and **`package.json` / `yarn.lock`** updated so dependents pick up the new ranges. > > **`@metamask/core-backend` 8.0.0** (major) carries a **breaking** alignment of `V6_DEFI_POSITION_TYPES` / `V6DeFiPositionType` with Accounts API / Zerion fungible position types (`deposit`, `loan`, `locked`, `staked`, `reward`, `wallet`, `investment`). Anything on **`^7.0.0`** is bumped to **`^8.0.0`** in this diff (e.g. `assets-controller`, `assets-controllers`, `client-utils`, `transaction-controller`). > > **Minor** releases: **`@metamask/assets-controller` 11.3.0**, **`@metamask/assets-controllers` 110.0.1**, **`@metamask/transaction-controller` 69.3.0**. **Patch**: **`@metamask/client-utils` 1.3.1**. Downstream controllers (bridge, bridge-status, earn, EIP-5792, wallet, etc.) only change their **`transaction-controller`** (and where applicable **assets** / **core-backend**) dependency pins and changelog notes for **MetaMask#9693**. > > There is **no application source** in this diff beyond version metadata and lockfile resolution. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 45eb927. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ape (MetaMask#9650) ## Explanation Ramps (buy/sell) orders currently have no representation in the shared, cross-client `ActivityItem` model that Send/Swap/Bridge/etc. already use, so consumers (extension, mobile) have had to maintain bespoke, order-specific UI to show them instead of the generic activity list and details pipeline. This PR: - Adds `'rampBuy' | 'rampSell'` to `ActivityKind` and a matching arm to the `ActivityItem` union, covering the order's fiat/token amounts, fee, provider info (id/name/link), status description, and manual-payment details. - Adds an optional `id` field to the shared `ActivityData` base type. A pending ramp order has an empty `txHash` (no on-chain tx exists yet while awaiting fiat settlement), so consumers need a stable identifier to key list rows / open details before settlement. `id` is populated only for `rampBuy`/`rampSell` (from the provider's order id) and left `undefined` for every other kind — no behavior change for existing consumers. - Adds `mapRampsOrder`, a pure mapper (following the existing `mapLocalTransaction`/`mapApiTransaction`/`mapKeyringTransaction` pattern) that normalizes a ramps order into an `ActivityItem`, mapping the 8-value `RampsOrderStatus` down to the existing 4-value `Status`. - The mapper's input type (`RampsOrderLike`) redeclares the narrow subset of `RampsOrder` (from `@metamask/ramps-controller`) it needs, rather than importing it — `client-utils` and `ramps-controller` don't depend on each other today, and this keeps it that way. - Chain id normalization is chain-agnostic: it reuses the existing `formatChainIdToCaip` helper (already used elsewhere in this package for Solana/Bitcoin/Stellar-aware mappers) rather than hardcoding an EVM namespace, so it passes through an already-CAIP-formatted chain id unchanged and only assumes `eip155` for a bare numeric/hex reference (today's only observed format from the ramps API). ## References Extension Client PR: MetaMask/metamask-extension#44689 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive types and a pure mapper in client-utils; no auth or transaction submission changes, though consumers must handle new `ActivityKind` variants and optional ramp `chainId`. > > **Overview** > Adds **`rampBuy` / `rampSell`** to the shared activity model so ramps orders can use the same list/details pipeline as other activity kinds. Ramp items carry fiat/token amounts, fees, provider metadata, payment instructions, and an optional **`data.id`** when there is no real **`hash`** yet (pending fiat settlement). > > Introduces **`mapRampsOrder`** (plus exported **`RampsOrderLike`**) to normalize ramps controller–shaped orders: status → shared **`Status`**, buy/sell/deposit typing, chain id resolution (network → crypto chain/asset fallbacks), and filtering that returns **`null`** for hidden statuses or **`excludeFromPurchases`**. Placeholder **`txHash`** values are dropped so list keys do not collide. > > **`formatChainIdToCaip`** now treats an empty chain id as **`undefined`** (avoids **`eip155:0`** on precreated stub orders). Changelog updated; tests cover the mapper and CAIP edge case. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 68ca1fb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: George Weiler <george.weiler@consensys.net> Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation analytics-controller is enrolled in `lint:tsc` (it has a `tsconfig.lint.json` and is listed in the root `tsconfig.lint.json`). Once it starts importing geolocation-controller and controller-utils (the geo enrichment work in MetaMask#9691), and neither of those is a `lint:tsc` project yet, tsc pulls their source straight into the analytics-controller project and fails with rootDir errors: ``` error TS6059: File '.../controller-utils/src/types.ts' is not under 'rootDir' '.../analytics-controller' error TS6307: File '.../controller-utils/src/types.ts' is not listed within the file list of project '.../analytics-controller/tsconfig.lint.json' ``` This turns the `lint:tsc` job red on MetaMask#9691 and blocks the work. This PR removes `packages/analytics-controller/tsconfig.lint.json` and its entry in the root `tsconfig.lint.json`, so analytics-controller is no longer type checked by `lint:tsc`. `yarn lint:tsc` is green with this change. This is a stopgap to unblock the current work, not the end state. The proper fix is to make controller-utils and geolocation-controller `lint:tsc` projects (give them their own `tsconfig.lint.json`) and enroll analytics-controller again. That is a bigger change because it also turns on type checking for those packages' test files, which surfaces separate pre existing type errors that need their own cleanup. ## References - Unblocks MetaMask#9691 ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
## Explanation Minor - client-utils (`1.3.1` → `1.4.0`) - Add `mapRampsOrder` for mapping ramps buy/sell orders into the shared activity item shape, and add `rampBuy`/`rampSell` to `ActivityKind` and `ActivityItem` ([MetaMask#9650](MetaMask#9650)) ## References * Related to MetaMask#9650 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
…troller (MetaMask#9270) ## Explanation Today `NetworkController` emits no analytics. It publishes `rpcEndpointUnavailable` and `rpcEndpointDegraded` events, and **each client app independently** subscribes to them and turns them into the `RPC Service Unavailable` / `RPC Service Degraded` Segment events. The extension still routes those through `MetaMetricsController`; mobile already routes them through `AnalyticsController`. The handlers are near identical duplication across both repos. This moves that translation into `NetworkController` so it is written once and delivered through the `AnalyticsController:trackEvent` action over the messenger (which is the whole reason `AnalyticsController` exists). Clients can then delete their duplicated handlers and just wire the new option. The feature is fully opt in via a new optional `analytics` constructor option. When omitted, no analytics are emitted and `AnalyticsController` is never called, so existing consumers are unaffected. The two genuinely client specific pieces are injected because core cannot know them: - `isRpcEndpointUrlPublic(url)`: whether an endpoint URL is safe to report verbatim (depends on the client network lists, Quicknode URLs, Infura key) - `rpcServiceEventsSampleRate`: the proportion of events to emit (depends on the build environment, typically 1% in production and 100% in dev) Core reads `analyticsId` via `AnalyticsController:getState`, applies the sample rate with `generateDeterministicRandomNumber`, skips local connection errors, builds the properties, and delivers the event. `AnalyticsController` keeps owning consent gating. ## What changed - New `src/rpc-service-events.ts`: pure helpers (`sanitizeRpcEndpointUrl`, `buildRpcServiceEventProperties`, `toAnalyticsTrackingEvent`) plus the `NetworkControllerAnalyticsOptions` and `RpcServiceEventName` types - `src/NetworkController.ts`: new optional `analytics` option, widened `AllowedActions` with `AnalyticsController:getState` + `AnalyticsController:trackEvent`, two guarded self subscriptions, and a private `#trackRpcServiceEvent` - Added `@metamask/analytics-controller` dependency and tsconfig references (no dependency cycle) - `index.ts`, `tests/helpers.ts`, `CHANGELOG.md`, and a new `tests/NetworkController.analytics.test.ts` ## Open question `AnalyticsTrackingEvent` has no `category` field, so the old `category: 'Network'` is not carried for now. Confirming with the analytics owners how they want category mapped before relying on it downstream. ## Follow ups (separate repos) - metamask-extension: delete `messenger-action-handlers.ts` + `utils.ts`, pass the `analytics` option, delegate the two AnalyticsController actions. This is where the extension stops using `MetaMetricsController` for these events. - metamask-mobile: same deletion and wiring (it already delegates AnalyticsController). ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate - [x] I've manually tested code paths covered by the new tests <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking messenger requirements affect all NetworkController consumers; analytics touches RPC failure paths but defaults to off and failures are swallowed via captureException. > > **Overview** > **Centralizes RPC health analytics in `NetworkController`** so clients no longer need duplicate subscribers on `rpcEndpointUnavailable` / `rpcEndpointDegraded`. The controller subscribes to those events and, when configured, emits **RPC Service Unavailable** and **RPC Service Degraded** via `AnalyticsController:trackEvent`. > > New optional constructor/instance option **`analyticsOptions`** (`isRpcEndpointUrlPublic`, `rpcServiceEventsSampleRate`) defaults to no tracking (`sample rate 0`, URLs reported as `'custom'`). Tracking skips local connection errors, requires a non-empty `analyticsId`, applies deterministic sampling, and sanitizes endpoint URLs in new `rpc-service-analytics` helpers. > > **Breaking messenger wiring:** `NetworkControllerMessenger` must delegate **`AnalyticsController:getState`** and **`AnalyticsController:trackEvent`**. `@metamask/wallet` passes `analyticsOptions` through and delegates those actions; **`wallet-cli`** registers no-op Analytics handlers (empty `analyticsId`) so daemon startup does not throw. Adds `@metamask/analytics-controller` dependency and tests. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 10a3f4c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…Mask#9655) ## Explanation Abandoned `release/*` PRs can sit open and block other engineers from starting a new release. There is no automated cleanup today, so stale release candidates have to be closed by hand. This PR adds a scheduled GitHub Actions workflow (every 30 minutes, plus `workflow_dispatch`) that: - Finds open same-repo PRs whose head branch starts with `release/` - Closes them after **3 hours of inactivity** (`updated_at`) - Leaves a comment explaining why - Deletes the release branch **only when its tip SHA is unchanged** Fork heads, PRs in the merge queue, PRs with auto-merge enabled, and PRs labeled `release:keep-open` are skipped. The releasing docs are updated to describe this policy. Before destructive actions, the workflow: - Fetches a fresh GraphQL snapshot and skips if the PR is no longer eligible (open / skip label / fork / 3h / merge-queue or auto-merge) - Closes before commenting so a failed close does not reset the stale timer - Re-fetches the branch tip immediately before `deleteRef` and skips deletion unless the SHA still matches - Comments only after a successful close and the delete attempt; tip-move skips and real delete failures get distinct note wording (successful deletes rely on GitHub’s normal closed-PR UI) - Continues past per-PR API failures so one error does not abort the rest of the run ## Flow ```mermaid flowchart TD trigger(["cron every 30m / workflow_dispatch"]) --> checkout[Checkout and setup] checkout --> listOpen[List open PRs via REST paginate] listOpen --> candidates[Filter candidates:<br/>same-repo release/*,<br/>no release:keep-open] candidates --> more{"More candidates?"} more -->|no| done([Done]) more -->|yes| snap[GraphQL snapshot] snap --> elig{"Eligible?<br/>OPEN, not fork, not skip label,<br/>updatedAt older than 3h,<br/>not merge-queue / auto-merge"} elig -->|no| more elig -->|yes| closePR[Close PR] closePR --> closeOk{Close succeeded?} closeOk -->|no| more closeOk -->|yes| getRef[git.getRef for head branch] getRef --> tipOk{"Tip SHA still matches<br/>snapshot headRefOid?"} tipOk -->|refresh failed| keepRefresh[Keep branch:<br/>kept-refresh-failed] tipOk -->|moved| keepMoved[Keep branch:<br/>kept-head-moved] tipOk -->|unchanged| deleteRef[git.deleteRef] deleteRef --> delOk{Delete succeeded?} delOk -->|yes| deleted[Branch deleted] delOk -->|no| keepFail[Keep branch:<br/>kept-delete-failed] keepRefresh --> comment[Comment with outcome] keepMoved --> comment deleted --> comment keepFail --> comment comment --> more ``` ## References N/A ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them ## Test plan - [ ] Confirm workflow checks / actionlint pass on this PR - [ ] Optionally run via `workflow_dispatch` against a throwaway stale `release/*` PR and verify comment, close, and branch delete - [ ] Confirm a recently updated release PR is left alone - [ ] Confirm a PR with `release:keep-open` is left alone - [ ] Confirm a late push (head SHA change) causes branch deletion to be skipped and the comment says the branch was kept <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > The workflow can close PRs and delete `release/*` branches using `GITHUB_TOKEN`; safeguards (fresh snapshot, SHA check, skip label) limit mistakes but mistaken closure would disrupt in-flight releases. > > **Overview** > Adds automated cleanup for abandoned **release** PRs so they no longer block new releases. > > A new scheduled GitHub Actions workflow runs every 30 minutes (and on `workflow_dispatch`) and executes `scripts/close-stale-release-prs.mts`. That script finds open same-repo PRs on `release/*` heads, closes them after **3 hours** without activity (`updatedAt`), posts an explanatory comment, and deletes the branch only if the tip SHA is unchanged. It skips fork heads, merge-queue/auto-merge PRs, and anything labeled `release:keep-open`. Per-PR failures are logged and do not stop the rest of the run. > > **Releasing** docs now describe the 3-hour policy and the opt-out label. Root `package.json` gains `@actions/core` and `@actions/github` for the script. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 528c27c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation After MetaMask#9671 removed the hardcoded nativeAssetsByCaipChainId list, clients are left to resolve native tokens for certain activity types where the API has not provided metadata. This PR restores native `assetId`s using the `@metamask/slip44` package. ## References - Follow-up to MetaMask#9671 - Extension consumer will bump `@metamask/client-utils` after publish ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com)
## Explanation This is a release for `network-enablement-controller` and all the dependent packages. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > No new code in the diff, but shipping network-enablement 6.0.0 and config-registry 1.0.0 propagates documented breaking messenger and state-shape changes to all dependents that adopt this release train. > > **Overview** > Monorepo release **1157.0.0** with no runtime code edits in this PR—only version bumps, changelogs, `package.json` dependency ranges, and `yarn.lock`. > > **`@metamask/network-enablement-controller` 6.0.0** is the anchor release; dependents are republished to pull **`^6.0.0`** and aligned asset packages: **`assets-controller` 11.3.1**, **`assets-controllers` 110.0.2**, **`network-connection-banner-controller` 0.1.1**, plus **`bridge-controller`** and **`transaction-pay-controller`** dependency updates. **`@metamask/config-registry-controller`** is released as **1.0.0** and wired into network-enablement at **`^1.0.0`**. > > Consumers upgrading through this release inherit **breaking changes already documented** in those packages’ changelogs (e.g. network-enablement’s **`ConfigRegistryController:getState`** messenger requirement and config-registry **`networks`** keyed by **`Caip2ChainId`**), not new logic introduced here. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 06b5ed2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Explanation Releases `@metamask/client-utils@1.5.0`. Restores native `assetId` on activity tokens and network fees when available. ## References - Follow-up release for [MetaMask#9701](MetaMask#9701) - Replaces closed [MetaMask#9703](MetaMask#9703) (auto-closed after 3hr inactivity window) ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com)
…#9691) ## Explanation Metric events need to carry the user's geolocation (country, region, timezone) before reaching downstream destinations, as decided in [ADR-0008](MetaMask/decisions#217). Today Extension events reach Segment unenriched, and the underlying geolocation API only returned a country-level location code. This PR implements the MetaMask-native, client-side enrichment path the ADR selected. **`geolocation-controller`** - `GeolocationApiService` now targets the `v2` geolocation endpoint and exposes `fetchGeolocationData()`, returning a `GeolocationData` object (`{ country, region, timezone }`) where each field is independently validated and `null` when missing/invalid. The existing `fetchGeolocation()` location-code API is preserved, now derived from the data via a shared `toLocationCode()` helper. TTL caching and in-flight request deduplication are retained. - `GeolocationController` gains `getGeolocationData()` and three new (non-persisted) state fields: `country`, `region`, `timezone`. - New messenger actions: `GeolocationController:getGeolocationData` and `GeolocationApiService:fetchGeolocationData`. **`analytics-controller`** - Optionally attaches `country_code`, `region`, and `timezone` to `context.location` on non-anonymous track, identify, and view payloads. Only fields the API could determine are included, and caller-provided `location` fields are preserved (resolved fields take precedence). - Enrichment is gated behind a new `isGeolocationEnabled` constructor option (default `false`), matching the existing `isAnonymousEventsFeatureEnabled` / `isEventQueuePersistenceEnabled` / `isPreConsentQueueEnabled` pattern. When disabled, the controller never calls the geolocation action, so compositions that don't opt in don't need to register it. - When enabled, geolocation is resolved once during `init` — now asynchronous (`Promise<void>`) — before any queued/pre-consent events are replayed, so replayed events carry the same location as new ones. Resolution is best-effort: failures are logged and events still deliver. - Per the ADR, **anonymous payloads carry no location**. Both packages' changelogs are updated, and the monorepo dependency graph in the README reflects the new `analytics-controller → geolocation-controller` edge. ## References - Implements [ADR-0008: Geo-Enrichment of Extension and Mobile Metric Events](MetaMask/decisions#217) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking async `init()` and new geolocation API semantics affect all analytics consumers; opt-in enrichment touches PII-adjacent context on identified events, with documented wiring order when enabled. > > **Overview** > **Geolocation** moves to the **`v2`** JSON API: new **`fetchGeolocationData`** / **`getGeolocationData`** return validated **`country`**, **`region`**, and **`timezone`** (each nullable). Legacy **`fetchGeolocation`** / **`getGeolocation`** stay best-effort and derive ISO location codes via **`toLocationCode`** (US/CA still get region suffix). **`getGeolocationData`** **rejects** on failure so callers avoid stale enrichment. Controller state gains **`country`**, **`region`**, **`timezone`**. > > **Analytics** optionally adds **`country_code`**, **`region`**, and **`timezone`** under **`context.location`** on **non-anonymous** track, identify, and view payloads when **`isGeolocationEnabled`** is true (default **false**). Location is resolved once in async **`init()`** (before queue replay) via **`GeolocationController:getGeolocationData`**; failures are logged and events still send without location. Anonymous split payloads still omit location; **`init()`** is now **`Promise<void>`** and deduplicates concurrent calls. > > Adds **`@metamask/geolocation-controller`** to analytics-controller and documents the new dependency edge in the README. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 06cd92e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Explanation Create a new package to extract various pieces of code related to the money account. The intention of this is to capture as much of non-platform specific code as possible, such that we can more easily share code between mobile and extension. <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > New additive package with no in-repo consumers yet; behavior is covered by tests and only affects clients once they adopt the dependency. > > **Overview** > Introduces **`@metamask/money-account-utils`** as a new Earn-owned workspace package so money-account logic can be shared across Extension and Mobile instead of living only in clients. > > The initial export is **mUSD support ported from MetaMask Mobile**: token metadata and per-chain address/CAIP asset maps, **`getTokenDisplaySymbol`** (registry `MUSD` → branded `mUSD`), and guards **`isMusdToken`**, **`isMusdTokenOnChain`**, and **`isMusdOnMoneyAccountChain`** (Money Account activity limited to Monad while mUSD is also on Mainnet/Linea/BSC). Unit tests target full coverage. > > Monorepo wiring adds the package to **CODEOWNERS**, **`teams.json`**, root **`tsconfig`**, README package list/dependency graph, and **`yarn.lock`**. Root **`lint:eslint`** Node heap is raised from 8192MB to 10240MB. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1c82229. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ts (MetaMask#9699) ## Explanation The `PhishingController` address poisoning check ([MetaMask#8171](MetaMask#8171)) builds its known-recipients set from confirmed transactions in `TransactionController` state, using `txParams.to` as the recipient. For ERC-20/ERC-721/ERC-1155 token transfer methods (`transfer`, `transferFrom`, `safeTransferFrom`), `txParams.to` is the **token contract**, not the address receiving the tokens — the actual recipient is encoded in the calldata. As a result: * A lookalike of the actual token recipient did **not** trigger a poisoning warning (the address the user actually pays is missing from the set). * A lookalike of the token contract **did** trigger a warning (the contract is not a recipient the user pays). This was validated live against a dev build of the extension: after a confirmed ERC-20 `transfer`, the token contract address appeared in the known-recipients set while the decoded calldata recipient did not. ### Solution `TransactionController` already solves this exact problem for first-time-interaction checks with a private `getEffectiveRecipient` helper that decodes the recipient from calldata for token transfer types (falling back to `txParams.to` otherwise). This PR: * Extracts `getEffectiveRecipient` from `utils/first-time-interaction.ts` into a new shared `utils/recipient.ts` and exports it from `@metamask/transaction-controller` (behavior unchanged for the first-time-interaction flow). * Uses it in `PhishingController.#getRecipientAddressesFromTransaction` so confirmed token transfers contribute the **decoded recipient** to the known-recipients set, replacing the token contract address. Generic contract interactions are intentionally unchanged: their `txParams.to` is still added, since there is no decodable recipient to substitute. ## Manual testing 1. In `metamask-extension/package.json`, add both Core preview packages to `resolutions`: ```json "@metamask/phishing-controller": "npm:@metamask-previews/phishing-controller@17.3.0-preview-5da60b6", "@metamask/transaction-controller": "npm:@metamask-previews/transaction-controller@69.3.0-preview-5da60b6" ``` 2. Run `yarn install` and start a development build of MetaMask Extension. 3. Submit and confirm a standard ERC-20 `transfer` to a test recipient address. 4. Inspect the address-poisoning known recipients after the transaction is confirmed, or check lookalike candidates through `PhishingController.checkAddressPoisoning`. 5. Confirm that a lookalike of the recipient encoded in the transfer calldata matches the actual recipient and triggers address-poisoning detection. 6. Confirm that a lookalike of the ERC-20 contract address does not match the contract as a known recipient from this transfer. 7. Submit and confirm a generic contract interaction, then confirm its `txParams.to` contract address is still added as a known recipient. ## References * Related to [MetaMask#8171](MetaMask#8171) (original address poisoning detection) * Related to PSAFE-555 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes security-sensitive address-poisoning recipient hydration; logic is covered by tests and reuses existing decoding with fallbacks to `txParams.to`. > > **Overview** > **Address poisoning** now builds known recipients from the real payee on confirmed ERC-20/ERC-721/ERC-1155 transfers, not the token contract in `txParams.to`. > > `getEffectiveRecipient` is moved out of first-time-interaction into shared `utils/recipient.ts`, exported from `@metamask/transaction-controller`, and wired into `PhishingController.#getRecipientAddressesFromTransaction`. First-time-interaction behavior is unchanged; generic contract interactions still use `txParams.to`. Tests cover decoding and the phishing integration case. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5da60b6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation
* Replace KycService.submitConsents (POST /consents) with session-scoped
GET/POST /sessions/{id}/disclaimers for idOS + KYC-provider consents ({
key, version } + credentialReusabilityConsentGiven).
* Record Iron T&Cs separately via POST /vendors/{vendor}/disclaimers
(submitVendorDisclaimers); those content ids are no longer sent on the
session disclaimer POST.
*Consents path order: vendor T&Cs → create UKYC session → session
catalog/consents → SumSub. A 409 is re-checked with a GET and only
treated as success when every accepted document is consented.
## References
<!--
Are there any issues that this pull request is tied to?
Are there other links that reviewers should consult to understand these
changes better?
Are there client or consumer pull requests to adopt any breaking
changes?
For example:
* Fixes #12345
* Related to #67890
-->
## Checklist
- [ ] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [ ] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **High Risk**
> Breaking API and consent orchestration for identity/KYC flows;
incorrect 409 or catalog handling could block verification or skip
required legal consents.
>
> **Overview**
> **Breaking:** Removes `KycService.submitConsents` (`POST /consents`)
and replaces it with three endpoints: **`submitVendorDisclaimers`** for
Iron/vendor T&C ids, **`fetchSessionDisclaimers`** /
**`submitSessionDisclaimers`** for the idOS + SumSub catalog using `{
key, version }` and **`credentialReusabilityConsentGiven`**.
>
> On the non-MoonPay consents path, **`KycController`** now runs
**vendor T&Cs → UKYC session create → session disclaimers → SumSub**
(vendor content ids are no longer bundled into session consent POSTs).
**`acceptTermsAndStartSession`** accepts optional
**`credentialReusabilityConsentGiven`**; T&C2 booleans map onto catalog
documents. **409** on session disclaimer POST triggers a re-GET and only
continues when every accepted item is actually consented—otherwise the
flow fails closed and rewinds to **`terms`**.
>
> UKYC session creation is extracted into **`#createUkycSession`** so
the consents path can create the session before recording disclaimers;
**`startSumSub`** skips wrapping-key/session steps when a session id
already exists. Adds in-memory **`sessionDisclaimers`** /
**`credentialReusabilityConsentGiven`** state and related types; docs
and tests follow the new contract.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
06dc3da. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…9955) ## Explanation `RampsService.getProviders` and `RampsController.getProviders` previously discarded the API `sorted` ranking metadata and returned only the provider list. Clients that need the backend-ranked default (for example, de-ranking Transak Native in EU/US) had to reconstruct order locally or hardcode a preferred provider. This change keeps `sorted` on `ProvidersResponse`, returns the full response from both methods, and exports `ProvidersResponse` / `ProviderSortOrder` from the package. Controller state still stores `providers` only. Existing callers that read `.providers` are unchanged. This is not a breaking change. After this package is published, clients can consume `sorted` instead of maintaining a local response type. ## Verification `RampsService.getProviders` and `RampsController.getProviders` now return backend ranking metadata (`sorted`) and keep controller state as the provider list only.  Live ranking this package now preserves (from [API MetaMask#1135](consensys-vertical-apps/va-mmcx-onramp-api#1135)): ### Before  ### After  User-visible default-provider selection is covered on [Mobile #35200](MetaMask/metamask-mobile#35200). ## References * Related to [TRAM-3837](https://consensyssoftware.atlassian.net/browse/TRAM-3837) * API: consensys-vertical-apps/va-mmcx-onramp-api#1135 * Mobile: MetaMask/metamask-mobile#35200 * Workload: consensys-vertical-apps/va-mmcx-onramp-workload#564 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them [TRAM-3837]: https://consensyssoftware.atlassian.net/browse/TRAM-3837?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive API surface on an existing fetch path; controller state and `.providers`-only callers are unchanged. > > **Overview** > **`getProviders`** on **`RampsService`** and **`RampsController`** now forwards the API’s **`sorted`** ranking metadata instead of returning only **`providers`**. New exported types **`ProvidersResponse`** and **`ProviderSortOrder`** describe the shape; **`sorted`** is normalized to an empty array when missing or not an array. > > The controller still writes only the provider list into **`state.providers.data`** and does not persist **`sorted`**, so callers that only use **`.providers`** behave as before. Consumers can read **`sorted`** from the method return value to apply backend default ordering (e.g. de-ranking certain providers by region) without duplicating types or hardcoding order locally. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit aa9cf92. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…k#9944) ## Explanation - Aligns the KYC client with the new capability-auth API: POST /sessions returns encryption schemas; wrapped secrets go to POST /sessions/:id/authorizations. - Drops POST /wrapping-key - Wraps both the `data_encryption_key` and the `ukyc_capability_token` with the same NaCl box, packing clientPublicKey || ciphertext into { data, nonce }. - Verify both schemas’ `jwtChains` before wrapping. Stop the SumSub sub-flow if reset() lands mid-flight so a superseded run does not create a journey or launch the SDK. ## References See: consensys-vertical-apps/va-mmcx-kyc-api#38 ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Breaking API and crypto flow changes for UKYC session authorization; mistakes could block SumSub or mishandle encryption keys and capability tokens. > > **Overview** > **Breaking change:** UKYC session setup no longer uses `POST /wrapping-key` or `KycService.getWrappingKey`. **`createUkycSession`** only creates the session and returns per-secret **encryption schemas**; wrapped **`data_encryption_key`** and **`ukyc_capability_token`** are sent in a follow-up **`setAuthorizations`** call (`POST /sessions/{id}/authorizations`). > > **`KycController`** verifies both schemas’ **`jwtChain`** values, wraps each secret with **`wrapEncryptionKey`**, and posts authorizations before **`createJourney`**. The **`vendorProcessing`** short-circuit (relay approved, vendor still pending) is driven by the **authorizations** response, not session creation. > > **Wire format:** **`wrapEncryptionKey`** now returns **`{ data, nonce }`** where **`data`** embeds the client X25519 public key ahead of the ciphertext so the server can open the box without a prior key-registration step; the capability token is wrapped as UTF-8 bytes of the encoded header string, not a plaintext API field. > > Tests and docs are updated; exports drop wrapping-key types and add **`EncryptionSchema`**, **`CapabilityAuthorization`**, and **`SetAuthorizationsParams`**. Additional **`reset()`** guards prevent stale **`setAuthorizations`**, journey creation, or SDK launch after the flow is superseded. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 56ae4c8. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation When the Terminal v2 snapshot fails, the controller falls back to Hyperliquid market data and its local category map. That map still labels `xyz:CBRS` and `xyz:SPCX` as pre-IPO, although both are public stocks. This changes only those two fallback entries to `stock`. It does not call the deprecated Terminal v1 endpoint or change the v2 request path. ## References No issue. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Static category map correction for two symbols on the Hyperliquid fallback path only; no order, auth, or API behavior changes. > > **Overview** > When Terminal v2 market metadata is unavailable, perps still classifies HIP-3 markets via **`HIP3_ASSET_MARKET_TYPES`**. **`xyz:CBRS`** and **`xyz:SPCX`** were still mapped to **pre-IPO** even though they trade as public stocks. > > This PR moves both symbols into the **stock** entries (and drops them from the pre-IPO block) so fallback-driven badges, filters, and category pills match their listing status. **`xyz:IPOP`** stays pre-IPO. Tests and the **Unreleased** changelog entry are updated accordingly; Terminal v2 routing is unchanged. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 252b563. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…#9991) ## Description `yarn validate:changelog` does not exist. The script is `changelog:validate`, both at the root and per package. AGENTS.md had the two halves the wrong way round in both places it mentions it. Running the documented command just errors with `Couldn't find a script named "validate:changelog"`, which is easy to misread as "nothing to validate" rather than "you ran the wrong thing". ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only correction with no runtime or API impact. > > **Overview** > **AGENTS.md** now documents the real Yarn script for changelog checks: both mentions of the linting/formatting and post-update changelog steps use **`yarn changelog:validate`** instead of the non-existent **`yarn validate:changelog`**. > > That aligns agent and contributor docs with the root **`package.json`** script and workspace usage elsewhere in the repo, so following the guide no longer fails with a missing-script error. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit fc5f8bb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…taMask#9992) ## Explanation Pending trade drafts currently restore amount, leverage, TP/SL, limit price, order type, and reduce-only, but not long/short. Clients that restore a sized draft therefore reopen a short as a long (the form default). That is an accidental-trade risk once Pro starts restoring drafts ([MetaMask/metamask-mobile#35367](MetaMask/metamask-mobile#35367)). This adds an optional `direction` (`'long' | 'short'`) to `savePendingTradeConfiguration` / `getPendingTradeConfiguration` and to the pending-config selector. Older drafts without the field still restore as today. Direction is not a global preference; it lives only on the 30-second per-market draft and is not copied onto `selectedOrderType`. ## References * Related to [MetaMask/metamask-mobile#35367](MetaMask/metamask-mobile#35367) * Related to [MetaMask#9922](MetaMask#9922) * Related to [TAT-3706](https://consensyssoftware.atlassian.net/browse/TAT-3706) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com) [TAT-3706]: https://consensyssoftware.atlassian.net/browse/TAT-3706?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Minor release for `@metamask/remote-feature-flag-controller` -> `6.0.0` -> `6.1.0` Minor release for `@metamask/perps-controller` -> `13.0.0` -> `13.1.0` Also bumps dependencies ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Dependency and changelog-only release with a backward-compatible optional API on feature-flag refresh; no breaking changes in this PR. > > **Overview** > **Release 1213.0.0** cuts `@metamask/remote-feature-flag-controller` at **6.1.0** (from 6.0.0) and rolls that dependency through the monorepo. > > The **6.1.0** release documents an optional **`force`** parameter on **`updateRemoteFeatureFlags`**, so callers can refresh remote flags even when the cache has not expired (default `false`; still no fetch when the controller is disabled). > > This PR updates **`package.json`** / **`yarn.lock`** and **Unreleased changelogs** for every direct consumer—e.g. **`assets-controller`**, **`assets-controllers`**, **`bridge-controller`**, **`config-registry-controller`**, **`core-backend`**, **`network-controller`**, **`transaction-controller`**, **`wallet`**, **`wallet-cli`**, and others—to depend on **`^6.1.0`**. A few packages (e.g. **`ramps-controller`**, **`wallet-cli`**) consolidate changelog lines to reflect the full bump path to 6.1.0. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3b2fc15. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Explanation
`@metamask/platform-api-docs` documents the platform API — every
messenger action and event a project exposes. Until now it had one way
of finding them: parse every TypeScript source and declaration file it
can reach (the scan directories, `packages/*/src`, and
`node_modules/@metamask/*/dist/**/*.d.cts`) and walk every type alias
named `*Messenger`.
That is the right approach for this monorepo, which has no single
messenger aggregating every capability. It is a poor fit for a client,
which already declares the complete set on its root messenger.
Re-deriving that from the whole dependency tree means parsing ~11,600
files in `metamask-mobile` and ~4,500 in `metamask-extension`, to
rediscover something the client has written down in one place.
This PR adds a second strategy that reads what the client already
declares.
### `--strategy`
- **`scan`** (default) — unchanged behaviour, and the only option for a
project with no single aggregating messenger.
- **`root-messenger`** — resolves the two types named by
`--root-actions` and `--root-events` (each written `<file>#<TypeName>`)
and lets the TypeScript type checker enumerate them. Only the named
files are opened; the checker pulls in the rest.
Flags belonging to the strategy that wasn't selected are rejected rather
than ignored, via a yargs `.check`, so a mistaken invocation fails
loudly instead of quietly producing docs built the wrong way. The
`<file>#<TypeName>` references are parsed in a yargs `.coerce`, so a
malformed one is reported like any other
bad argument before work begins.
### Why the type checker rather than the AST
This is the non-obvious part. The two clients declare their root unions
differently:
- `metamask-mobile` writes `GlobalActions` by hand as a union of type
references. A syntactic walk would work.
- `metamask-extension` derives `RootMessengerActions` from a registry of
messenger factories via
`MessengerActions<ReturnType<(typeof
MESSENGER_FACTORIES)[…]['getMessenger']>>`.
**There is no syntactic union to walk** — only the type checker can say
what it contains.
Going through the checker handles both shapes with one code path. Once
it reports *which* capability types are in the union, each declaration
is handed to the **existing** extractor in `extraction.ts`, so JSDoc,
handler/payload signatures, source links, and deprecation flags come out
identical to `scan`. The new module is a discovery front-end, not a
second extractor.
Two details worth knowing:
- A capability declared as a **type alias** carries its name and JSDoc
on the *alias* symbol; the plain symbol points at the anonymous object
type. An **interface** has no alias symbol, being its own declaration,
so both are
consulted. Missing the interface case silently dropped 61 actions and 8
events on mobile before it was fixed.
- For a lone generic instantiation (`type Actions = Foo<Bar>`) the
checker attributes the alias to the *root union itself*, which would
hand the extractor the wrong declaration. That case is guarded.
### Failure behaviour
Generation now fails loudly instead of producing an empty site.
`writeOutput` deletes `docs/` before writing, so a root union that
resolves to nothing — a renamed type, or imports that don't resolve —
would previously have replaced a
published docs directory with an empty one and exited `0`. It now
throws, naming both references.
Capability types that can't be documented are reported with their names
rather than counted, in three buckets: declared inline (no name or
JSDoc), unresolved (`any`/`unknown`, usually a failed import), and
unextractable (a shape the extractor rejects). A count alone isn't
actionable at this scale.
### Also fixed: MDX escaping
`escapeJsDocTextForMdx` escaped `{` and `}` but not `<`, which MDX reads
as the start of a JSX tag. A `@returns` comment such as
`Promise<PointsBoostDto[]>` therefore **failed the site build** rather
than rendering:
```
Unexpected character `[` (U+005B) in name, expected a name character…
```
This is pre-existing and independent of the new strategy — `scan`
produces the byte-identical line — but it blocked `--build` and
`--serve` for both clients, so it is fixed here. Affects description,
`@param`, and `@returns` text; handler and payload signatures were
already safe inside fenced code blocks.
---
## Benchmarks
Measured on a warm checkout, doc generation only.
| Project | `scan` | `root-messenger` | Speedup |
| --- | --- | --- | --- |
| `metamask-extension` | 47.9s | **4.7s** | ~10× |
| `metamask-mobile` | 96.9s | **5.7s** | ~17× |
`scan` parses ~5,600 `app/**/*.ts` plus ~6,026 `.d.cts` in mobile, and
~4,472 `.d.cts` in the extension. `root-messenger` opens the entry file
and lets the checker pull in only what the union references.
## Strategy comparison
### `metamask-mobile`
| | `scan` | `root-messenger` |
| --- | --- | --- |
| Namespaces | 112 | 103 |
| Actions | 1184 | 1157 |
| Events | 181 | 171 |
| Unique capabilities | 1365 | 1328 (97.3%) |
The 37 not documented are mostly controllers genuinely **not on the root
messenger** — `PasskeyController` alone accounts for 17, plus
`RatesController` (4), the sample controllers, and the decrypt/encrypt
message managers. Nothing is found by `root-messenger` that `scan`
misses.
### `metamask-extension`
| | `scan` | `root-messenger` |
| --- | --- | --- |
| Namespaces | 119 | 115 |
| Actions | 1209 | 1085 |
| Events | 183 | 176 |
| Unique capabilities | 1392 | 1261 (90.6%) |
99 of the 131-capability gap is `PerpsController`, and the cause is
worth flagging to the extension team rather than treating as a tool
limitation:
```ts
export type PerpsControllerMessenger = Messenger<
'PerpsController',
AllowedActions, // actions Perps may CALL
AllowedEvents
>;
```
`RootMessengerActions` is `MessengerActions<ChildMessengers>` — the
union of what each child messenger is **allowed to call**, not what each
controller **provides**. A controller whose actions are only invoked
from the UI, never from another controller's messenger, never appears.
`PerpsController` is registered in
`MESSENGER_FACTORIES` yet contributes **zero** constituents.
`root-messenger` documents exactly what the named types contain. Full
coverage in. the extension needs an aggregate of *provided* actions,
which is an extension-side change.
Conversely, `root-messenger` finds 6 capabilities `scan`
misses(`MultichainRoutingService` ×4, `PPOMController` ×2).
Reported-but-skipped, current run: mobile 39 unextractable; extension 2
inline + 33 unextractable.
---
## Usage in clients
Once published, add the dependency and two scripts. For
`metamask-mobile`:
```json
{
"scripts": {
"docs:platform-api:build": "platform-api-docs --build --project-label Mobile --strategy root-messenger --root-actions 'app/core/Engine/types.ts#GlobalActions' --root-events 'app/core/Engine/types.ts#GlobalEvents'",
"docs:platform-api:serve": "platform-api-docs --serve --project-label Mobile --site-base-url / --strategy root-messenger --root-actions 'app/core/Engine/types.ts#GlobalActions' --root-events 'app/core/Engine/types.ts#GlobalEvents'"
}
}
```
For `metamask-extension`, the label and references change:
```
--project-label Extension
--root-actions 'app/scripts/lib/messenger.ts#RootMessengerActions'
--root-events 'app/scripts/lib/messenger.ts#RootMessengerEvents'
```
Notes for consumers:
- Keep the `#` **inside quotes** — unquoted, most shells treat it as a
comment and silently truncate the argument.
- `--root-actions` / `--root-events` are relative to the project path,
not the shell's working directory.
- Output defaults to `<project-path>/.platform-api-docs`; gitignore it.
- `metamask-extension` additionally needs its `postcss-loader/jiti`
resolution narrowed to `postcss-loader@^8.2.1/jiti`. The unversioned
form also stubs the `postcss-loader@^7.3.4` that `@docusaurus/bundler`
depends on, replacing `jiti` with an empty package and breaking the site
build. Narrowing preserves the stub's original intent for the
extension's own `postcss-loader@8.2.1`.
## References
Fixes: https://consensyssoftware.atlassian.net/browse/WPC-1202
* Consumer PR (mobile):
MetaMask/metamask-mobile#26526
* Consumer PR (extension, includes the `jiti` resolution fix):
MetaMask/metamask-extension#40352
## Checklist
- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Changes are confined to the docs CLI and generation pipeline; default
`scan` behavior is preserved with an explicit `strategy` field in tests.
>
> **Overview**
> Adds a **`root-messenger`** discovery path alongside the existing
**`scan`** default: the CLI accepts `--strategy`, `--root-actions`, and
`--root-events` (`<file>#<TypeName>`), validates that strategy-specific
flags are not mixed, and routes generation through type-checker
resolution of the project’s root action/event unions instead of scanning
the whole tree.
>
> New **`root-messenger-discovery`** walks those unions (including
checker-derived unions like `MessengerActions<…>`), reuses the shared
extractor in **`extraction.ts`** (with
**`classifyMessengerCapabilityTypeDeclaration`** exported for reuse),
warns on skipped inline or unextractable capabilities, and **throws**
when unions resolve to `any`/`unknown` or would produce zero docs so an
empty site cannot overwrite published output.
>
> **`escapeJsDocTextForMdx`** now escapes `<` as well as braces so
generic types in JSDoc do not break MDX builds. README, changelog, and
broad CLI/generate/discovery tests cover the new behavior.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1da2bbd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
## Explanation `isMaliciousC2Domain` previously checked each request against the C2 blocklist using `Array.includes`, which performs a linear O(n) scan of hashed domain strings on every call. For a list in the thousands-of-entries range, each call scans the full list up to 6 times — once for the exact hostname hash, up to 5 times for parent domain hashes. This PR switches the internal representation to `Set<string>`, reducing each of those scans to an O(1) hash lookup. **Benchmark results (internal profiling):** - Per-request time: 2–8ms → 0.05–0.5ms (~10–100x improvement depending on page complexity; ~50x averaged across pages) - Potential CPU savings: up to ~300ms per page load - Background tabs also benefit — confirmed they generate significant background network requests that all route through this check **Implementation notes:** - The conversion from `string[]` → `Set<string>` happens once at construction time via a new unexported `InternalPhishingDetectorConfiguration` type. The public `PhishingDetectorConfiguration` type keeps `c2DomainBlocklist?: string[]` unchanged — no API break for downstream consumers. - `isMaliciousC2Domain` now uses `.size` / `.has` in place of `.length` / `.includes` - `getDefaultPhishingDetectorConfig` accepts and threads `c2DomainBlocklist` through as `string[]`; `processConfigs` no longer performs a redundant intermediate `Set` construction Fixes: MetaMask/MetaMask-planning#5611 ## References - Fixes MetaMask/MetaMask-planning#5611 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Behavior-preserving internal data-structure change on a hot path; public types and matching logic are unchanged. > > **Overview** > **C2 domain blocklist checks** in `PhishingDetector.isMaliciousC2Domain` now use a `Set` internally instead of scanning a `string[]` with `includes`, so each hostname and parent-domain hash lookup is O(1) rather than O(n) (up to several lookups per request). > > Arrays from config are converted to `Set<string>` once in the constructor via an internal `InternalPhishingDetectorConfiguration` type; the exported `PhishingDetectorConfiguration` still exposes `c2DomainBlocklist?: string[]`. `getDefaultPhishingDetectorConfig` now accepts and forwards an optional `c2DomainBlocklist` override. The changelog records the performance change. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0efe622. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Hyperliquid historical orders were mapped through a separate inline path that dropped `triggerPx` and the normalized trigger order type. Consumers therefore received trigger orders without `Order.triggerPrice`, preventing clients such as MetaMask Mobile from displaying the trigger price in order history. This change reuses the existing `adaptOrderFromSDK` mapping used for open orders, then overlays historical lifecycle fields such as status, timestamps, and filled/remaining size. It also preserves ordinary historical market-order classification when Hyperliquid supplies a non-empty `limitPx` as a slippage cap. Provider tests now cover distinct trigger and limit prices for Take Profit Limit and Stop Market orders, plus a regular market order carrying a slippage-cap price. There are no public type changes or breaking changes. Validation: - `yarn workspace @metamask/perps-controller run test` - `yarn eslint packages/perps-controller/src/providers/HyperLiquidProvider.ts packages/perps-controller/tests/src/providers/HyperLiquidProvider.history.test.ts` - `yarn build` ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> - Related to [MetaMask/metamask-mobile#35118](MetaMask/metamask-mobile#35118) - Addresses [the production history boundary review finding](MetaMask/metamask-mobile#35118 (comment)) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Localized to HyperLiquid historical order mapping and adapter edge cases; no public API or breaking type changes, with expanded unit tests. > > **Overview** > **HyperLiquid order history** now goes through the same `adaptOrderFromSDK` path as open orders, instead of a separate inline mapper that dropped trigger metadata. > > Historical rows still get lifecycle overlays (`status`, `statusTimestamp` / `lastUpdated`, and `remainingSize` from the exchange’s current `sz`). **`orderType`** for history is derived from HyperLiquid’s detailed type via `HISTORICAL_ORDER_TYPE_BY_DETAILED_TYPE`, so trigger and market orders are not mislabeled as limits when `limitPx` is only a slippage cap. > > **`adaptOrderFromSDK`** tolerates missing `oid`, `orderType`, and empty `limitPx` so partial history payloads still map instead of failing silently. > > Tests cover trigger/limit price preservation for TP/SL history, market orders with slippage-cap prices, and the detailed-type → limit/market mapping table. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit cdd11cd. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation `@metamask/perps-controller` v13 treated HyperLiquid's valid `waitingForTrigger` TP/SL acknowledgement as a failure because it has no order ID. The trigger was created, but Mobile displayed `TPSL_UPDATE_FAILED`. This change accepts `waitingForTrigger` and reconciles its order ID only when needed to clean up a mixed-result batch. It reuses existing REST/cache order IDs to avoid an extra happy-path request, while preserving rollback for rejected, unknown, and incomplete responses. No dependencies or other packages changed, and there are no breaking changes. ## References - Related to TAT-3846 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes failure/rollback paths for `updatePositionTPSL` and open-order reconciliation; incorrect matching could mis-cancel triggers or mis-report protection loss, though matching requires a single unambiguous candidate. > > **Overview** > **Fixes false `TPSL_UPDATE_FAILED` when HyperLiquid accepts position TP/SL with a bare `waitingForTrigger` acknowledgement (no order ID in the response).** > > `updatePositionTPSL` now classifies TP/SL placement via `#readTpslOrderPlacementOutcome`, treating `waitingForTrigger` as success alongside resting and filled. On the happy path it returns success without requiring an order ID for every leg. When a mixed batch fails, `#reconcileTpslOrderPlacementOutcomes` can match new trigger orders from `frontendOpenOrders` (size, trigger price, side, TP vs SL) using a pre-placement order ID snapshot so cleanup can cancel reconciled legs; unreconciled `waitingForTrigger` legs surface `TPSL_PROTECTION_LOST` instead of a generic update failure. Restoration after partial failure uses the same TP/SL outcome logic. Tests cover combined/mixed statuses, replacement with cancel, reconciliation before mixed-failure cleanup, and restore-on-unknown/incomplete responses. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d3cffda. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation Currently, the lint, build, and test matrix jobs in `lint-build-test.yml` cancel all other in-progress matrix combinations as soon as one fails, because `fail-fast` defaults to `true`. This means a single failing package can hide the results of other packages in the same run, making it harder to see the full picture of what's broken. This PR sets `fail-fast: false` on all matrix strategies in the workflow, so every matrix combination runs to completion regardless of whether another one fails. ## References N/A ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Workflow-only CI behavior change; no application code, security, or release paths affected. > > **Overview** > Sets **`fail-fast: false`** on matrix strategies in `lint-build-test.yml` so one failing combination no longer cancels the rest of the matrix. > > This applies to **`lint`** (per lint script), **`validate-changelog`** (per package), **`test-18`**, **`test-20`**, and **`test-22`** (per changed workspace package), and **`test-wallet-cli-e2e`** (per Node version). CI runs may take slightly longer when something fails, but PR checks should surface **all** failing lint scripts, changelog validations, and package tests in a single run instead of stopping after the first failure. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 175ba85. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation The first TP/SL edit after Mobile reload could fail before cancellation or placement because the builder-fee approval lookup sent the one-off `maxBuilderFee` request over a reconnecting WebSocket. HyperLiquid rejected it with `WebSocketRequestError`, which `updatePositionTPSL` surfaced as `TPSL_UPDATE_FAILED`; a later retry succeeded after reconnection. This routes builder-fee approval reads through the existing HTTP `InfoClient`, which is designed for request/response operations and remains available during WebSocket churn. `maxBuilderFee` was not a subscription, so this does not remove live updates or change existing approval-cache semantics. Genuine approval failures still block replacement before existing protection is cancelled. Adds regression coverage with distinct WebSocket and HTTP clients and updates the perps-controller changelog. No dependencies or other packages are changed. Validation: - Full `@metamask/perps-controller` test suite - Builder-fee and strategy-order suites - Monorepo build - Changelog validation and scoped lint/format checks - Mobile simulator: first `+50/-50` TP/SL edit succeeded after reload ## References - Discovered while validating [MetaMask#9995](MetaMask#9995) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Narrow transport change for a read-only approval lookup on an existing HTTP code path; regression test covers the TP/SL cold-start scenario. > > **Overview** > Fixes a case where the **first position TP/SL update after app reload** could fail with `TPSL_UPDATE_FAILED` because the builder-fee approval check (`maxBuilderFee`) went over a reconnecting HyperLiquid WebSocket and threw `WebSocketRequestError`. > > `#checkBuilderFeeApproval` in `HyperLiquidProvider` now requests the info client with **`useHttp: true`**, so that read uses the HTTP `InfoClient` path that stays usable during socket churn. Approval caching and real approval/signing behavior are unchanged; only the lookup transport changes. > > Adds a regression test that simulates a failing WebSocket `maxBuilderFee` and asserts cold-start `updatePositionTPSL` succeeds via HTTP, plus an **Unreleased** changelog entry. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3e2cffb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Arthur Breton <arthur.breton@consensys.net>
## Summary - Add `has_sufficient_funds` to Unified SwapBridge Quotes Received metrics for [SWAPS-4977](https://consensyssoftware.atlassian.net/browse/SWAPS-4977). - Preserve explicit slippage intent and normalized slippage limits across quote, submission, and history metrics for [SWAPS-4979](https://consensyssoftware.atlassian.net/browse/SWAPS-4979). - Support client fallbacks for incomplete quote USD and token-symbol data, while preserving Quick Buy and Batch Sell legacy behavior. ## Test plan - [x] `yarn workspace @metamask/bridge-controller test --coverage=false --runInBand --testPathPatterns=src/utils/metrics/properties.test.ts` - [x] `yarn workspace @metamask/bridge-status-controller test --coverage=false --runInBand --testPathPatterns=src/utils/metrics.test.ts` - [x] Bridge status-controller tests and Batch Sell tests - [x] Core typecheck and targeted ESLint - [x] Prettier and `git diff --check` [SWAPS-4977]: https://consensyssoftware.atlassian.net/browse/SWAPS-4977?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [SWAPS-4979]: https://consensyssoftware.atlassian.net/browse/SWAPS-4979?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Analytics and metrics payload changes only; no transaction or auth logic. Snapshot/test updates reflect corrected slippage and custom_slippage semantics. > > **Overview** > Fixes **Unified SwapBridge** analytics so quote-through-completion events carry consistent balance and slippage fields. > > **Quotes Received** now includes **`has_sufficient_funds`** (from the quote request’s insufficient-balance flag). **`slippage_limit`** is always a number (**`0`** means auto when no explicit limit), including on request metadata and in **`getQuotesReceivedProperties`**, which also accepts optional client fallbacks for slippage, **`custom_slippage`**, USD amount, and token symbols when quote data is incomplete. > > **Bridge status** threads **`quotesReceivedContext`** into submit/pre-confirmation metrics, persists **`customSlippage`** on history, and prefers that (with Batch Sell / Quick Buy legacy inference) when rebuilding **`custom_slippage`** and **`slippage_limit`** after submission instead of inferring only from quote slippage. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit b915d67. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation `ExampleDataService` is not type checked, so we had missed some type inference issues. This PR fixes them. ## References N/A ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only example service typing and pagination guards; no production runtime behavior change. > > **Overview** > Updates **`ExampleDataService.getActivity`** so infinite-query types infer correctly once the example is type-checked. > > **`fetchInfiniteQuery`** no longer passes an explicit **`GetActivityResponse`** generic; **`initialPageParam`** is **`null as PageParam`** so **`TPageParam`** lines up with **`getPreviousPageParam`** / **`getNextPageParam`**. Pagination branches use **`'after' in pageParam`** and **`'before' in pageParam`** instead of optional chaining on **`pageParam`**, with eslint disables where the project restricts that syntax. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f9f85dc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Explanation Release bridge controllers <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Version and changelog-only release with dependency alignment; no new runtime logic in this diff. > > **Overview** > This PR **cuts release 1214.0.0** by bumping versions and finalizing changelogs—there is no application source change in the diff. > > **`@metamask/bridge-controller` 80.1.0** documents shipped work from earlier PRs: Unified SwapBridge **quote** metrics now include sufficient-funds and normalized slippage fields ([MetaMask#9986]), and `@metamask/remote-feature-flag-controller` is bumped to `^6.1.0` ([MetaMask#9980). > > **`@metamask/bridge-status-controller` 75.4.0** documents post-submission Unified SwapBridge metrics that preserve **slippage intent** and **normalized slippage limits** ([MetaMask#9986]), and raises its dependency on `@metamask/bridge-controller` to `^80.1.0`. Root `package.json` and `yarn.lock` are updated to match. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 811ae77. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Explanation Bumps `@metamask/eth-sig-util` from `^8.2.0` to `^9.0.0` across the four packages that depend on it (`eth-json-rpc-middleware`, `keyring-controller`, `message-manager`, `signature-controller`). `v9.0.0` contains two breaking changes: - Drops Node.js 18 and 20 support (minimum is now Node.js 22) — this repo already runs on Node.js 22+, so no impact. - `signTypedData` now rejects ambiguous `bool` values (e.g. `0`, `1`, `'0'`, `''`) instead of coercing them via `Boolean()`. None of the affected packages pass non-boolean values to `bool` fields, so no code changes were required. **Dependency update cascade:** - `eth-sig-util@9.0.0` update: [MetaMask/eth-sig-util CHANGELOG](https://github.com/MetaMask/eth-sig-util/blob/main/CHANGELOG.md#900) - Core controllers: this PR - Keyring updates: [MetaMask/accounts#626](MetaMask/accounts#626) - Mobile resolution: [MetaMask/metamask-mobile#35410](MetaMask/metamask-mobile#35410) - Extension resolution: [MetaMask/metamask-extension#45854](MetaMask/metamask-extension#45854) ## References - [`@metamask/eth-sig-util` v9.0.0 changelog](https://github.com/MetaMask/eth-sig-util/blob/main/CHANGELOG.md#900) ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches signing and typed-data validation paths without local code or test updates; v9’s stricter bool handling could affect edge-case dapp payloads even if core packages don’t rely on coercion. > > **Overview** > Bumps **`@metamask/eth-sig-util`** from **`^8.2.0`** to **`^9.0.0`** in **`eth-json-rpc-middleware`**, **`keyring-controller`**, **`message-manager`**, and **`signature-controller`**, with matching **Unreleased** changelog entries and **`yarn.lock`** resolution to **9.0.0**. There are **no application source changes** in this PR. > > Consumers inherit **v9** behavior indirectly: **`signTypedData`** no longer coerces ambiguous values into EIP-712 **`bool`** fields, and the library’s minimum Node version is **22** (this monorepo is already on Node 22+). Downstream clients may need their own lockfile/resolution updates to stay aligned. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1565522. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
) ## Explanation Money account "Pay with" previously read Buy-scoped payment methods even though Money quotes use a deposit asset and a different eligible-provider set. That mismatch could offer methods such as Revolut Pay that cannot produce a Money deposit quote (TRAM-3838 / metamask-mobile#34109). ### Shared context API and provenance `getPaymentMethodsForContext` is a new shared Core API. It combines payment-method fetch and automatic-selection behavior that already existed in Mobile UB2 with Core's existing provider-resolution helpers, while allowing callers to choose whether the result updates Buy state. `#resolveProviderIdsForPaymentMethods` is a new adapter over existing Core behavior. It reuses `#resolveAllProvidersFlag`, `#filterProviderIdsBySupport`, `#getSupportingProvidersForRegion`, and `#resolveProviderIdsForQuote` instead of creating a separate provider policy. `mergePaymentMethodsById` was newly designed for multi-provider fan-out. It was not copied from Mobile. `normalizeRampsAssetId` is not new behavior. Mobile has carried the same rule since April 2026 as `normalizeAssetIdForApi` (metamask-mobile#29037). This PR promotes it into Core's public API so the rule lives in one place and Mobile can drop its copy. `providerServesAsset` now uses it too, which is a behavior change: non-EVM CAIP-19 references are no longer lowercased, since Solana base58 and bitcoin bech32 references are case-sensitive and lowercasing both sides could report a provider as serving an asset it does not serve. ### Merge scope `mergePaymentMethodsById` returns a single provider list unchanged by identity. It preserves the provider's array, row references, ordering, length, and duplicate IDs, so single-provider consumers such as standalone Buy keep verbatim provider output just as legacy `getPaymentMethods` did. Deduping runs only when two or more provider lists are combined, and it keeps the first-seen entry per canonical ID. There is no field-level collision policy, because payment-method metadata is provider-invariant: the API serves it from a per-region catalog (`PaymentMethodsList.ts`) and the `provider` query narrows that catalog by ID-set membership (`RegionsV2Service.ts`, `providerPaymentIds.has(payment.id)`) without rewriting fields. Two providers returning the same payment ID therefore return identical `score`, `delay`, and `name`. The same data is already deduped first-seen-wins by the API itself for its multi-region path. Provider fan-out does vary array order, because the API sorts by reliability per request. That affects ordering, not field values, and `mergePaymentMethodsById` preserves first-seen encounter order. ### Stateful write guards `updateState: true` reproduces legacy Buy catalog and selection writes, but it is guarded because the controller mutates shared state from inside the query function. A consumer's query cache cannot undo that side effect after a stale request commits. The write requires the stored region, selected asset, and selected provider to match the request context. Stored region is trimmed and lowercased before comparison, and a missing region remains fail-closed. EVM asset IDs are compared case-insensitively while non-EVM asset case is preserved. A region-only stale write is reachable when an older region request remains in flight, the user changes region, and the new region repopulates the same asset and provider before the older response returns. Legacy behavior checked asset and provider but not region. These guards close that latent gap rather than adding a cache-only safeguard. Two same-context stateful requests cannot be ordered by those guards, because the context key falls back to the globally selected provider when more than one provider is passed. Rather than order them with a request sequence, `updateState: true` now throws when provider resolution yields more than one provider, before any fetch is issued. The shared Buy catalog has one slot per region, token, and selected provider, so a fan-out write has nowhere unambiguous to land. Callers wanting a multi-provider catalog pass `updateState: false` and read the returned `methods`. UB2 already passes exactly one explicit selected provider, and MMPay deposit contexts are read-only, so no current caller is affected. An earlier revision of this PR ordered these writes with a per-request sequence counter instead. It was removed: the race it guarded is unreachable for a single-provider write (the state context key and the fetch cache key move together, so concurrent duplicates collapse in `executeRequest`'s pending dedupe), and the counter was the source of both Bugbot findings on this PR, the second introduced by the fix for the first. ### Catalog writers `paymentMethods.data` and `paymentMethods.selected` have two writers in Core: the pre-existing `getPaymentMethods` and the new `getPaymentMethodsForContext`. Neither one's write guard is aware of the other, so a client calling both for the same context has always been able to interleave them. The consumer PR removes Mobile's last call to `getPaymentMethods` (it was in `app/components/UI/Ramp/queries/paymentMethods.ts` on `main`), so once both land, `getPaymentMethodsForContext` is Mobile's only writer of that state. `getPaymentMethods` stays exported and unchanged for other consumers. ### Scope boundary This PR does not include `getSmartSelectedQuote` or `applyQuoteContext`. Payment-method context is the minimum shared boundary required by the implemented Money and UB2 consumers. Quote choice and quote-state application still have consumer-specific orchestration and remain deferred to the tracked UB2 dumbification follow-up (TRAM-3856). ## References * Fixes: TRAM-3838 * Related: MetaMask/metamask-mobile#34109 * Implemented and tested consumer: MetaMask/metamask-mobile#35346 (supersedes #34469) * Final consumer preview: `@metamask-previews/ramps-controller@20.0.0-preview-a2a428472` * Final Core commit: `a2a428472d39421ffb5aaa04789bfdc45f573bd1` * Preview publication: MetaMask#9801 (comment) * Related tech debt: TRAM-3856 (UB2 dumbification follow-up under TRAM-3460) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches payment-method catalog state, provider eligibility matching, and new concurrent-write guards; behavior change for non-EVM asset matching could affect which providers are considered to serve an asset. > > **Overview** > Adds **`getPaymentMethodsForContext`** (and the messenger action) so Money/headless flows can load payment methods for a **deposit asset and provider set** using the same resolution rules as **`getQuotes`**, without defaulting to the Buy catalog’s selected token/provider. By default the call is **request-only**; optional **`updateState: true`** writes `paymentMethods` only when region, selected token, and selected provider still match, and **throws** if resolution yields more than one provider. > > Multi-provider fetches **fan out**, **merge** via new **`mergePaymentMethodsById`** / **`pickPaymentMethod`**, and tolerate partial failures. **`normalizeRampsAssetId`** is exported and used for comparisons; **`providerServesAsset`** / **`getProvidersServingAsset`** now **stop lowercasing non-EVM** CAIP-19 ids so Solana/bitcoin references stay case-sensitive. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit aefeed7. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation Releases `@metamask/ramps-controller` 20.0.0 to 20.1.0. No other package is being released, so the monorepo version goes 1214.0.0 to 1215.0.0. The whole branch is `yarn create-release-branch` output plus one commit from `github-actions[bot]`. Nothing was edited by hand. The bump is minor. Everything in the new section is additive except one `Fixed` entry, and nothing in it is marked `**BREAKING:**`: - **Added** `getPaymentMethodsForContext()` and the `RampsController:getPaymentMethodsForContext` messenger action, plus the `normalizeRampsAssetId()` export ([MetaMask#9801](MetaMask#9801)). - **Added** the `TERMINAL_ORDER_STATUSES` / `isTerminalOrderStatus()` exports ([MetaMask#9679](MetaMask#9679)). - **Changed** `getProviders` to preserve backend ranking metadata ([MetaMask#9955](MetaMask#9955)), and bumped `@metamask/remote-feature-flag-controller` to `^6.1.0` ([MetaMask#9945](MetaMask#9945), [MetaMask#9980](MetaMask#9980)). - **Fixed** `providerServesAsset()` / `getProvidersServingAsset()` so non-EVM CAIP-19 references are no longer lowercased when matched against a provider's `supportedCryptoCurrencies` ([MetaMask#9801](MetaMask#9801)). Solana base58 and bitcoin bech32 references are case-sensitive, so lowercasing both sides could report a provider as serving an asset it does not serve. EVM ids still match regardless of checksum casing. ### Packages deliberately left out of this release `create-release-branch` flagged four dependencies of `ramps-controller` that have unreleased changes: `@metamask/base-controller`, `@metamask/controller-utils`, `@metamask/messenger`, and `@metamask/profile-sync-controller`. All four are listed as `intentionally-skip` in the release spec. None of the PRs in this release changed `packages/ramps-controller/package.json`, so the declared ranges are untouched and the currently published versions of those four still satisfy them. ### Dependent range bump `@metamask/transaction-pay-controller` is the only in-repo consumer of `ramps-controller`. Its range moves from `^20.0.0` to `^20.1.0`, which is the tool's usual dependent-range update, and `github-actions[bot]` added the matching changelog entry. It does not need a release of its own, since `^20.0.0` already accepted 20.1.0. `yarn changelog:validate`, `yarn constraints`, and `yarn install --immutable` all pass on this branch. ## References * Releases: MetaMask#9801 (TRAM-3838), MetaMask#9679, MetaMask#9955, MetaMask#9945, MetaMask#9980 * Consumer waiting on this release: MetaMask/metamask-mobile#35346 * Related: MetaMask/metamask-mobile#34109 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Version, changelog, and lockfile updates only; ramps 20.1.0 is a minor release with additive API and a targeted provider-matching fix, with no breaking changes in this cut. > > **Overview** > **Release-only PR** — no runtime code in the diff. It cuts **`@metamask/ramps-controller` `20.0.0` → `20.1.0`**, bumps the root monorepo version **`1214.0.0` → `1215.0.0`**, moves the existing `[Unreleased]` ramps changelog into **`[20.1.0]`**, and refreshes **`yarn.lock`**. > > The in-repo consumer **`@metamask/transaction-pay-controller`** gets a dependent range update **`@metamask/ramps-controller` `^20.0.0` → `^20.1.0`** plus an `[Unreleased]` changelog line; its own package version is **not** released here. > > What ships in **ramps-controller 20.1.0** (documented in the promoted changelog): **`getPaymentMethodsForContext`** / messenger action, **`normalizeRampsAssetId`**, **`TERMINAL_ORDER_STATUSES` / `isTerminalOrderStatus`**, **`getProviders` ranking metadata preservation**, dependency bump for **`remote-feature-flag-controller`**, and a **fix** so non-EVM CAIP-19 asset matching is no longer incorrectly lowercased in **`providerServesAsset` / `getProvidersServingAsset`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ec32856. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…etaMask#9973) ## Explanation Adds spam cleanup for users. This is additional filtering to ensure we are protected if any external system adds in spam (WS, API, Migration). This is because our external sources can fail and add in spam: - API filters can loosen, and add in spam tokens. - WS has no filtering right now, so airdrops are added to the wallet. This safeguards ourselves by adding additional cleanup on startup/unlock. We need a longer term discussion & decision with Assets team to highlight this cleanup issue and building a trustless system. Builds: - MetaMask/metamask-extension#45795 - MetaMask/metamask-mobile#35328 Code Walkthrough: https://www.loom.com/share/7c60194c8be44a6c8d1e7ee8dc559ae7 ## References https://consensyssoftware.atlassian.net/browse/ASSETS-3905 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Unlock can mutate persisted `assetsInfo`/`assetsBalance` based on Token API occurrence heuristics; misclassification or API errors could hide legitimate tokens, though the feature flag defaults off and failures are designed to no-op. > > **Overview** > **Adds unlock-time spam token cleanup** so wallets can drop airdrop/scam ERC-20s that slipped in via loose API filters or unfiltered websocket balances, without waiting on upstream fixes. > > On `KeyringController:unlock`, `AssetsController` now runs `#runSpamCleanup`, which calls `cleanSpamAssets` from the heal/migration module. That path hits the Token API (`suggestedOccurrenceFloors` + batched `/v3/assets`), classifies sweepable EVM ERC-20s by occurrence vs per-chain floors, and applies a single state patch removing spam from `assetsInfo` and `assetsBalance` while leaving natives, non-EVM assets, default-tracked tokens (e.g. mUSD), and `customAssets` alone. It only runs when the keyring is unlocked, basic functionality is on, and remote flags set `assetsUnifyState.useUnlockCleanup` to **true**; API failures leave state unchanged and can report via `captureException`. > > The PR is mostly **test and fixture work**: dedicated spam-cleanup unit and integration suites, `nock` HTTP stubs, production-derived scam-wallet state, captured API responses, and expanded `MockAssetControllerMessenger` helpers. Changelog and `nock` devDependency updates accompany the wiring change. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d00abac. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…mp API requests (MetaMask#9983) ## Explanation The on-ramp API is adding server-side feature flags (LaunchDarkly) that are gated per client product and client version — e.g. "enable feature X for MetaMask Mobile >= 8.10.0". To evaluate those gates, the API needs to know which client is calling. This PR adds optional `clientProduct` and `clientVersion` constructor options to `RampsService` and `TransakService`. When the host supplies them, every on-ramp API fetch carries two query params: - `clientProduct` (e.g. `metamask-mobile`) - `clientVersion` (the app SemVer, not the ramps-controller package version) Identity travels **in the URL, not in headers**, because the on-ramp CDN caches responses keyed by URL. If gating identity lived in headers, two clients in different flag cohorts would share the same cache entry and one cohort's response could be served to the other. Query params make the cache key and the flag decision agree by construction. Both fields are optional. Hosts that don't pass them send no identity params, and the API fails closed (feature off) — so this is fully backwards compatible. Also exported for hosts and tests: the `RampsClientIdentity` type, the `addRampsClientIdentityParams` helper, and the `RAMPS_CLIENT_PRODUCT_PARAM` / `RAMPS_CLIENT_VERSION_PARAM` constants. The `controller` query param is intentionally left as the ramps-controller package version; the new params carry the app identity instead of overloading it. ## References Related to the on-ramp API version-gated feature flag rollout (Unified Buy 2.0 catalog gating / fiat soft-default) — [TRAM-3828](https://consensyssoftware.atlassian.net/browse/TRAM-3828). - API: consensys-vertical-apps/va-mmcx-onramp-api#1141 - Mobile: MetaMask/metamask-mobile#35359 - Extension: MetaMask/metamask-extension#45823 ## Changelog Updated `packages/ramps-controller/CHANGELOG.md` (Unreleased → Added). ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc) for new or updated code as appropriate - [x] I've communicated my changes to consumers by updating changelogs for packages I've changed - [x] No breaking changes introduced Made with [Cursor](https://cursor.com) ## Demo <div> <a href="https://www.loom.com/share/9f913895e9bb4ccd95a3a79138566f4c"> <p>Introducing Launchdarkly flags for Ramps Backend - Watch Video</p> </a> <a href="https://www.loom.com/share/9f913895e9bb4ccd95a3a79138566f4c"> <img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/9f913895e9bb4ccd95a3a79138566f4c-36a975aecd13f632-full-play.gif#t=0.1"> </a> </div> [TRAM-3828]: https://consensyssoftware.atlassian.net/browse/TRAM-3828?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…k#9968) ## Explanation Pro mode's increase/decrease UX (TAT-3705) needs a current → projected margin and liquidation. That projection is protocol math, not UI formatting: HyperLiquid applies selected leverage to the whole isolated position before the fill, and maintenance margin depends on the tier at the *liquidation* notional. Clients that recompute this locally get both cases wrong, and they can also show a false projection for cross-margin positions or drop a valid margin preview when `liquidationPrice` is null. This adds a read-only `previewPositionModify` API on `PerpsController` / `PerpsProvider`. The client supplies the live position and proposed order; the HyperLiquid provider loads the asset's margin table from cached `meta` and returns a discriminated result: - `open` — increase, decrease, or flip with remaining size > 0 - `full_close` — no remaining size (invalid leftover states are unrepresentable) - `unsupported` — `cross_margin` or a provider that cannot project (MYX) - `none` — no meaningful modify Margin and liquidation availability are independent, so a missing live liquidation or missing multi-tier table withholds only liquidation. Isolated liquidation uses the maintenance tier at `size * liqPrice`, including that tier's maintenance deduction. Clients should use `resulting.direction` (not the order direction) when validating TP/SL against the projected liquidation. This is a **breaking** `PerpsProvider` interface addition. Mobile/Extension should consume it after the next `@metamask/perps-controller` release; they do not implement `PerpsProvider` themselves. Client wiring is intentionally not in this PR. ## References * TAT-3705 — UX for increasing / decreasing position in Pro mode (before → after). This PR supplies the calculation source of truth the ticket does not specify. * Related Mobile work: keep the before → after presentation, wrapping, and localization; replace the local `positionModifyPreview` arithmetic with this API after the controller is released. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR. Client consumption follows the `@metamask/perps-controller` release; no client source changes are included here. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > New read-only trading projection logic (isolated margin, leverage reallocation, tiered liquidation) could mislead TP/SL UX if wrong, and the breaking `PerpsProvider` addition requires all implementers to ship the new method. > > **Overview** > Adds a **breaking** read-only **`previewPositionModify`** API on `PerpsController` and `PerpsProvider` so clients can show before/after margin and liquidation when modifying an isolated position, without placing an order. > > Callers pass the live position plus a proposed order (size, direction, expected price, selected leverage, optional fees). Results are a discriminated union: **`open`** (increase / decrease / flip with projected size, entry, mark-based leverage, margin, and optional liquidation), **`full_close`**, **`unsupported`** (cross-margin or MYX), or **`none`** when the order would not meaningfully change the position. Margin and liquidation are reported independently so a missing margin table or live liq price does not hide a valid margin projection. > > **HyperLiquid** loads margin tiers from cached `meta` and implements the math in new `hyperLiquidPositionPreview` utilities (tiered maintenance, mark-based isolated liquidation). **Aggregated** routing follows `providerId` / `position.providerId`. Types, messenger actions, package exports, changelog, mocks, and broad unit tests cover delegation and edge cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4a2b2be. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
… on kyc session creation (MetaMask#9993) ## Explanation - remove unused wrapUserKey helper - pass sessionClientPublicKey into ukyc session creation - add residenceCountry to create ukyc session flow ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the UKYC session contract and crypto wiring for document verification; consumers calling `createUkycSession` directly must adopt the new required fields. > > **Overview** > **BREAKING:** `KycService.createUkycSession` (`POST /sessions`) now requires `sessionClientPublicKey` (unpadded base64url X25519) and `residenceCountry` (ISO 3166-1 alpha-3). Direct callers must supply both; the controller generates the per-session keypair, registers the public key at session creation, and uses the private key with the existing `wrapEncryptionKey` path for `setAuthorizations`. > > `KycController` resolves `residenceCountry` from `geoCountry` or `KycService:getGeoCountry`, persists it on state when fetched, and aborts UKYC session creation if `reset()` bumps the flow generation while geo is in flight. > > Removes the unused `wrapUserKey` helper and its tests; docs and `wrapEncryptionKey` comments now describe explicit session client public-key registration instead of implying registration was absent. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 578d94e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation Releases `@metamask/ramps-controller` 20.1.0 to 20.2.0 so Mobile and Extension can consume the client-identity query params from [MetaMask#9983](MetaMask#9983). The monorepo version goes 1215.0.0 to 1216.0.0. No other package is being published. `@metamask/transaction-pay-controller` only tightens its workspace range to `^20.2.0` (same pattern as [Release/1215.0.0](MetaMask#10004)). Unreleased deps of ramps-controller (`base-controller`, `controller-utils`, `messenger`, `profile-sync-controller`) are intentionally skipped — this release does not rely on those unreleased changes. The bump is **minor**. The new section is additive and nothing in it is marked `**BREAKING:**`: - **Added** optional `clientProduct` and `clientVersion` constructor options on `RampsService` and `TransakService`, sent as `clientProduct` / `clientVersion` query params on every on-ramp API fetch so the API can evaluate version-gated feature flags per client. Identity travels in the URL (not headers) because the on-ramp CDN cache key is the URL. Also exports `RampsClientIdentity`, `addRampsClientIdentityParams`, and the param name constants ([MetaMask#9983](MetaMask#9983)). Jira: [TRAM-3828](https://consensyssoftware.atlassian.net/browse/TRAM-3828) ## Changelog Updated `packages/ramps-controller/CHANGELOG.md` (Unreleased → 20.2.0) and recorded the workspace bump in `packages/transaction-pay-controller/CHANGELOG.md`. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc) for new or updated code as appropriate - [x] I've communicated my changes to consumers by updating changelogs for packages I've changed - [x] No breaking changes introduced Made with [Cursor](https://cursor.com) [TRAM-3828]: https://consensyssoftware.atlassian.net/browse/TRAM-3828?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…k#10008) ## Explanation - Adds `idosRelayBaseUrl` constructor param and uses it fetch well knowns to validate the ukycCapabilityToken public key - Renames `fractalEncryptionBaseUrl` to `idosEnclaveBaseUrl`. Continues using it to validate dataEncryptionKey public key ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes JWT attestation for UKYC encryption keys and capability tokens with breaking constructor and messenger renames; incorrect relay/enclave URLs or missing config could block SumSub or weaken trust assumptions. > > **Overview** > **Breaking:** UKYC authorization wrapping no longer validates both encryption schemas against a single Fractal JWKS. `fractalEncryptionBaseUrl` / `fetchJwks` are renamed to **`idosEnclaveBaseUrl`** / **`fetchIdosEnclaveJwks`**, and hosts must also pass **`idosRelayBaseUrl`** and wire **`KycService:fetchIdosRelayJwks`**. > > Before wrapping `data_encryption_key` and `ukyc_capability_token`, **`KycController`** fetches both well-known JWKS in parallel and verifies **`encryptionDataKey`** against the enclave keys and **`ukycCapabilityToken`** against the relay keys, then checks each attested session server public key as before. **`KycService`** implements the two fetches via a shared **`#fetchWellKnownJwks`** helper (cached, unauthenticated). > > Docs, changelog, messenger action types, and tests (including per-schema JWKS verification and reset-during-fetch cases) are updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 630ba21. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
## Explanation Releases `@metamask/perps-controller` **13.1.0 → 14.0.0**. The monorepo version goes **1216.0.0 → 1217.0.0**. No other package is being published. Perps-controller has no in-monorepo dependents that need a workspace range bump. The bump is **major**. Adding a required method on the public `PerpsProvider` type is a breaking type change: - **Added (breaking):** `PerpsController.previewPositionModify` and required `PerpsProvider.previewPositionModify` so clients can read an isolated-margin post-trade projection without placing an order ([MetaMask#9968](MetaMask#9968)). Consumers that **implement** `PerpsProvider` must add the method. In-repo providers (`HyperLiquidProvider`, `MYXProvider`, `AggregatedPerpsProvider`) already implement it. Mobile and Extension consume the controller; they do not implement `PerpsProvider`, so they can upgrade without a source change. Client wiring to *call* the API is follow-up (pro-mode increase/decrease UX). - **Fixed:** Transient HyperLiquid WebSocket disconnects no longer fail the first TP/SL update; builder-fee approval is checked over HTTP ([MetaMask#9997](MetaMask#9997)). - **Fixed:** `updatePositionTPSL` no longer reports `TPSL_UPDATE_FAILED` when HyperLiquid accepts a trigger with `waitingForTrigger`; accepted triggers without response order IDs are reconciled before mixed-failure cleanup ([MetaMask#9995](MetaMask#9995)). ## Changelog Moved Unreleased entries in `packages/perps-controller/CHANGELOG.md` under `[14.0.0]`. Breaking entry is prefixed with `**BREAKING:**` and includes adaptation notes. ## References - Source PRs: [MetaMask#9968](MetaMask#9968), [MetaMask#9997](MetaMask#9997), [MetaMask#9995](MetaMask#9995) - No draft Mobile/Extension PRs: neither client implements `PerpsProvider`. Preview-build upgrade PRs are not required to keep those clients compiling. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them — **N/A for clients:** they do not implement `PerpsProvider`. In-repo implementors were updated in [MetaMask#9968](MetaMask#9968). ## Release review - [x] No packages with version bumps that should not be published (`@metamask/perps-controller` only) - [x] New version string matches impact (major 14.0.0 for required `PerpsProvider` method) - [x] Changelog entries are consumer-facing, categorized, and link to the introducing PRs - [x] Breaking change is prefixed with `**BREAKING:**` and describes how to adapt - [x] No Unreleased leftover entries for this package - [ ] Recheck `main` for commits merged while this PR is open and update changelogs if they touch perps-controller <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > No runtime or type changes in the diff; risk is limited to consumers upgrading to 14.0.0 and implementing the new required `PerpsProvider` method if they are custom providers. > > **Overview** > **Release 1217.0.0** cuts a new publish of **`@metamask/perps-controller` only** (13.1.0 → **14.0.0**); the root monorepo version moves 1216.0.0 → 1217.0.0. > > The diff is versioning and changelog housekeeping: Unreleased notes are filed under **`[14.0.0]`**, compare links are updated, and a minor punctuation fix is applied on a 13.1.0 entry. The **14.0.0** notes describe what ships with this tag—chiefly the **breaking** required `PerpsProvider.previewPositionModify` / `PerpsController.previewPositionModify` API ([MetaMask#9968](MetaMask#9968)), plus HyperLiquid TP/SL fixes ([MetaMask#9997](MetaMask#9997), [MetaMask#9995](https://github.com/MetaMask/core/pull/9995))—already merged on `main`, not introduced in this PR’s file changes. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 95c2e07. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
|
Caution MetaMask internal reviewing guidelines:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 584bcba. Configure here.
| child-workspace-package-names: ${{ steps.workspace-package-names.outputs.child-workspace-package-names }} | ||
| merge-base: ${{ steps.fetch-merge-base.outputs.merge-base }} | ||
| package-names: ${{ steps.packages.outputs.package-names }} | ||
| changed-paths: ${{ steps.packages.outputs.changed-paths }} |
There was a problem hiding this comment.
Matrix outputs can drop package data
High Severity
The prepare job fans out across four Node versions, but only the 24.x shard sets package-names, changed-paths, and merge-base. GitHub keeps outputs from the last matrix shard to finish, so a later 18.x, 20.x, or 22.x finish can replace those values with empty strings. Downstream jobs then hit fromJson on empty output and fail, or skip the intended package set.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 584bcba. Configure here.
| TARGET_REF=$(git merge-base "origin/$BASE_REF" "origin/$PR_BRANCH") | ||
| git fetch origin "$TARGET_REF" | ||
|
|
||
| UPDATED_CHANGELOGS=$(git diff --name-only "$TARGET_REF" "origin/$PR_BRANCH" | grep -E 'CHANGELOG\.md$' || true) |
There was a problem hiding this comment.
Changelog check fails for fork PRs
Medium Severity
The changelog diff step runs git merge-base and git show against origin/$PR_BRANCH. That remote ref exists only for same-repo branches. Fork pull requests keep the head branch on the fork, so both commands fail under set -e and validate-changelog-diffs goes red for community PRs.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 584bcba. Configure here.
| MERGE_BASE=$(git merge-base HEAD refs/remotes/origin/main) | ||
| for package in "${PACKAGES[@]}"; do | ||
| MAIN_VERSION=$(git show "$MERGE_BASE:$package" | jq -r .version) | ||
| HEAD_VERSION=$(jq -r .version "$package") |
There was a problem hiding this comment.
Release check crashes on new packages
Medium Severity
The released-package scan runs git show on every current package.json at the merge base and treats failure as fatal. A package added in the PR does not exist on that base, so the command fails under set -euo pipefail and the release conflict check never finishes.
Reviewed by Cursor Bugbot for commit 584bcba. Configure here.


Explanation
References
Checklist
Note
Medium Risk
Changes affect merge gates, release/npm publish, and merge-queue behavior across the monorepo; the removed rpc-methods patch may surface type or dependency issues if the upstream package is not already aligned.
Overview
This PR overhauls repo plumbing rather than controller/runtime RPC behavior. The headline RPC tie-in is dropping the Yarn patch for
@metamask/rpc-methods(the oldgetLocaletyping workaround); everything else is CI, ownership, and developer tooling.CODEOWNERS moves from a single
@MetaMask/devsrule to a generated, team-scoped file (with release-file rules), driven bycodeowners.ts/yarn codeowners:generate. GitHub Actions gain merge-queue support, concurrency, security scanning, incremental lint/build/test (changed workspaces only), split Node matrices (18–24), Platform API docs build/deploy, wallet-cli e2e, and stricter “clean working tree” checks. New automation covers release validation (root version bump, release conflicts, PR comments), changelog checks (shared action + merge-queue[Unreleased]guard +@metamaskbot update-changelogs), stale release PR cleanup, extension/mobile update issues after publish, and delegated preview publish. Publish release is restructured (artifact upload, queued concurrency, npm publish v6, separate GitHub release job).Developer experience: root
.eslintrc.jsis removed (lint still wired vialint:eslintelsewhere), oxfmt is added alongside Prettier,.nvmrcswitches tolts/*, Dependabot gets cooldowns and GitHub Actions updates, PR template drops inline changelog blocks in favor of process links, and agent skills / Bugbot pointers are added. Misc:.gitignoreexpansions,actionlint/zizmorconfig, and linguist rules fortsconfig.**files.Reviewed by Cursor Bugbot for commit 584bcba. Bugbot is set up for automated code reviews on this repo. Configure here.