Is TokenPocket - Web3 & Crypto Wallet safe?

Medium risk

TokenPocket is medium risk. Enabling push notifications registers a push subscription, posted to TokenPocket's server. The same path posts wallet addresses (blockchain ID, address, type, action) to preserver.mytokenpocket.vip with push token/device ID. Not reached.…

TP Global Ltdv2.20.1Chrome Web Store
47Risk

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

Publishers can request a review.

Findings

Push opt-in binds wallet addresses to a push token

MediumunwantedCWE-359Dynamic sandbox

Enabling push notifications registers a push subscription, posted to TokenPocket's server.

What actually happens

You did this

You enable push notifications in the wallet settings.

The settings screen checks that Firebase messaging and browser push support are available before requesting a token.

The extension did this

The extension binds stored wallet addresses to the push subscription it registered for your browser.

Initial registration calls the address-sync path with forceAll enabled, so every enabled address-chain pair is eligible for posting.

Captured request

POSThttps://preserver.mytokenpocket.vip/v1/message/bind_regtoken_addresses

No response was captured because the dynamic test did not reach the notification opt-in control.

Fields constructed for the address-binding request

  • Blockchain ID
    1 (illustrative Ethereum chain record)

    This tells the server which blockchain a wallet address belongs to.

  • Wallet address
    0x742d35Cc6634C0532925a3b844Bc454e4438f44e (illustrative)

    This can identify a public wallet and connect balances or transactions to the push subscription.

  • Wallet type
    0 for a non-watch wallet; 1 for a watch wallet; 9 when no type is set

    This distinguishes normal, watch-only, or fallback wallet records in the request.

  • Push subscription token
    {"endpoint":"https://fcm.googleapis.com/fcm/send/eY7r3pV4mK9sQ2aL6tN8uB1cD5fG0hJ4kL7mP9qR2sT6vW8xY3zA5bC0dE1fG","keys":{"p256dh":"BOrTQ5w6xH9c2Vf8mN1aL4zP7qS0dE3rG6uI9oK2yM5bN8pQ1tS4vW7xZ0aC3eF6gH9iJ2kL5mN8oP1qR4sT7uV0wX3yZ6","auth":"q1w2e3r4t5y6u7i8o9p0a1b2"}} (illustrative)

    This lets the server connect wallet addresses to the browser push subscription used for notifications.

  • Extension device ID
    mfgccjchihfkkindfppnaooecgfneiii

    This links the address binding to the extension installation rather than only to a single wallet record.

The code that does this

Notification opt-in and address binding in shipped code

Readable version

Readable UI toggle path

deobfuscated/462.js
onChange: async r => {  if (!n) {    a(!0);    try {      if (r) {        if (!(0, Tt.J)()) throw new Error("Firebase messaging config missing");        if (!("undefined" != typeof window && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window)) throw new Error("Current environment does not support push notifications");        (0, y.log)("[togglePush] start", r), (0, y.log)("[togglePush] before getFirebaseRegToken");        const e = await pc((0, C.j)(t), 15e3, "Get Firebase registration token timeout");        (0, y.log)("[togglePush] after getFirebaseRegToken", e), await pc(t.registerNotificationToken(e), 1e4, "Register notification token timeout"), l((0, ic.W)(await pc(t.getNotificationSettings(), 1e4, "Load notification settings timeout")))      } else {        const e = {          ...o,          pushEnabled: !1        };        l(e), await pc(t.disableNotificationPush(), 1e4, "Disable notification push timeout"), await pc((0, C.t)(), 1e4, "Unsubscribe push subscription timeout"), l((0, ic.W)(await pc(t.getNotificationSettings(), 1e4, "Load notification settings timeout")))      }    } catch (n) {      console.error("[NotificationMessage.togglePush]", n), S.ZP.error(n?.message || e("RequestError")), l((0, ic.W)(await t.getNotificationSettings()))    } finally {      a(!1)    }  }}

Readable push subscription serialization

