> ## 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.

# Redeem Outcome Tokens

> How to redeem determined outcome tokens

<Info>
  During development, you can use the [developer endpoints](/get-started/endpoints)
  without an API key. For production use, [request an API key](/get-started/api-key)
  for higher rate limits.
</Info>

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.

<Steps>
  <Step title="Set up">
    Imports, env config, and the wallet/connection used across every step.

    <AccordionGroup>
      <Accordion title="Imports, env, and wallet">
        ```typescript theme={null}
        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;
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Check if Outcome Token is Redeemable">
    Use the [`/api/v1/market/by-mint/{mint_address}`](/resources/metadata-api/markets/market-by-mint) 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

    <a id="scalar-outcomes" style={{ display: 'block', scrollMarginTop: '8rem' }} />

    <Note>
      **Edge Case: Scalar Outcome Payouts**

      In 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.
    </Note>

    <AccordionGroup>
      <Accordion title="Check Redemption Eligibility">
        ```typescript theme={null}
        /// 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,
        });
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Request Redemption Order">
    Use the Trade API [`/order`](/resources/trading-api/order/order) endpoint to request a redemption order. The redemption is treated as a trade where you swap your outcome token for the settlement stablecoin.

    <AccordionGroup>
      <Accordion title="Request Redemption Order">
        ```typescript theme={null}
        // 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}`
          );
        }
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Determine Event Outcome">
    Wait for the settlement authority to write the outcome into the
    **Market Ledger**. You cannot redeem until the outcome is determined.

    <img alt="Determine Event Outcome" className="mx-auto" noZoom src="https://mintcdn.com/dflow/CxrIQMjWZhe4p262/images/prediction/Determine%20Outcome.png?fit=max&auto=format&n=CxrIQMjWZhe4p262&q=85&s=ddc2eb8d8392875b56d7cab621a7116b" width="1273" height="348" data-path="images/prediction/Determine Outcome.png" />

    <AccordionGroup>
      <Accordion title="Check Outcome Status">
        ```typescript theme={null}
        // 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;
        }
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Fund Outcome">
    Wait for the settlement authority to fund redemption by moving
    stablecoins from the **Settlement Vault** to the **Redemption Vault**.

    <img alt="Fund Outcome" className="mx-auto" noZoom src="https://mintcdn.com/dflow/CxrIQMjWZhe4p262/images/prediction/Fund%20Outcome.png?fit=max&auto=format&n=CxrIQMjWZhe4p262&q=85&s=4a559ad5fc4d4519a7879a2ec2fe9483" width="1227" height="598" data-path="images/prediction/Fund Outcome.png" />

    <AccordionGroup>
      <Accordion title="Check Redemption Funding">
        ```typescript theme={null}
        // 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;
        }
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Request Redemption Order">
    Redeem your expired outcome tokens into the settlement
    stablecoin using the Trade API.

    <img alt="Redeem Payouts" className="mx-auto" noZoom src="https://mintcdn.com/dflow/CxrIQMjWZhe4p262/images/prediction/Redeem.png?fit=max&auto=format&n=CxrIQMjWZhe4p262&q=85&s=e964edcdfd3400820ff467f6e6f9bf47" width="1006" height="596" data-path="images/prediction/Redeem.png" />

    <AccordionGroup>
      <Accordion title="Request Redemption Order">
        ```typescript theme={null}
        // 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());
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Sign and Submit the Transaction">
    Submit the redemption order transaction so the Trade API can execute the
    payout onchain.

    <AccordionGroup>
      <Accordion title="Sign and Submit the Transaction">
        ```typescript theme={null}
        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}`);
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Monitor Order Status">
    Track the order until it closes so you can confirm the redemption was
    finalized.

    <AccordionGroup>
      <Accordion title="Monitor Order Status">
        ```typescript theme={null}
        const statusResponse = await fetch(
          `${DFLOW_TRADE_API_URL}/order-status?signature=${signature}`,
          { headers }
        ).then((x) => x.json());

        console.log(statusResponse.status, statusResponse.fills);
        ```
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

## Related Resources

<CardGroup cols={2}>
  <Card title="Buy Outcome Tokens" href="/prediction-markets/recipes/buy-outcome-tokens" icon="wallet">
    Open a prediction market position from any input token.
  </Card>

  <Card title="Monitor Market Lifecycle" href="/prediction-markets/recipes/monitor-market-lifecycle" icon="clock">
    Track status changes and know when redemption opens.
  </Card>

  <Card title="Market Lifecycle" href="/prediction-markets/market-lifecycle" icon="diagram-project">
    States a market moves through and what each one allows.
  </Card>

  <Card title="Reclaim Rent" href="/prediction-markets/recipes/close-outcome-token-accounts" icon="trash-can">
    Close empty outcome token accounts and reclaim SOL rent.
  </Card>
</CardGroup>

## API Routes

* [GET /api/v1/market/by-mint/{mint_address}](/resources/metadata-api/markets/market-by-mint)
* [GET /order](/resources/trading-api/order/order)
* [GET /order-status](/resources/trading-api/order/order-status)
