Code Examples

Complete, copy-paste TypeScript for the most common Rain integrations. Every example uses 1-based option indexes, 1e18-scale prices, and per-token decimals (parseUnits(x, 6) for USDT, parseUnits(x, 18) for RAIN).

Shared setup

All examples below assume this boilerplate:

TypeScript
// setup.ts
import { Rain, TradingModel, OptionSide } from 'rain-sdk-v2';
import {
  createWalletClient, createPublicClient, http,
  parseUnits, parseEther, type Hex,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum } from 'viem/chains';

export const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
export const wallet = createWalletClient({ account, chain: arbitrum, transport: http() });
export const publicClient = createPublicClient({ chain: arbitrum, transport: http() });

export const rain = new Rain({ environment: 'development' });
export const config = rain.getEnvironmentConfig();

// Helper: sign + send raw transactions sequentially and wait for receipts
export async function send(txs: { to: Hex; data: Hex; value?: bigint }[] | { to: Hex; data: Hex; value?: bigint }) {
  const list = Array.isArray(txs) ? txs : [txs];
  for (const tx of list) {
    const hash = await wallet.sendTransaction({ to: tx.to, data: tx.data, value: tx.value ?? 0n });
    await publicClient.waitForTransactionReceipt({ hash });
    console.log('✓ confirmed', hash);
  }
}

Create a market

A public Yes/No market with 10 USDT initial liquidity, AI resolution, trading for 7 days.

TypeScript
import { rain, config, account, send } from './setup';
import { TradingModel } from 'rain-sdk-v2';
import { parseUnits } from 'viem';

const now = Math.floor(Date.now() / 1000);

const txs = await rain.buildCreateMarketTx({
  marketQuestion: 'Will BTC close above $150k this quarter?',
  marketOptions: ['Yes', 'No'],
  marketTags: ['crypto'],
  marketDescription: 'Resolves Yes if BTC/USD closes above $150,000 on the last day of the quarter.',
  isPublic: true,
  isPublicPoolResolverAi: true,         // AI resolver
  creator: account.address,
  startTime: BigInt(now + 120),         // opens in 2 min
  endTime: BigInt(now + 7 * 86400),     // 7 days of trading
  no_of_options: 2n,
  disputeTimer: 259200,                 // 3-day oracle window
  inputAmountWei: parseUnits('10', 6),  // 10 USDT (6 decimals!)
  barValues: [50, 50],                  // sums to 100
  baseToken: config.tokens.usdt.address,
  tradingModel: TradingModel.AMM,       // or TradingModel.OrderBook
  marketImage: 'https://cdn.example.com/btc-market.png',
});

await send(txs); // [approveTx?, createPoolTx]

Buy shares (AMM)

Buy 5 USDT of Yes on option 1. Approval and slippage protection are automatic.

TypeScript
import { rain, account, send } from './setup';
import { OptionSide } from 'rain-sdk-v2';
import { parseUnits } from 'viem';

const MARKET = '0xYourMarketContract' as const;

// Optional: quote first
const quote = await rain.getEntryShares({
  marketContractAddress: MARKET,
  option: 1n,                 // 1-based index
  optionSide: 1,              // 1 = Yes
  amount: parseUnits('5', 6),
});
console.log('expected shares:', quote.returnedShares);

const txs = await rain.buildEnterOptionTx({
  marketContractAddress: MARKET,
  selectedOption: 1n,
  optionSide: OptionSide.Yes,
  buyAmountInWei: parseUnits('5', 6), // 5 USDT
  walletAddress: account.address,
  slippageTolerance: 5n,              // 5%
  deadline: 600n,                     // 10 min
});

await send(txs); // [approveTx?, enterOptionTx]

Market-sell shares

Sell shares into the resting buy order book. No approval needed — you're selling shares, not tokens.

TypeScript
import { rain, account, send } from './setup';
import { OptionSide } from 'rain-sdk-v2';

const MARKET = '0xYourMarketContract' as const;

// How many Yes shares do I hold on option 1?
const myShares = await rain.getUserOptionShares({
  marketContractAddress: MARKET,
  option: 1n,
  optionSide: 1, // Yes
  userAddress: account.address,
});

const tx = await rain.buildSellOptionTx({
  marketContractAddress: MARKET,
  selectedOption: 1n,
  optionSide: OptionSide.Yes,
  sharesAmount: myShares,    // sell everything
  slippageTolerance: 5n,
  deadline: 600n,
});

await send(tx); // single tx, no approval

Place a limit order

A limit buy at 50% and a limit sell at 70% on the same option. Prices are probabilities in 1e18 scale.