deobfuscated/background.js
Cae = "BG5R2ox8rpbza2VQ1J_XPL4Eu02eYympO-_VCjUR9ob8GklAiHCax2M-EN6KhiSAdcVmJt_TvWBXB1UCRSHWlgo";requestToken = async () => {  if (this.log("requestToken start"), !self?.registration?.pushManager) throw this.log("requestToken no pushManager"), new Error("Push manager is not available in extension service worker");  let e = await (self?.registration.pushManager.getSubscription());  this.log("requestToken existing subscription", !!e), e || (this.log("requestToken subscribe start"), e = await (self?.registration.pushManager.subscribe({    userVisibleOnly: !0,    applicationServerKey: _ae(Cae)  })), this.log("requestToken subscribe success")), await this.syncDebugSubscription(e.toJSON());  const t = JSON.stringify(e.toJSON());  return this.log("requestToken finish", {    endpoint: e?.endpoint  }), MS.setNotificationSettings({    regToken: t  }), t};

Readable token and address binding path

deobfuscated/background.js
Xne = async e => gU("/v1/message/newregtoken", {  method: "post",  data: e});$ne = async e => gU("/v1/message/bind_regtoken_addresses", {  method: "post",  data: e});registerToken = async e => {  if (this.log("registerToken start"), !e) throw new Error("Missing regToken");  const t = {    ...this.getSettings(),    pushEnabled: !0,    regToken: e  };  return await this.registerDevice(e, t), await this.syncRegToken(e, t), await this.subscribeDefaultTopics(e), await this.syncAddresses(e, 0, {    forceAll: !0  }), MS.setNotificationSettings({    regToken: e,    pushEnabled: !0  }), this.log("registerToken finish"), this.getSettings()};syncRegToken = async (e, t = this.getSettings()) => {  this.log("syncRegToken"), await Xne({    device_id: MS.getExtensionId(),    reg_token: e,    push_status: t.pushEnabled ? 1 : 0,    transfer_status: t.transferStatus ? 1 : 0,    watch_status: t.watchStatus ? 1 : 0,    activity_status: t.activityStatus ? 1 : 0,    ts: Date.now()  }), MS.setNotificationSettings({    lastSyncAt: Date.now()  })};getAccountWalletAddressParams = async (e, t) => {  const r = await wae.getSupportChains(),    n = new Map;  r.forEach((e => {    n.set(e.enum, e), n.set(e.native_token_id, e)  }));  const i = new Map;  return MS.getAccountEnableChainsData(e).map((e => n.get(e))).filter(Boolean).forEach((r => {    const n = this.getAccountChainAddresses(e, r);    r?.blockchain_id && n.length && !String(r.native_token_id).includes("PublicKey") && n.forEach((n => {      const a = {        blockchain_id: Number(r.blockchain_id),        address: n,        wallet_type: this.getWalletType(e),        action: t      };      i.set(this.getAddressKey(a), a)    }))  })), Array.from(i.values())};syncAddressParams = async (e, t) => {  if (!t.length) return;  this.log("syncAddressParams paramsList", t);  const r = await $ne({    params_list: t,    reg_token: e,    device_id: MS.getExtensionId()  });  this.log("syncAddressParams bind result", JSON.stringify(r))};syncAddresses = async (e, t, r = {}) => {  this.log("syncAddresses start", {    action: t,    forceAll: r.forceAll  });  const n = this.getSettings(),    i = Array.isArray(n.boundAddressKeys) ? n.boundAddressKeys : [],    a = await this.getAllWalletAddressParams(t),    o = 0 === t ? a.map((e => ({      ...e,      action: 0    }))) : [],    s = 0 === t ? (({      boundAddressKeys: e = [],      currentAddressParams: t = [],      action: r = 1    }) => {      const n = new Set(t.map((e => Oae(e))));      return e.filter((e => !n.has(e))).map((e => Nae(e, r))).filter(Boolean)    })({      boundAddressKeys: i,      currentAddressParams: o,      action: 1    }) : [];  s.length && (this.log("syncAddresses staleParamsList", s), await this.syncAddressParams(e, s), this.updateBoundAddressKeys(s, 1));  let c = 0 !== t || r.forceAll ? a : a.filter((e => !i.includes(this.getAddressKey(e))));  if (1 === t && r.forceAll && i.length) {    const e = new Map;    i.forEach((t => {      const r = Nae(t, 1);      r && e.set(this.getAddressKey(r), r)    })), a.forEach((t => {      e.set(this.getAddressKey(t), t)    })), c = Array.from(e.values())  }  c.length ? (await this.syncAddressParams(e, c), this.updateBoundAddressKeys(c, t), this.log("syncAddresses finish")) : this.log("syncAddresses no address params", {    allParamsCount: a.length,    boundAddressCount: i.length,    staleParamsCount: s.length  })};

