Skip to content

quicknode/canary-orders

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐀 canary

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.


Powered by Quicknode Solana gRPC

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.


What you'll see (β‰ˆ30 seconds)

  1. 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.
  2. As spot nears your level the bird gets agitated and the word changes to Getting close….
  3. 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).
  4. 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.

The canary firing ↑ 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.


Quick start (zero config)

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 ~5s

npm run dev runs the single backend process and the dashboard together; the browser opens automatically.

Run the real pipeline

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

How it works (the real pipeline)

   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
  1. Place the canary. Use Jupiter's Trigger API to place a tiny resting canary order (~$5) buying BTC with USDC at your target. createOrder builds 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.
  2. 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.
  3. 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.
  4. 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 / swap actions 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:


Architecture

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

The strategy hook

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

⚠️ swap moves real money. It's gated behind I_UNDERSTAND_REAL_MONEY=true and 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.


Demo modes

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.


Prerequisites

  • Node.js v22+.
  • A Quicknode Solana Mainnet endpoint with the Solana gRPC add-on enabled. gRPC runs on port 10000; pass the host as https://NAME.solana-mainnet.quiknode.pro:10000 and 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.

Configuration

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

Optional: route placement through Quicknode Metis

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.


Which BTC? (cbBTC)

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.


Notes & caveats

  • The strategy hook is real, and yours. On fire we call src/server/onTrigger.ts with the configured action (log / webhook / swap). swap moves 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). live is 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 with ORDER_ACCOUNT=<pubkey> npm run cancel.
  • Don't commit secrets. .env and 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors