Is TronLink safe?

High risk

TronLink is high risk. Every 6h on TRON mainnet with asset-sync on, TronLink reads TRX/USDT balances, token value, and tx stats from IndexedDB, POSTed to api.mixpanelink.org/api/stat/some with a per-wallet UUID. mixpanelink.org is unnamed in TronLink's policy.

Helix Tech Company Limitedv4.11.0Chrome Web Store
72Risk
Who publishes it

Helix Tech Company Limited - no other listings under this identity, 2 shared hostnames

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Helix Tech Company Limited

Shared hosts - 2 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

shasta.tronscan.org
Also called by 4 other listings, including MetaMask
nile.tronscan.org
Also called by 6 other listings, including MetaMask

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

Publishers can request a review.

Findings

Wallet balances and transaction stats sent to api.mixpanelink.org

Every 6h on TRON mainnet with asset-sync on, TronLink reads TRX/USDT balances, token value, and tx stats from IndexedDB, POSTed to api.mixpanelink.org/api/stat/some with a per-wallet UUID. mixpanelink.org is unnamed in TronLink's policy.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You install TronLink, create or import a wallet, and leave the default settings on TRON mainnet.

No interaction with the wallet is required after install; the upload runs from the background service worker on a clock, not in response to a click.

The extension did this

Every six hours the extension reads your wallet balances and transaction-history statistics from its local database and POSTs them to https://api.mixpanelink.org/api/stat/some.

The report includes a stable per-wallet UUID that lets the receiving server link successive reports back to the same wallet. The request body is AES-encrypted, so the contents are not visible in DevTools.

Captured request
POSThttps://api.mixpanelink.org/api/stat/some?address=a&nonce=2808&secretId=AE68A487AA919CAE&signature=J2m4VOTb7FvbjRx4TlHqEKeOxr4%3D

200 OK with a server response object that controls future encryption (toggles isEncryptReportData on the client based on response.visible).

Headers
ts
1744640329122
content-type
application/json
Body
{  "X": "u8mEPq2Z4xZ1Q9YfWvD3kJxNcR7tBpL6sH0aVgK4eM5dC1nUjF8wAyXrIoTbVhKpQ2sLmNzE9YgWfDxKjC7uHvBpRtY3sN6mLkXqZeF4hOaT2yV1nC8gJrM5dBpLqK7eR4tHsXfYwZ"}
Per-wallet fields in each X (asset) report row, one V1X row per address
  • Per-wallet UUID
    f3b8a212-7c41-4d9e-a06b-9e2dca8b0117

    A stable ID per wallet address; successive reports from one wallet share it, letting the server link them over time.

  • Address type
    0

    Whether this is your primary address, an HD-derived sub-account, an imported private key, etc. Reveals the structure of your wallet setup.

  • TRX balance
    1245.736842

    How much TRX this wallet currently holds, expressed in the wallet's stored units.

  • USDT balance
    382.500000

    How much USDT (Tether on TRON) this wallet currently holds.

  • Total token value (USD)
    1631.42

    The estimated total US-dollar value of all tokens in this wallet at report time.

  • Report date
    2026-04-14

    The date the snapshot was taken. Combined with the UUID, this builds a time series of your wallet's value.

Per-action fields in each Y (tx-stat) row, one V1Y row per wallet/action pair with history
  • Per-wallet UUID
    f3b8a212-7c41-4d9e-a06b-9e2dca8b0117

    Same stable per-wallet identifier as in the X report, so transaction stats can be joined to the asset snapshots.

  • Action type
    transfer

    What kind of on-chain action this row summarises (e.g. send, receive, freeze, contract interaction).

  • Action count
    17

    How many times this wallet performed this action in the report window.

  • Token contract address
    TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

    The TRC20 token contract this row is for (e.g. USDT, USDC). Identifies which assets you are transacting in.

  • Total token amount
    1850.000000

    Sum of the token amounts moved by this action in the window.

  • Energy / bandwidth / burn
    energy=24350 bandwidth=345 burn=2.4

    TRON resource usage and TRX burned for these actions. Reveals usage intensity and fee patterns.

  • Amount-bucket histogram
    A4:6,A5:9,A6:2

    How many transactions fall in each amount bucket (e.g. 100-1k USD, 1k-10k USD, 10k-100k USD). Reveals the size profile of your transactions.

  • Report date
    2026-04-14

    The date the stats were collected.

