Get started
What you need beneath it, where it runs, then install two packages and wrap your app in one provider. Every config — endpoints, chain, wallet, venue id — in one place.
Before you start
Reads need nothing but the packages. Get these in place first anyway if you’re heading for a full venue — each one is a wall people hit later, at a worse moment:
- npm access. The packages are published under the restricted
@prophecy-devscope. You need anNPM_TOKENin your environment beforenpm installwill resolve them — ask us for one. - A Privy app, if you want login and trading. Yours, not ours. From its dashboard you’ll need the app id, and you must configure two things — see Privy setup below, because both fail in ways that don’t name themselves.
- A venue id and an API key, if you want your venue’s trades attributed to you, sponsored gas, or usage reporting. There’s no self-serve signup yet — ask us and we’ll issue them. Reads and even trading work without one; you just won’t be a venue as far as the protocol is concerned.
What’s beneath
| Requirement | Notes | |
|---|---|---|
| Modules | ESM only | Every package is "type": "module". There is no CommonJS build — require() won’t resolve them. |
| React | >=18 by peer range, 19 in practice |
Everything we build, test and ship runs on React 19. 18 should work and isn’t what we exercise. |
fetch |
a global, or inject one | Node 18+, browsers, Workers, Deno and Bun all have it. Otherwise pass fetchImpl. |
WebSocket |
a global, or inject one — realtime only | Browsers and Workers have it; Node only from 22. Otherwise pass WebSocketImpl. Plain HTTP reads don’t need it. |
| TypeScript | optional, but the point | The types are the contract — see The wire. |
| A CSS framework | not required | The npm packages have no styling dependency. Tailwind v4 is needed only if you opt into the venue-kit registry blocks — Studio’s generated venues use plain CSS instead. See Structure vs skin. |
The npm packages pull in viem and zod, and nothing else. No CSS framework, no component library, no
state manager — the kit’s stylesheet is self-contained and lives in a low-priority cascade layer.
Where it runs
| React SPA (Vite, CRA, Astro islands) | The main path. Providers at the root, hooks anywhere. This docs site is one. |
| Next.js App Router | Works — with one catch, below. |
| Not React (Svelte, Vue, vanilla) | Use connect-sdk directly. Same reads, same socket, no hooks. |
| Server / Workers / Node / Bun / Deno | connect-sdk is runtime-neutral: no window, no document, no localStorage. Good for server rendering, bots, and cron jobs. |
| Cloudflare Workers | Yes — fetch and WebSocket are both globals there. |
| React Native | Untested. The SDK has no DOM dependency, so it’s plausible; the components aren’t. |
The Next.js catch: the packages ship without 'use client' directives. Import a hook into a
server component and the build will fail on createContext. Put your own 'use client' at the
boundary — usually the file holding <ConnectProvider>, plus any component using hooks:
'use client'
import { ConnectProvider } from '@prophecy-dev/connect-react'
(The venue-kit registry blocks do carry 'use client' — they’re copied into your app, so they
can.) Market data is live and per-user, so client-rendering it is usually right anyway; if you want
markets in your server-rendered HTML for SEO, fetch them with connect-sdk on the server and pass
them down.
Reads only (no wallet)
This is the whole thing. One provider, one hook:
npm install @prophecy-dev/connect-react @prophecy-dev/connect-sdk
import { ConnectProvider, useEvents } from '@prophecy-dev/connect-react'
function App() {
return (
<ConnectProvider baseUrl="https://api.prophecyhosting.com">
<Markets />
</ConnectProvider>
)
}
function Markets() {
const { events, loading } = useEvents({ limit: 12 }, { refreshMs: 20_000 })
return <ul>{events.map((e) => <li key={e.id}>{e.title}</li>)}</ul>
}
No API key, no signup — that is the try-it path, and it is real: reads fail open, so the code above runs as written. A venue you intend to ship wants a key, and gets one at creation rather than by asking us. See Your venue’s key below, and Event Grid for the same thing with live odds.
What you configure
| Setting | Where | Value |
|---|---|---|
baseUrl |
<ConnectProvider> / new ConnectClient() |
https://api.prophecyhosting.com (mainnet) — see Environments |
streamUrl |
optional | Derived from baseUrl. Only set it if your socket lives elsewhere. |
apiKey |
ConnectClient — set it for anything real |
Your venue key (pck_…). Scopes reads to your venue, attributes usage, and binds the venue on the gateway so sponsored trading works. Reads still work without one, so the SDK warns once if you leave it unset. Pass null if keyless is deliberate. |
timeoutMs |
optional, ConnectClient |
Deadline for every request. Unset = unbounded (fine in a browser). Set it when server-rendering — e.g. 4000 — so a slow Connect degrades the section instead of hanging the render. |
venueId |
the wallet adapter | Your venue’s id — the routing + fee tag carried on every trade. |
Privy appId |
<PrivyProvider> |
Your own Privy app. Public value. |
| chain | <PrivyProvider> + viem |
Somnia mainnet 5031 / testnet 50312 |
| gateway | the wallet adapter | https://gateway.prophecyhosting.com — login and the sponsorship gate |
Only baseUrl is required for reads. Everything below the line is for trading.
Your venue’s key
A key is issued at creation, by you, and is not something to request from us.
POST /admin/keys with { venueId, scopes: ["read","trade"], label }, signed in with Privy. If
nobody holds that venue id yet, the same call claims it — you become its owner and it is created
with a sponsorship grant, so its users get gasless trading from the first visit. The raw key comes
back once and cannot be read again; store it then, or rotate.
pck_<64 hex> read + trade — the key a venue ships in its own frontend
Three rules worth knowing before you pick an id, because a claim is permanent:
- 6–64 characters, lowercase alphanumeric with hyphens. Shorter ids are scarce, so they are allocated by us rather than first-come.
- Reserved names are refused — anything starting
prophecy…orsomnia…, plus platform names likedemo,studio,gateway. - A venue that already exists cannot be claimed, whether or not it has an owner set. If it holds a policy, a grant or any key, it is spoken for.
Is it safe in the browser?
Yes, and it is meant to be there. This key is scoped to read + trade — the two things a venue’s
own visitors do — so it grants nothing a visitor could not already do. It ships in your bundle the
same way your Privy appId does.
What must never go in a browser is a key with any other scope, or the admin credential that mints keys. Those belong on a server.
Without one
Reads fail open, so a keyless venue works — which is exactly why forgetting the key is easy. What
you lose is invisible until it matters: your traffic is unattributed and unmetered, the gateway binds
sessions to the venue your client claims to be rather than the one the key proves, and sponsored
trading stops the day key enforcement is switched on. If keyless is deliberate, say so with
apiKey: null and the SDK stays quiet about it.
Adding a wallet
Trading needs three providers and one adapter. The order matters — auth outermost, data plane, then the Prophecy layer that joins them:
import { PrivyProvider } from '@privy-io/react-auth'
import { SmartWalletsProvider } from '@privy-io/react-auth/smart-wallets'
import { ConnectProvider } from '@prophecy-dev/connect-react'
import { ConnectAuth } from '@prophecy-dev/connect-sdk'
// ONE ConnectAuth for the whole app, handed to BOTH providers.
const auth = new ConnectAuth()
<PrivyProvider
appId={PRIVY_APP_ID}
config={{
// Privy signs headlessly — the {c} drawer is the only confirm surface the user sees.
embeddedWallets: { showWalletUIs: false, ethereum: { createOnLogin: 'users-without-wallets' } },
defaultChain: somnia,
supportedChains: [somnia],
}}
>
<SmartWalletsProvider>
<ConnectProvider baseUrl="https://api.prophecyhosting.com" auth={auth}>
<ProphecyWithPrivy venueId="your-venue" auth={auth}>
<App />
</ProphecyWithPrivy>
</ConnectProvider>
</SmartWalletsProvider>
</PrivyProvider>
Two ConnectAuth instances will race — each mints its own session, and writes land on whichever
won. Create one, pass it to both.
ProphecyWithPrivy is the ~20-line adapter that turns Privy’s Safe smart wallet into a Connect
Wallet (address, executor, token getter). It’s the only wallet-specific code a venue writes — copy
it from the reference venue, or write your own for a different
wallet stack (BYO Adapter).
Then predicting is one hook:
const { predict, openCheckout } = useCheckout()
See Login & Session and Predict + {c} Checkout for the live versions.
The chain
Build it from the network table rather than typing the numbers in — that table also holds the API and gateway hosts, so the chain and the servers cannot end up on different networks:
import { NETWORKS } from '@prophecy-dev/connect-wire'
import { defineChain } from 'viem'
const net = NETWORKS.mainnet // or NETWORKS.testnet
export const somnia = defineChain({
id: net.chainId,
name: net.chainName,
nativeCurrency: net.nativeCurrency,
rpcUrls: { default: { http: [net.rpcUrl] } },
})
Networks: name one, get all of it
A venue carries five things that must agree — the data plane, the gateway, the chain, the contract addresses, and the network you think you are on. Name the network and the rest follows:
<ConnectProvider network="testnet" apiKey={KEY}>
<ProphecyProvider network="testnet" venueId={VENUE} wallet={wallet} reader={reader}>
apiUrl and gatewayUrl are then derived, and their default is no longer a different network.
Pass one explicitly only to override it — staging, a local wrangler dev — and a dev build warns if
an explicit host contradicts the network you named. That combination is a mistake nearly every other
time: it has shipped four times, and each time produced a venue that rendered perfectly, signed
people in on one network and wrote to another, with nothing on the page to suggest it.
Privy setup
Two settings in the Privy dashboard, both of which fail confusingly if you skip them.
1. Allowed origins. Every origin you load the app from must be listed — and an origin includes
the port. http://localhost:5173 and http://localhost:5174 are two different origins, so a dev
server that picked a different port than usual is, to Privy, an unknown site.
When an origin isn’t listed, login fails with a generic “something went wrong” that names nothing and points nowhere. If login breaks the first time you run the app — or breaks today having worked yesterday — check the port before you check anything else.
List every origin you use: each local port, preview/staging deploys, and production.
2. Somnia as a supported chain. Configure chain id 5031 (or 50312 for testnet) in the
dashboard with a bundler and paymaster, and pass the same chain to the provider:
config={{ defaultChain: somnia, supportedChains: [somnia] }}
Miss this and login works fine — it’s the first smart-wallet send that fails, which is a much later and more confusing place to discover it.
When something breaks
The failures people actually hit first, and what they mean:
| Symptom | Cause |
|---|---|
| Privy: generic “something went wrong” on login | The origin isn’t in the allowed list. Check the port. |
npm install 404s on @prophecy-dev/* |
No NPM_TOKEN in the environment. It can also silently drop devDependencies. |
| Login fine, but the first predict fails | Somnia isn’t configured on the Privy app, or supportedChains doesn’t include it. |
Next.js build fails on createContext |
Missing 'use client' at your boundary — the packages don’t ship the directive. |
require() can’t resolve the package |
ESM-only. There is no CommonJS build. |
| Trade reverts on a market that looks open | Un-resolved ≠ tradeable. Gate on isTradeable() — see Structure vs skin. |
Checklist
NPM_TOKENin your environment; an ESM project on React 19 with a globalfetch(plus aWebSocketif you want realtime).npm install @prophecy-dev/connect-react @prophecy-dev/connect-sdk- Wrap in
<ConnectProvider baseUrl=…>→ reads and realtime work. - Trading? Add Privy — your
appId, chain5031, and your origins allowlisted — plus the wallet adapter and oneConnectAuth. - Want your venue’s trades attributed and sponsored? Issue its key — see Your venue’s key. No longer something to ask us for.
- Building the whole UI? Start from
venue-kit(Structure vs skin) rather than assembling cards by hand.