Skip to main content

Documentation Index

Fetch the complete documentation index at: https://pond.dflow.net/llms.txt

Use this file to discover all available pages before exploring further.

During development, you can use the developer endpoints without an API key. For production use, request an API key for higher rate limits.
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.
import "dotenv/config";

import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";

const DFLOW_METADATA_API_URL = process.env.DFLOW_METADATA_API_URL ?? "https://dev-prediction-markets-api.dflow.net";
const DFLOW_TRADE_API_URL = process.env.DFLOW_TRADE_API_URL ?? "https://dev-quote-api.dflow.net";
const DFLOW_API_KEY = process.env.DFLOW_API_KEY; // optional; not needed for dev endpoints
const DFLOW_SETTLEMENT_MINT = process.env.DFLOW_SETTLEMENT_MINT ?? "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC
const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";

const connection = new Connection(SOLANA_RPC_URL, "confirmed");
const keypair = Keypair.fromSecretKey(
  bs58.decode(process.env.SOLANA_PRIVATE_KEY ?? "")
);

const headers: HeadersInit = {};
if (DFLOW_API_KEY) headers["x-api-key"] = DFLOW_API_KEY;
2

Check if Outcome Token is Redeemable

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.
/// Outcome token mint address (YES or NO token)
const outcomeMint = "OUTCOME_TOKEN_MINT_ADDRESS_HERE";

/// Fetch market details by mint address
const 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 approach
if (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.
// 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.Determine Event Outcome
// 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.Fund Outcome
// 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.Redeem Payouts
// Reuses DFLOW_TRADE_API_URL, settlementMint (from Step 1), outcomeMint, keypair, headers from the setup above.
const redeemAmount = 1_000_000; // 1 outcome token (6 decimals)

const queryParams = new URLSearchParams();
queryParams.append("inputMint", outcomeMint);
queryParams.append("outputMint", settlementMint!);
queryParams.append("amount", redeemAmount.toString());
queryParams.append("userPublicKey", keypair.publicKey.toBase58());

const orderResponse = await fetch(
  `${DFLOW_TRADE_API_URL}/order?${queryParams.toString()}`,
  { headers }
).then((x) => x.json());
7

Sign and Submit the Transaction

Submit the redemption order transaction so the Trade API can execute the payout onchain.
const transactionBuffer = Buffer.from(orderResponse.transaction, "base64");
const transaction = VersionedTransaction.deserialize(transactionBuffer);

transaction.sign([keypair]);
const signature = await connection.sendTransaction(transaction);
console.log(`  Tx: https://orbmarkets.io/tx/${signature}`);
8

Monitor Order Status

Track the order until it closes so you can confirm the redemption was finalized.
const statusResponse = await fetch(
  `${DFLOW_TRADE_API_URL}/order-status?signature=${signature}`,
  { headers }
).then((x) => x.json());

console.log(statusResponse.status, statusResponse.fills);

Buy Outcome Tokens

Open a prediction market position from any input token.

Monitor Market Lifecycle

Track status changes and know when redemption opens.

Market Lifecycle

States a market moves through and what each one allows.

Reclaim Rent

Close empty outcome token accounts and reclaim SOL rent.

API Routes