Why you can't catch this in DevTools

On the wire, the body is two AES-encrypted base64 strings (X and Y), DevTools shows opaque blobs. Decoded with the per-request key, the strings reveal the pipe-delimited V1X / V1Y rows above. The example below shows a single-address asset report with no transaction history (matches the captured request).

Decoded value
{  "X": "V1X|f3b8a212-7c41-4d9e-a06b-9e2dca8b0117|0|1245.736842|382.500000|1631.42|2026-04-14|",  "Y": null}
The code that does this

The reporter and the request, from the extension's shipping background service worker.

Readable version

The 6-hourly trigger

// Fires every minute; only continues at :00 of UTC hours 0, 6, 12, 18setInterval(async () => {  const now = new Date();  const hour = now.getUTCHours();  const minute = now.getUTCMinutes();  if ((hour === 0 || hour === 6 || hour === 12 || hour === 18) && minute === 0) {    log.info('version452_enter_background_setInterval_checkAndReportXY');    await checkAndReportXY();   // = Mt()  }}, 60_000);

The reporter

// background.js function Mt -> checkAndReportXYasync function checkAndReportXY() {  // Gate: only on TRON mainnet AND only if isOpenTokenAsync setting is on (default true)  const state = store.getState();  if (state.settings.isOpenTokenAsync === false) return;  if (chainOf(state) !== ChainId.MAINNET) return;  // Read all wallet asset snapshots and transaction-stat rows from IndexedDB  const assetRows = (await loadAll(db, ASSET_PRECIPITATION)).array;  const txnRows   = (await loadAll(db, TRANSACTION_RECORD)).array;  // Pack into V1X (assets) and V1Y (transaction stats) pipe-delimited strings  const X = assetRows.length    ? assetRows.map(r =>        `V1X|${r.uid}|${r.addressType}|${r.trxBalance}|${r.usdtBalance}|${r.realTokenUsd}|${r.date}|`      ).join('')    : undefined;  const Y = txnRows.length    ? txnRows.map(r => {        const buckets = (r.txnAmountDistributions || [])          .map(b => `${BUCKET_CODE[b.range] ?? b.range}:${b.count}`)          .join(',');        return `V1Y|${r.uid}|${r.addressType}|${r.actionType}|${r.count}|`             + `${r.tokenAddress}|${r.tokenAmount}|${r.energy}|${r.bandwidth}|${r.burn}|`             + `${r.date}|${buckets}|`;      }).join('')    : undefined;  if (!X && !Y) return;  // Optional AES encryption (default on; toggled by server response to a prior report)  const cfg = await storage.get(SYSTEM_CONFIG) ?? {};  const headers = buildHeaders();          // includes ts  const params  = { address: 'a', ...sign('a', 'post', '/api/stat/some', headers) };  const xMsg = cfg.isEncryptReportData ? aesEncrypt({ headers, params, message: X }) : X;  const yMsg = cfg.isEncryptReportData ? aesEncrypt({ headers, params, message: Y }) : Y;  await httpPost('https://api.mixpanelink.org/api/stat/some',                 { X: xMsg, Y: yMsg },                 { headers, params });}

The encryption helper

