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 via / query_docs_filesystem_d_flow
— don't guess.
- CLI (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
field on the Metadata API market object) or an
outcome mint (API;
/
) 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
— each with its own
,
, and
. They share an orderbook (the top-level
/
/
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
when picking a rail. There is
no top-level field on the
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 ( / / ).
- CLI:
dflow trade <atomic-amount> USDC --market <marketLedger> --side yes|no
— auto-polls for up to 120s. accepts either the base58 mint or the shorthand / (CLI resolves a small symbol set, same as spot). takes the for the settlement track matching the arg — i.e. market.accounts[<USDC-or-CASH-mint>].marketLedger
on the Metadata API response. The CLI derives the YES/NO outcome mint from + , so the same value is used for both and . Don't pass a / here — 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
, 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 . The API takes the outcome mint directly (no indirection). Field details via the docs MCP.
Sell (decrease or close)
Flip the mints — outcome in, settlement out.
No KYC required. No
/
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 call as buy, with input/output mints flipped.
Redeem (post-settlement)
Once the market is
/
and , 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 (from
market.accounts[<settlement-mint>].marketLedger
) plus . For API: the YES or NO outcome mint directly as .
- Settlement rail — USDC or CASH. Both exist on every initialized market; default to USDC unless the user says otherwise. This determines which (CLI) or / (API) you use.
- Amount in atomic units — every Kalshi mint is 6 decimals ( = $8 of USDC; = 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 call.
- API only — DFlow API key (only when the script is making direct HTTP calls to or 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 "is set?" 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 — same unlocks the Trade API and the Metadata API, REST and WebSocket. If yes → prod host
https://quote-api.dflow.net
with on every request. If no → dev host https://dev-quote-api.dflow.net
(same features, rate-limited). Point them at https://pond.dflow.net/build/api-key
for 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 ). 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
prioritizationFeeLamports
on : | | | | | integer lamports. Live estimates for tuning: (snapshot), (WebSocket). ( doesn't apply to Kalshi — PM is imperative-only.)
- CLI — no tuning flag; always uses the server-side default. If the user needs finer control (an exact lamport value, or ), they'll have to drop to the API.
- 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 , 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. Optional picks 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 via GET /prediction-market-init
.
- The CLI doesn't support either sponsorship lever.
Do NOT ask about:
- RPC — CLI users set it during . 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 , which is right for CLP-sourced fills. Override only on explicit user request ( CLI;
predictionMarketSlippageBps
API).
- Platform fee — defer to if 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 () 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 ≥ atomic = $0.43). Quote first if the user is anywhere near the floor.
- Async fills, no exceptions. PM returns . The transaction landing onchain is not the fill — the order can still expire or fail in the CLP. Always poll to a terminal state. CLI auto-polls for 120s; on timeout, follow up with
dflow 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 , gate the buy UI off the cache. is still authoritative; on the rare miss, fall back on unverified_wallet_not_allowed
(API) / (CLI) using .
- 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 . Policy:
https://pond.dflow.net/legal/prediction-market-compliance
.
- Maintenance window. Kalshi is offline Thursdays 3:00–5:00 AM ET, every week. CLPs stop serving routes; returns (the CLI annotates with a maintenance note). Block PM submissions for the whole window.
- is 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 , that leg uses the CLI's stored config from (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 call, , sibling HTTP tools), that HTTP client needs the key handed in explicitly (env var, , flag, 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 (
,
query_docs_filesystem_d_flow
). Don't guess.
Sibling skills
Defer if the user pivots to:
dflow-kalshi-market-scanner
— discover markets, filter by event/category
- — live prices, orderbooks, streams
- — view positions, unrealized P&L
- — set up Proof verification on a wallet
- — charge a builder fee on PM trades
- — non-Kalshi token swaps