@toncast/sdk-react
v0.0.6
Published
React hooks + provider for @toncast/sdk (paris, bets, live streams, betting flow, optional TonConnect sync)
Maintainers
Readme
@toncast/sdk-react
React hooks for @toncast/sdk — built on top of @tanstack/react-query. REST methods become useQuery queries.
Two live-data paths: WS-backed list and single-pari snapshots (paris.streamList, paris.subscribe) use useLiveStreamQuery (useSyncExternalStore) so post-initial errors and updates stay live without treating the stream as a one-shot promise. Observable-style resources such as betting.subscribeSummary use useObservableQuery (TanStack cache + forced per-emission renders). Pick the hook the SDK already exposes; only reach for the low-level adapters when composing custom streams.
Use toncastQueryKeys for every prefetchQuery, setQueryData, and targeted invalidateQueries so keys match the built-in hooks exactly (including serializeKey for params with bigint).
Status: 0.0.1 (pre–1.0.0). Pin exact versions until
1.0.0. See CHANGELOG.md and repositoryAGENTS.mdfor betting and address handling.
Pattern lifted from
@ston-fi/omniston-sdk-react— thin wrappers around TanStack Query, single Observable adapter for streaming endpoints.
The SDK layer does not sign transactions; hooks surface errors from the client — do not swallow ToncastError / ToncastApiError / ToncastWsError in production UIs.
Install
npm install @toncast/sdk @toncast/sdk-react @tanstack/react-query
# Optional, only if you use TonConnect for wallet auth:
npm install @tonconnect/ui-reactPeer dependencies: react ^18 || ^19, @tanstack/react-query ^5.
Quick start
import { TonClient, ToncastClient } from "@toncast/sdk";
import { ToncastProvider, useStreamList } from "@toncast/sdk-react";
const tonClient = new TonClient({
endpoint: "https://toncenter.com/api/v2/jsonRPC",
apiKey: import.meta.env.VITE_TONCENTER_API_KEY,
});
const client = new ToncastClient({ tonClient });
function App() {
return (
<ToncastProvider client={client}>
<ParisFeed />
</ToncastProvider>
);
}
function ParisFeed() {
const { data, isLoading } = useStreamList({ feed: "active" });
// `data` is the latest `Pari[]` snapshot (re-emitted on every WS update)
if (isLoading) return <p>Loading…</p>;
return data?.map((p) => <div key={p.id}>{p.name}</div>);
}<ToncastProvider> creates an internal QueryClient automatically. Pass queryClient={appQueryClient} to share with an existing TanStack-Query app.
import { toncastQueryKeys } from "@toncast/sdk-react";
// Prefetch must use the same builders as the hooks
void queryClient.prefetchQuery({
queryKey: toncastQueryKeys.paris.detail(pariId),
queryFn: ({ signal }) => client.paris.get(pariId, signal),
});Hooks
Provider
<ToncastProvider client={...} queryClient?={...}>— wires both clients into context.useToncastClient()— read the SDK client (throws outside a provider).
High-level (recommended)
useBet(params)— all-in-one hook for the full bet flow: summary stream, coin picker, quote, confirm. Returns{ summary, coins, quote, confirm, side, mode, tickets, … }. Use this unless you need fine-grained control.
Read (REST → useQuery)
| Hook | Wraps | Notes |
|---|---|---|
| useParis(params) | paris.list | Single page (cursor-paginated). |
| usePari(id) | paris.get | Disabled when id is falsy. |
| useBets(params) | bets.listForUser / listForPariByUser | pariId optional → cross-pari history. |
| useCategories() | categories.list | Raw { id, title } categories, staleTime: Infinity. |
| useCategoryFilters() | categories.listFilters | UI-ready chips whose param goes into useStreamList. |
| useCoins(opts) | coins.list | TON + jettons via toncenter v3. Requires tonClient. |
| useBetQuote(params \| null) | betting.quoteFixedBet/quoteLimitBet/quoteMarketBet | Auto re-quotes on params change. |
Pass any TanStack UseQueryOptions (enabled, staleTime, select, refetchInterval, …) as a second arg to any read hook.
Live (useSyncExternalStore)
useStreamList(params, options?)— wrapsparis.streamList.datais the latestPari[]snapshot; updates on every WS broadcast. Also exposeshasNextPage,fetchNextPage,isFetchingNextPagefor infinite scroll. Options:keepPreviousData(defaulttrue; setfalseto reset the list when category/filter changes).useSubscribe(pariId)— wrapsparis.subscribe.datais the latestPariStreamSnapshot({ pari, oddsState, coefficientHistory }). Returns the same live state shape asuseStreamList.useBetSummary(pariId)— wrapsbetting.subscribeSummary. Emits two phases: TON-only (~200 ms) then full jetton pricing (STON.fi, warm: instant / cold: 3–8 s). UsesuseObservableQueryinternally, not a simpleuseQuery.
Mutations
useConfirmBet()— TanStackuseMutationforbetting.confirmQuote. Trigger right before signing and pass acknowledged params:mutateAsync({ quote, params: { ...quoteParams, financialRiskAcknowledged: true } }).
TonConnect bridge (optional peer-dep)
useTonConnectClient(userAddress)— mirrors any wallet-bridge address intoclient.userAddress. With@tonconnect/ui-react:
import { useTonAddress } from "@tonconnect/ui-react";
function WalletSync() {
useTonConnectClient(useTonAddress()); // "" when disconnected
return null;
}Custom Observables
If you build your own Observable on top of the SDK (e.g. transformed paris.streamList), wrap it via the low-level adapter:
useObservableQuery({ queryKey, requestFn })— same shape asuseQuery, butrequestFnreturns aSubscribable<T>(anything withsubscribe(observer)). Eachnext()updatesdataand forces a re-render viauseSyncExternalStore(so you don't lose intermediate values to TanStack's microtask batching).
End-to-end bet flow
The simplest approach — use useBet, which composes summary, quote, and confirm into one ergonomic API:
import { useBet } from "@toncast/sdk-react";
import { useTonAddress, useTonConnectUI } from "@tonconnect/ui-react";
function BetCard({ pariId }: { pariId: string }) {
const userAddress = useTonAddress();
const [tc] = useTonConnectUI();
const bet = useBet({ pariId: userAddress ? pariId : null, defaultSide: "yes" });
return (
<button
disabled={!bet.quote.isFeasible || bet.confirm.isPending}
onClick={async () => {
const confirmed = await bet.confirmCurrent({ financialRiskAcknowledged: true });
if (!confirmed) return;
await tc.sendTransaction({
messages: confirmed.messages,
validUntil: Math.floor(Date.now() / 1000) + 5 * 60,
});
}}
>
Bet {bet.side.toUpperCase()}
</button>
);
}For advanced use-cases (custom UI, partial control) you can compose the lower-level hooks directly — useBetSummary → useBetQuote → useConfirmBet. See the example app's BetCard.tsx for a full reference implementation using useBet.
License
MIT — see LICENSE.
