dflow-kalshi-trading
DFlow Kalshi Trading
Buy, sell, and redeem YES/NO outcome tokens on Kalshi prediction markets. PM trades are imperative and asynchronous — submit, then poll until terminal.
Prerequisites
- DFlow docs MCP (
https://pond.dflow.net/mcp) — install per the repo README. This skill is the recipe; the MCP is the reference. Look up endpoint shapes, parameter details, error codes, and anything else field-level viasearch_d_flow/query_docs_filesystem_d_flow— don't guess. dflowCLI (optional, for command-line/agent use) — install per the repo README.
Choose your surface
- CLI — command line, scripts, local agents. Manages keys, signs, submits, polls.
- API — web/mobile apps with a browser wallet (Phantom, Privy, Turnkey, etc.). Wallet handles signing + RPC; app must proxy HTTP through its backend (the Trading API serves no CORS).
If unclear, ask once: "From the command line, or wired into an app?"
Workflows
All three workflows assume the user already has a market ledger mint (CLI; the marketLedger field on the Metadata API market object) or an outcome mint (API; yesMint / noMint) in hand. If they only have a ticker / event name, defer to dflow-kalshi-market-scanner.
One market, two settlement rails. Every initialized Kalshi market on DFlow exposes both a USDC rail and a CASH rail in market.accounts — each with its own marketLedger, yesMint, and noMint. They share an orderbook (the top-level yesBid / yesAsk / volume24hFp are market-wide), but trades and holdings are rail-scoped: USDC-rail YES tokens are a different SPL mint from CASH-rail YES tokens and aren't fungible. Default to the USDC rail unless the user holds CASH, explicitly asks for CASH, or the active DFlow vault only has CASH. Don't write defensive "fall back to CASH if USDC rail missing" code — it never fires, and it hides the rail choice from the user. State the default at the top of the script instead.
Settlement mint constants (Solana, Token-2022 for CASH and the classic SPL token program for USDC):
- USDC:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v - CASH:
CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH
Use these as the keys into market.accounts[mint] when picking a rail. There is no top-level market.settlementMint field on the /markets response (despite what some recipe snippets might suggest with market.accounts?.[market.settlementMint] — that pattern shows up in position-side code, where the rail is already known from the held outcome mint, not in market-discovery code). Key by the mint directly.
Buy (open or increase a YES/NO position)
- Confirm the buy gates (KYC + geo + maintenance window) are passable for this user — see Gotchas.
- Submit the order with the settlement mint as input (USDC or CASH) and the outcome mint as output.
- Poll status until terminal (
closed/expired/failed).
- CLI:
dflow trade <atomic-amount> USDC --market <marketLedger> --side yes|no— auto-polls for up to 120s.<FROM>accepts either the base58 mint or the shorthandUSDC/CASH(CLI resolves a small symbol set, same as spot).--markettakes themarketLedgerfor the settlement track matching the<from>arg — i.e.market.accounts[<USDC-or-CASH-mint>].marketLedgeron the Metadata API response. The CLI derives the YES/NO outcome mint from--side+<from>, so the samemarketLedgervalue is used for both--side yesand--side no. Don't pass ayesMint/noMinthere — those are inputs to the API surface, not the CLI. The docs' "market mint" and "market ledger mint" phrasing both refer to this one field.
"Buy N whole contracts" from a scan snapshot. Kalshi buys submit a USDC amount and get back as many whole contracts as it covers, refunding leftovers. When you're executing N contracts off a snapshotted yesAsk, compute Math.ceil(N * yesAsk * 1e6) atomic USDC and optionally add a small buffer (≤ ~1%, a few basis points is typically enough) so a tick-up in the ask between scan and submit doesn't leave you with N-1. Leftover stablecoin is refunded by the CLP, so over-funding slightly is cheap insurance. Don't over-fund by more than a percent or two — at some point it's no longer insurance, it's a different order size.
- API:
GET /order?userPublicKey=&inputMint=<settlement>&outputMint=<yesMint|noMint>&amount=<atomic>, then sign + submit + poll/order-status. The API takes the outcome mint directly (no--marketindirection). Field details via the docs MCP.
Sell (decrease or close)
Flip the mints — outcome in, settlement out. No KYC required. No --market / --side on the CLI; pass the outcome mint as the FROM positional and the CLI auto-resolves the settlement mint.
- CLI:
dflow trade <atomic-outcome> <outcome-mint> - API: same
/ordercall as buy, with input/output mints flipped.
Redeem (post-settlement)
Once the market is determined / finalized and redemptionStatus: "open", redemption is just a regular sell of the winning side back to the settlement mint. No special flag, no KYC.
What to ASK the user (and what NOT to ask)
Trade shape — infer if unambiguous, confirm if not:
- Operation — buy / sell / redeem. Infer from intent ("bet on X" → buy YES; "cash out" → sell; "my YES tokens just won" → redeem). Don't make the user pick a mode.
- Market + side — for CLI: the
marketLedger(frommarket.accounts[<settlement-mint>].marketLedger) plus--side yes|no. For API: the YES or NO outcome mint directly asoutputMint. - Settlement rail — USDC or CASH. Both exist on every initialized market; default to USDC unless the user says otherwise. This determines which
marketLedger(CLI) oryesMint/noMint(API) you use. - Amount in atomic units — every Kalshi mint is 6 decimals (
8_000_000= $8 of USDC;10_000_000= 10 outcome tokens). Buys submit settlement-mint amounts (USDC/CASH); sells/redeems submit outcome-token amounts.
Infra — always ask, never infer:
- API only — wallet pubkey (base58). Required for every
/ordercall. - API only — DFlow API key (only when the script is making direct HTTP calls to
/orderor other Trade API endpoints; pure CLI scripts don't need one — see the "two auth paths" gotcha). Ask with a clean, neutral question: "Do you have a DFlow API key?" Don't presuppose where the key lives — phrasings like "do you have it in env?" or "isDFLOW_API_KEYset?" nudge the user toward env-var defaults they didn't ask for. Surface the choice; don't silently fall back to env or to dev. It's one key for everything DFlow — samex-api-keyunlocks the Trade API and the Metadata API, REST and WebSocket. If yes → prod hosthttps://quote-api.dflow.netwithx-api-keyon every request. If no → dev hosthttps://dev-quote-api.dflow.net(same features, rate-limited). Point them athttps://pond.dflow.net/build/api-keyfor a prod key. When you generate a script that does its own HTTP, log the resolved host + key-presence at startup so the user can see which rails they're on. - Priority fee (both surfaces) — "Any priority-fee preference, or just use DFlow's default?" Default on both surfaces = DFlow-auto, capped at 0.005 SOL (documented default on
/order). Surface this explicitly so the user knows the lever exists for congested periods or cost-sensitive flows. Don't editorialize about what percentage of trades this covers — DFlow doesn't publish one and you don't know.- API — pass
prioritizationFeeLamportson/order:auto|medium|high|veryHigh|disabled| integer lamports. Live estimates for tuning:GET /priority-fees(snapshot),/priority-fees/stream(WebSocket). (/intentdoesn't apply to Kalshi — PM is imperative-only.) - CLI — no tuning flag;
dflow tradealways uses the server-side default. If the user needs finer control (an exact lamport value, ordisabled), they'll have to drop to the API.
- API — pass
- Sponsored / gasless (API only — skip for CLI) — "Does the user need to hold SOL for this trade, or is your app covering fees?" Default = user pays everything. Two levers on
/order, depending on what you want to cover:sponsor=<sponsor-wallet-base58>— sponsor pays tx fee + ATA creation + market-init. Tx must be co-signed by both user and sponsor. OptionalsponsorExec=true|falsepicks sponsor-executes (default) vs. user-executes.predictionMarketInitPayer=<wallet>— covers only the one-time market-init rent; user still signs and pays their own tx fee and ATA creation. Useful when you only want to eat the init cost. Markets can also be pre-initialized out-of-band viaGET /prediction-market-init.- The CLI doesn't support either sponsorship lever.
Do NOT ask about:
- RPC — CLI users set it during
dflow setup. API users on a browser wallet never need their own RPC (the wallet handles it). Only ask if signing server-side. When one is needed, suggest Helius. - Slippage — both surfaces default to
"auto", which is right for CLP-sourced fills. Override only on explicit user request (--slippageCLI;predictionMarketSlippageBpsAPI). - Platform fee — defer to
dflow-platform-feesif the user pivots there.
Gotchas (the docs MCP won't volunteer these)
- Token-2022 outcome mints. Kalshi outcome mints use the Token-2022 program. Declarative trades (
/intent) don't support Token-2022 — that's why Kalshi is imperative-only. - All Kalshi mints are 6 decimals. USDC, CASH, every outcome token. Always pass atomic units to the API.
- Buys are whole-contract only — no fractional contracts. Submit a USDC/CASH amount; the system buys as many whole contracts as that amount covers and refunds any leftover stablecoin. Per-order floor is 0.01 USDC, but the practical floor in any given market is one contract at the current YES/NO price (e.g. if YES is trading at 0.43, you need ≥
430_000atomic = $0.43). Quote first if the user is anywhere near the floor. - Async fills, no exceptions. PM
/orderreturnsexecutionMode: "async". The transaction landing onchain is not the fill — the order can still expire or fail in the CLP. Always poll/order-statusto a terminal state. CLI auto-polls for 120s; on timeout, follow up withdflow status <orderAddress> --poll. - Buy gates exist; check once per session, not per call.
- Proof KYC — required to buy (not sell, not redeem). Hit
GET https://proof.dflow.net/verify/{address}(public, no auth) once at session start, cache{ verified: boolean }, gate the buy UI off the cache./orderis still authoritative; on the rare miss, fall back onunverified_wallet_not_allowed(API) /PROOF_NOT_VERIFIED(CLI) usingdetails.deepLink. - Geoblock — restricted in some jurisdictions. API builders enforce in their own UI (cache the user's country once per session). The CLI handles this internally and returns
category: "geoblock". Policy:https://pond.dflow.net/legal/prediction-market-compliance.
- Proof KYC — required to buy (not sell, not redeem). Hit
- Maintenance window. Kalshi is offline Thursdays 3:00–5:00 AM ET, every week. CLPs stop serving routes;
/orderreturnsroute_not_found(the CLI annotates with a maintenance note). Block PM submissions for the whole window. route_not_foundis a catch-all. Wrong mint, amount below the contract-price floor, no liquidity right now, or the maintenance window. Verify mint, atomic units, and that the amount covers ≥ 1 contract before assuming illiquidity.- Browser apps must proxy. The Trading API serves no CORS — call it from a backend (Next.js API route or equivalent), never directly from the browser.
- CLI shell-outs authenticate themselves; direct HTTP calls don't. If your script or backend shells out to
dflow trade, that leg uses the CLI's stored config fromdflow setup(key, wallet, RPC) — you plumb nothing for CLI invocations. If the same script also hits the Trade API or Metadata API directly over HTTP (e.g. scanner-style discovery, your own/ordercall,/quote, sibling HTTP tools), that HTTP client needs the key handed in explicitly (env var,.env,--api-keyflag, header). The CLI's stored key is not reusable by a sibling HTTP client, and an env-var key is not injected into the CLI either — they're independent plumbing sites for the same DFlow key. Only ask about an API key for the HTTP portion; pure CLI scripts don't need one.
When something doesn't fit
For anything not covered above — full parameter lists, full error tables, response schemas, partial-fill handling, rare flags, new features — query the docs MCP (search_d_flow, query_docs_filesystem_d_flow). Don't guess.
For runnable code, point the user at the DFlow docs recipes (each links to the DFlow Cookbook Repo for clone-and-go): /build/recipes/prediction-markets/increase-position, /build/recipes/prediction-markets/decrease-position, /build/recipes/prediction-markets/redeem-outcome-tokens.
Sibling skills
Defer if the user pivots to:
dflow-kalshi-market-scanner— discover markets, filter by event/categorydflow-kalshi-market-data— live prices, orderbooks, streamsdflow-kalshi-portfolio— view positions, unrealized P&Ldflow-proof-kyc— set up Proof verification on a walletdflow-platform-fees— charge a builder fee on PM tradesdflow-spot-trading— non-Kalshi token swaps
More from dflowprotocol/dflow-skills
dflow-spot-trading
Swap any pair of Solana tokens via DFlow. Use when the user wants to trade, swap, or convert tokens on Solana, get a price quote, build a swap UI, tune priority fees so a swap lands under congestion, or build a gasless / sponsored swap where the app pays fees. Covers both the `dflow` CLI and the DFlow Trading API. Do NOT use for Kalshi prediction-market YES/NO trades or builder-side platform fees.
21dflow-platform-fees
Monetize a DFlow integration by collecting a builder-defined fee on trades your app routes through the Trade API — either a fixed percentage (spot + PM) via `platformFeeBps`, or a probability-weighted dynamic fee (PM outcome tokens only) via `platformFeeScale`. Use when the user asks "how do I take a cut of trades?", "add a builder fee", "monetize my swap UI", "charge a platform fee", "how does platformFeeBps / platformFeeScale work?", or "where do my fees get paid?". Do NOT use to run a trade itself (use `dflow-spot-trading` or `dflow-kalshi-trading` — both also cover priority fees and sponsored / gasless flows).
21dflow-kalshi-market-data
Read market data for a known Kalshi prediction market on DFlow — orderbook, trades, top-of-book prices, candlesticks, forecast-percentile history, and Kalshi in-game live data — via one-shot REST snapshots, historical ranges, or live WebSocket streams. Use when the user asks "show me the orderbook for X", "get last hour of trades", "build a live price ticker", "stream orderbook depth", "pull 1-minute candles for the last day", "watch in-game scores for this sports market", or "alert me when the orderbook moves". Do NOT use to discover markets matching a criterion (use `dflow-kalshi-market-scanner`), to place orders (use `dflow-kalshi-trading`), or to read a user's own positions/P&L (use `dflow-kalshi-portfolio`).
21dflow-proof-kyc
Integrate DFlow Proof — a Solana wallet identity-verification primitive (Stripe Identity under the hood) — for either (a) gating your own app's features behind KYC, or (b) completing the mandatory verification step for Kalshi prediction-market buys on DFlow. Use when the user asks "how do I KYC a wallet?", "check if a wallet is verified", "add KYC to my DeFi app", "handle unverified_wallet_not_allowed / PROOF_NOT_VERIFIED", "redirect to dflow.net/proof", or "gate a feature by jurisdiction or identity". Do NOT use to actually place trades (use `dflow-kalshi-trading`), for geoblocking (separate concern, handled inline in the trading skill), for age gating (Proof doesn't currently verify age), or for spot swaps (no KYC required).
20dflow-kalshi-portfolio
View what a wallet holds on DFlow's Kalshi prediction markets — current positions, unrealized mark-to-market, realized P&L, activity history, and redeemable winners. Use when the user asks "what are my positions?", "what do I own?", "am I up or down?", "what's my fill history?", "what can I redeem?", "mark my portfolio to market", or "show me this wallet's DFlow activity". Read-only. Do NOT use to place sells or redemptions (use `dflow-kalshi-trading`), for market-wide data unrelated to a wallet (use `dflow-kalshi-market-data`), or to discover new markets (use `dflow-kalshi-market-scanner`).
20dflow-kalshi-market-scanner
Find Kalshi prediction markets on DFlow that match a criterion — arbitrage (YES+NO<$1), cheap long-shots, near-certain short-dated plays, biggest movers, widest spreads, highest volume, closing soonest, and series/event-level scans. Use when the user asks "where's the free money?", "any mispriced markets?", "cheap YES with volume", "what moved today?", "markets closing soon", "cheapest YES in this event", "top markets by volume", or "alert me when X happens" (streaming). Do NOT use to place orders (use `dflow-kalshi-trading`), to view a user's own positions (use `dflow-kalshi-portfolio`), or for general live-data plumbing unrelated to a scan (use `dflow-kalshi-market-data`).
20