npm package: @magistery/sdk
Description: TypeScript SDK for Magistery, a permissionless prediction market order book on Polygon using USDC and Conditional Tokens.
Author: Magistery
Latest version: 0.1.4

# @magistery/sdk

TypeScript SDK for [Magistery](https://www.magistery.org) — permissionless prediction market protocol on Polygon. On-chain order book, pluggable resolution, USDC settlement.

**[Documentation](https://www.magistery.org/docs/sdk/getting-started)**

## Install

```bash
npm install @magistery/sdk
```

Requires Node.js 18+.

## Quick Start

```ts
import { createPublicClient, createWalletClient, http } from "viem";
import { polygon } from "viem/chains";
import { MagisteryClient, DEPLOYED_ADDRESSES } from "@magistery/sdk";

// Read-only client (no wallet needed)
const publicClient = createPublicClient({
  chain: polygon,
  transport: http("https://polygon-rpc.com"),
});

const sdk = new MagisteryClient({
  publicClient,
  addresses: DEPLOYED_ADDRESSES,
});

// Read a market
const market = await sdk.getMarket(conditionId);
console.log(market.questionId, market.deadline, market.resolved);
```

### With a wallet (for writes)

```ts
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount("0x...");
const walletClient = createWalletClient({
  account,
  chain: polygon,
  transport: http("https://polygon-rpc.com"),
});

const sdk = new MagisteryClient({
  publicClient,
  walletClient,
  addresses: DEPLOYED_ADDRESSES,
});

// Create a market
const conditionId = await sdk.createMarket(
  "Will ETH hit $10k by end of 2026?",
  2,                    // binary: YES/NO
  1798761600n,          // deadline (unix timestamp)
  DEPLOYED_ADDRESSES.umaResolver!,
);
```

## API Reference

### Constructor

```ts
new MagisteryClient({
  publicClient: PublicClient,       // viem public client (Polygon)
  walletClient?: WalletClient,      // optional, required for writes
  addresses: ProtocolAddresses,     // use DEPLOYED_ADDRESSES for mainnet
})
```

### Reads

| Method | Returns | Description |
|--------|---------|-------------|
| `getMarket(conditionId)` | `Market` | Market struct (questionId, deadline, outcomes, resolved, creator, resolver) |
| `nextOrderId()` | `bigint` | Next order ID that will be assigned |
| `isTrading(conditionId)` | `boolean` | True if market exists and not resolved |
| `isResolvable(conditionId)` | `boolean` | True if past deadline and not yet resolved |
| `getOrder(orderId)` | `Order` | Order struct (maker, side, price, amount, filled, cancelled) |
| `getPosition(owner, conditionId, outcomeIndex)` | `Position` | Token balance for a specific outcome |
| `getPositionId(conditionId, outcomeIndex)` | `bigint` | CTF position ID for an outcome |
| `bondAmount(resolverType?)` | `bigint` | Current UMA/Kalshi bond requirement (default: `"uma"`) |
| `getActiveAssertion(conditionId, resolverType?)` | `Hex` | Active UMA/Kalshi assertion ID (0x0 if none) |
| `getKalshiTicker(conditionId)` | `Hex` | Linked Kalshi ticker for a market |
| `getPolymarketCondition(conditionId)` | `Hex` | Linked Polymarket condition ID |

### Fee Math (pure, no RPC)

| Method | Returns | Description |
|--------|---------|-------------|
| `calculateFee(amount)` | `bigint` | Protocol fee (0.1%, rounded up) |
| `estimateFillBuyVsBuy(price0, remaining0, price1, remaining1)` | `FillEstimate` | Estimate fill for two opposing buy orders |
| `estimateFillBuyVsSell(buyPrice, buyRemaining, sellPrice, sellRemaining)` | `FillEstimate` | Estimate fill for buy vs sell on same outcome |

### Writes (require wallet)

#### Markets

| Method | Returns | Description |
|--------|---------|-------------|
| `createMarket(question, outcomeSlotCount, deadline, resolver)` | `Hex` (conditionId) | Create a new market |
| `forceVoid(conditionId)` | `Hex` (tx hash) | Void a stale market (90d timeout, 180d emergency) |
| `withdrawFees(token)` | `Hex` (tx hash) | Withdraw accumulated fees to treasury recipient |

#### Orders

| Method | Returns | Description |
|--------|---------|-------------|
| `placeBuyOrder(conditionId, outcomeIndex, price, amount)` | `bigint` (orderId) | Place a limit buy order |
| `placeBuyOrders(conditionIds[], outcomeIndexes[], prices[], amounts[])` | `bigint[]` (orderIds) | Batch place buy orders |
| `placeSellOrder(conditionId, outcomeIndex, price, amount)` | `bigint` (orderId) | Place a limit sell order |
| `fillBuyVsBuy(buyId0, buyId1, fillAmount)` | `Hex` (tx hash) | Match two opposing buy orders |
| `fillBuyVsSell(buyId, sellId, fillAmount)` | `Hex` (tx hash) | Match buy and sell on same outcome |
| `cancelOrder(orderId)` | `Hex` (tx hash) | Cancel your own order |
| `cancelOrders(orderIds[])` | `Hex` (tx hash) | Batch cancel orders |
| `cancelExpiredOrder(orderId)` | `Hex` (tx hash) | Cancel any order on a resolved market |

#### Approvals

| Method | Returns | Description |
|--------|---------|-------------|
| `approveUsdc(spender, amount?)` | `Hex` (tx hash) | Approve USDC spending (default: max) |
| `approveCtf(operator)` | `Hex` (tx hash) | Approve CTF token transfers |

#### Resolution (UMA)

| Method | Returns | Description |
|--------|---------|-------------|
| `assertOutcome(conditionId, assertedOutcome)` | `Hex` (tx hash) | Assert market outcome (requires bond) |
| `assertOutcomeFor(conditionId, assertedOutcome, asserter)` | `Hex` (tx hash) | Assert on behalf of another (bond from caller) |
| `settleAssertion(assertionId, resolverType?)` | `Hex` (tx hash) | Settle after liveness period |

#### Resolution (Kalshi)

| Method | Returns | Description |
|--------|---------|-------------|
| `kalshiLinkMarket(conditionId, ticker)` | `Hex` (tx hash) | Link market to Kalshi ticker |
| `kalshiAssertOutcome(conditionId, assertedOutcome)` | `Hex` (tx hash) | Assert via Kalshi resolver |
| `kalshiAssertOutcomeFor(conditionId, assertedOutcome, asserter)` | `Hex` (tx hash) | Assert on behalf via Kalshi |

#### Resolution (Polymarket)

| Method | Returns | Description |
|--------|---------|-------------|
| `polymarketLinkMarket(conditionId, polymarketConditionId)` | `Hex` (tx hash) | Link to Polymarket condition |
| `polymarketResolve(conditionId)` | `Hex` (tx hash) | Resolve from Polymarket outcome |

#### Resolution (Operator)

For operator-deployed resolver contracts (community resolution, bot-managed markets).

| Method | Returns | Description |
|--------|---------|-------------|
| `operatorResolve(conditionId, winnerIndex, evidenceHash?)` | `Hex` (tx hash) | Resolve directly (operator only) |
| `operatorResolveBySig(conditionId, winnerIndex, evidenceHash, nonce, expiry, signature)` | `Hex` (tx hash) | Resolve via EIP-712 signature |
| `operatorResolverOperator()` | `Address` | Get the operator address |
| `operatorResolverDomainSeparator()` | `Hex` | EIP-712 domain separator |
| `operatorResolverNonceUsed(nonce)` | `boolean` | Check if signature nonce is consumed |

#### Redemption

| Method | Returns | Description |
|--------|---------|-------------|
| `redeemWinnings(conditionId, outcomeIndices[])` | `Hex` (tx hash) | Redeem winning outcome tokens for USDC |

#### Multi-Outcome (NegRisk)

| Method | Returns | Description |
|--------|---------|-------------|
| `createMultiOutcomeMarket(question, labels[], deadline, resolver)` | `bigint` (marketId) | Create multi-outcome market (3+ outcomes) |
| `getOutcomeCount(marketId)` | `bigint` | Number of outcomes |
| `getOutcomeConditionId(marketId, outcomeIndex)` | `Hex` | ConditionId for a specific leg |
| `getAllConditionIds(marketId)` | `Hex[]` | All leg conditionIds |
| `getOutcomeLabel(marketId, outcomeIndex)` | `string` | Label for a specific outcome |
| `isMultiOutcomeResolved(marketId)` | `boolean` | True if all legs resolved |

### Safety Helpers (for frontends)

| Method | Returns | Description |
|--------|---------|-------------|
| `isTrustedResolver(conditionId, trustedAddresses[])` | `boolean` | Check resolver against whitelist |
| `isKalshiLinked(conditionId)` | `boolean` | Check if Kalshi market is linked to a ticker |
| `isPolymarketLinked(conditionId)` | `boolean` | Check if Polymarket market is linked |
| `isPastDeadline(conditionId)` | `boolean` | True if past deadline and not resolved (danger zone) |

## Types

```ts
interface Market {
  questionId: Hex;
  conditionId: Hex;
  deadline: bigint;
  outcomeSlotCount: number;
  resolved: boolean;
  creator: Address;
  resolver: Address;
}

interface Order {
  orderId: bigint;
  maker: Address;
  side: Side;          // Side.BUY (0) or Side.SELL (1)
  cancelled: boolean;
  conditionId: Hex;
  outcomeIndex: bigint;
  price: bigint;       // 6 decimals (1e6 = $1.00)
  amount: bigint;
  filled: bigint;
}

interface Position {
  conditionId: Hex;
  outcomeIndex: number;
  positionId: bigint;
  balance: bigint;
}

interface FillEstimate {
  fillAmount: bigint;
  cost: bigint;
  fee: bigint;
  surplus: bigint;
  sellerProceeds: bigint;
}

interface ProtocolAddresses {
  factory: Address;
  orderBook: Address;
  treasury: Address;
  negRiskAdapter: Address;
  umaResolver?: Address;
  kalshiResolver?: Address;
  polymarketResolver?: Address;
  operatorResolver?: Address;
}
```

## Constants

```ts
import {
  DEPLOYED_ADDRESSES,    // All 7 mainnet contract addresses
  CTF_ADDRESS,           // Gnosis CTF (0x4D97...045)
  USDC_ADDRESS,          // Native USDC (0x3c49...359)
  UMA_OOV3_ADDRESS,      // UMA Oracle (0x5953...3D)
  POLYGON_CHAIN_ID,      // 137
  PRICE_SCALE,           // 1_000_000n ($1.00)
  MIN_ORDER_COST,        // 10_000n ($0.01)
  FEE_BPS,               // 10n (0.1%)
  BOND_AMOUNT,           // 750_000_000n (750 USDC)
  LIVENESS,              // 7200n (2 hours)
  VOID_TIMEOUT,          // 7_776_000n (90 days)
  EMERGENCY_VOID_TIMEOUT,// 15_552_000n (180 days)
} from "@magistery/sdk";
```

## Price Scale

All prices use 6 decimals (USDC decimals). `1_000_000n` = $1.00.

| Price | Meaning |
|-------|---------|
| `650_000n` | $0.65 (65% probability) |
| `1_000_000n` | $1.00 (100% — max) |
| `10_000n` | $0.01 (1% — minimum order) |

## Approval Flow

Before placing orders, approve the OrderBook to spend your USDC and transfer your CTF tokens:

```ts
await sdk.approveUsdc(DEPLOYED_ADDRESSES.orderBook);
await sdk.approveCtf(DEPLOYED_ADDRESSES.orderBook);
```

Before asserting outcomes (UMA/Kalshi), approve the resolver to spend the bond:

```ts
const bond = await sdk.bondAmount(); // 750 USDC
await sdk.approveUsdc(DEPLOYED_ADDRESSES.umaResolver!, bond);
```

## Example: Full Trading Flow

```ts
// 1. Create market
const cid = await sdk.createMarket("Will X happen?", 2, deadline, resolver);

// 2. Place buy order on YES at $0.60
const buyId = await sdk.placeBuyOrder(cid, 0, 600_000n, 10_000_000n);

// 3. Someone else places buy on NO at $0.45
const noId = await sdk.placeBuyOrder(cid, 1, 450_000n, 10_000_000n);

// 4. Match the orders (anyone can call this)
await sdk.fillBuyVsBuy(buyId, noId, 10_000_000n);

// 5. After resolution, redeem winnings
await sdk.redeemWinnings(cid, [0]); // redeem YES tokens
```

## CLI

The SDK ships a CLI for direct protocol interaction. No code required.

```bash
# Set environment
export POLYGON_RPC_URL=https://...
export MAGISTERY_PRIVATE_KEY=0x...  # only for write commands

# Read commands
npx @magistery/sdk market get <conditionId>
npx @magistery/sdk market list
npx @magistery/sdk market search "ETH"
npx @magistery/sdk order get <orderId>
npx @magistery/sdk position list

# Trading
npx @magistery/sdk order buy <conditionId> 0 0.55 100
npx @magistery/sdk order sell <conditionId> 0 0.6 50
npx @magistery/sdk order cancel <orderId>

# Resolution
npx @magistery/sdk market assert <conditionId> 0
npx @magistery/sdk market settle <assertionId>
npx @magistery/sdk market redeem <conditionId>
```

All commands output JSON. Prices are 0–1 exclusive. Amounts are in shares with max 6 decimal precision.

## Testing

```sh
# Unit tests (no RPC needed)
pnpm test

# Fork tests (requires Polygon RPC)
POLYGON_RPC=https://... pnpm test:fork
```

135 tests total (84 unit + 51 fork).

## License

MIT