Is Nightly safe?

Medium risk

Nightly Wallet is medium risk. When the DeFi view loads for a connected Solana or Movement wallet, Nightly's code puts the wallet address in a Trackall positions URL sent to trackall.nightly.app with the Trackall API header. A prior network capture missed this request.

Nightlyv1.51.24Chrome Web Store
45Risk
Who publishes it

Akudama GmbH - no other listings under this identity

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

Publisher
Nightly
Declared legal entity
Akudama GmbH
Registered address
Dorfplatz 16, Urnäsch 9107, CH
Registered contact
Akudama GmbH

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

Publishers can request a review.

Findings

Wallet address sent to Trackall from the DeFi tab

When the DeFi view loads for a connected Solana or Movement wallet, Nightly's code puts the wallet address in a Trackall positions URL sent to trackall.nightly.app with the Trackall API header.

A prior network capture missed this request.

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

You open or refresh Nightly's DeFi portfolio tab for a connected Solana or Movement wallet.

The extension did this

The extension requests portfolio positions from Trackall with the wallet address in the URL.

The request is built for the selected chain and sent to trackall.nightly.app.

Fields the Trackall positions lookup carries
  • Wallet address
    9xQeWvG816bUx9EPfXx5Zfxx2XWyZtQkRdrk8b4Z5Y6n

    This identifies the blockchain account whose portfolio positions are being requested.

  • Chain name
    solana

    This tells the remote service which network the wallet address belongs to.

  • Positions URL
    https://trackall.nightly.app/api/solana/positions?address=9xQeWvG816bUx9EPfXx5Zfxx2XWyZtQkRdrk8b4Z5Y6n

    The address is part of the web address itself, so it can be logged by the remote service and network intermediaries that record URLs.

  • Trackall API header
    x-api-key: <redacted>

    This marks the request as coming from Nightly's extension client rather than from the mobile client.

Captured request
GEThttps://trackall.nightly.app/api/solana/positions?address=9xQeWvG816bUx9EPfXx5Zfxx2XWyZtQkRdrk8b4Z5Y6n

No response body was captured in the traffic-only run; behavioral verification confirmed the shipped code path that builds this GET request.

Headers
x-api-key
<redacted>
The code that does this

The DeFi tab dispatches refresh actions that lead to Trackall requests

Readable version

Solana DeFi tab refresh handler

chunks/DeFiTab-QSG7uFs5.js
J = () => {  const e = (0, P.c)(11),    s = c(),    t = l(S),    r = l(f),    n = l(h);  let i, a, o, x;  if (e[0] !== s || e[1] !== n ? (i = () => {      n && s(m.refreshSolanaDeFi({        silentRefresh: !0      }))    }, a = [s, n], e[0] = s, e[1] = n, e[2] = i, e[3] = a) : (i = e[2], a = e[3]), (0, B.useEffect)(i, a), !n) {    let s;    return e[4] === Symbol.for("react.memo_cache_sentinel") ? (s = (0, U.jsx)(z, {      isLoading: !1,      items: []    }), e[4] = s) : s = e[4], s  }  return e[5] !== s ? (o = () => s(m.refreshSolanaDeFi({})), e[5] = s, e[6] = o) : o = e[6], e[7] !== r || e[8] !== t || e[9] !== o ? (x = (0, U.jsx)(z, {    isLoading: r,    items: t,    onClickRefresh: o,    isRefreshDisabled: r,    isRefreshAnimating: r  }), e[7] = r, e[8] = t, e[9] = o, e[10] = x) : x = e[10], x}

Movement DeFi tab refresh handler

chunks/DeFiTab-QSG7uFs5.js
X = () => {  const e = (0, P.c)(11),    s = c(),    t = l(_),    r = l(d),    n = l(b);  let i, a, o, x;  if (e[0] !== s || e[1] !== n ? (i = () => {      n && s(m.refreshMovementDeFi({        silentRefresh: !0      }))    }, a = [s, n], e[0] = s, e[1] = n, e[2] = i, e[3] = a) : (i = e[2], a = e[3]), (0, B.useEffect)(i, a), !n) {    let s;    return e[4] === Symbol.for("react.memo_cache_sentinel") ? (s = (0, U.jsx)(z, {      isLoading: !1,      items: []    }), e[4] = s) : s = e[4], s  }  return e[5] !== s ? (o = () => s(m.refreshMovementDeFi({})), e[5] = s, e[6] = o) : o = e[6], e[7] !== r || e[8] !== t || e[9] !== o ? (x = (0, U.jsx)(z, {    isLoading: r,    items: t,    onClickRefresh: o,    isRefreshDisabled: r,    isRefreshAnimating: r  }), e[7] = r, e[8] = t, e[9] = o, e[10] = x) : x = e[10], x}
The code that does this

The Trackall client puts the wallet address into the positions request

Readable version

Trackall client initialization and positions request