TypeScript
import { rain, account, send } from './setup';
import { OptionSide } from 'rain-sdk-v2';
import { parseEther, parseUnits } from 'viem';

const MARKET = '0xYourMarketContract' as const;

// --- Limit BUY: 10 USDT bid at 50% ---
const buyTxs = await rain.buildPlaceBuyOrderTx({
  marketContractAddress: MARKET,
  option: 1n,
  optionSide: OptionSide.Yes,
  price: parseEther('0.5'),     // 0.50 = 50% (1e18 scale)
  amount: parseUnits('10', 6),  // 10 USDT
  walletAddress: account.address,
});
await send(buyTxs); // [approveTx?, placeBuyOrderTx]

// --- Limit SELL: offer my shares at 70% ---
const shares = await rain.getUserOptionShares({
  marketContractAddress: MARKET,
  option: 1n, optionSide: 1, userAddress: account.address,
});

const sellTx = rain.buildPlaceSellOrderTx({
  marketContractAddress: MARKET,
  option: 1n,
  optionSide: OptionSide.Yes,
  price: parseEther('0.7'),  // 70%
  shares,                    // escrowed by the contract
});
await send(sellTx);

// --- Inspect the book ---
const bestBid = await rain.getFirstBuyOrderPrice({
  marketContractAddress: MARKET, option: 1n, optionSide: 1,
});
const bestAsk = await rain.getFirstSellOrderPrice({
  marketContractAddress: MARKET, option: 1n, optionSide: 1,
});
console.log('best bid:', bestBid, 'best ask:', bestAsk); // 1e18 scale

const level = await rain.getBuyOrdersAtPrice({
  marketContractAddress: MARKET, option: 1n, optionSide: 1,
  price: parseEther('0.5'),
});
console.log('orders resting at 50%:', level.count);
Note: limit buy orders are available when the AMM pool is closed (order book trading model). Order IDs come from the PlaceBuyOrder event in the receipt logs — keep them for cancellation.

Cancel orders

Cancel multiple resting orders in one transaction using the order IDs from the PlaceBuyOrder event.

TypeScript
import { rain, send } from './setup';
import { parseEther } from 'viem';

const MARKET = '0xYourMarketContract' as const;

// Verify an order is still resting before cancelling
const check = await rain.checkOrderExists({
  marketContractAddress: MARKET,
  option: 1n,
  optionSide: 1,
  price: parseEther('0.5'),
  orderID: 1n,
});

if (check.exists) {
  const tx = rain.buildCancelBuyOrdersTx({
    marketContractAddress: MARKET,
    option: 1n,
    optionSides: [1, 1],                              // Yes, Yes
    prices: [parseEther('0.5'), parseEther('0.6')],
    orderIDs: [1n, 2n],                               // from PlaceBuyOrder events
  });
  await send(tx);
}

// Sell-order cancellation is identical:
// rain.buildCancelSellOrdersTx({ ...same shape... })

Add (and remove) liquidity

TypeScript
import { rain, account, send } from './setup';
import { parseUnits } from 'viem';

const MARKET = '0xYourMarketContract' as const;

// Add 10 USDT of liquidity to option 1
const addTxs = await rain.buildAddLiquidityTx({
  marketContractAddress: MARKET,
  option: 1n,
  totalAmountInWei: parseUnits('10', 6),
  walletAddress: account.address,
  slippageTolerance: 5n, // min deposits auto-derived from AMM reserves
});
await send(addTxs); // [approveTx?, addLiquidityTx]

// Later: remove it all
const lpShares = await rain.getUserOptionLPShares({
  marketContractAddress: MARKET,
  option: 1n,
  userAddress: account.address,
});

const removeTx = await rain.buildRemoveLiquidityTx({
  marketContractAddress: MARKET,
  option: 1n,
  lpShares,
  slippageTolerance: 5n, // min outputs auto-derived from getRemovedLiquidity
});
await send(removeTx);

Full lifecycle: create → trade → close → claim

An end-to-end script covering the whole market lifecycle, including resolution and claiming.

TypeScript
import { rain, config, account, send } from './setup';
import { TradingModel, OptionSide } from 'rain-sdk-v2';
import { parseUnits } from 'viem';

const sleep = (s: number) => new Promise(r => setTimeout(r, s * 1000));
const now = Math.floor(Date.now() / 1000);