// Derives an AES key from the signature query param and the ts header,// then encrypts the message with that key.function aesEncrypt({ headers, params, message }) {  const password = params.signature;        // also visible in the URL query string  const ts       = headers.ts;  const aesKey   = deriveKey({ password, ts });   // Le.N$ — HMAC-SHA256 derived  return aesEncryptWithKey(message, aesKey);      // Le.w}
Where the reports go
    • api.mixpanelink.org

    Receives periodic wallet-balance and tx-stat reports at /api/stat/some. Not mixpanel.com; Mixpanel Inc.'s service is api.mixpanel.com. Unnamed in the listing or policy.

Reproduce it yourself

Run this in the TronLink service worker DevTools console. It hooks fetch and logs every request to api.mixpanelink.org along with the URL params and the (still-encrypted) request body, so you can see for yourself when the periodic upload fires. To capture the same evidence end-to-end without waiting up to 6 hours, the script also exposes window.__tronlinkForceReport(), which calls the internal checkAndReportXY function immediately if you have at least one wallet on TRON mainnet.

Requires
  • Chrome with Developer mode enabled
  • TronLink installed with at least one wallet imported on TRON mainnet
  • Settings > Token Async Update (isOpenTokenAsync) left at its default (on) for the periodic call to fire
tronlink-mt-trace.js · js
// tronlink-mt-trace.js// Paste into the TronLink background service worker DevTools console.// 1. Open chrome://extensions, enable Developer mode.// 2. Find TronLink, click the 'service worker' link to open its DevTools.// 3. Paste this whole script into the console and press Enter.// 4. Either wait until the next UTC hour 0/6/12/18 (minute :00),//    or call __tronlinkForceReport() in the console to trigger an upload now.(function () {  const TARGET_HOST = 'api.mixpanelink.org';  const origFetch = self.fetch.bind(self);  self.fetch = async function (input, init) {    const url = (typeof input === 'string') ? input : input.url;    if (url && url.includes(TARGET_HOST)) {      try {        const u = new URL(url);        const params = Object.fromEntries(u.searchParams.entries());        const body = init && init.body ? init.body : null;        const headers = init && init.headers ? init.headers : null;        console.group('[TRONLINK_REPORT] -> ' + u.origin + u.pathname);        console.log('params:', params);     // address, secretId, nonce, signature        console.log('headers:', headers);   // includes ts (used as AES salt)        console.log('body (encrypted):', body);        console.groupEnd();      } catch (e) { /* ignore logging errors */ }    }    return origFetch(input, init);  };  // Optional: trigger the periodic reporter on demand.  // Walks the service-worker globals for a function whose source mentions  // 'V1X|' and 'api/stat/some' and calls it. Only works if you have a  // TRON mainnet wallet and isOpenTokenAsync left at its default (true).  self.__tronlinkForceReport = function () {    const seen = new Set();    function walk(obj, depth) {      if (!obj || depth > 4 || seen.has(obj)) return null;      seen.add(obj);      for (const k of Object.keys(obj)) {        let v;        try { v = obj[k]; } catch { continue; }        if (typeof v === 'function') {          const src = v.toString();          if (src.includes('V1X|') && src.includes('/api/stat/some')) return v;        } else if (typeof v === 'object') {          const hit = walk(v, depth + 1);          if (hit) return hit;        }      }      return null;    }    const fn = walk(self, 0);    if (!fn) {      console.warn('[TRONLINK_REPORT] could not locate checkAndReportXY in this build');      return;    }    console.log('[TRONLINK_REPORT] invoking checkAndReportXY()...');    return fn();  };  console.log('[TRONLINK_REPORT] hook installed. Watching for fetch() to ' + TARGET_HOST + '.');})();
How to run it
  1. 1chrome://extensions, enable Developer mode.
  2. 2Click TronLink's 'service worker' link for DevTools.
  3. 3Paste the script, Enter.
  4. 4Wait for UTC 0/6/12/18, or call __tronlinkForceReport().
  5. 5The report logs its params, headers, body.
Updated 30 September 2026ibnejdfjmmkpcnlpebklmnkoeoihofec