Vest (next.vestmarkets.com) is a perps prop firm: index, metal and energy
perps on CME hours, plus stocks, FX and crypto. It joins as a fourth
platform, 'vest', on the DXtrade engine paths. Those are now keyed by
isNotionalPlatform() instead of === 'dxtrade': the firm's own symbols,
percent fees, notional sizing from volatility, and a scheduler lane of its
own.
- Login is relayed, not stored. Vest signs people in with Google through
Privy and renews the session with a device key the browser won't export.
Extension 0.17.0's vest-relay.js reads the token the Vest app renews for
itself (~15 min). It renews nothing, so the tab is never logged out. The
background script posts that token to POST /api/vest/session, which holds
it in memory only. With no Vest tab open on a machine that trades Vest,
the extension opens one.
- The firm's credential is the Vest login's wallet address. The dashboard's
form offers the relayed wallet with one click.
- VestClient (lib/vest-class.ts), mapped from the web app's traffic and code
since Vest publishes no API:
- Reads the capital accounts, balances (free amount plus position
collateral), positions and open orders with the user token.
- Mints per-account tokens for orders and transactions.
- Names accounts for their rules (V50K-E-D3-T10-34, V50K-F-D3-34,
V25K-I-7) so account-type prefixes can match.
- Takes Vest's own max-drawdown and daily-loss floors. dayStartBalance =
floor + the type's limit, so the engine's room is never more than
Vest's.
- Uses each symbol's taker fee and margin rate from exchangeInfo, and
hourly volatility over in-session hours only.
- Places take-profit and stop as position legs, which Vest cancels as an
OCO pair.
- Daily P&L and fund rows come from /v3/transactions by Vest's 20:00 ET
day. A books check holds an account, naming the transaction types, when
the balance and the transactions disagree.
- scripts/check-vest.mjs (in npm run check): Vest's day across DST, account
names, parsing, volatility gaps, market hours, the session store.
Not yet run against Vest with a live session: the order and exit bodies
come from the app's code, and the transaction classification from its enum.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
5.6 KiB
Markdown
117 lines
5.6 KiB
Markdown
# AutoFirmer Capture (Chromium extension)
|
|
|
|
Scrapes the firm tab you are looking at and posts its HTML back to the
|
|
dashboard. There is no on/off switch for that — see **Scope** below for what
|
|
limits it instead.
|
|
|
|
## Install (unpacked)
|
|
|
|
1. Start the dashboard (`npm run dev`) so `http://localhost:3000` is up.
|
|
2. Open `chrome://extensions`, enable **Developer mode** (top right).
|
|
3. **Load unpacked** → select this `extension/` folder.
|
|
4. Click the extension icon to open the popup and set:
|
|
- **Dashboard URL** — default `http://localhost:3000`. Use the VPS IP if the
|
|
dashboard runs elsewhere.
|
|
- **Poll interval** — seconds between status checks (default 3).
|
|
- **Element selector** — optional CSS selector; matching elements get their
|
|
on-screen position measured alongside the HTML.
|
|
|
|
The badge shows `!` (red) if the dashboard is unreachable, and nothing otherwise
|
|
— blank is the working state. Open the popup for what the last tick actually did.
|
|
|
|
## Flow
|
|
|
|
```
|
|
extension --GET /api/autobuyer/status--> { pendingLocate }
|
|
extension --POST /api/autobuyer/capture-> html + viewport + element rects
|
|
AutoBuyer page --GET /api/autobuyer/capture-> renders the HTML
|
|
```
|
|
|
|
Nothing in that loop is switchable. Every tick serves any queued clicks and then
|
|
captures the tab you are on.
|
|
|
|
## Scope
|
|
|
|
Capture has no switch, so scope is what keeps it honest. Three limits, all
|
|
enforced by Chrome rather than by this code:
|
|
|
|
1. **`host_permissions` is deliberately narrow** — the firm sites plus localhost
|
|
for the dashboard. The extension is *technically incapable* of reading any
|
|
other site, so an accidental capture of your bank or mail tab cannot happen
|
|
however the logic here is changed.
|
|
2. **Only the tab you are on.** `pickTab()` asks for the active tab of the
|
|
last-focused window *and* a host_permissions match. A firm tab sitting in the
|
|
background is not captured, and neither is anything else. Switch away and
|
|
capture simply stops until you switch back.
|
|
3. **One content script, same hosts.** `captcha-hook.js` runs at document_start
|
|
on the firm sites only. It wraps `turnstile.render` / `grecaptcha.render` /
|
|
`hcaptcha.render` and remembers what the page passed them — the sitekey, and
|
|
above all the callback a solved token has to be handed to, which a
|
|
programmatically rendered widget puts nowhere else. It reads nothing else and
|
|
sends nothing anywhere; the remembered arguments stay in the page's own world
|
|
until a `solveCaptcha` step comes to collect them.
|
|
|
|
To automate a different broker, add its pattern to `host_permissions` in
|
|
`manifest.json` and reload the extension — that alone scopes capture to it. Avoid
|
|
going back to `<all_urls>`: it would put every tab you visit in reach, and limit
|
|
1 is the only thing standing between this and scraping your whole browser. A new
|
|
firm needs its host in *both* lists — `host_permissions` and the `content_scripts`
|
|
matches — or captchas on that site will have no callback to fire.
|
|
|
|
## 2FA codes
|
|
|
|
On a firm site, the extension shows the machine's person's authenticator code
|
|
for that site in a small chip in the bottom-right corner, with a Copy button
|
|
(minimise it to a "2FA" pill; that sticks for the tab's session).
|
|
|
|
```
|
|
extension --GET /api/autobuyer/otp?host=<tab host>--> dashboard
|
|
dashboard --GET <master>/api/otp?person=<instance_id>&host=<tab host>--> master
|
|
```
|
|
|
|
The master holds the secrets and matches the host against each firm's **Site**
|
|
link (Firms page), then returns the codes assigned to this person and that firm
|
|
(Codes page). The chip only needs a code to be assigned — it doesn't wait for a
|
|
code field. It stays hidden for a minute after the runner last located
|
|
something, so automated clicks never land on it.
|
|
|
|
A new 2FA site needs its host in `host_permissions` (not the captcha
|
|
`content_scripts` list), a Site link on its firm on the master, and its codes
|
|
assigned on the master's Codes page.
|
|
|
|
## Vest session relay
|
|
|
|
Vest signs people in with Google (through Privy) in the browser and renews the
|
|
session with a device key the browser will not export, so the dashboard cannot
|
|
log in to Vest by itself. The extension relays the browser's session instead:
|
|
|
|
```
|
|
vest-relay.js (Vest tab, MAIN world) keeps the access token the Vest app last renewed
|
|
background.js relayVestSession lifts it every 10s, posts it when it changes
|
|
dashboard POST /api/vest/session holds it in memory for the firm's Vest client
|
|
```
|
|
|
|
- The hook only reads the Vest app's own `/v3/auth/refresh` and `/register`
|
|
answers. It renews nothing and sends nothing; the session stays the app's,
|
|
so the bot never logs the tab out.
|
|
- The token lives ~15 minutes and the app renews it by itself while the tab is
|
|
open. The tab is marked not auto-discardable, since a discarded tab stops
|
|
renewing.
|
|
- When the dashboard has a Vest firm (its credential is the login's wallet)
|
|
and no Vest tab is open, the extension opens one in the background. The app
|
|
signs back in from its 30-day session. After that the person signs in again
|
|
in that tab.
|
|
- This is the one place the extension reads a tab you are not looking at. It
|
|
only reads `next.vestmarkets.com` tabs, and only that one value.
|
|
|
|
## Notes
|
|
|
|
- The extension never captures the dashboard's own pages — otherwise it would
|
|
just mirror its own output back.
|
|
- `chrome://`, `about:` and Web Store pages cannot be scripted by any extension;
|
|
they're skipped.
|
|
- MV3 service workers are torn down when idle. Each poll makes an extension API
|
|
call, which keeps the worker alive; a 30-second alarm revives it if Chrome
|
|
kills it anyway. So worst-case cadence is 30s, normal cadence is the poll
|
|
interval.
|