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:
// 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.
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.
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.
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 approvalPlace a limit order
A limit buy at 50% and a limit sell at 70% on the same option. Prices are probabilities in 1e18 scale.
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);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.
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
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.
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.
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.
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' });