Is TokenPocket - Web3 & Crypto Wallet safe?
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.…
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
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.
What actually happens
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 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
No response was captured because the dynamic test did not reach the notification opt-in control.
Fields constructed for the address-binding request
- Blockchain ID1 (illustrative Ethereum chain record)
This tells the server which blockchain a wallet address belongs to.
- Wallet address0x742d35Cc6634C0532925a3b844Bc454e4438f44e (illustrative)
This can identify a public wallet and connect balances or transactions to the push subscription.
- Wallet type0 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 IDmfgccjchihfkkindfppnaooecgfneiii
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 UI toggle path
deobfuscated/462.jsonChange: 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.jsCae = "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.jsXne = 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
TokenPocket sends a persistent clientId UUID to Google Analytics on every use.
Code also defines tp_tx_confirm_chain and tp_lt (unlock) events on the same transport, not captured live.
No wallet keys sent; not disclosed in the listing.
What actually happens
You create a wallet, move between onboarding screens, unlock your wallet, or confirm a transaction.
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
Google Analytics Measurement Protocol collect endpoint; no response body is used by the extension.
- content-type
- application/json
{ "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 ID1783561224676
A timestamp-based identifier that resets after 30 minutes of inactivity, grouping events within one sitting.
- Screen / page namecreate_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
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.