// ── 1. CREATE ────────────────────────────────────────────────
const createTxs = await rain.buildCreateMarketTx({
  marketQuestion: 'Will it rain in London tomorrow?',
  marketOptions: ['Yes', 'No'],
  marketTags: ['weather'],
  marketDescription: 'Resolves Yes if measurable precipitation is recorded.',
  isPublic: true,
  isPublicPoolResolverAi: true,
  creator: account.address,
  startTime: BigInt(now + 60),
  endTime: BigInt(now + 600),            // short demo window: 10 minutes
  no_of_options: 2n,
  disputeTimer: 259200,
  inputAmountWei: parseUnits('10', 6),
  barValues: [50, 50],
  baseToken: config.tokens.usdt.address,
  tradingModel: TradingModel.AMM,
  marketImage: 'https://cdn.example.com/rain-market.png',
});
await send(createTxs);

// Find the market contract via the REST API once indexed
// const pool = await rain.getPoolByContractAddress({ contractAddress });
const MARKET = '0xNewMarketContract' as const;

// ── 2. TRADE (after startTime) ───────────────────────────────
await sleep(90);
const buyTxs = await rain.buildEnterOptionTx({
  marketContractAddress: MARKET,
  selectedOption: 1n,
  optionSide: OptionSide.Yes,
  buyAmountInWei: parseUnits('5', 6),
  walletAddress: account.address,
});
await send(buyTxs);

// ── 3. CLOSE (after endTime) ─────────────────────────────────
// AI resolver — posts a resolver bond (approval handled automatically)
const closeTxs = await rain.buildClosePoolAITx({
  marketContractAddress: MARKET,
  option: 1n,
});
await send(closeTxs);

// ── 4. RESOLVE & CLAIM (after the dispute window) ────────────
const window = await rain.getDisputeWindow({ marketContractAddress: MARKET });
console.log(`dispute window: ${window}s`);

const calcTx = await rain.buildCalculateWinnerTx({
  marketContractAddress: MARKET,
  option: 1n,
});
await send(calcTx);

const claimed = await rain.getOptionClaimed({
  marketContractAddress: MARKET,
  option: 1n,
  userAddress: account.address,
});
if (!claimed) {
  const claimTx = rain.buildClaimTx({ marketContractAddress: MARKET, option: 1n });
  await send(claimTx);
  console.log('🎉 winnings claimed');
}

Listen to live prices (WebSocket)

Stream realtime prices, order events, and your own claims via Socket.IO.

TypeScript
import { RainSocket } from 'rain-sdk-v2';

const socket = new RainSocket('https://dev-api.rain.one');
socket.connect();

const poolId = 'your-pool-id'; // from the REST API (getPoolById / getPublicPools)

// Live price updates
const unsubPrice = socket.onSyncPrice(poolId, (data) => {
  for (const p of data.prices) {
    console.log(
      `subPool ${p.subPoolIndex} side ${p.side === 1 ? 'YES' : 'NO'}: ` +
      `${(p.price * 100).toFixed(1)}%`
    );
  }
});

// Order book activity
socket.onOrderCreated(poolId, (d) => console.log('order created', d.order));
socket.onOrderFilled(poolId, (d) => {
  console.log('filled', d.filledOrder);
  if (d.pendingOrder) console.log('partial — still resting:', d.pendingOrder);
});
socket.onOrderCancelled(poolId, (d) => console.log('order cancelled', d.order));

// Trades & lifecycle
socket.onEnterOption(poolId, (d) => console.log('buy', d.enterOption));
socket.onPoolClosed(poolId, (d) => console.log('pool closed', d.pool));

// New pools across the protocol
socket.onNewPool((d) => console.log('new pool:', d.pool));

// Clean up
// unsubPrice();
// socket.disconnect();

Login flow (REST authentication)

Sign the lowercased wallet address, exchange it for a JWT, and call authenticated endpoints.

TypeScript
import { rain, wallet, account } from './setup';
import { signLoginMessage } from 'rain-sdk-v2';

// 1. Sign the login message (personal_sign of the lowercased address)
const signature = await signLoginMessage(wallet, account.address);

// 2. Exchange for a JWT
const { accessToken, userId } = await rain.login({
  signature,
  walletAddress: account.address,
  smartWalletAddress: account.address, // or your RainAA smart account address
  // referredBy: 'CODE',               // optional
});

// 3. Call authenticated endpoints
const profile = await rain.getUserProfile(accessToken);
const positions = await rain.getOpenPositions({ poolId: 'your-pool-id' }, accessToken);
const pnl = await rain.calculateUserPnl({ walletAddress: account.address }, accessToken);
const orders = await rain.getUserOrders({ limit: 10, offset: 1, filter: 'pending' }, accessToken);

// 4. Public endpoints need no token
const pools = await rain.getPublicPools({ limit: 10, offset: 0, status: 'Live', sortBy: 'trending' });
const book = await rain.getOrderBook({ pool: 'your-pool-id' });
Want more? The SDK Reference documents every method, and the CLOB page previews the next-generation fully on-chain order book.