Redeem outcome tokens after a market is determined and funded for redemption by trading expired outcome tokens back into the stablecoin you opened your position with.
1
Set up
Imports, env config, and the wallet/connection used across every step.
Use the /api/v1/market/by-mint/{mint_address} endpoint to fetch market details and verify that the outcome token is redeemable. A token is redeemable when:
The market status is "determined" or "finalized"
The redemption status for the settlement mint is "open"
Either:
The market result ("yes" or "no") matches the user’s outcome token (the outcome mint must match the yesMint or noMint for the determined side), OR
The market resolved as a scalar outcome (result is "" or "scalar") and scalarOutcomePct is defined, in which case both YES and NO are redeemable
Edge Case: Scalar Outcome PayoutsIn rare cases, a market may have redemptionStatus = "open" but result = "" or result = "scalar" (no winning side). This typically happens when an event is canceled before it resolves and Kalshi determines it as a scalar outcome. Use scalarOutcomePct to determine the payout:
scalarOutcomePct represents the payout percentage for YES tokens in basis points (0-10000, where 10000 = 100%)
YES token payout = scalarOutcomePct / 10000
NO token payout = (10000 - scalarOutcomePct) / 10000
Example: If scalarOutcomePct = 5000, then:
YES tokens redeem for 50% (5000/10000 = 0.5)
NO tokens redeem for 50% ((10000-5000)/10000 = 0.5)
Both YES and NO tokens are redeemable in this case.
Check Redemption Eligibility
/// Outcome token mint address (YES or NO token)const outcomeMint = "OUTCOME_TOKEN_MINT_ADDRESS_HERE";/// Fetch market details by mint addressconst response = await fetch( `${DFLOW_METADATA_API_URL}/api/v1/market/by-mint/${outcomeMint}`, { headers });if (!response.ok) { throw new Error("Failed to fetch market details");}const market = await response.json();/// Check if market is determined (status can be "determined" or "finalized")if (market.status !== "determined" && market.status !== "finalized") { throw new Error(`Market is not determined. Current status: ${market.status}`);}/// Check if the outcome mint matches the market result/// The result can be "yes", "no", "" or "scalar" (the last two indicate a scalar outcome)const result = market.result; // "yes", "no", "", or "scalar"const isScalarResult = result === "" || result === "scalar";let isDeterminedOutcome = false;let settlementMint;/// Option 1: Use a constant settlement mint (e.g., USDC)/// If you only support one settlement mint, use this approachif (market.accounts[DFLOW_SETTLEMENT_MINT]) { const usdcAccount = market.accounts[DFLOW_SETTLEMENT_MINT]; /// Check if redemption is open if (usdcAccount.redemptionStatus === "open") { /// Case 1: Standard determined outcome (result is "yes" or "no") if (result === "yes" || result === "no") { if ( (result === "yes" && usdcAccount.yesMint === outcomeMint) || (result === "no" && usdcAccount.noMint === outcomeMint) ) { isDeterminedOutcome = true; settlementMint = DFLOW_SETTLEMENT_MINT; } } /// Case 2: Scalar outcome (result is "" or "scalar"; use scalarOutcomePct) /// Both YES and NO tokens are redeemable in this case else if ( isScalarResult && usdcAccount.scalarOutcomePct !== null && usdcAccount.scalarOutcomePct !== undefined ) { /// Both YES and NO tokens are redeemable when scalarOutcomePct is defined if ( usdcAccount.yesMint === outcomeMint || usdcAccount.noMint === outcomeMint ) { isDeterminedOutcome = true; settlementMint = DFLOW_SETTLEMENT_MINT; /// Calculate payout percentages for display/logging const yesPayoutPct = usdcAccount.scalarOutcomePct / 10000; const noPayoutPct = (10000 - usdcAccount.scalarOutcomePct) / 10000; console.log( `Scalar outcome detected. YES payout: ${ yesPayoutPct * 100 }%, NO payout: ${noPayoutPct * 100}%` ); } } } else { throw new Error(`Redemption is not open for ${outcomeMint}`); }}/// Option 2: Find settlement mint dynamically (if you support multiple)/// Uncomment this if you need to support multiple settlement mints/*if (!settlementMint) { for (const [mint, account] of Object.entries(market.accounts)) { if (account.redemptionStatus === "open") { /// Case 1: Standard determined outcome if (result === "yes" || result === "no") { if (result === "yes" && account.yesMint === outcomeMint) { isDeterminedOutcome = true; settlementMint = mint; break; } else if (result === "no" && account.noMint === outcomeMint) { isDeterminedOutcome = true; settlementMint = mint; break; } } /// Case 2: Scalar outcome (both YES and NO are redeemable) else if (isScalarResult && account.scalarOutcomePct !== null && account.scalarOutcomePct !== undefined) { if (account.yesMint === outcomeMint || account.noMint === outcomeMint) { isDeterminedOutcome = true; settlementMint = mint; break; } } } else { throw new Error(`Redemption is not open for ${outcomeMint}`); } }}*/if (!isDeterminedOutcome) { if (isScalarResult) { throw new Error( `Outcome token does not match any outcome mint for this market. Token: ${outcomeMint}` ); } else { throw new Error( `Outcome token does not match market result. Market result: ${result}, Token: ${outcomeMint}` ); }}if (!settlementMint) { throw new Error("No settlement mint with open redemption status found");}const settlementAccount = market.accounts[settlementMint];console.log("Token is redeemable!", { outcomeMint, settlementMint, redemptionStatus: settlementAccount.redemptionStatus, marketTitle: market.title,});
3
Request Redemption Order
Use the Trade API /order endpoint to request a redemption order. The redemption is treated as a trade where you swap your outcome token for the settlement stablecoin.
Request Redemption Order
// Preview a redemption order before the outcome is determined.// Uses DFLOW_TRADE_API_URL, settlementMint (from Step 1), outcomeMint, keypair, headers from the setup above.async function previewRedemption() { const previewAmount = 1_000_000; // Example: 1 outcome token (6 decimals) const previewParams = new URLSearchParams(); previewParams.append("userPublicKey", keypair.publicKey.toBase58()); previewParams.append("inputMint", outcomeMint); previewParams.append("outputMint", settlementMint!); previewParams.append("amount", previewAmount.toString()); const preview = await fetch( `${DFLOW_TRADE_API_URL}/order?${previewParams.toString()}`, { headers } ).then((x) => x.json()); console.log( `Redemption preview: ${preview.inAmount} of ${preview.inputMint} is redeemable for ${preview.outAmount} of ${preview.outputMint}` );}
4
Determine Event Outcome
Wait for the settlement authority to write the outcome into the
Market Ledger. You cannot redeem until the outcome is determined.
Check Outcome Status
// Polling pattern: call this on an interval until the outcome is determined.// Uses DFLOW_METADATA_API_URL, outcomeMint, headers from the setup above.async function checkOutcomeStatus() { const latest = await fetch( `${DFLOW_METADATA_API_URL}/api/v1/market/by-mint/${outcomeMint}`, { headers } ).then((x) => x.json()); if (latest.status === "determined" || latest.status === "finalized") { console.log("Outcome determined"); return latest; } console.log("Outcome not determined yet"); return null;}
5
Fund Outcome
Wait for the settlement authority to fund redemption by moving
stablecoins from the Settlement Vault to the Redemption Vault.
Check Redemption Funding
// Check redemption funding for a given market. settlementMint comes from Step 1.function checkRedemptionFunding(latestMarket: any) { const account = latestMarket.accounts?.[settlementMint!]; if (account?.redemptionStatus === "open") { console.log("Redemption funded"); return true; } console.log("Redemption not funded yet"); return false;}
6
Request Redemption Order
Redeem your expired outcome tokens into the settlement
stablecoin using the Trade API.