packages/sdk is the browser side of zPredict. It mirrors zpredict-core byte for byte and talks to the three relayers, sending each one only its own share. Dependencies: @noble/hashes and @noble/ciphers (BLAKE2b, ChaCha20-Poly1305); X25519 is WebCrypto. It is a workspace package ("@zpredict/sdk": "workspace:*"), not published to npm.
pnpm --filter @zpredict/sdk build # tsc → dist/
pnpm --filter @zpredict/sdk test # node --test, incl. the Rust test vectorsEvery function that talks to more than one relayer first calls distinct(relayers), which throws unless the three URLs are three different origins, so no host ever receives two shares.
Setup
import { RELAYERS, newTag, type Relayers } from "@zpredict/sdk";
const relayers: Relayers = RELAYERS; // the hosted testnet relayers r0, r1, r2
const tag = newTag(); // 16 random bytes, hex: the account's credential. Keep it.RELAYERS is ["https://r0.zpredict.site", "https://r1.zpredict.site", "https://r2.zpredict.site"].
Accounts
import { accountHex, claimJudge, getAccount, keyShares, TableFullError } from "@zpredict/sdk";
accountHex(tag); // public id, hex16
keyShares(tag); // [s0, s1, s2]: relayer i's share of the tag, deterministic
await claimJudge(relayers, tag); // Judge mode (testnet): mock credit; safe to retry
const { balance, seat, mock } = await getAccount(relayers, tag); // balance in half-ticksclaimJudge throws TableFullError when the balance table has no free row. getBalance(relayers, tag) returns only the balance.
submitOrder
import { submitOrder, submitWithCounterparty, getFill } from "@zpredict/sdk";
const order = { market: 1, side: 0, price: 60, size: 10, key: tag } as const; // YES at 60¢ × 10
const { orderId, batch, shares, tokens } = await submitOrder(relayers, order);
// Poll until the batch ran. Keep `tokens`: relayer i answers only to tokens[i].
const res = await getFill(relayers, orderId, tokens);
if (res.status === "filled" && res.fill?.filled) {
const avgCents = res.fill.totalCost / res.fill.size / 2;
}side:0= YES,1= NO.price: 1..=99.size≤MAX_SIZE(1,000,000).getFillreturnspending,unfilled(expired before batching) orfilledwith the decoded record; it throws if the relayers disagree.submitWithCounterparty(relayers, order)is the demo counterparty: it also sendscounterOrder(order)(the other side at100 − price, same account), so a lone judge sees a self-trade fill that nets out at resolution.
Deposits
import { encodeMemo, depositUri, getMemoKeys } from "@zpredict/sdk";
const keys = await getMemoKeys(relayers); // each fetched from its own relayer, checked in order
const memo = await encodeMemo(tag, keys); // 512 bytes; fresh ephemeral key per memo
const uri = depositUri(escrowUa, 5, memo); // ZIP 321: zcash:<ua>?amount=0.05&memo=…DENOMINATIONS is [1, 5, 25] units; depositUri refuses any other amount. unitsToZec(5) is "0.05".
Withdrawals
import { submitWithdrawal, getWithdrawal, payoutZats } from "@zpredict/sdk";
const { withdrawalId } = await submitWithdrawal(relayers, tag, 5000n, freshShieldedUa);
const { status, txid } = await getWithdrawal(relayers, withdrawalId); // relayer 0 pays, so its view
payoutZats(5000n); // (5000 − 2) × 5000 zats: 2 half-ticks go toward the feeamount is in half-ticks and must exceed WITHDRAWAL_FEE (2). Use a fresh unified address with an Orchard-shaped receiver; payouts go to the Ironwood pool.
verifyOutcome
import { getGroupKey, getMarkets, verifyOutcome } from "@zpredict/sdk";
const groupKey = await getGroupKey(relayers); // null unless all three serve the same key
const markets = await getMarkets(relayers[0]);
for (const m of markets) {
if (m.status === "resolved" && groupKey) console.log(m.id, verifyOutcome(m, groupKey));
}verifyOutcome rebuilds the signed message (outcomeMessage: block form or Polymarket form) and checks the RedPallas signature over Pallas in plain BigInt arithmetic; only public data is involved.
Low level
| Export | What |
|---|---|
encodeOrder, decodeOrder, decodeFill, decodeBalance | The byte layouts in Relayer API |
split(bytes), combine(shares) | Three XOR shares and back |
viewDigest(share) | The digest /view publishes for a share, to find your own items |
getView(url, after) | One relayer's /view |
ORDER_LEN, FILL_LEN, BALANCE_LEN, HALF_TICKS_PER_UNIT, UNIT_ZATS, ZATS_PER_HALF_TICK | Constants |
Market, Health, OrderStatus, WithdrawalStatus, … | Reply types |
Source: index.ts, memo.ts, withdraw.ts, outcome.ts.