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

# Track User Positions

> How to track user prediction market positions

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

<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 { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
        import { Connection, PublicKey } from "@solana/web3.js";

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

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

        const connection = new Connection(SOLANA_RPC_URL);

        const userWalletAddress = "USER_WALLET_ADDRESS_HERE";
        if (userWalletAddress === "USER_WALLET_ADDRESS_HERE") {
          throw new Error("Replace USER_WALLET_ADDRESS_HERE with a real Solana wallet address.");
        }
        const userWallet = new PublicKey(userWalletAddress);
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Fetch Wallet Token Accounts">
    Fetch SPL token accounts for the wallet and keep only non-zero balances.
    Outcome tokens are Token-2022 mints.

    <AccordionGroup>
      <Accordion title="Fetch Token Accounts">
        ```typescript theme={null}
        const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
          userWallet,
          { programId: TOKEN_2022_PROGRAM_ID }
        );

        const userTokens = tokenAccounts.value.map(({ account }) => {
          const info = account.data.parsed.info;

          return {
            mint: info.mint,
            rawBalance: info.tokenAmount.amount,
            balance: info.tokenAmount.uiAmount,
            decimals: info.tokenAmount.decimals,
          };
        });

        const nonZeroBalances = userTokens.filter((token) => token.balance > 0);
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Filter for Outcome Mints">
    Filter the wallet mints down to prediction market outcome tokens using the
    Metadata API.

    <AccordionGroup>
      <Accordion title="Filter Outcome Mints">
        ```typescript theme={null}
        const allMintAddresses = nonZeroBalances.map((token) => token.mint);

        const response = await fetch(
          `${DFLOW_METADATA_API_URL}/api/v1/filter_outcome_mints`,
          {
            method: "POST",
            headers: { ...headers, "Content-Type": "application/json" },
            body: JSON.stringify({ addresses: allMintAddresses }),
          }
        );

        if (!response.ok) {
          throw new Error("Failed to filter outcome mints");
        }

        const data = await response.json();
        const outcomeMints = data.outcomeMints ?? [];
        const outcomeTokens = nonZeroBalances.filter((token) =>
          outcomeMints.includes(token.mint)
        );
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Fetch Market Details in Batch">
    Pull market metadata for those outcome mints so you can label YES/NO and
    display event and market context.

    <AccordionGroup>
      <Accordion title="Fetch Markets Batch">
        ```typescript theme={null}
        const marketsResponse = await fetch(
          `${DFLOW_METADATA_API_URL}/api/v1/markets/batch`,
          {
            method: "POST",
            headers: { ...headers, "Content-Type": "application/json" },
            body: JSON.stringify({ mints: outcomeMints }),
          }
        );

        if (!marketsResponse.ok) {
          throw new Error("Failed to fetch markets batch");
        }

        const marketsData = await marketsResponse.json();
        const markets = marketsData.markets ?? [];

        const marketsByMint = new Map<string, any>();
        markets.forEach((market: any) => {
          Object.values(market.accounts ?? {}).forEach((account: any) => {
            if (account.yesMint) marketsByMint.set(account.yesMint, market);
            if (account.noMint) marketsByMint.set(account.noMint, market);
          });
        });
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Build Position Rows">
    Map each outcome token to a market, determine YES/NO, and shape the data
    for your UI.

    <AccordionGroup>
      <Accordion title="Build Positions List">
        ```typescript theme={null}
        const positions = outcomeTokens.map((token) => {
          const market = marketsByMint.get(token.mint);
          if (!market) {
            return {
              mint: token.mint,
              balance: token.balance,
              position: "UNKNOWN",
              market: null,
            };
          }

          const accounts = Object.values(market.accounts ?? {});
          const isYesToken = accounts.some((account: any) => account.yesMint === token.mint);
          const isNoToken = accounts.some((account: any) => account.noMint === token.mint);

          return {
            mint: token.mint,
            balance: token.balance,
            decimals: token.decimals,
            position: isYesToken ? "YES" : isNoToken ? "NO" : "UNKNOWN",
            market,
          };
        });
        ```
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

## Related Resources

<CardGroup cols={2}>
  <Card title="Find Markets" href="/prediction-markets/recipes/find-markets" icon="search">
    Discover markets by category, tag, status, or series.
  </Card>

  <Card title="Bulk Fetch Markets" href="/prediction-markets/recipes/bulk-fetch-markets" icon="layer-group">
    Hydrate multiple markets and outcome mints in one call.
  </Card>

  <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="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

* [POST /api/v1/filter\_outcome\_mints](/resources/metadata-api/markets/filter-outcome-mints)
* [POST /api/v1/markets/batch](/resources/metadata-api/markets/markets-batch)
