Vest: one refused account token fails only its account, and the relay sends the current session's token

Every open Vest tab renews the login on its own, and each renewal moves the
session on: a token from before still reads, but Vest refuses to mint an
account's token with it (401, code 1002). The extension relayed the first
tab's token, so mints failed most of the time with two tabs open, and each
refusal dropped the whole session — every later account in a history pass
failed "no Vest session", and new accounts waited out 5-minute retries for
up to an hour (2026-10-28/29).

The client now fails only that account's call on a refused mint, keeps the
session and the cached account tokens, and logs the refusal now and then
with the login token's life left. An active account with no history is read
again every minute even while a whole pass's retry waits; a pass reads those
first and inactive accounts last, whose failures no longer set the retry.
Exits, lone take-profits and flattens wait up to 90s for the session and the
account's token instead of failing at once; entries still go now or not at
all. Closed accounts' history is read through the login first.

Extension 0.18.0: the hook records each token's session id and takes sign-in
tokens too; the relay reads every Vest tab and Vest's own current-session
record, and sends the token that session holds, else the newest, saying in
the popup when none is current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-29 10:55:01 -05:00
co-authored by Claude Opus 5.5
parent bf7e147507
commit 0eb56b3949
8 changed files with 529 additions and 37 deletions
+22 -9
View File
@@ -6,10 +6,16 @@
*
* Vest's app renews its own ~15-minute access token by itself: at load, then
* whenever one nears expiry (POST /v3/auth/refresh, or /v3/auth/register at
* sign-in). Those answers carry the token, its expiry and the login's wallet.
* This hook reads them off the app's own XHRs and keeps the latest on
* `window.__afVestSession`, where background.js collects it and relays it to
* the local dashboard, whose Vest client trades with it.
* sign-in). Those answers carry the token, its expiry, the login's wallet and
* the session it belongs to (refreshTokenId). This hook reads them off the
* app's own XHRs and keeps the latest on `window.__afVestLogin`, where
* background.js collects it and relays it to the local dashboard, whose Vest
* client trades with it.
*
* Every tab renews on its own, and each renewal moves the login on to a new
* session (localStorage `vest-auth-session`): a token from before still reads,
* but Vest refuses to mint an account's token with it (401, code 1002). The
* session id lets background.js relay the tab whose token is the current one.
*
* It sends nothing anywhere, changes no request and never renews anything
* itself: the session stays the app's, refreshed by the app with its own
@@ -19,14 +25,19 @@
* /v3/auth/refresh, sending that account's refresh token in the body (the
* login's rides in a cookie) and getting it back renewed. Those tokens see
* one account only, so they are skipped: relaying one would leave the
* dashboard with a single account.
* dashboard with a single account. A sign-in's answer carries the login's
* own refresh token, and is taken.
*
* Version 2. A page hooked by version 1 (`__afVestHook === true`, keeping
* `__afVestSession`, no session id, no sign-ins) takes this one beside it.
*/
(() => {
if (window.__afVestHook) return;
window.__afVestHook = true;
if (window.__afVestHook === 2) return;
window.__afVestHook = 2;
const AUTH = /^https:\/\/api-gateway\.hz\.vestmarkets\.com\/v3\/auth\/(refresh|register)(\?|$)/;
const SIGN_IN = /\/v3\/auth\/register(\?|$)/;
/** A request body that carries a refresh token: an account's renewal. */
function accountRenewal(reqBody) {
@@ -38,14 +49,16 @@
let body;
try { body = JSON.parse(text); } catch { return; }
if (!body || typeof body.accessToken !== 'string' || !body.accessToken) return;
if (typeof body.refreshToken === 'string' && body.refreshToken) return; // an account's token, renewed
// A renewal answering with a refresh token is an account's.
if (!SIGN_IN.test(url) && typeof body.refreshToken === 'string' && body.refreshToken) return;
const expiresAt = Number(body.accessExpiresAtMs);
if (!Number.isFinite(expiresAt)) return;
window.__afVestSession = {
window.__afVestLogin = {
accessToken: body.accessToken,
expiresAt,
primaryAddr: typeof body.primaryAddr === 'string' ? body.primaryAddr : '',
userId: typeof body.userId === 'string' ? body.userId : '',
refreshTokenId: typeof body.refreshTokenId === 'string' && body.refreshTokenId ? body.refreshTokenId : null,
at: Date.now(),
};
}