Is TokenPocket - Web3 & Crypto Wallet safe?
TokenPocket is medium risk. 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.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
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.
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.
| 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
}
}
]
}| Field | Value | Why it matters | |
|---|---|---|---|
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. |
Event transport (background.js) and the wallet-unlock / transaction-confirm callers
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);
}
}async getOrCreateClientId() {
let { clientId } = await chrome.storage.local.get("clientId");
return clientId || (clientId = self.crypto.randomUUID(),
await chrome.storage.local.set({ clientId }),
clientId);
}analytics.fireEvent("tp_tx_confirm_chain", {
blockchain_id: chain?.blockchain_id,
name: chain?.name
});eventBus.addEventListener("initKeyringsOtherChainEnd", async (payload) => {
refreshChains();
analytics.analyticsFireEvent("tp_lt", payload);
});- 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.
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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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. |
Notification opt-in and address binding in shipped code
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)
}
}
}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
};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
})
};- 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.
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.