chunks/store-C7D8UbGk.js
var vy = {    extension: "<redacted>",    mobile: "<redacted>"  },  Ay = (e = "extension") => function(e, t) {    const n = t?.origin ?? function() {      if ("undefined" == typeof window) return "http://localhost"    }();    return a(e, {      headers: {        ...t?.apiKey && {          "x-api-key": t.apiKey        },        ...n && {          origin: n        }      }    })  }("https://trackall.nightly.app", {    apiKey: vy[e]  }),  Oy = (e, t) => {    if (e.error) throw new Error(`${t}: ${String(e.error)}`);    if (null === e.data) throw new Error(t);    return e.data  },  by = async ({    clientApp: e  } = {}) => Oy(await Ay(e).platforms.get(), "Failed to fetch Trackall platforms"), Ny = async ({    chain: e,    address: t,    useCache: n = !0,    clientApp: r  }) => {    const a = (({      chain: e,      address: t,      clientApp: n    }) => {      const r = Ay(n);      switch (e) {        case "solana":          return r.api.solana.positions({            address: t          });        case "movement":          return r.api.movement.positions({            address: t          })      }    })({      chain: e,      address: t,      clientApp: r    });    if (n) {      const t = Oy(await a.cache.get(), `Failed to fetch cached Trackall ${e} positions`);      if ("error" in t) throw new Error(t.error);      return t.positions    }    return Oy(await a.get(), `Failed to fetch Trackall ${e} positions`)  }

Solana saga passes the selected wallet address

chunks/store-C7D8UbGk.js
function* xy(e) {  try {    const t = yield* Lo(In.network), n = yield* Lo(St.publicKey);    if (t !== fr.SOLANA || n !== e.payload.publicKey) return;    e.payload.silentRefresh || (yield* Xo(oi.clearDeFiData({      network: fr.SOLANA,      publicKey: e.payload.publicKey    })), yield* Xo(oi.setNetworkLoading({      network: fr.SOLANA,      isLoading: !0    })));    const r = yield* Yo(() => Ny({      chain: "solana",      address: e.payload.address,      useCache: e.payload.useCache,      clientApp: "extension"    })), a = yield* Yo(() => by({      clientApp: "extension"    })), o = yield* Yo(() => My({      chain: "solana",      positions: r,      platforms: a    })), s = yield* Lo(In.network), i = yield* Lo(St.publicKey);    if (s !== fr.SOLANA || i !== e.payload.publicKey) return;    yield* Xo(oi.setTrackallDeFiData({      network: fr.SOLANA,      portfolioValue: o.portfolioValue,      items: o.items,      publicKey: e.payload.publicKey    }))  } catch (t) {    return void console.log(t)  } finally {    const t = yield* Lo(In.network), n = yield* Lo(St.publicKey);    t === fr.SOLANA && n === e.payload.publicKey && (e.payload.silentRefresh || (yield* Xo(oi.setNetworkLoading({      network: fr.SOLANA,      isLoading: !1    }))), yield* Xo(oi.setShowDeFiTab({      network: fr.SOLANA,      show: !0    })))  }}

Movement saga passes the selected wallet address

chunks/store-C7D8UbGk.js
function* Uy(e) {  try {    const t = yield* Lo(In.network), n = yield* Lo(xs.network), r = yield* Lo(Ct.publicKey);    if (t !== fr.MOVEMENT || n !== $r.MAINNET || r !== e.payload.publicKey) return;    e.payload.silentRefresh || (yield* Xo(oi.clearDeFiData({      network: fr.MOVEMENT,      publicKey: e.payload.publicKey    })), yield* Xo(oi.setNetworkLoading({      network: fr.MOVEMENT,      isLoading: !0    })));    const a = yield* Yo(() => Ny({      chain: "movement",      address: e.payload.address,      useCache: e.payload.useCache,      clientApp: "extension"    })), o = yield* Yo(() => by({      clientApp: "extension"    })), s = yield* Yo(() => My({      chain: "movement",      positions: a,      platforms: o    })), i = yield* Lo(In.network), c = yield* Lo(xs.network), d = yield* Lo(Ct.publicKey);    if (i !== fr.MOVEMENT || c !== $r.MAINNET || d !== e.payload.publicKey) return;    yield* Xo(oi.setTrackallDeFiData({      network: fr.MOVEMENT,      portfolioValue: s.portfolioValue,      items: s.items,      publicKey: e.payload.publicKey    }))  } catch (t) {    return void console.log(t)  } finally {    const t = yield* Lo(In.network), n = yield* Lo(xs.network), r = yield* Lo(Ct.publicKey);    t === fr.MOVEMENT && n === $r.MAINNET && r === e.payload.publicKey && (e.payload.silentRefresh || (yield* Xo(oi.setNetworkLoading({      network: fr.MOVEMENT,      isLoading: !1    }))), yield* Xo(oi.setShowDeFiTab({      network: fr.MOVEMENT,      show: !0    })))  }}
Remote service receiving the lookup
    • trackall.nightly.app

    Receives the Solana or Movement portfolio positions lookup with the selected wallet address in the request URL.

Updated 30 September 2026fiikommddbeccaoicoejoniammnalkfa