SDK Reference
rain-sdk-v2 — TypeScript SDK for the Rain prediction markets protocol on Arbitrum One. Transaction builders, on-chain reads, REST API wrapper, and Socket.IO realtime events.
parseEther('0.5') = 50%) · token amounts use parseUnits(x, 6) for USDT and parseUnits(x, 18) for RAIN · OptionSide.Yes = 1, OptionSide.No = 2.Core classes
Rain
Stateless class for building transactions and querying on-chain data. Never holds keys — it returns RawTransaction objects you sign yourself.
const rain = new Rain({
environment: 'production', // 'development' | 'stage' | 'production' (default: development)
rpcUrl: 'https://arb1.arbitrum.io/rpc', // optional
apiUrl: 'https://prod2-api.rain.one', // optional, derived from environment
});| Constructor param | Type | Default | Description |
|---|---|---|---|
environment | 'development' | 'stage' | 'production' | development | Target environment |
rpcUrl | string | random public RPC | Custom Arbitrum RPC |
apiUrl | string | from environment | Custom backend API URL |
All builders return a RawTransaction or RawTransaction[] (approval first when needed):
interface RawTransaction {
to: `0x${string}`;
data: `0x${string}`;
value?: bigint;
}RainAA — smart accounts (Account Abstraction)
Stateful class for smart account management with Alchemy gas sponsorship. Wraps any RawTransaction from the builders into a sponsored user operation.
import { RainAA } from 'rain-sdk-v2';
const rainAA = new RainAA({
walletClient, // viem WalletClient (required)
alchemyApiKey: '...', // required
paymasterPolicyId: '...', // required
chain: arbitrum, // required
rpcUrl: '...', // optional
});
const smartAccountAddress = await rainAA.connect(); // init smart account
const hash = await rainAA.sendTransaction(rawTx); // gas-sponsored send
rainAA.address; // getter — smart account address (throws if not connected)
rainAA.client; // getter — smart wallet client
rainAA.disconnect(); // reset connection stateMarket creation
buildCreateMarketTx(params) write
Creates a new prediction market. Returns [approveTx?, createPoolTx].
const txs = await rain.buildCreateMarketTx({
marketQuestion: 'Will ETH reach $5000 by end of year?',
marketOptions: ['Yes', 'No'],
marketTags: ['crypto'],
marketDescription: 'Prediction on ETH price target',
isPublic: true,
isPublicPoolResolverAi: true,
creator: '0x...',
startTime: BigInt(Math.floor(Date.now() / 1000) + 120),
endTime: BigInt(Math.floor(Date.now() / 1000) + 86400),
no_of_options: 2n,
disputeTimer: 259200,
inputAmountWei: parseUnits('10', 6),
barValues: [50, 50],
initialYesPrices: [parseEther('0.5'), parseEther('0.5')], // optional
baseToken: config.tokens.usdt.address,
tradingModel: TradingModel.AMM, // AMM = 0, OrderBook = 1
marketImage: 'https://cdn.example.com/market-image.png',
});| Parameter | Type | Description |
|---|---|---|
marketQuestion | string | The market question |
marketOptions | string[] | Option labels (2–26 options) |
marketTags | string[] | Tags (1–3) |
marketDescription | string | Description |
isPublic | boolean | Public market |
isPublicPoolResolverAi | boolean | true for AI resolver, false for manual |
creator | 0x${string} | Creator / pool owner address |
startTime | bigint | Unix timestamp (seconds) when trading opens |
endTime | bigint | Unix timestamp (seconds) when trading ends |
no_of_options | bigint | Number of options |
inputAmountWei | bigint | Initial liquidity in base token wei |
disputeTimer | number | Oracle end-time duration in seconds (e.g. 259200 = 3 days). Auto-set from environment config |
barValues | number[] | Probability distribution, 0–100 scale, sums to 100 |
initialYesPrices | bigint[] | Optional. Initial Yes prices per option in 1e18 scale. Defaults to 50% |
baseToken | 0x${string} | Base token address (USDT or RAIN) |
tradingModel | TradingModel | AMM (0) or OrderBook (1) |
marketImage | string | Market image URL (required) |
liquidity + oracleFixedFeePerOption × nOptions. RAIN: liquidity + nOptions × 20% of liquidity (e.g. 1000 RAIN with 5 options → 2000 RAIN approval).Trading
buildEnterOptionTx(params) write
Buy shares of an option (AMM trade). Reads the market's base token, checks allowance, includes an approval TX if needed, and auto-calculates slippage protection via on-chain getEntryShares. Returns [approveTx?, enterOptionTx].
const txs = await rain.buildEnterOptionTx({
marketContractAddress: '0x...',
selectedOption: 1n, // 1-based
optionSide: OptionSide.Yes, // Yes = 1, No = 2
buyAmountInWei: parseUnits('5', 6), // 5 USDT
walletAddress: '0x...',
slippageTolerance: 5n, // optional, default 5%
deadline: 600n, // optional, default 600s
});| Parameter | Type | Description |
|---|---|---|
marketContractAddress | 0x${string} | Market contract |
selectedOption | bigint | Option index (1-based) |
optionSide | OptionSide | Yes (1) or No (2) |
buyAmountInWei | bigint | Amount in base token wei |
walletAddress | 0x${string} | For allowance check |
minSharesOut | bigint | Optional. Auto-calculated from getEntryShares with slippage |
slippageTolerance | bigint | Optional. Percent, default 5n |
deadline | bigint | Optional. Duration in seconds, default 600n |
buildSellOptionTx(params) write
Market-sell shares into the resting buy order book. No approval needed (you sell shares, not tokens). Slippage protection auto-calculated from on-chain getCurrentPrice. Returns a single RawTransaction.
const tx = await rain.buildSellOptionTx({
marketContractAddress: '0x...',
selectedOption: 1n,
optionSide: OptionSide.Yes,
sharesAmount: parseUnits('10', 6), // shares to sell
slippageTolerance: 5n, // optional
deadline: 600n, // optional
});Split & merge
buildSplitTx(params) write
Split base tokens into equal Yes + No shares for an option (e.g. 5 USDT → 5 Yes + 5 No). Allowance handled automatically. Returns [approveTx?, splitTx].
const txs = await rain.buildSplitTx({
marketContractAddress: '0x...',
option: 1n,
amount: parseUnits('5', 6),
walletAddress: '0x...',
});buildMergeTx(params) write
Merge equal Yes + No share pairs back into base tokens. Burns shares, returns base token; no approval needed.
const tx = rain.buildMergeTx({
marketContractAddress: '0x...',
option: 1n,
amount: 5000000n, // raw shares (from getUserOptionShares)
});Liquidity
buildAddLiquidityTx(params) write
Add liquidity to a specific option. Slippage protection auto-calculated proportionally from on-chain ammYesReserve / ammNoReserve. Returns [approveTx?, addLiquidityTx].
const txs = await rain.buildAddLiquidityTx({
marketContractAddress: '0x...',
option: 1n,
totalAmountInWei: parseUnits('10', 6),
walletAddress: '0x...',
slippageTolerance: 5n, // optional
deadline: 600n, // optional
});buildRemoveLiquidityTx(params) write
Remove liquidity by burning LP shares. Min outputs auto-calculated via on-chain getRemovedLiquidity.
const lpShares = await rain.getUserOptionLPShares({
marketContractAddress: '0x...', option: 1n, userAddress: '0x...',
});
const tx = await rain.buildRemoveLiquidityTx({
marketContractAddress: '0x...',
option: 1n,
lpShares,
slippageTolerance: 5n, // optional
});Order book
Each market option/side has an on-chain limit order book with price-level FIFO queues (price-time priority). Order IDs are emitted in the PlaceBuyOrder event.
buildPlaceBuyOrderTx(params) write
Place a limit buy order. Allowance checked automatically; available when the AMM pool is closed. Returns [approveTx?, placeBuyOrderTx].
const txs = await rain.buildPlaceBuyOrderTx({
marketContractAddress: '0x...',
option: 1n,
optionSide: OptionSide.Yes,
price: parseEther('0.5'), // 50% in 1e18 scale
amount: parseUnits('10', 6), // 10 USDT
walletAddress: '0x...',
});buildPlaceSellOrderTx(params) write
Place a limit sell order. Shares are escrowed by the contract; no token approval needed.
const tx = rain.buildPlaceSellOrderTx({
marketContractAddress: '0x...',
option: 1n,
optionSide: OptionSide.Yes,
price: parseEther('0.7'), // 70%
shares: 5000000n, // from getUserOptionShares
});buildCancelBuyOrdersTx / buildCancelSellOrdersTx write
Cancel one or more orders in a single transaction. Both share the same interface.
const tx = rain.buildCancelBuyOrdersTx({
marketContractAddress: '0x...',
option: 1n,
optionSides: [1, 1], // Yes, Yes
prices: [parseEther('0.5'), parseEther('0.6')],
orderIDs: [1n, 2n], // from PlaceBuyOrder event
});Order book reads read
| Method | Returns | Description |
|---|---|---|
getFirstBuyOrderPrice({ marketContractAddress, option, optionSide }) | bigint | Best bid price (1e18 scale) |
getFirstSellOrderPrice({ ... }) | bigint | Best ask price (1e18 scale) |
getBuyOrdersAtPrice({ ..., price }) | OrderLevelInfo | Level info: { headIndex, tailIndex, count, isInitialized } — FIFO queue per price level |
getSellOrdersAtPrice({ ..., price }) | OrderLevelInfo | Same, for sells |
checkOrderExists({ ..., price, orderID }) | { exists, index } | Check a specific resting order |
getUserActiveBuyOrders({ marketContractAddress, userAddress }) | bigint | Count of user's active buy orders |
getUserActiveSellOrders({ ... }) | bigint | Count of user's active sell orders |
Market resolution
| Method | Description |
|---|---|
buildClosePoolAITx({ marketContractAddress, option }) | Close with AI resolver. Returns [approveTx (resolver bond), closePoolTx] |
buildClosePoolManualTx({ ..., proposedWinner }) | Close with manual resolver, proposing a winner (1 = Yes, 2 = No) |
buildChooseWinnerTx({ ..., optionSide }) | Finalize winner after pool is closed |
buildCalculateWinnerTx({ marketContractAddress, option }) | Calculate winner via the oracle resolver. Must be called before claim. Reads optionResolver(option) and calls calculateWinner() on it |
buildExtendTimeTx({ ..., newEndTime, signature }) | Extend resolution time. Requires a backend signature from signOraclesExtendTime |
// Close with AI resolver (after endTime)
const closeTxs = await rain.buildClosePoolAITx({ marketContractAddress: '0x...', option: 1n });
// Or close manually, proposing the winner
const closeManual = await rain.buildClosePoolManualTx({
marketContractAddress: '0x...', option: 1n, proposedWinner: 1, // 1 = Yes
});
// After the dispute window: calculate the winner, then claim
const calcTx = await rain.buildCalculateWinnerTx({ marketContractAddress: '0x...', option: 1n });Claiming
buildClaimTx(params) write
Claim winnings after resolution. Call buildCalculateWinnerTx first; optionally check getOptionClaimed to avoid reverts.
const tx = rain.buildClaimTx({ marketContractAddress: '0x...', option: 1n });Disputes & appeals
buildOpenDisputeTx(params) write
Open a dispute — or an appeal, if a dispute already exists. The fee is read from getDisputeAppealFee(option). Returns [approveTx (dispute fee), openDisputeTx].
// Call once to open a dispute; call again after the dispute window to appeal
const txs = await rain.buildOpenDisputeTx({ marketContractAddress: '0x...', option: 1n });Evidence and dispute messaging (text, images, video, PDFs, YouTube links) goes through the REST API — see createDisputeMessage / getPoolDisputeConvo in the REST overview.
Token approvals
buildApprovalTx(params) write
Standalone ERC-20 approve (most builders handle approvals automatically — use this for manual control).
const tx = rain.buildApprovalTx({
tokenAddress: config.tokens.usdt.address,
spender: '0x...', // market contract
amount: parseUnits('100', 6),
});Read methods (on-chain queries)
| Method | Returns | Description |
|---|---|---|
getTokenAllowance({ tokenAddress, owner, spender }) | bigint | Current ERC-20 allowance |
getUserOptionShares({ ..., option, optionSide, userAddress }) | bigint | User's Yes or No shares for an option |
getUserOptionLPShares({ ..., option, userAddress }) | bigint | User's LP shares for an option |
getEntryShares({ ..., option, optionSide, amount }) | { returnedShares, expectedReward } | Quote: shares received for a buy amount |
getOptionClaimed({ ..., option, userAddress }) | boolean | Whether the user already claimed winnings |
getDynamicPayout({ ..., userAddress, option }) | bigint[] | Payout amounts per side: [yesPayoutWei, noPayoutWei] |
getDisputeWindow({ marketContractAddress }) | bigint | Dispute window duration in seconds |
| Plus the order book reads listed above. | ||
Authentication
Wallet-based login to the Rain backend: sign the lowercased wallet address with personal_sign, then exchange the signature for a JWT.
import { signLoginMessage } from 'rain-sdk-v2';
const signature = await signLoginMessage(walletClient, walletAddress);
const result = await rain.login({
signature,
walletAddress, // EOA address
smartWalletAddress, // smart account address
referredBy: 'CODE', // optional referral code
});
// result.accessToken — JWT for authenticated REST routes
// result.userId — backend user ID
// Later: check token validity
const status = await rain.checkTokenExpiration(result.accessToken);REST API overview
The SDK wraps the full Rain backend REST API. Authenticated routes take an accessToken (from login) as the last argument. Highlights by domain:
| Domain | Key methods |
|---|---|
| Pools | getPublicPools (filter/sort, e.g. status: 'Live', sortBy: 'trending'), getPrivatePools, getPoolById, getPoolByContractAddress, searchPool, getFeaturedPools, getRelatedPools, getPoolResolutionHistory, accessPool / verifyAccessCode (private pools), getPoolTotalParticipants, updatePoolResolutionTime, signOraclesExtendTime |
| Investments | getUserPositions (per-side net shares, avg buy price, projected payout), getOpenPositions, getUserTotalInvestment, getOptionsTotalVolume, getPoolActivity, getTopHolders, getUserInvestedPools, calculateUserPnl, getUserPnlGraph, getPlatformTvl, getTvlGraph, getPlatformVolume, getTopWinnersLosers, calculateUserOpenInterest |
| Orders | getOrderBook({ pool }), getUserOrders({ filter: 'pending' }), getUserOrderByPoolId, getOrdersListingByPool, getOrderById |
| Users | findUserByWalletAddress, getUserProfile, viewUserProfile, updateUserProfile, getUserHistory, getUsersTotalCount, checkTokenExpiration |
| Points | getUserPoints, getUserPointsGraph, addUserPoints, userSuccessfulOnboarding |
| Notifications | getNotifications, markAllNotificationsAsRead, markNotificationAsRead |
| Price data | getPriceData({ contractAddress, side, filter }) — filters '1H' | '6H' | '1D' | '1W' | '1M' | 'ALL' |
| Comments & reviews | createComment (with replies), getCommentsListing, likeComment / unlikeComment, addReview, getUserReviews |
| Follow | toggleFollow, checkFollow, getFollowers, getFollowing, getFollowStats |
| RAIN burn | getTotalBurned, getBurnPerPool |
| Disputes | createDisputeMessage (text / image / video / file / YouTube evidence, max 25MB per file), getPoolDisputeConvo |
| Tokens | getTokenPrice(tokenAddress) — USD price |
WebSocket events (RainSocket)
Realtime event subscriptions via Socket.IO. Every on* helper returns an unsubscribe function.
import { RainSocket } from 'rain-sdk-v2';
const socket = new RainSocket('https://dev-api.rain.one');
socket.connect();
const unsub = socket.onSyncPrice(poolId, (data) => {
// data.prices = [{ side: 1, price: 0.62, percentage: 62, subPoolIndex: 1 }, ...]
});
socket.onChannel('custom-event/123', (data) => { /* generic subscription */ });
unsub();
socket.disconnect();Pool-scoped events
| Channel | Helper | Fires when |
|---|---|---|
enter-option/{poolId} | onEnterOption | Shares bought (AMM or order match) |
exit-option/{poolId} | onExitOption | Shares sold (order match) |
sync-price/{poolId} | onSyncPrice | Price update |
order-created/{poolId} | onOrderCreated | New order placed |
order-cancelled/{poolId} | onOrderCancelled | Order cancelled |
order-filled/{poolId} | onOrderFilled | Order filled (full or partial; payload has filledOrder + optional pendingOrder) |
liquidity/{poolId} | onLiquidity | Liquidity added |
remove-liquidity/{poolId} | onRemoveLiquidity | Liquidity removed |
split/{poolId} / merge/{poolId} | onSplit / onMerge | Tokens split / merged |
pool-closed/{poolId} | onPoolClosed | Pool or sub-pool finalized |
pool-reverted/{poolId} | onPoolReverted | Resolution reverted |
pool-token-set/{poolId} | onPoolTokenSet | Token set for pool |
streamingStatusChanged/{poolId} | onStreamingStatusChanged | Streaming toggled |
winner/{poolId} / winner-proposer/{poolId} | onWinner / onWinnerProposer | Winner proposed / proposer recorded |
reveal-winner-available/{poolId} | onRevealWinnerAvailable | Appeal winner ready |
dispute-opened/{poolId} | onDisputeOpened | Dispute opened |
oracle-created/{poolId} | onOracleCreated | Dispute oracle deployed |
dispute-time-extented/{poolId} | onDisputeTimeExtended | Deadline extended |
appeal-opened/{poolId} | onAppealOpened | Appeal filed |
appeal-winner-calculated/{poolId} | onAppealWinnerCalculated | Oracle calculated winner |
dispute-winner/{poolId} / appeal-winner/{poolId} | onDisputeWinner / onAppealWinner | Dispute / final appeal decided |
claim-reward/{poolId} | onClaimReward | Reward claimed (broadcast) |
User-scoped events
| Channel | Helper | Fires when |
|---|---|---|
claim-reward/{poolId}/{userId} | onUserClaimReward | Your reward claimed |
dispute-refund/{poolId}/{userId} | onDisputeRefund | Dispute bond refunded (disputer won) |
appeal-refund/{poolId}/{userId} | onAppealRefund | Appeal fee refunded (appealer won) |
resolution-refund/{poolId}/{userId} | onResolutionRefund | Resolution bond refunded (proposer correct) |
resolver-reward/{poolId}/{userId} | onResolverReward | Resolver closing-share reward |
notifications/{userId} | onNotifications | Personal notification |
Global events
| Channel | Helper | Fires when |
|---|---|---|
pool | onNewPool | New pool created anywhere on the protocol |
Enums
enum TradingModel {
AMM = 0,
OrderBook = 1,
}
enum OptionSide {
Yes = 1,
No = 2,
}Environments
| Environment | API | USDT | RAIN |
|---|---|---|---|
development | https://dev2-api.rain.one | 6 decimals | 18 decimals |
stage | https://stg2-api.rain.one | 6 decimals | 18 decimals |
production | https://prod2-api.rain.one | 6 decimals | 18 decimals |
Factory contract addresses and token addresses per environment are exposed by rain.getEnvironmentConfig() — always read them from the SDK rather than hard-coding.
const config = rain.getEnvironmentConfig();
// config.apiUrl
// config.market_factory_address
// config.dispute_initial_timer
// config.tokens.usdt — { address, symbol, decimals, oracle_fixed_fee_per_option }
// config.tokens.rain — { address, symbol, decimals, oracle_fixed_fee_per_option }
const tokenInfo = rain.getTokenConfig('0x...'); // lookup by address, or nullTypical flow
1. Create Market (buildCreateMarketTx)
2. Wait for startTime
3. Enter Option (buildEnterOptionTx) / Split (buildSplitTx)
4. Place Orders (buildPlaceBuyOrderTx / buildPlaceSellOrderTx)
5. Wait for endTime
6. Close Pool (buildClosePoolAITx / buildClosePoolManualTx)
7. Calculate Winner (buildCalculateWinnerTx)
8. Check claimed (getOptionClaimed) — optional
9. Claim (buildClaimTx)
If disputed: 6b. Open Dispute (buildOpenDisputeTx) 6c. Appeal (call again)
If extending: 5b. signOraclesExtendTime API → buildExtendTimeTxReady to write code? Head over to the complete examples.