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.