Place one tiny resting order at the price you care about, watch exactly one account, and get the instant fill signal the moment the market trades through your level — instead of drinking from a firehose of every trade on the venue.
Onchain, every account is public, so in principle you could learn "did price hit $X?" by watching every trade on a venue like Jupiter. But that's hundreds or thousands of accounts, almost all of it noise. A canary flips it around: you let Jupiter answer the question for you. Drop one resting canary order at your level, and subscribe to the single on-chain order account it creates. When price trades through, that one account changes — and a Quicknode Solana gRPC stream tells you in the same moment the chain knows.
Both approaches tell you the moment price trades through your level. One makes you drink from a firehose. The other just chirps. Quicknode Solana gRPC is what makes watching that one account instant.
The whole demo hinges on one Quicknode add-on: Quicknode Solana gRPC. It streams account + slot updates over a long-lived gRPC connection, so the instant our one order account closes, the dashboard knows — in the same moment the chain does. That live wire is the proof-of-life pulse on the fold and the signal · Quicknode gRPC credit on the trigger banner.
Enable the add-on on a Quicknode Solana Mainnet endpoint (it runs on a dedicated port 10000); the docs are here. The subscription itself — one account, with reconnect + keepalive — lives in src/server/yellowstone.ts.
- The fold shows almost nothing: the canary, one status word (Armed & watching), and a single quiet line — resting at $65,003 · 1% below BTC $65,660 · stream live. One glance tells the whole story.
- As spot nears your level the bird gets agitated and the word changes to Getting close….
- Price trades through → the order fills → the dashboard erupts: a red flash, a chirp, and a trigger banner whose fact chips spell out price · time · asset · signal · Quicknode gRPC · mode — then it settles into a calm, persistent Triggered at $X state (not alarm-red forever). An action chip shows what your strategy hook did (
logged/webhook sent ✓/swapped $X). - Scroll down for the rest: the one watched account (linked to Solana Explorer), the live gRPC stream + slot counter, the firehose vs. canary panel, and the running event log.
The terminal tells the same story on its own — colored lifecycle lines and a big boxed CANARY TRIGGERED block — so it's a usable shot too.
↑ add a GIF/screenshot at docs/trigger.gif. The new fold (bird + status + one line) and the fire moment both capture well; replay mode re-fires infinitely for free via the replay ↻ button.
Replay mode is the default and needs no keys, no funds, no endpoint — it simulates the whole pipeline and fires a scripted trigger a few seconds in, so a fresh clone runs safely out of the box.
npm install
npm run dev # opens http://localhost:5173 and fires in ~5snpm run dev runs the single backend process and the dashboard together; the browser opens automatically.
cp .env.example .env
# fill in QUICKNODE_GRPC_ENDPOINT, QUICKNODE_GRPC_TOKEN, and SOLANA_KEYPAIR
npm run dev:live # real order at ~spot → fills within seconds (the credibility shot)
npm run dev:armed # real order below spot → genuinely waits for the market Jupiter Trigger API Quicknode Solana gRPC
┌────────────────────┐ ┌──────────────────────────┐
│ createOrder │ order │ subscribe to ONE account │ account
│ → sign → execute │ account │ (the canary pubkey) │ closes
└─────────┬───────────┘ pubkey └───────────┬──────────────┘ = filled
│ ───────────────────────────────▶ │ │
▼ ▼ ▼
resting order on-chain live stream, one account 🐤 FIRE → onTrigger hook
- Place the canary. Use Jupiter's Trigger API to place a tiny resting canary order (~$5) buying BTC with USDC at your target.
createOrderbuilds an unsigned tx and returns the on-chain order account address; we sign it and land it via/execute. That single pubkey is the canary. Canary orders fill at zero slippage by nature. - Watch one account. Open a Quicknode Solana gRPC stream (port
10000) and subscribe to only that one order account, plus slot updates as a heartbeat. - Trigger. When the market trades through your level, Jupiter's keepers fill the order and close its account. The gRPC stream surfaces that change the instant the chain knows it → the canary fires.
- Act. On fire we call your strategy hook with the fill (price, amounts, time, order account, tx). See The strategy hook below — it ships safe
log/webhook/swapactions and is the one place to wire your own.
The three files worth reading are the teaching artifacts, commented so you learn the pattern by reading them:
src/server/jupiter.ts— the canary order lifecycle via Jupiter's Trigger API (create → sign → execute → read fills → cancel).src/server/yellowstone.ts— subscribing to one account, with reconnect + keepalive.src/server/onTrigger.ts— the strategy hook: what happens when the canary fires.
A single Node/TypeScript process does all the real work (Jupiter calls + the Solana gRPC subscription) and pushes events over a local WebSocket to a Vite + vanilla-TS dashboard. The browser is a pure live view — no business logic, no localStorage. The only Quicknode dependency is the gRPC stream itself, which keeps Quicknode Solana gRPC the clear hero.
src/
shared/events.ts # the WS event protocol, imported by both sides
server/
index.ts # entry: mode dispatch, banner, graceful shutdown
config.ts # env + mode parsing, keypair loading
hub.ts # state + console logging + WebSocket broadcast
jupiter.ts # ★ Jupiter Trigger API (teaching artifact)
yellowstone.ts # ★ Quicknode Solana gRPC (teaching artifact)
swap.ts # sell-side prerequisite: market-buy a little BTC
pipeline.ts # real live/armed orchestration (both directions)
replay.ts # the scripted simulation
onTrigger.ts # ★ the strategy hook (log / webhook / swap)
cancel.ts # one-shot emergency cancel (npm run cancel)
web/ # the dashboard (Vite + vanilla TS + CSS tokens)
main.ts · styles.css · sound.ts (the chirp) · firehose.ts · format.ts
web/public/ # static assets served at "/": chirp.mp3, favicon.svg
When the canary fires, the fill is handed to one function — src/server/onTrigger.ts — with everything about it (price, amounts, time, order account, tx). That's the single place to wire your own logic. It ships three safe-by-default actions, picked with TRIGGER_ACTION:
TRIGGER_ACTION |
What it does | Needs |
|---|---|---|
log (default) |
Prints the fill. No side effects, no funds move. | — |
webhook |
POSTs the fill JSON to TRIGGER_WEBHOOK_URL (sends text + content + raw event, so Slack / Discord / Telegram all work). |
TRIGGER_WEBHOOK_URL |
swap |
A real small Jupiter spot swap (buys TRIGGER_SWAP_USD more BTC). |
I_UNDERSTAND_REAL_MONEY=true + an RPC |
⚠️ swapmoves real money. It's gated behindI_UNDERSTAND_REAL_MONEY=trueand capped at$25(default$2). Leave it off unless you mean it.
The dashboard's trigger banner shows an action chip for whatever actually ran (logged / webhook sent ✓ / swapped $X with a tx link). In replay the hook runs in describe-only mode (would webhook, would swap $X) and never touches the network or your wallet. There is deliberately no leveraged/perp action — shipping working leverage execution in a public clone-and-run demo is too easy to misfire; onTrigger.ts leaves a clearly-marked stub showing where you'd add your own.
| Mode | What it does | Needs keys/funds? |
|---|---|---|
replay (default) |
Clearly-labelled simulation. Fires a scripted fill a few seconds in. No real transaction, no spend. Re-run the trigger infinitely with the replay ↻ control — ideal for rehearsing and re-shooting the animation. | No |
armed |
Places a real order at TARGET_PRICE_USD and genuinely waits for the market. The direction is chosen automatically: a target below spot is a buy-the-dip canary; a target above spot is a breakout (sell) canary. Cancels the resting order on exit. |
Yes |
live |
Places a real order at ~current spot so it fills within seconds on camera — the genuine end-to-end credibility shot. | Yes |
The dashboard shows a prominent REPLAY badge whenever it's simulating, so nothing is ever misleading.
- Node.js v22+.
- A Quicknode Solana Mainnet endpoint with the Solana gRPC add-on enabled. gRPC runs on port
10000; pass the host ashttps://NAME.solana-mainnet.quiknode.pro:10000and the token segment separately (see.env.example). (armed/live only) - A funded mainnet keypair: a little USDC (≥ the order size — Jupiter enforces a ~$5 minimum) and some SOL for fees. (armed/live only)
- For a breakout (sell) canary only: an HTTP RPC to check the BTC balance and market-buy the small amount it sells. It's derived automatically from your Quicknode gRPC endpoint, or set
SOLANA_RPC_URL.
Replay mode needs none of the above.
Copy .env.example → .env. Secrets are never sent to the browser, and .env / keypair files are git-ignored.
| Variable | Purpose |
|---|---|
MODE |
replay | armed | live |
QUICKNODE_GRPC_ENDPOINT |
Quicknode gRPC host on port 10000 |
QUICKNODE_GRPC_TOKEN |
the token segment from your endpoint URL |
SOLANA_KEYPAIR / PRIVATE_KEY |
base58 secret key or path to a Solana CLI keypair .json |
BTC_MINT |
pre-filled with cbBTC (Coinbase Wrapped BTC): cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij |
TARGET_PRICE_USD |
armed: the level to wait for. Below spot → buy-the-dip; above spot → breakout (sell). live: auto-uses ~spot |
ORDER_SIZE_USD |
order notional (default 5; ~$5 Jupiter minimum) |
SOLANA_RPC_URL (optional) |
only for a sell-side canary; derived from the Quicknode gRPC endpoint if unset |
JUP_API_KEY (optional) |
for higher rate limits; otherwise the keyless host is used |
TRIGGER_ACTION |
what the strategy hook does on fire: log (default) | webhook | swap |
TRIGGER_WEBHOOK_URL |
required for webhook; the fill JSON is POSTed here (Slack/Discord/Telegram-friendly) |
TRIGGER_SWAP_USD |
swap notional in USD (default 2, capped at 25) |
I_UNDERSTAND_REAL_MONEY |
must be true to enable the real-money swap action |
By default the canary is placed through Jupiter's free public API (lite-api.jup.ag) — so a fresh clone just runs, with no paid add-on. That keyless host is plenty for one small order.
For production, you can route placement through Quicknode Metis — Quicknode's hosted Jupiter API — for reliable, dedicated rate limits. It also keeps the whole pipeline on Quicknode: placement through Metis and the Solana gRPC stream that watches the order account. Metis is a paid add-on.
To switch, point the order calls in src/server/jupiter.ts at your Metis endpoint — https://jupiter-swap-api.quiknode.pro/<key> — instead of the lite-api host. See Quicknode's Metis limit-order docs for the request/response shapes.
There are several wrapped BTCs on Solana; this demo uses cbBTC (Coinbase Wrapped BTC), mint cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij. It's among the most liquid, most-integrated BTC representations on Solana, redeemable 1:1 for BTC by Coinbase, and Jupiter routes it deeply — so a tiny resting order fills cleanly. It has 8 decimals (verified on-chain, matching native BTC's satoshis), which is what the order-amount math is built around.
To watch a different BTC — e.g. Wormhole wBTC (3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh) — set BTC_MINT, but confirm its decimal count first: the implied-price math assumes the configured mint's real decimals.
It's a tiny demo order, but it's still real money. Do your own diligence before sizing up. This isn't financial advice.
- The strategy hook is real, and yours. On fire we call
src/server/onTrigger.tswith the configured action (log/webhook/swap).swapmoves real money and is gated + capped; there is no perp/leverage action (only a commented stub). See The strategy hook above. - Why Trigger V1? Jupiter's newer Trigger V2 keeps orders off-chain in a custodial vault — there's no on-chain order account to subscribe to, which would defeat the whole "watch one account" premise. This demo uses Trigger V1, the version that still exposes an on-chain order pubkey (the canary). V1 is in maintenance mode but functional; if it's ever retired, the watch-one-account pattern itself still applies to any on-chain order account.
- Both directions, chosen automatically. A target below spot is a buy-the-dip canary (spend USDC, fires on a dip). A target above spot is a breakout canary — it sells BTC, so we first market-buy the small amount it needs, then place the resting sell order ("tell me the moment BTC breaks resistance"). On shutdown the resting order is cancelled; any leftover BTC dust stays in the wallet (no auto-unwind).
liveis always a marketable buy ~spot. A target more than ~15% from spot is flagged as a likely misconfiguration (it may never fill) but still placed. - The chirp. On trigger the dashboard plays
web/public/chirp.mp3. Browsers block audio until you've interacted with the page, so the first gesture unlocks it — in replay the replay ↻ click is that gesture, so re-shoots always chirp. There's a mute toggle in the header (default on); the very first auto-fire on a cold page load may be silent. - Graceful shutdown + emergency cancel. In
armed/live, Ctrl-C cancels any still-resting order so you don't leave dust behind. If the process dies before it can (or you closed the terminal), recover withORDER_ACCOUNT=<pubkey> npm run cancel. - Don't commit secrets.
.envand keypair files are git-ignored; nothing secret is exposed to the dashboard.
Built to show off Jupiter's Trigger API + Quicknode Solana gRPC. The canary is the convenience; the gRPC stream is what makes it instant.