Skip to content

fix: use decoded token recipient for address poisoning known recipients - #9699

Merged
adonesky1 merged 2 commits into
mainfrom
fix/address-poisoning-effective-recipient
Jul 30, 2026
Merged

fix: use decoded token recipient for address poisoning known recipients#9699
adonesky1 merged 2 commits into
mainfrom
fix/address-poisoning-effective-recipient

Conversation

@adonesky1

@adonesky1 adonesky1 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Explanation

The PhishingController address poisoning check (#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:

    "@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 #8171 (original address poisoning detection)
  • Related to PSAFE-555

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
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

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.

Reviewed by Cursor Bugbot for commit 5da60b6. Bugbot is set up for automated code reviews on this repo. Configure here.

For ERC-20/ERC-721/ERC-1155 token transfers, txParams.to is the token
contract rather than the address receiving the tokens. PhishingController
was adding the token contract to the known-recipients set used by
checkAddressPoisoning, missing the actual recipient encoded in the
calldata.

Extract the existing getEffectiveRecipient helper from the
first-time-interaction util into a shared exported util in
@metamask/transaction-controller, and use it in PhishingController so
token transfers contribute the decoded recipient instead of the token
contract address.
@adonesky1
adonesky1 requested review from a team as code owners July 29, 2026 19:19
@adonesky1
adonesky1 requested a review from a team as a code owner July 29, 2026 19:20
@adonesky1

Copy link
Copy Markdown
Contributor Author

@metamaskbot preview-builds

@adonesky1

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-previews

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.5-preview-5da60b6
@metamask-previews/accounts-controller@39.0.5-preview-5da60b6
@metamask-previews/address-book-controller@7.1.2-preview-5da60b6
@metamask-previews/ai-controllers@0.8.0-preview-5da60b6
@metamask-previews/analytics-controller@1.2.1-preview-5da60b6
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-5da60b6
@metamask-previews/announcement-controller@8.1.0-preview-5da60b6
@metamask-previews/app-metadata-controller@2.0.1-preview-5da60b6
@metamask-previews/approval-controller@9.0.2-preview-5da60b6
@metamask-previews/assets-controller@11.3.0-preview-5da60b6
@metamask-previews/assets-controllers@110.0.1-preview-5da60b6
@metamask-previews/authenticated-user-storage@3.0.1-preview-5da60b6
@metamask-previews/base-controller@9.1.0-preview-5da60b6
@metamask-previews/base-data-service@0.1.3-preview-5da60b6
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-5da60b6
@metamask-previews/bridge-controller@78.0.1-preview-5da60b6
@metamask-previews/bridge-status-controller@74.6.0-preview-5da60b6
@metamask-previews/build-utils@3.0.4-preview-5da60b6
@metamask-previews/chain-agnostic-permission@1.7.0-preview-5da60b6
@metamask-previews/chomp-api-service@4.0.0-preview-5da60b6
@metamask-previews/claims-controller@0.5.3-preview-5da60b6
@metamask-previews/client-controller@1.0.1-preview-5da60b6
@metamask-previews/client-utils@1.4.0-preview-5da60b6
@metamask-previews/compliance-controller@2.1.0-preview-5da60b6
@metamask-previews/composable-controller@12.0.1-preview-5da60b6
@metamask-previews/config-registry-controller@0.4.1-preview-5da60b6
@metamask-previews/connectivity-controller@0.3.0-preview-5da60b6
@metamask-previews/controller-utils@12.3.0-preview-5da60b6
@metamask-previews/core-backend@8.0.0-preview-5da60b6
@metamask-previews/delegation-controller@3.0.2-preview-5da60b6
@metamask-previews/earn-controller@12.2.2-preview-5da60b6
@metamask-previews/eip-5792-middleware@3.0.5-preview-5da60b6
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-5da60b6
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-5da60b6
@metamask-previews/ens-controller@19.1.5-preview-5da60b6
@metamask-previews/eth-block-tracker@15.0.1-preview-5da60b6
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-5da60b6
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-5da60b6
@metamask-previews/foundryup@1.0.1-preview-5da60b6
@metamask-previews/gas-fee-controller@26.3.0-preview-5da60b6
@metamask-previews/gator-permissions-controller@4.2.3-preview-5da60b6
@metamask-previews/geolocation-controller@0.1.3-preview-5da60b6
@metamask-previews/java-tron-up@1.0.0-preview-5da60b6
@metamask-previews/json-rpc-engine@10.5.0-preview-5da60b6
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-5da60b6
@metamask-previews/keyring-controller@27.1.0-preview-5da60b6
@metamask-previews/local-node-utils@1.0.0-preview-5da60b6
@metamask-previews/logging-controller@8.0.2-preview-5da60b6
@metamask-previews/message-manager@14.1.2-preview-5da60b6
@metamask-previews/messenger@2.0.0-preview-5da60b6
@metamask-previews/messenger-cli@0.2.0-preview-5da60b6
@metamask-previews/money-account-api-data-service@0.4.0-preview-5da60b6
@metamask-previews/money-account-balance-service@2.3.0-preview-5da60b6
@metamask-previews/money-account-controller@0.3.3-preview-5da60b6
@metamask-previews/money-account-upgrade-controller@3.0.0-preview-5da60b6
@metamask-previews/multichain-account-service@13.0.0-preview-5da60b6
@metamask-previews/multichain-api-middleware@4.0.1-preview-5da60b6
@metamask-previews/multichain-network-controller@3.2.1-preview-5da60b6
@metamask-previews/multichain-transactions-controller@7.1.1-preview-5da60b6
@metamask-previews/name-controller@9.1.2-preview-5da60b6
@metamask-previews/network-connection-banner-controller@0.1.0-preview-5da60b6
@metamask-previews/network-controller@34.0.0-preview-5da60b6
@metamask-previews/network-enablement-controller@5.6.0-preview-5da60b6
@metamask-previews/notification-services-controller@26.0.0-preview-5da60b6
@metamask-previews/passkey-controller@3.0.0-preview-5da60b6
@metamask-previews/permission-controller@13.1.1-preview-5da60b6
@metamask-previews/permission-log-controller@5.1.0-preview-5da60b6
@metamask-previews/perps-controller@10.0.0-preview-5da60b6
@metamask-previews/phishing-controller@17.3.0-preview-5da60b6
@metamask-previews/platform-api-docs@0.0.0-preview-5da60b6
@metamask-previews/polling-controller@16.0.8-preview-5da60b6
@metamask-previews/preferences-controller@23.1.0-preview-5da60b6
@metamask-previews/profile-metrics-controller@4.0.2-preview-5da60b6
@metamask-previews/profile-sync-controller@28.3.0-preview-5da60b6
@metamask-previews/ramps-controller@18.0.0-preview-5da60b6
@metamask-previews/rate-limit-controller@7.0.1-preview-5da60b6
@metamask-previews/react-data-query@0.2.2-preview-5da60b6
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-5da60b6
@metamask-previews/sample-controllers@5.0.3-preview-5da60b6
@metamask-previews/seedless-onboarding-controller@10.1.0-preview-5da60b6
@metamask-previews/selected-network-controller@26.1.5-preview-5da60b6
@metamask-previews/sentinel-api-service@1.0.0-preview-5da60b6
@metamask-previews/shield-controller@5.1.3-preview-5da60b6
@metamask-previews/signature-controller@39.2.7-preview-5da60b6
@metamask-previews/smart-transactions-controller@25.0.1-preview-5da60b6
@metamask-previews/snap-account-service@2.0.0-preview-5da60b6
@metamask-previews/social-controllers@2.7.0-preview-5da60b6
@metamask-previews/solana-test-validator-up@1.0.0-preview-5da60b6
@metamask-previews/stellar-quickstart-up@0.0.0-preview-5da60b6
@metamask-previews/storage-service@1.0.2-preview-5da60b6
@metamask-previews/subscription-controller@6.2.1-preview-5da60b6
@metamask-previews/transaction-controller@69.3.0-preview-5da60b6
@metamask-previews/transaction-pay-controller@26.0.1-preview-5da60b6
@metamask-previews/user-operation-controller@41.2.7-preview-5da60b6
@metamask-previews/wallet@8.1.0-preview-5da60b6
@metamask-previews/wallet-cli@0.0.0-preview-5da60b6

@matthewwalsh0
matthewwalsh0 requested a review from jpuri July 30, 2026 11:03
@adonesky1
adonesky1 added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit dcf2720 Jul 30, 2026
132 checks passed
@adonesky1
adonesky1 deleted the fix/address-poisoning-effective-recipient branch July 30, 2026 14:56
@superdog1743617-bit

Copy link
Copy Markdown

Inucustoms@gmail.com is the correct email we're is my money goinig

@OGPoyraz OGPoyraz mentioned this pull request Jul 31, 2026
4 tasks
@adonesky1 adonesky1 mentioned this pull request Jul 31, 2026
4 tasks
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jul 31, 2026
## Explanation

Phishing controller

## [17.3.1]

### Changed

- Bump `@metamask/transaction-controller` from `^69.0.0` to `^69.4.0`
([MetaMask#9568](MetaMask#9568),
[MetaMask#9589](MetaMask#9589),
[MetaMask#9593](MetaMask#9593),
[MetaMask#9693](MetaMask#9693),
[MetaMask#9735](MetaMask#9735))

### Fixed

- Address poisoning known recipients now use the actual token recipient
decoded from calldata for confirmed ERC-20/ERC-721/ERC-1155 token
transfers, instead of the token contract address from `txParams.to`
([MetaMask#9699](MetaMask#9699))

### Notes for reviewers

**Why a patch.** Nothing here extends the package's surface area, so per
[reviewing-release-prs.md](https://github.com/MetaMask/core/blob/main/docs/processes/reviewing-release-prs.md#4-review-new-version-strings)
this lands on patch. The only code change since `17.3.0` is one line in
`PhishingController` (`transaction.txParams.to` →
`getEffectiveRecipient(transaction)`); the rest of the diff to this
package is `.js` import-extension churn from MetaMask#9620 and the Jest 30
migration. No new exports, types, or arguments.

The `@metamask/transaction-controller` range bump doesn't push this to a
minor either — the same doc lists "adding or upgrading a runtime
dependency" under "Everything else" rather than as an addition.

Note that `checkAddressPoisoning` output does shift in both directions:
lookalikes of the real ERC-20/721/1155 recipient now match, and
lookalikes of the token contract address no longer do. That's captured
in the `Fixed` entry above.

**`@metamask/transaction-controller` needs no release here.** The
`getEffectiveRecipient` export this fix depends on already shipped in
`69.4.0`, published as part of release 1163.0.0.

**Intentionally skipped.** `@metamask/address-book-controller`,
`@metamask/base-controller`, `@metamask/controller-utils`, and
`@metamask/messenger` all have unreleased changes and are dependencies
of `@metamask/phishing-controller`, so the release tool flagged them.
Their pending entries are pre-existing chore/refactor/deprecation work
unrelated to this fix (and still uncategorized), so releasing them is a
separate call for their codeowners.

**Dependent bumps.** `@metamask/assets-controller` and
`@metamask/assets-controllers` had their `@metamask/phishing-controller`
range bumped to `^17.3.1` by the release tooling; both are recorded in
their changelogs under `Unreleased`.

## References

- Releases the fix from MetaMask#9699
- Unblocks pulling the fix into the mobile and extension clients

## 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]
> **Medium Risk**
> The released phishing-controller fix alters security-sensitive
address-poisoning heuristics for token transfers; this PR is
dependency/version churn but ships that behavior to extension/mobile
consumers.
> 
> **Overview**
> **Monorepo release 1167.0.0** publishes
**`@metamask/phishing-controller@17.3.1`** and wires consumers to that
version.
> 
> The new phishing-controller patch (documented in its changelog, not in
this diff’s source) fixes **address-poisoning “known recipient”
tracking** for confirmed ERC-20/721/1155 transfers: recipients are taken
from **decoded calldata via `getEffectiveRecipient`** instead of
**`txParams.to`** (the token contract). That can change which lookalike
warnings fire—real transfer recipients match; token contract addresses
no longer do.
> 
> **`@metamask/assets-controller`** and
**`@metamask/assets-controllers`** bump their
**`@metamask/phishing-controller`** dependency from **`^17.3.0`** to
**`^17.3.1`**, with matching **Unreleased** changelog entries.
**`yarn.lock`** and root **`package.json`** version are updated
accordingly.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1f778f9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
adonesky1 added a commit to MetaMask/metamask-extension that referenced this pull request Jul 31, 2026
Picks up the address-poisoning fix from MetaMask/core#9699, so known
recipients use the token recipient decoded from calldata rather than the
token contract address for confirmed ERC-20/721/1155 transfers.

Deduped so the 69.x `@metamask/transaction-controller` range resolves to a
single 69.4.0 entry rather than adding a parallel copy alongside 69.3.0.
adonesky1 added a commit to MetaMask/metamask-mobile that referenced this pull request Jul 31, 2026
Picks up the address-poisoning fix from MetaMask/core#9699, so known
recipients use the token recipient decoded from calldata rather than the
token contract address for confirmed ERC-20/721/1155 transfers.

`@metamask/transaction-controller` moves 69.3.0 -> 69.4.0 because
phishing-controller 17.3.1 requires ^69.4.0 for the `getEffectiveRecipient`
export. The exact `resolutions` pin has to move too, otherwise it collapses
phishing-controller back onto 69.3.0, where that export does not exist.
pull Bot pushed a commit to firas9941/metamask-extension that referenced this pull request Aug 3, 2026
## **Description**

Bumps `@metamask/phishing-controller` from `^17.3.0` to `^17.3.1` to
pick up the address-poisoning fix from
[MetaMask/core#9699](MetaMask/core#9699).

Before that fix, the known-recipients list used to detect address
poisoning took `txParams.to` from confirmed transactions. For
ERC-20/721/1155 transfers `txParams.to` is the *token contract*, not the
address receiving the tokens, so two things were wrong:

- a lookalike of the real token recipient did **not** trigger
address-poisoning detection
- a lookalike of the token contract address **did** trigger it, which is
meaningless noise

`17.3.1` decodes the actual recipient from calldata via the new
`getEffectiveRecipient` utility, so token transfers now contribute the
real recipient. Plain sends and other contract interactions are
unchanged.

### Why `@metamask/transaction-controller` moves in the lockfile

`phishing-controller@17.3.1` declares `@metamask/transaction-controller:
^69.4.0` (that's where `getEffectiveRecipient` is exported from), so the
69.x range resolves to `69.4.0` instead of `69.3.0`. I ran a scoped
`yarn dedupe` so this stays a **single** 69.x entry rather than
installing `69.4.0` alongside `69.3.0`. The same dedupe collapses the
`core-backend`, `gas-fee-controller`, and `polling-controller` ranges
that `69.4.0` nudges forward.

Two notes for reviewers on the dependency graph:

- **`network-controller` is unaffected.**
`transaction-controller@69.4.0` declares `network-controller: ^35.0.0`,
but this repo's `resolutions` pin holds it at `34.0.0` and the lockfile
still has exactly one `34.0.0` entry. That pin is safe here: the
published `69.3.0` and `69.4.0` bundles are byte-identical apart from
the `getEffectiveRecipient` export, an optional `strategy?: string`
field on `MetamaskPayMetadata`, and the moved helper file. No compiled
module in `69.4.0` references `network-controller`, `NetworkClient`,
`BuiltInNetworkClientId`, or `analyticsOptions`, so its `^35.0.0` range
is just the mechanical release-wide bump from MetaMask/core#9735 rather
than an adoption of v35 APIs.
- **`remote-feature-flag-controller@5.0.0` is added next to the existing
`4.2.2`.** These can't be deduped, since `^4.2.x` cannot accept `5.0.0`.
`transaction-controller@69.4.0` only references it from type
declarations (`dist/TransactionController.d.cts`) and never from runtime
JS, so nothing new gets pulled into the bundle. Happy to add a
`resolutions` pin instead if the team would rather avoid the duplicate
entry.

## **Changelog**

CHANGELOG entry: Fixed address-poisoning detection so lookalikes of the
actual recipient of a token transfer are flagged, and lookalikes of the
token contract address are no longer flagged.

## **Related issues**

Fixes:

- Consumes
[MetaMask/core#9699](MetaMask/core#9699)
- Released in
[MetaMask/core#9746](MetaMask/core#9746)
(`@metamask/phishing-controller@17.3.1`)

## **Manual testing steps**

1. Pull this branch and run `yarn install`, then start a development
build.
2. Add a test ERC-20 token, then send some of it to a recipient address
you control. Let the transaction confirm.
3. Start a new send of that same token, and enter a recipient that is a
lookalike of the address from step 2 — same first four and last four hex
characters, different in the middle.
4. Confirm the address-poisoning warning now appears. On `main` it does
not.
5. Repeat step 3, but use a lookalike of the *token contract* address
instead. Confirm no address-poisoning warning appears, since the
contract is no longer treated as a known recipient.
6. Send and confirm a plain ETH transfer and a generic contract
interaction, and confirm a lookalike of each recipient still triggers
the warning — those paths should be unchanged.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

A lookalike of the real ERC-20 recipient produced no warning, while a
lookalike of the token contract produced one.

### **After**

A lookalike of the real ERC-20 recipient produces a warning; a lookalike
of the token contract does not.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
pull Bot pushed a commit to Reality2byte/metamask-mobile that referenced this pull request Aug 3, 2026
## **Description**

Bumps `@metamask/phishing-controller` from `^17.3.0` to `^17.3.1` to
pick up the address-poisoning fix from
[MetaMask/core#9699](MetaMask/core#9699).

Before that fix, the known-recipients list used to detect address
poisoning took `txParams.to` from confirmed transactions. For
ERC-20/721/1155 transfers `txParams.to` is the *token contract*, not the
address receiving the tokens, so two things were wrong:

- a lookalike of the real token recipient did **not** trigger
address-poisoning detection
- a lookalike of the token contract address **did** trigger it, which is
meaningless noise

`17.3.1` decodes the actual recipient from calldata via the new
`getEffectiveRecipient` utility, so token transfers now contribute the
real recipient. Plain sends and other contract interactions are
unchanged.

### Why `@metamask/transaction-controller` moves to 69.4.0

`phishing-controller@17.3.1` declares `@metamask/transaction-controller:
^69.4.0`, because `getEffectiveRecipient` is exported from `69.4.0`.

This repo pins `@metamask/transaction-controller` to an exact version in
`resolutions`, which collapses every range in the tree onto that one
version. **That pin has to move as well** — leaving it at `69.3.0` would
silently resolve `phishing-controller@17.3.1` against `69.3.0`, where
`getEffectiveRecipient` exists only as an internal helper inside
`first-time-interaction` and is *not* exported from the package entry
point. The import would be `undefined` and would throw on every
confirmed transaction. So this PR moves the `resolutions` pin and the
declared range together, and the lockfile still has exactly one
`transaction-controller` entry.

Two notes for reviewers on the dependency graph:

- **`network-controller` is unaffected.**
`transaction-controller@69.4.0` declares `network-controller: ^35.0.0`,
but this repo's `resolutions` pin holds it at `34.0.0` and the lockfile
still has exactly one `34.0.0` entry. That pin is safe here: the
published `69.3.0` and `69.4.0` bundles are byte-identical apart from
the `getEffectiveRecipient` export, an optional `strategy?: string`
field on `MetamaskPayMetadata`, and the moved helper file. No compiled
module in `69.4.0` references `network-controller`, `NetworkClient`,
`BuiltInNetworkClientId`, or `analyticsOptions`, so its `^35.0.0` range
is just the mechanical release-wide bump from MetaMask/core#9735 rather
than an adoption of v35 APIs. `accounts-controller` is likewise held at
`39.0.3` by its existing pin.
- **`remote-feature-flag-controller@5.0.0` is added next to the existing
`4.2.2`.** These can't be deduped, since `^4.2.x` cannot accept `5.0.0`.
`transaction-controller@69.4.0` only references it from type
declarations (`dist/TransactionController.d.cts`) and never from runtime
JS, so nothing new gets pulled into the bundle. Happy to add a
`resolutions` pin instead if the team would rather avoid the duplicate
entry.

A scoped `yarn dedupe` collapses the `core-backend`,
`gas-fee-controller`, and `polling-controller` ranges that `69.4.0`
nudges forward, so those don't gain parallel copies either.

## **Changelog**

CHANGELOG entry: Fixed address-poisoning detection so lookalikes of the
actual recipient of a token transfer are flagged, and lookalikes of the
token contract address are no longer flagged.

## **Related issues**

Fixes:

- Consumes
[MetaMask/core#9699](MetaMask/core#9699)
- Released in
[MetaMask/core#9746](MetaMask/core#9746)
(`@metamask/phishing-controller@17.3.1`)

## **Manual testing steps**

Feature: address-poisoning detection for token transfer recipients

    Scenario: user is warned about a lookalike of a real token recipient
Given the user has previously sent an ERC-20 token to an address and
that transaction is confirmed

When user starts a new transfer of that token and enters a recipient
that matches the previous recipient's first four and last four hex
characters but differs in the middle
    Then the address-poisoning warning is shown

    Scenario: user is not warned about a lookalike of the token contract
Given the user has previously sent an ERC-20 token and that transaction
is confirmed

When user enters a recipient that is a lookalike of the token contract
address
Then no address-poisoning warning is shown, because the contract is no
longer treated as a known recipient

    Scenario: plain sends and contract interactions are unchanged
Given the user has a confirmed plain ETH transfer and a confirmed
generic contract interaction

    When user enters a lookalike of either of those recipients
    Then the address-poisoning warning is still shown

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

A lookalike of the real ERC-20 recipient produced no warning, while a
lookalike of the token contract produced one.

### **After**

A lookalike of the real ERC-20 recipient produces a warning; a lookalike
of the token contract does not.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches security-sensitive address-poisoning logic and requires the
transaction-controller pin to match; runtime impact is limited to
dependency behavior, but incorrect resolution would break confirmed-tx
handling.
> 
> **Overview**
> Bumps **`@metamask/phishing-controller`** from `^17.3.0` to
**`^17.3.1`** so address-poisoning detection uses the real token
transfer recipient (via **`getEffectiveRecipient`**) instead of
**`txParams.to`**, which for ERC-20/721/1155 is the token contract.
Lookalikes of the actual recipient should warn; lookalikes of the
contract should not.
> 
> Because **`17.3.1`** depends on an exported
**`getEffectiveRecipient`** from **`@metamask/transaction-controller`**,
this PR also moves the **`resolutions`** pin and dependency from
**`69.3.0`** to **`69.4.0`** and refreshes **`yarn.lock`** (including
transitive bumps such as **`remote-feature-flag-controller@5.0.0`**
alongside the existing **`4.2.2`**). No application source files
change—behavior comes from the upgraded packages.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6155857. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Bigshmow pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 5, 2026
## **Description**

Bumps `@metamask/phishing-controller` from `^17.3.0` to `^17.3.1` to
pick up the address-poisoning fix from
[MetaMask/core#9699](MetaMask/core#9699).

Before that fix, the known-recipients list used to detect address
poisoning took `txParams.to` from confirmed transactions. For
ERC-20/721/1155 transfers `txParams.to` is the *token contract*, not the
address receiving the tokens, so two things were wrong:

- a lookalike of the real token recipient did **not** trigger
address-poisoning detection
- a lookalike of the token contract address **did** trigger it, which is
meaningless noise

`17.3.1` decodes the actual recipient from calldata via the new
`getEffectiveRecipient` utility, so token transfers now contribute the
real recipient. Plain sends and other contract interactions are
unchanged.

### Why `@metamask/transaction-controller` moves in the lockfile

`phishing-controller@17.3.1` declares `@metamask/transaction-controller:
^69.4.0` (that's where `getEffectiveRecipient` is exported from), so the
69.x range resolves to `69.4.0` instead of `69.3.0`. I ran a scoped
`yarn dedupe` so this stays a **single** 69.x entry rather than
installing `69.4.0` alongside `69.3.0`. The same dedupe collapses the
`core-backend`, `gas-fee-controller`, and `polling-controller` ranges
that `69.4.0` nudges forward.

Two notes for reviewers on the dependency graph:

- **`network-controller` is unaffected.**
`transaction-controller@69.4.0` declares `network-controller: ^35.0.0`,
but this repo's `resolutions` pin holds it at `34.0.0` and the lockfile
still has exactly one `34.0.0` entry. That pin is safe here: the
published `69.3.0` and `69.4.0` bundles are byte-identical apart from
the `getEffectiveRecipient` export, an optional `strategy?: string`
field on `MetamaskPayMetadata`, and the moved helper file. No compiled
module in `69.4.0` references `network-controller`, `NetworkClient`,
`BuiltInNetworkClientId`, or `analyticsOptions`, so its `^35.0.0` range
is just the mechanical release-wide bump from MetaMask/core#9735 rather
than an adoption of v35 APIs.
- **`remote-feature-flag-controller@5.0.0` is added next to the existing
`4.2.2`.** These can't be deduped, since `^4.2.x` cannot accept `5.0.0`.
`transaction-controller@69.4.0` only references it from type
declarations (`dist/TransactionController.d.cts`) and never from runtime
JS, so nothing new gets pulled into the bundle. Happy to add a
`resolutions` pin instead if the team would rather avoid the duplicate
entry.

## **Changelog**

CHANGELOG entry: Fixed address-poisoning detection so lookalikes of the
actual recipient of a token transfer are flagged, and lookalikes of the
token contract address are no longer flagged.

## **Related issues**

Fixes:

- Consumes
[MetaMask/core#9699](MetaMask/core#9699)
- Released in
[MetaMask/core#9746](MetaMask/core#9746)
(`@metamask/phishing-controller@17.3.1`)

## **Manual testing steps**

1. Pull this branch and run `yarn install`, then start a development
build.
2. Add a test ERC-20 token, then send some of it to a recipient address
you control. Let the transaction confirm.
3. Start a new send of that same token, and enter a recipient that is a
lookalike of the address from step 2 — same first four and last four hex
characters, different in the middle.
4. Confirm the address-poisoning warning now appears. On `main` it does
not.
5. Repeat step 3, but use a lookalike of the *token contract* address
instead. Confirm no address-poisoning warning appears, since the
contract is no longer treated as a known recipient.
6. Send and confirm a plain ETH transfer and a generic contract
interaction, and confirm a lookalike of each recipient still triggers
the warning — those paths should be unchanged.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

A lookalike of the real ERC-20 recipient produced no warning, while a
lookalike of the token contract produced one.

### **After**

A lookalike of the real ERC-20 recipient produces a warning; a lookalike
of the token contract does not.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
n3ps pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 6, 2026
## **Description**

Bumps `@metamask/phishing-controller` from `^17.3.0` to `^17.3.1` to
pick up the address-poisoning fix from
[MetaMask/core#9699](MetaMask/core#9699).

Before that fix, the known-recipients list used to detect address
poisoning took `txParams.to` from confirmed transactions. For
ERC-20/721/1155 transfers `txParams.to` is the *token contract*, not the
address receiving the tokens, so two things were wrong:

- a lookalike of the real token recipient did **not** trigger
address-poisoning detection
- a lookalike of the token contract address **did** trigger it, which is
meaningless noise

`17.3.1` decodes the actual recipient from calldata via the new
`getEffectiveRecipient` utility, so token transfers now contribute the
real recipient. Plain sends and other contract interactions are
unchanged.

### Why `@metamask/transaction-controller` moves in the lockfile

`phishing-controller@17.3.1` declares `@metamask/transaction-controller:
^69.4.0` (that's where `getEffectiveRecipient` is exported from), so the
69.x range resolves to `69.4.0` instead of `69.3.0`. I ran a scoped
`yarn dedupe` so this stays a **single** 69.x entry rather than
installing `69.4.0` alongside `69.3.0`. The same dedupe collapses the
`core-backend`, `gas-fee-controller`, and `polling-controller` ranges
that `69.4.0` nudges forward.

Two notes for reviewers on the dependency graph:

- **`network-controller` is unaffected.**
`transaction-controller@69.4.0` declares `network-controller: ^35.0.0`,
but this repo's `resolutions` pin holds it at `34.0.0` and the lockfile
still has exactly one `34.0.0` entry. That pin is safe here: the
published `69.3.0` and `69.4.0` bundles are byte-identical apart from
the `getEffectiveRecipient` export, an optional `strategy?: string`
field on `MetamaskPayMetadata`, and the moved helper file. No compiled
module in `69.4.0` references `network-controller`, `NetworkClient`,
`BuiltInNetworkClientId`, or `analyticsOptions`, so its `^35.0.0` range
is just the mechanical release-wide bump from MetaMask/core#9735 rather
than an adoption of v35 APIs.
- **`remote-feature-flag-controller@5.0.0` is added next to the existing
`4.2.2`.** These can't be deduped, since `^4.2.x` cannot accept `5.0.0`.
`transaction-controller@69.4.0` only references it from type
declarations (`dist/TransactionController.d.cts`) and never from runtime
JS, so nothing new gets pulled into the bundle. Happy to add a
`resolutions` pin instead if the team would rather avoid the duplicate
entry.

## **Changelog**

CHANGELOG entry: Fixed address-poisoning detection so lookalikes of the
actual recipient of a token transfer are flagged, and lookalikes of the
token contract address are no longer flagged.

## **Related issues**

Fixes:

- Consumes
[MetaMask/core#9699](MetaMask/core#9699)
- Released in
[MetaMask/core#9746](MetaMask/core#9746)
(`@metamask/phishing-controller@17.3.1`)

## **Manual testing steps**

1. Pull this branch and run `yarn install`, then start a development
build.
2. Add a test ERC-20 token, then send some of it to a recipient address
you control. Let the transaction confirm.
3. Start a new send of that same token, and enter a recipient that is a
lookalike of the address from step 2 — same first four and last four hex
characters, different in the middle.
4. Confirm the address-poisoning warning now appears. On `main` it does
not.
5. Repeat step 3, but use a lookalike of the *token contract* address
instead. Confirm no address-poisoning warning appears, since the
contract is no longer treated as a known recipient.
6. Send and confirm a plain ETH transfer and a generic contract
interaction, and confirm a lookalike of each recipient still triggers
the warning — those paths should be unchanged.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

A lookalike of the real ERC-20 recipient produced no warning, while a
lookalike of the token contract produced one.

### **After**

A lookalike of the real ERC-20 recipient produces a warning; a lookalike
of the token contract does not.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants