useParlayPositions reads every parlay from the escrow (newest first, auto-refetching) and returns two selectors for the two standard surfaces:
mine(address): the connected user’s parlays, for a profile or portfolio page
onMarket(marketId): parlays with a leg on a given market, for a market page’s open-orders table
Both return the same ParlayRow shape, so you write one row component and reuse it everywhere.
The hook
import {useAccount} from "wagmi";
import {useParlayPositions} from "@parlays-live/taker/react";
const {rows, count, isLoading, mine, onMarket} = useParlayPositions({
refetchMs: 8000, // default; leg data refetches at max(refetchMs, 30000)
enabled: true, // gate on isConfigured for multi-network hosts
});
const {address} = useAccount();
const myRows = mine(address); // profile page
const marketRows = onMarket(101); // market page, market 101
rows is the full unfiltered feed (useful for an activity page). The selectors are memoized callbacks over it, so calling both costs nothing extra.
The row component
ParlayRow carries everything a table row needs; derive the pot and booked odds with the pure helpers:
import {formatUsdc, potOf, bookedOddsBps, type ParlayRow} from "@parlays-live/taker";
export function ParlayPositionRow({
row,
labelFor,
}: {
row: ParlayRow;
labelFor: (marketId: number, outcome: number) => string;
}) {
const odds = Number(bookedOddsBps(row)) / 10000; // pot / stake as a decimal multiple
return (
<tr data-settled={row.settled}>
<td>#{row.id}</td>
<td>
<div className="parlay-legs">
{row.legs.map((leg) => (
<span key={leg.marketId} className="parlay-leg-chip">
{labelFor(leg.marketId, leg.outcome)}
</span>
))}
{row.legs.length === 0 && <span>{row.legCount} legs</span>}
</div>
</td>
<td>${formatUsdc(row.stake)}</td>
<td>{odds.toFixed(2)}x</td>
<td>${formatUsdc(potOf(row))}</td>
<td>{row.settled ? "Settled" : "Open"}</td>
</tr>
);
}
row.legs can be empty for a render cycle while leg data loads (legs refetch on a slower interval than the parlay tuples); row.legCount is always populated from the parlay itself, so fall back to a count as above.
Interleaving with existing single-market positions
A market page usually already has a positions or open-orders table. The clean pattern is a discriminated union row type, so parlay rows and your native rows flow through one sorted list:
import {useAccount} from "wagmi";
import {useParlayPositions} from "@parlays-live/taker/react";
import type {ParlayRow} from "@parlays-live/taker";
import {ParlayPositionRow} from "./ParlayPositionRow";
// your existing single-market position shape
type SinglePosition = {id: string; side: "YES" | "NO"; size: bigint; openedAt: number};
type TableRow =
| {kind: "single"; key: string; position: SinglePosition}
| {kind: "parlay"; key: string; row: ParlayRow};
export function MarketPositions({
marketId,
singles,
labelFor,
}: {
marketId: number;
singles: SinglePosition[];
labelFor: (marketId: number, outcome: number) => string;
}) {
const {onMarket, isLoading} = useParlayPositions();
const parlays = onMarket(marketId);
const rows: TableRow[] = [
...singles.map((p): TableRow => ({kind: "single", key: `s-${p.id}`, position: p})),
...parlays.map((r): TableRow => ({kind: "parlay", key: `p-${r.id}`, row: r})),
];
return (
<table>
<tbody>
{rows.map((r) =>
r.kind === "single" ? (
<YourExistingPositionRow key={r.key} position={r.position} />
) : (
<ParlayPositionRow key={r.key} row={r.row} labelFor={labelFor} />
),
)}
{!isLoading && rows.length === 0 && (
<tr><td>No open positions</td></tr>
)}
</tbody>
</table>
);
}
On the profile page, the identical component renders mine(address) instead of onMarket(marketId); nothing else changes.
Filtering to open positions
rows includes settled parlays (useful for history). To show only live ones, filter on the flag, or use the exported pure selector:
import {openPositions} from "@parlays-live/taker";
const live = openPositions(mine(address));
Non-React and server contexts
The same rows are available without wagmi via fetchParlays and a viem PublicClient (a bot, a cron job, an API route):
import {createPublicClient, http} from "viem";
import {fetchParlays, positionsOf} from "@parlays-live/taker";
const publicClient = createPublicClient({transport: http("https://rpc.hyperliquid-testnet.xyz/evm")});
const rows = await fetchParlays(publicClient, {escrow: "0x77DeFc3E3B66bE01B8468Eb696Ec9F330D24332a"}, {max: 500});
const mine = positionsOf(rows, "0xYourAddress");
Adding cash-out to a row
Live rows (!row.settled, row.taker === address) should offer an early exit. That is its own flow: see the cash-out guide.