Services involved in the push-registration flow

    • preserver.mytokenpocket.vip

    TokenPocket server that receives the push token and wallet-address binding request.

    • fcmregistrations.googleapis.com

    Google Firebase Cloud Messaging service used by the browser push-registration flow.

    • firebaseinstallations.googleapis.com

    Google Firebase Installations service used by Firebase messaging to manage installation identity.

Traffic caveat

Dynamic analysis did not reach the notification settings screen within the available test sessions, so it observed no requests to the two TokenPocket push-registration endpoints. The shipped code still contains the opt-in path and the request construction shown above.

TokenPocket sends a persistent ID and wallet activity to Google Analytics

LowunwantedCWE-359Dynamic sandbox

TokenPocket sends a persistent clientId UUID to Google Analytics on every use.

What actually happens

You did this

You create a wallet, move between onboarding screens, unlock your wallet, or confirm a transaction.

The extension did this

The extension sends a named event describing that action to Google Analytics, tagged with a persistent installation ID.

This happens on every onboarding screen transition and, per the extension's own code, on every wallet unlock and transaction confirmation.

Captured request

POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-B15TQE99Q2&api_secret=<redacted>

Google Analytics Measurement Protocol collect endpoint; no response body is used by the extension.

Headers
content-type
application/json
Body
{  "client_id": "3a57ac91-6de2-494f-8ec4-7bf5d6c55314",  "events": [    {      "name": "tp_start_page",      "params": {        "start_page": "create_account",        "session_id": "1783561224676",        "engagement_time_msec": 100      }    }  ]}

Fields sent with every Google Analytics event

  • Installation ID (client_id)
    3a57ac91-6de2-494f-8ec4-7bf5d6c55314

    A UUID generated on first run, stored locally, unchanged across sessions, letting Analytics link every event below to this install.

  • Session ID
    1783561224676

    A timestamp-based identifier that resets after 30 minutes of inactivity, grouping events within one sitting.

  • Screen / page name
    create_account

    Which onboarding or wallet screen you were on when the event fired, e.g. the create-account or unlock screen.

  • Chain name & ID (tp_tx_confirm_chain)
    {"blockchain_id":56,"name":"BNB Smart Chain"}

    Per the extension's source code, every transaction confirmation reports which blockchain network you transacted on.

  • Keyring counts (tp_lt)
    {"lt":42,"kn":3,"khn":1,"khsn":2}

    Per the extension's source code, every wallet unlock reports how many keyrings/accounts you have set up, split by type.

The code that does this

Event transport (background.js) and the wallet-unlock / transaction-confirm callers

Readable version

fireEvent() — deobfuscated

async fireEvent(eventName, params = {}) {  params.session_id || (params.session_id = await this.getOrCreateSessionId());  params.engagement_time_msec || (params.engagement_time_msec = 100);  try {    const res = await fetch(      (this.debug        ? "https://www.google-analytics.com/debug/mp/collect"        : "https://www.google-analytics.com/mp/collect") +        "?measurement_id=G-B15TQE99Q2&api_secret=<redacted>",      {        method: "POST",        body: JSON.stringify({          client_id: await this.getOrCreateClientId(),          events: [{ name: eventName, params }]        })      }    );    if (!this.debug) return;    console.log(await res.text());  } catch (err) {    console.error("Google Analytics request failed with an exception", err);  }}

getOrCreateClientId() — deobfuscated

async getOrCreateClientId() {  let { clientId } = await chrome.storage.local.get("clientId");  return clientId || (clientId = self.crypto.randomUUID(),    await chrome.storage.local.set({ clientId }),    clientId);}

Transaction-confirm caller — deobfuscated (background.js)

analytics.fireEvent("tp_tx_confirm_chain", {  blockchain_id: chain?.blockchain_id,  name: chain?.name});

Wallet-unlock caller — deobfuscated (ui.js)

eventBus.addEventListener("initKeyringsOtherChainEnd", async (payload) => {  refreshChains();  analytics.analyticsFireEvent("tp_lt", payload);});

Destination receiving wallet-installation telemetry

    • www.google-analytics.com

    Google's Measurement Protocol endpoint. Receives the persistent client_id, session data, and named events (page views; per source, tx-confirm, unlock) for GA property G-B15TQE99Q2.

Updated 30 September 2026mfgccjchihfkkindfppnaooecgfneiii