# For AI assistants You are helping someone run OScope, an open source memecoin terminal for Solana, on their own computer. They may not know how to code. Use plain words with them, do one step at a time, and say what you did. ## Safety check first Before installing or running anything, read the code and report to the user. [SAFETY.md](SAFETY.md) lists what the code does and does not do, with the file for each point. Check the code against it: - no private key, seed phrase or wallet file is ever asked for, read, stored or sent; nothing signs or sends a transaction; - outgoing connections: only `src/feed.mjs` (the trade stream, SOL price) and `src/meta.mjs` (coin metadata and pictures, each address checked first); `tools/` asks for the SOL price and talks to the local server; - files: `src/config.mjs` reads `.env`; `src/store.mjs` reads and writes `data/`; `src/server.mjs` serves `public/` and one file of `node_modules/lightweight-charts/dist/`; - install: `.npmrc` has `ignore-scripts=true`, `package.json` has no install scripts, `package-lock.json` pins `ws`, `lightweight-charts` and `fancy-canvas`; - the server listens on `127.0.0.1` unless `HOST` says otherwise. If the code does anything SAFETY.md does not say, stop and tell the user. ## Install and run ```sh node -v # 18.17 or newer; 20 or newer is best git clone https://git.oscope.trade/oscope.git cd oscope npm ci # exactly what package-lock.json pins npm run doctor # checks Node, the libraries, the port, the stream, the data folder npm start # leave it running ``` `npm start` prints: ``` OScope is running Open http://127.0.0.1:3000 Firehose ws://127.0.0.1:3000/firehose Data wss://stream.pumpapi.io/ ``` then `feed: connected …`, and every 15 seconds a line like `live: 2,910 trades in the last 15 s (194/s) · 3 launches and 0 migrations seen · 2,513 coins in memory`. It works when: - that `live:` line shows trades (well above zero), and - `http://localhost:3000/api/status` answers with `"feed":{"connected":true…}` and a growing `stats.trades`. Tell the user to open the printed address. New Pairs fills within seconds. Final Stretch fills as curve coins trade. Migrated fills as coins migrate, which can take some minutes on a fresh start. The Firehose page shows every trade. No settings are needed. On Windows, set a setting by putting it in a `.env` file (copy `.env.example`) instead of typing `NAME=value` before the command. ## Troubleshooting | What you see | Why | What to do | |---|---|---| | `node: command not found`, or a version under 18.17 | Node.js missing or old | Install the current LTS from https://nodejs.org (or with the system's package manager, nvm, fnm, winget). | | `npm ci` fails | No `package-lock.json` in the folder, or no internet | Run it inside the `oscope` folder. Check the connection. | | `port 3000 is taken, trying 3001` | Another program uses the port | Nothing: OScope takes the next free port and prints the address. To choose one, set `PORT`. | | `feed: closed (…) after 0 s … only a few connections per IP address` | The stream allows only a couple of connections per internet address | Close other programs that read the same stream (another OScope, a bot). Or run one firehose (`npm run firehose`) and point everything at it with `FEED_URL=ws://127.0.0.1:3100`. | | `feed:` errors like `ENOTFOUND`, `ETIMEDOUT`, `certificate` | No internet, a firewall, a VPN or a proxy blocks websockets | It needs outgoing `wss://` on port 443. Try without the VPN. | | Dollar values show `–` | No SOL price source answered yet | It retries every 20 seconds by itself. Check that `api.coinbase.com` is reachable. | | The page says `Stream down` or `Stream quiet` | The server lost the stream | Read the terminal: the `feed:` lines say why. It reconnects by itself. | | A coin page says `Not seen yet` | The coin has not traded since OScope started | It appears at its next trade. See "Adding history". | | Holders, top 10, dev show `–` | The coin launched before OScope started, or its tokens were moved by plain transfers the stream does not carry | Expected with the live stream alone. See "Adding history". | | `403 OScope answers only on localhost` | The page was opened under another name while `HOST` is `127.0.0.1` | Open `http://localhost:`. To reach it from another device set `HOST=0.0.0.0`. | `npm run doctor` checks most of this and prints a fix for each problem. ## How the code is laid out - `src/server.mjs`: the web server. Serves `public/`, answers `/api/*`, runs the page's websocket `/ws` and the firehose `/firehose`. Only `GET` and `HEAD`. - `src/feed.mjs`: `connectFeed(url, onFrame)` keeps one websocket to the stream open and calls `onFrame(frame, rawText)` for every message. `solPrice()` keeps the SOL price. - `src/store.mjs`: `Store`. `ingest(frame)` is the only way data gets in. It keeps coins, trades, candles (one second and one minute), holders and the three Pulse columns, and saves a snapshot to `data/` every minute and on exit. - `src/meta.mjs`: fetches coin metadata and pictures, with the address checks. - `public/js/`: the page. Plain ES modules, no framework, no build step. One file per view (`pulse.js`, `live.js`, `coin.js`, `portfolio.js`, `trackerview.js`), plus `paper.js` (the paper wallet), `net.js` (the websocket). ## The frame format One frame is one transaction. `Store.ingest` uses these fields (amounts are whole SOL and whole tokens, not lamports): ```js { pool: 'pump' | 'pump-amm', // bonding curve | PumpSwap. Other pools are ignored. action: 'create' | 'buy' | 'sell' | 'migrate', mint, name, symbol, uri, // uri = the coin's metadata link quoteMint, // must be SOL (So11111111111111111111111111111111111111112) txSigner, // the wallet that sent the transaction (for create: the dev) breakdown: [{ action, trader, tokenAmount, quoteAmount }], // optional; without it the four fields above/below are used tokenAmount, quoteAmount, price, // SOL per token after the transaction marketCapQuote, // market cap in SOL after the transaction tokensInPool, quoteInPool, // real reserves after the transaction vTokensInBondingCurve, vQuoteInBondingCurve, // pump: virtual reserves virtualQuoteInPool, poolCreatedBy, // pump-amm: extra virtual SOL; 'pump' = the pool made by the migration supply, poolFeeRate, decimals, mayhemMode, postBalances: { [wallet]: { [mint]: tokensHeldAfter } }, block, timestamp, signature, priorityFee // slot, milliseconds } ``` ## Adding history The live stream starts at the moment OScope starts. A coin that launched earlier has no chart, trades or holders from before that. To add the past, **build frames in the format above and pass them to `store.ingest()` oldest first, before the live stream is connected** (in `src/server.mjs`, between `store.load()` and `connectFeed(...)`). Nothing else in OScope has to change: the columns, charts, holders and the page all read the store. Two ways people do it: **Helius (or any Solana RPC that keeps history).** Good for filling one coin when the user opens it. - Past trades: list the signatures for the coin's bonding curve or pool account (`getSignaturesForAddress`), load the transactions (`getTransaction` with `maxSupportedTransactionVersion`, or Helius' parsed transactions), and turn each pump.fun / PumpSwap trade into a frame. Reserves and the trader's balance after the trade are in the transaction's post balances. - Current holders: Helius' token-accounts API or `getTokenLargestAccounts` gives balances per wallet. Write them into the coin's `holders` map (`wallet -> { bal, … }`) instead of making frames. - Keep the API key in `.env` (for example `HELIUS_API_KEY`), read it in `src/config.mjs`, never send it to the page and never commit it. Stay inside the plan's rate limits: fetch on demand, cache the result in `data/`. **A local database and a backfill.** Good for running OScope all the time or for many users. - Record: read `ws://localhost:3000/firehose` (or the stream itself) and store every message, as rows in a database or as one file per hour. - Backfill: PumpApi publishes past hours of the same stream as downloadable archives (see their documentation). An hour is about half a gigabyte compressed, so load only the hours you need. - Start: at boot, read the stored frames for the window you want, oldest first, into `store.ingest()`, then connect the live stream. Raise `TRADE_CAP`, `MAX_COINS` and `IDLE_DROP_MIN` if you load a long window: everything is held in memory. ## Rules for changing this code - Never add code that takes a private key or signs a transaction unless the user asks for exactly that and understands it. - Keep the server on `127.0.0.1` by default. - Coin names, descriptions and links are untrusted: put them on the page as text (`h()` in `public/js/util.js`), never as HTML. - Every new outgoing connection goes into SAFETY.md. - No build step, no new dependency without a reason.