zPredictdocs
App
Menu

SDK

View .md

@zpredict/sdk: order encoding, XOR sharing, deposit memos, withdrawals, outcome verification and the relayer client, as used by the web app.

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 vectors

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

claimJudge 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).
  • getFill returns pending, unfilled (expired before batching) or filled with the decoded record; it throws if the relayers disagree.
  • submitWithCounterparty(relayers, order) is the demo counterparty: it also sends counterOrder(order) (the other side at 100 − 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 fee

amount 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

ExportWhat
encodeOrder, decodeOrder, decodeFill, decodeBalanceThe 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_TICKConstants
Market, Health, OrderStatus, WithdrawalStatus, …Reply types

Source: index.ts, memo.ts, withdraw.ts, outcome.ts.