npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rain-sdk-v2

v2.5.8

Published

Rain SDK V2 — TypeScript SDK for Rain prediction markets on Arbitrum. Market creation, trading, liquidity, order book, split/merge, dispute, and smart account support.

Readme

Rain SDK V2

TypeScript SDK for the Rain prediction markets protocol on Arbitrum One. Provides transaction builders for market creation, trading, liquidity management, order book operations, dispute resolution, and smart account (Account Abstraction) support.

⚠️ Security notice — upgrade to >= 2.5.0

Versions below 2.5.0 build sell transactions with no slippage protection by default: buildSellOptionTx defaulted minAmountOut to 0 and the documented slippageTolerance option was ignored, exposing sells to MEV/sandwich loss. Fixed in 2.5.0 — sells now quote getSellProceeds on-chain and apply slippageTolerance (default 5%). Versions <2.5.0 are deprecated on npm.

Migration is one line: buildSellOptionTx is now async — add await.

// before (< 2.5.0)
const tx = rain.buildSellOptionTx({ /* ... */ });
// after (>= 2.5.0)
const tx = await rain.buildSellOptionTx({ /* ... */ });

Installation

npm install rain-sdk-v2 viem

Optional (for Smart Account / Account Abstraction)

npm install @alchemy/aa-core @account-kit/infra @account-kit/wallet-client

Quick Start

import { Rain, RainAA, TradingModel, OptionSide } from 'rain-sdk-v2';
import { createWalletClient, custom, parseUnits, parseEther } from 'viem';
import { arbitrum } from 'viem/chains';

// Initialize SDK
const rain = new Rain({
  environment: 'development', // 'development' | 'stage' | 'production'
  rpcUrl: 'https://arb1.arbitrum.io/rpc', // optional; falls back to a public RPC (pass your own in production)
});

// Get environment config
const config = rain.getEnvironmentConfig();
console.log(config.market_factory_address); // Factory contract
console.log(config.tokens.usdt.address);    // USDT token address
console.log(config.tokens.rain.address);    // RAIN token address
console.log(config.tokens.usdr.address);    // USDR token address
console.log(config.tokens.usdt.decimals);   // 6
console.log(config.tokens.rain.decimals);   // 18
console.log(config.tokens.usdr.decimals);   // 18

// Get token config by address
const tokenInfo = rain.getTokenConfig('0x...');
console.log(tokenInfo?.decimals, tokenInfo?.symbol);

// USDR token ABI (ERC20 + AccessControl + EIP-2612 permit, role-gated mint/burn)
import { USDRAbi } from 'rain-sdk-v2';

Classes

Rain

Stateless class for building transactions and querying on-chain data. Does not require a wallet.

Constructor

const rain = new Rain(config?: RainCoreConfig);

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | environment | 'development' \| 'stage' \| 'production' | 'development' | Target environment | | rpcUrl | string | Fixed public RPC | Custom Arbitrum RPC URL. Omitting it uses a single deterministic public RPC; in production this also emits a console.warn — pass a dedicated endpoint. | | apiUrl | string | From environment | Custom API URL |


RainAA

Stateful class for smart account (Account Abstraction) management with Alchemy gas sponsorship.

⚠️ Browser-only. RainAA persists session keys in indexedDB, so it is not supported in Node.js or SSR — session methods throw a descriptive error in those environments. For server-side flows use the stateless Rain class (transaction building and on-chain reads), which runs anywhere.

Constructor

const rainAA = new RainAA(config: RainConfig);

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | walletClient | WalletClient | Yes | Viem wallet client | | alchemyApiKey | string | Yes | Alchemy API key | | paymasterPolicyId | string | Yes | Alchemy paymaster policy ID | | chain | Chain | Yes | Viem chain (e.g. arbitrum) | | rpcUrl | string | No | Custom RPC URL |

Methods

connect()

Initializes the smart account. Returns the smart account address.

const smartAccountAddress = await rainAA.connect();
sendTransaction(rawTx: RawTransaction)

Sends a transaction from the smart account (gas-sponsored).

const hash = await rainAA.sendTransaction(rawTx);
disconnect()

Resets the connection state.

rainAA.disconnect();
address (getter)

Returns the smart account address. Throws if not connected.

client (getter)

Returns the smart wallet client instance. Throws if not connected.


Transaction Builders

All transaction builders return RawTransaction (or RawTransaction[] for multi-step operations):

interface RawTransaction {
  to: `0x${string}`;
  data: `0x${string}`;
  value?: bigint;
}

Market Creation

buildCreateMarketTx(params: CreateMarketTxParams): Promise<RawTransaction[]>

Creates a new prediction market. Returns approval TX (if needed) + createPool TX.

const config = rain.getEnvironmentConfig();

// Create market with USDT (6 decimals)
const txs = await rain.buildCreateMarketTx({
  marketQuestion: 'Will ETH reach $5000 by end of 2025?',
  marketOptions: ['Yes', 'No'],
  marketTags: ['crypto'],
  marketDescription: 'Prediction on ETH price target',
  isPublic: true,
  isPublicPoolResolverAi: true, // true = AI resolver, false = manual resolver
  creator: '0x...', // smart account or EOA address
  startTime: BigInt(Math.floor(Date.now() / 1000) + 120), // 2 min from now
  endTime: BigInt(Math.floor(Date.now() / 1000) + 86400), // 24h from now
  no_of_options: 2n,
  disputeTimer: 259200, // oracle end time duration in seconds (e.g. 259200 = 3 days)
  inputAmountWei: parseUnits('10', 6), // 10 USDT
  barValues: [50, 50], // probability distribution (0-100, sums to 100)
  initialYesPrices: [500000000000000000n, 500000000000000000n], // optional, 1e18 scale (50% = 5e17)
  baseToken: config.tokens.usdt.address, // USDT
  tradingModel: TradingModel.AMM, // AMM = 0, OrderBook = 1
  marketImage: 'https://cdn.example.com/market-image.png',
});

// Create market with RAIN token (18 decimals)
const txsRain = await rain.buildCreateMarketTx({
  ...params,
  inputAmountWei: parseUnits('10', 18), // 10 RAIN
  baseToken: config.tokens.rain.address, // RAIN token
});

// Approval for USDT: liquidity + (oracleFixedFeePerOption * numberOfOptions)
// Approval for RAIN: liquidity + (numberOfOptions * 20% of liquidity)
// e.g. 1000 RAIN with 5 options → 1000 + (5 * 200) = 2000 RAIN approval

| Parameter | Type | Description | |-----------|------|-------------| | marketQuestion | string | The market question | | marketOptions | string[] | Option labels. 1 = binary (single Yes/No) market; 2+ = multi-outcome. Length must equal no_of_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) | | endTime | bigint | Unix timestamp (seconds) | | 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) | | initialYesPrices | bigint[] | (Optional) Initial Yes prices per option in 1e18 scale (e.g. 500000000000000000n = 50%). Defaults to 50% for both AMM and OrderBook | | baseToken | 0x${string} | Base token address (USDT or RAIN) | | tradingModel | TradingModel | AMM (0) or OrderBook (1) | | marketImage | string | Market image URL (required) |

Constraints (validated, throws if violated): no_of_options ≥ 2; marketOptions, barValues, and (if provided) initialYesPrices must each have length no_of_options; every barValue must be within 0–100 and the set must sum to 100; each initialYesPrice must be within (0, 1e18) exclusive. Previously out-of-spec bar values were silently redistributed onto the last option — they are now rejected.


Trading

buildEnterOptionTx(params): Promise<RawTransaction[]>

Buy shares of an option (AMM trade). Automatically reads the market's base token, checks allowance, includes approval TX if needed, and calculates slippage protection via on-chain getEntryShares.

const txs = await rain.buildEnterOptionTx({
  marketContractAddress: '0x...',
  selectedOption: 1n, // 1-based option index
  optionSide: OptionSide.Yes, // Yes = 1, No = 2
  buyAmountInWei: parseUnits('5', 6), // 5 USDT (or parseUnits('5', 18) for RAIN)
  walletAddress: '0x...', // user's wallet address
  slippageTolerance: 5n, // optional, default 5% — percentage tolerance for minSharesOut
  deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
});
// Returns [approveTx?, enterOptionTx]

| Parameter | Type | Description | |-----------|------|-------------| | marketContractAddress | 0x${string} | Market contract address | | selectedOption | bigint | Option index (1-based) | | optionSide | OptionSide | Yes (1) or No (2) | | buyAmountInWei | bigint | Amount in base token wei | | walletAddress | 0x${string} | User's wallet address (for allowance check) | | minSharesOut | bigint | (Optional) Minimum shares to receive. Auto-calculated from getEntryShares with slippage if not set | | slippageTolerance | bigint | (Optional) Slippage percentage (e.g. 5n = 5%). Default: 5% | | deadline | bigint | (Optional) Absolute unix timestamp in seconds, e.g. BigInt(Math.floor(Date.now()/1000) + 600). Omit for a default 10-min window. A small duration like 600n is rejected (RainValidationError). |

Note: Approval is handled automatically. The SDK reads baseToken from the market contract and checks allowance before building transactions. Slippage protection is auto-calculated: the SDK calls getEntryShares on-chain to get expected shares, then applies the slippage tolerance.


buildSellOptionTx(params): Promise<RawTransaction>

Market-sell shares of an option into the resting buy order book. No approval needed (you're selling shares, not tokens). Slippage protection is auto-calculated from on-chain getSellProceeds (default 5%) when minAmountOut is omitted. Pass minAmountOut: 0n to opt out explicitly.

const tx = await rain.buildSellOptionTx({
  marketContractAddress: '0x...',
  selectedOption: 1n, // 1-based option index
  optionSide: OptionSide.Yes, // Yes = 1, No = 2
  sharesAmount: parseUnits('10', 6), // shares to sell
  slippageTolerance: 5n, // optional, default 5%
  deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
});
// Returns a single RawTransaction (no approval needed)

| Parameter | Type | Description | |-----------|------|-------------| | marketContractAddress | 0x${string} | Market contract address | | selectedOption | bigint | Option index (1-based) | | optionSide | OptionSide | Yes (1) or No (2) | | sharesAmount | bigint | Number of shares to sell | | minAmountOut | bigint | (Optional) Minimum base tokens to receive. Auto-calculated from getSellProceeds with slippage if not set. Pass 0n to opt out | | slippageTolerance | bigint | (Optional) Slippage percentage (e.g. 5n = 5%). Default: 5% | | deadline | bigint | (Optional) Absolute unix timestamp in seconds, e.g. BigInt(Math.floor(Date.now()/1000) + 600). Omit for a default 10-min window. A small duration like 600n is rejected (RainValidationError). |


Split & Merge

buildSplitTx(params): Promise<RawTransaction[]>

Split base tokens into equal Yes + No shares for an option. Automatically checks allowance and includes approval TX if needed.

const txs = await rain.buildSplitTx({
  marketContractAddress: '0x...',
  option: 1n,
  amount: parseUnits('5', 6), // 5 USDT -> 5 Yes shares + 5 No shares
  walletAddress: '0x...', // user's wallet address
});
// Returns [approveTx?, splitTx]

Note: Approval is handled automatically.

buildMergeTx(params: MergeTxParams): RawTransaction

Merge equal Yes + No share pairs back into base tokens.

const tx = rain.buildMergeTx({
  marketContractAddress: '0x...',
  option: 1n,
  amount: 5000000n, // raw shares amount (from getUserOptionShares)
});

Note: No approval needed. Burns your shares and returns base token.


Liquidity

buildAddLiquidityTx(params): Promise<RawTransaction[]>

Add liquidity to a specific option. Automatically checks allowance, includes approval TX if needed, and calculates slippage protection from on-chain AMM reserves.

const txs = await rain.buildAddLiquidityTx({
  marketContractAddress: '0x...',
  option: 1n,
  totalAmountInWei: parseUnits('10', 6), // 10 USDT
  walletAddress: '0x...', // user's wallet address
  slippageTolerance: 5n, // optional, default 5%
  deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
});
// Returns [approveTx?, addLiquidityTx]

| Parameter | Type | Description | |-----------|------|-------------| | marketContractAddress | 0x${string} | Market contract address | | option | bigint | Option index (1-based) | | totalAmountInWei | bigint | Amount in base token wei | | walletAddress | 0x${string} | User's wallet address (for allowance check) | | minYesToDeposit | bigint | (Optional) Min yes tokens to deposit. Auto-calculated from reserves if not set | | minNoToDeposit | bigint | (Optional) Min no tokens to deposit. Auto-calculated from reserves if not set | | slippageTolerance | bigint | (Optional) Slippage percentage (e.g. 5n = 5%). Default: 5% | | deadline | bigint | (Optional) Absolute unix timestamp in seconds, e.g. BigInt(Math.floor(Date.now()/1000) + 600). Omit for a default 10-min window. A small duration like 600n is rejected (RainValidationError). |

Note: Approval is handled automatically. Slippage protection is auto-calculated from ammYesReserve/ammNoReserve proportionally.

buildRemoveLiquidityTx(params: RemoveLiquidityTxParams): Promise<RawTransaction>

Remove liquidity by burning LP shares. Automatically calculates slippage protection 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, // raw LP shares amount
  slippageTolerance: 5n, // optional, default 5%
  deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
});

| Parameter | Type | Description | |-----------|------|-------------| | marketContractAddress | 0x${string} | Market contract address | | option | bigint | Option index (1-based) | | lpShares | bigint | LP shares to remove | | minYesOut | bigint | (Optional) Min yes tokens to receive. Auto-calculated from getRemovedLiquidity if not set | | minNoOut | bigint | (Optional) Min no tokens to receive. Auto-calculated if not set | | slippageTolerance | bigint | (Optional) Slippage percentage (e.g. 5n = 5%). Default: 5% | | deadline | bigint | (Optional) Absolute unix timestamp in seconds, e.g. BigInt(Math.floor(Date.now()/1000) + 600). Omit for a default 10-min window. A small duration like 600n is rejected (RainValidationError). |


Order Book

buildPlaceBuyOrderTx(params): Promise<RawTransaction[]>

Place a limit buy order. Automatically checks allowance and includes approval TX if needed.

const txs = await rain.buildPlaceBuyOrderTx({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: OptionSide.Yes,
  price: parseEther('0.5'), // 50% price in 1e18 scale
  amount: parseUnits('10', 6), // 10 USDT
  postOnly: false, // optional, defaults to false. Set true for maker-only (reverts OrderWouldCross if it would cross the book)
  walletAddress: '0x...', // user's wallet address
});
// Returns [approveTx?, placeBuyOrderTx]

Note: Approval is handled automatically. Available when AMM pool is closed.

postOnly: When true, the matching loop is skipped — the order rests directly. If price would cross a resting ask the call reverts OrderWouldCross, so the caller is never charged taker fees. When false (default), behaviour is unchanged: matches the sell book first and rests only the residual.

buildPlaceSellOrderTx(params: PlaceSellOrderTxParams): RawTransaction

Place a limit sell order.

const tx = rain.buildPlaceSellOrderTx({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: OptionSide.Yes,
  price: parseEther('0.7'), // 70% price in 1e18 scale
  shares: 5000000n, // shares to sell (from getUserOptionShares)
  postOnly: false, // optional, defaults to false. Set true for maker-only (reverts OrderWouldCross if it would cross the book)
});

Note: No approval needed. Shares are escrowed by the contract.

postOnly: Same semantics as buildPlaceBuyOrderTx — true rejects orders that would immediately cross the buy book (revert OrderWouldCross); false matches the buy book first and rests only the residual.

buildCancelBuyOrdersTx(params: CancelBuyOrdersTxParams): RawTransaction

Cancel one or more buy orders.

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
});

buildCancelSellOrdersTx(params: CancelSellOrdersTxParams): RawTransaction

Cancel one or more sell orders. Same interface as buildCancelBuyOrdersTx.


Market Resolution

buildClosePoolAITx(params: ClosePoolAITxParams): Promise<RawTransaction[]>

Close a market pool with AI resolver. Returns approval (for resolver bond) + closePool TX.

const txs = await rain.buildClosePoolAITx({
  marketContractAddress: '0x...',
  option: 1n,
});

buildClosePoolManualTx(params: ClosePoolManualTxParams): Promise<RawTransaction[]>

Close a market pool with manual resolver, proposing a winner side. Returns approval (for resolver bond) + closePool TX.

const txs = await rain.buildClosePoolManualTx({
  marketContractAddress: '0x...',
  option: 1n,
  proposedWinner: 1, // 1 = Yes, 2 = No
});

buildChooseWinnerTx(params: ChooseWinnerTxParams): RawTransaction

Finalize the winner after pool is closed.

const tx = rain.buildChooseWinnerTx({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: 1, // 1 = Yes, 2 = No
});

buildCalculateWinnerTx(params: CalculateWinnerTxParams): Promise<RawTransaction>

Calculate winner via the oracle resolver. Must be called before claim. Reads optionResolver(option) from the market and calls calculateWinner() on that resolver contract.

const tx = await rain.buildCalculateWinnerTx({
  marketContractAddress: '0x...',
  option: 1n,
});

buildExtendTimeTx(params: ExtendTimeTxParams): Promise<RawTransaction>

Extend the resolution time of a market option. Requires a signature from the oracle backend (obtained via signOraclesExtendTime API). Reads optionResolver(option) from the market and calls extendTime(newEndTime, signature) on that resolver contract.

// 1. Get the signature from the API
const signResult = await rain.signOraclesExtendTime(
  { contractAddress: '0x...', walletAddress: '0x...' },
  accessToken
);

// 2. Build the extend time transaction
const tx = await rain.buildExtendTimeTx({
  marketContractAddress: '0x...',
  option: 1n,
  newEndTime: BigInt(Math.floor(Date.now() / 1000) + 86400), // extend by 1 day
  signature: signResult.signature, // from API
});

| Param | Type | Description | |---|---|---| | marketContractAddress | 0x${string} | Market contract address | | option | bigint | Option index (1-based) | | newEndTime | bigint | New end time as unix timestamp (seconds) | | signature | 0x${string} | Backend signature from signOraclesExtendTime |


Claiming

buildClaimTx(params: ClaimTxParams): RawTransaction

Claim winnings after market resolution. Call buildCalculateWinnerTx first.

const tx = rain.buildClaimTx({
  marketContractAddress: '0x...',
  option: 1n,
});

Dispute / Appeal

buildOpenDisputeTx(params: OpenDisputeTxParams): Promise<RawTransaction[]>

Open a dispute (or appeal if dispute already exists). Returns approval (for dispute fee) + openDispute TX. The fee is read from getDisputeAppealFee(option).

const txs = await rain.buildOpenDisputeTx({
  marketContractAddress: '0x...',
  option: 1n,
});

Note: Call once to open dispute. Call again (after dispute window) to appeal.


Token Approval

buildApprovalTx(params: ApproveTxParams): RawTransaction

Build an ERC20 approve transaction.

const tx = rain.buildApprovalTx({
  tokenAddress: config.tokens.usdt.address,
  spender: '0x...', // market contract address
  amount: parseUnits('100', 6), // 100 USDT
});

Read Methods (On-Chain Queries)

getTokenAllowance(params): Promise<bigint>

Check current ERC20 allowance.

const allowance = await rain.getTokenAllowance({
  tokenAddress: config.tokens.usdt.address,
  owner: '0x...', // your address
  spender: '0x...', // market contract
});

getUserOptionLPShares(params): Promise<bigint>

Get user's LP shares for a specific option.

const lpShares = await rain.getUserOptionLPShares({
  marketContractAddress: '0x...',
  option: 1n,
  userAddress: '0x...',
});

getUserOptionShares(params): Promise<bigint>

Get user's Yes or No shares for a specific option.

const yesShares = await rain.getUserOptionShares({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: 1, // 1 = Yes, 2 = No
  userAddress: '0x...',
});

getOptionClaimed(params): Promise<boolean>

Check if a user has already claimed winnings for a specific option.

const claimed = await rain.getOptionClaimed({
  marketContractAddress: '0x...',
  option: 1n,
  userAddress: '0x...',
});
// true if already claimed, false otherwise

getEntryShares(params): Promise<{ returnedShares: bigint; expectedReward: bigint }>

Calculate how many shares you'd receive for a given buy amount, plus the expected reward.

const result = await rain.getEntryShares({
  marketContractAddress: '0x...',
  option: 1n,       // 1-based option index
  optionSide: 1,    // 1 = Yes, 2 = No
  amount: 1000000n, // buy amount in base token wei
});
// result.returnedShares = shares you'd receive
// result.expectedReward = expected reward amount

getClaimableAmount(params): Promise<bigint>

Get the total claimable amount for a user on a specific option.

const claimable = await rain.getClaimableAmount({
  marketContractAddress: '0x...',
  userAddress: '0x...',
  option: 1n, // 1-based option index
});
// claimable = total claimable amount in base token wei

getSellProceeds(params): Promise<{ proceeds: bigint; sharesSold: bigint }>

Get the expected proceeds and shares sold for selling shares of an option.

const result = await rain.getSellProceeds({
  marketContractAddress: '0x...',
  option: 1n,       // 1-based option index
  optionSide: 1,    // 1 = Yes, 2 = No
  shares: 1000000n, // shares to sell (raw amount)
});
// result.proceeds = base token amount you'd receive
// result.sharesSold = shares that would actually be sold

getDisputeWindow(params): Promise<bigint>

Get the dispute window duration (in seconds) for a market contract.

const window = await rain.getDisputeWindow({
  marketContractAddress: '0x...',
});
// window = dispute window in seconds (e.g. 7200n = 2 hours)

getDynamicPayout(params): Promise<bigint[]>

Get the dynamic payout amounts for a user on a specific option. Returns an array of payout values per side.

const payouts = await rain.getDynamicPayout({
  marketContractAddress: '0x...',
  userAddress: '0x...',
  option: 1n, // 1-based option index
});
// payouts = [yesPayoutWei, noPayoutWei]

getResolverBondAmount(params): Promise<bigint>

Get the bond amount (in base-token wei) a resolver must post to propose a resolution for an option.

const bond = await rain.getResolverBondAmount({
  marketContractAddress: '0x...',
  option: 1n, // 1-based option index
});

getDisputeAppealFee(params): Promise<bigint>

Get the fee (in base-token wei) required to open a dispute or appeal on an option.

const fee = await rain.getDisputeAppealFee({
  marketContractAddress: '0x...',
  option: 1n,
});

optionResolverBond(params): Promise<bigint>

Get the resolver bond currently posted on an option (0n if no resolution has been proposed).

const bond = await rain.optionResolverBond({
  marketContractAddress: '0x...',
  option: 1n,
});

optionResolutionProposer(params): Promise<0x${string}>

Get the address that proposed the resolution for an option (zero address if none).

const proposer = await rain.optionResolutionProposer({
  marketContractAddress: '0x...',
  option: 1n,
});

isOptionDisputed(params): Promise<boolean>

Check whether an option's resolution has been disputed.

const disputed = await rain.isOptionDisputed({
  marketContractAddress: '0x...',
  option: 1n,
});

isOptionAppealed(params): Promise<boolean>

Check whether an option's dispute resolution has been appealed.

const appealed = await rain.isOptionAppealed({
  marketContractAddress: '0x...',
  option: 1n,
});

optionDispute(params): Promise<OptionDisputeInfo>

Get the dispute details for an option: fee paid, the winner claimed by the disputer, the disputer address, and the dispute resolver.

const dispute = await rain.optionDispute({
  marketContractAddress: '0x...',
  option: 1n,
});
// { disputeFee: bigint, disputedWinner: bigint, disputer: '0x...', disputeResolver: '0x...' }

optionAppeal(params): Promise<OptionAppealInfo>

Get the appeal details for an option: fee paid, the winner claimed by the appellant, the disputer address, and the appeal resolver.

const appeal = await rain.optionAppeal({
  marketContractAddress: '0x...',
  option: 1n,
});
// { disputeFee: bigint, disputedWinner: bigint, disputer: '0x...', appealResolver: '0x...' }

getUserActiveBuyOrders(params): Promise<bigint>

Get count of user's active buy orders.

const count = await rain.getUserActiveBuyOrders({
  marketContractAddress: '0x...',
  userAddress: '0x...',
});

getUserActiveSellOrders(params): Promise<bigint>

Get count of user's active sell orders.

const count = await rain.getUserActiveSellOrders({
  marketContractAddress: '0x...',
  userAddress: '0x...',
});

getFirstBuyOrderPrice(params): Promise<bigint>

Get the best (first) buy order price for an option/side.

const price = await rain.getFirstBuyOrderPrice({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: 1, // Yes
});
// price is in 1e18 scale. 500000000000000000n = 0.5 = 50%

getFirstSellOrderPrice(params): Promise<bigint>

Get the best (first) sell order price for an option/side.

getBuyOrdersAtPrice(params): Promise<OrderLevelInfo>

Get order book info at a specific price level.

const info = await rain.getBuyOrdersAtPrice({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: 1,
  price: parseEther('0.5'),
});
// { headIndex, tailIndex, count, isInitialized }

getSellOrdersAtPrice(params): Promise<OrderLevelInfo>

Same as above for sell orders.

checkOrderExists(params): Promise<{ exists: boolean; index: bigint }>

Check if a specific order exists.

const result = await rain.checkOrderExists({
  marketContractAddress: '0x...',
  option: 1n,
  optionSide: 1,
  price: parseEther('0.5'),
  orderID: 1n,
});

Authentication

login(params: LoginParams): Promise<LoginResult>

Wallet-based login to the Rain backend.

signLoginMessage(walletClient, walletAddress)

Signs the lowercased wallet address using personal_sign. This signature is required for login.

import { signLoginMessage } from 'rain-sdk-v2';

const walletClient = createWalletClient({ chain: arbitrum, transport: custom(window.ethereum) });
const signature = await signLoginMessage(walletClient, '0x...' as `0x${string}`);

login(params)

const result = await rain.login({
  signature, // from signLoginMessage
  walletAddress: '0x...', // EOA address
  smartWalletAddress: '0x...', // optional — defaults to walletAddress for plain-EOA integrations
  referredBy: 'CODE', // optional referral code
});

console.log(result.accessToken); // JWT token
console.log(result.userId); // Backend user ID

Configuration

getEnvironmentConfig()

Returns the current environment configuration.

const config = rain.getEnvironmentConfig();

Returns: | Field | Description | |-------|-------------| | apiUrl | Backend API URL | | market_factory_address | Factory contract address | | dispute_initial_timer | Dispute timer in seconds | | tokens.usdt | USDT token config (address, symbol, decimals, oracle_fixed_fee_per_option) | | tokens.rain | RAIN token config (address, symbol, decimals, oracle_fixed_fee_per_option) |

getTokenConfig(tokenAddress)

Looks up token configuration by contract address. Returns TokenConfig or null.

const tokenInfo = rain.getTokenConfig('0x...');
// { address, symbol, decimals, oracle_fixed_fee_per_option }

Enums

TradingModel

enum TradingModel {
  AMM = 0,
  OrderBook = 1,
}

OptionSide

enum OptionSide {
  Yes = 1,
  No = 2,
}

Environments

| Environment | API | Factory | USDT | RAIN | |-------------|-----|---------|------|------| | development | https://dev2-api.rain.one | 0xbbDd...f02f | 6 decimals | 18 decimals | | stage | https://stg2-api.rain.one | 0x16cc...628d | 6 decimals | 18 decimals | | production | https://prod2-api.rain.one | 0x38B3...C677 | 6 decimals | 18 decimals |

Supported Tokens

Each environment includes token configs for both USDT (6 decimals) and RAIN (18 decimals). The SDK automatically handles decimal conversions for oracle fees and minimum liquidity validation based on the token used.

const config = rain.getEnvironmentConfig();

// USDT
config.tokens.usdt.address   // Token contract address
config.tokens.usdt.decimals  // 6
config.tokens.usdt.symbol    // "USDTm" (dev) or "USD₮0" (stage/production)

// RAIN
config.tokens.rain.address   // Token contract address
config.tokens.rain.decimals  // 18
config.tokens.rain.symbol    // "RAIN"

Typical Flow

1. Create Market (buildCreateMarketTx)
2. Wait for startTime to pass
3. Enter Option (buildEnterOptionTx) / Split (buildSplitTx)
4. Place Orders (buildPlaceBuyOrderTx / buildPlaceSellOrderTx)
5. Wait for endTime to pass
6. Close Pool (buildClosePoolAITx / buildClosePoolManualTx)
7. Calculate Winner (buildCalculateWinnerTx)
8. Check if claimed (getOptionClaimed) — optional
9. Claim (buildClaimTx)

If disputed:

6b. Open Dispute (buildOpenDisputeTx)
6c. Appeal (buildOpenDisputeTx again)

If extending time:

5b. Extend Time (signOraclesExtendTime API → buildExtendTimeTx)

REST API Methods

The SDK wraps all Rain backend REST API endpoints. Each method requires accessToken for authenticated routes.

Users

// Find user by wallet address (public)
const user = await rain.findUserByWalletAddress({ walletAddress: '0x...' });

// Get authenticated user's profile
const profile = await rain.getUserProfile(accessToken);

// View another user's profile
const profile = await rain.viewUserProfile({ userId: '...' }, accessToken);

// Update profile
await rain.updateUserProfile({
  name: 'John',
  bio: 'Trader',
  profilePic: 'https://...',
  twitterLink: 'https://...',
}, accessToken);

// Get total user count (public)
const count = await rain.getUsersTotalCount();

// Remove profile picture
await rain.removeUserProfilePic(accessToken);

// Get user activity history (deposits, withdrawals, trades, rewards)
const history = await rain.getUserHistory({ limit: 20, offset: 1 }, accessToken);

// Check if access token is still valid
const status = await rain.checkTokenExpiration(accessToken);

Comments

// Create a comment
await rain.createComment({ comment: 'Great market!', poolId: '...' }, accessToken);

// Reply to a comment
await rain.createComment({ comment: 'I agree', poolId: '...', parentCommentId: '...' }, accessToken);

// Get comments for a pool
const comments = await rain.getCommentsListing({ poolId: '...', limit: 10, offset: 0 });

// Update a comment
await rain.updateComment({ commentId: '...', comment: 'Updated text' }, accessToken);

// Like / Unlike a comment
await rain.likeComment({ commentId: '...' }, accessToken);
await rain.unlikeComment({ commentId: '...' }, accessToken);

// Get comment count for a pool
const count = await rain.getCommentsCount({ poolId: '...' });

Pools

// Get public pools (paginated, filterable)
const pools = await rain.getPublicPools({ limit: 10, offset: 0, status: 'Live', sortBy: 'trending' });

// Get private pools
const pools = await rain.getPrivatePools({ limit: 10, offset: 0 }, accessToken);

// Get pool by ID
const pool = await rain.getPoolById({ id: '...' });

// Get pool by contract address
const pool = await rain.getPoolByContractAddress({ contractAddress: '0x...' });

// Search pools
const results = await rain.searchPool({ query: 'bitcoin', limit: 10, offset: 0 });

// Get trending tags (ranked by market activity in last 24h)
const tags = await rain.getTrendingTags({ limit: 10 });

// Get featured pools
const featured = await rain.getFeaturedPools({ limit: 10, offset: 0 });

// Get all pools count
const count = await rain.getAllPoolsCount();

// Get related pools
const related = await rain.getRelatedPools({ poolId: '...' });

// Get pool resolution history (per sub-pool)
const history = await rain.getPoolResolutionHistory({ poolId: '...', subPool: '...' });

// Access a private pool
const access = await rain.accessPool({ poolId: '...', accessCode: '1234' });

// Verify access code
await rain.verifyAccessCode({ poolId: '...', accessCode: '1234' }, accessToken);

// Get pools created by a user
const pools = await rain.getPoolListingByCreator({ limit: 10, offset: 0 }, accessToken);

// Get total participants in a pool
const count = await rain.getPoolTotalParticipants({ poolId: '...' });

// Get total pools created by user
const count = await rain.getTotalPoolsCreatedByUser(accessToken);

// Get total predictions by user
const count = await rain.getTotalPredictionsByUser(accessToken);

// Update streaming status
await rain.updateStreaming({ poolId: '...', streaming: true }, accessToken);

// Update pool resolution time
await rain.updatePoolResolutionTime({ poolId: '...', newEndDate: '...' }, accessToken);

// Find pool fallback
const pool = await rain.findPoolFallback({ poolId: '...' });

// Sign oracles extend time
await rain.signOraclesExtendTime({ poolId: '...' }, accessToken);

Investments

// Get user's total investment in a pool
const investment = await rain.getUserTotalInvestment({ poolId: '...' }, accessToken);

// Get options total volume for a pool
const volume = await rain.getOptionsTotalVolume({ poolId: '...' });

// Get pool activity (trades)
const activity = await rain.getPoolActivity({ poolId: '...', limit: 10, offset: 0 });

// Get top holders in a pool
const holders = await rain.getTopHolders({ poolId: '...', limit: 10, offset: 0 });

// Get user's invested pools
const pools = await rain.getUserInvestedPools({ limit: 10, offset: 0 }, accessToken);

// Get platform TVL
const tvl = await rain.getPlatformTvl();

// Get investment volume graph
const graph = await rain.getInvestmentVolumeGraph({ period: '7d' }, accessToken);

// Calculate user PNL
const pnl = await rain.calculateUserPnl({ walletAddress: '0x...' }, accessToken);

// Get user total volume
const volume = await rain.calculateUserTotalVolume(accessToken);

// Get PNL graph
const graph = await rain.getUserPnlGraph({ period: '30d' }, accessToken);

// Get TVL graph
const graph = await rain.getTvlGraph();

// Get PNL per pool
const pnl = await rain.calculateUserPnlPerPool(accessToken);

// Get PNL by specific pool
const pnl = await rain.getPnlByPoolId({ poolId: '...' }, accessToken);

// Get top winners and losers
const leaderboard = await rain.getTopWinnersLosers({ limit: 10, period: '7d' });

// Get user overall investment
const overall = await rain.getUserOverallInvestment(accessToken);

// Get platform volume
const volume = await rain.getPlatformVolume();

// Get user invested live pools count
const count = await rain.getUserInvestedLivePoolsCount(accessToken);

// Calculate user open interest
const oi = await rain.calculateUserOpenInterest(accessToken);

// Get the authenticated user's positions in a specific subPool
// (per-side net shares, weighted average buy price, projected payout on win)
const positions = await rain.getUserPositions(
  { poolId: '...', subPoolIndex: 1 },
  accessToken
);

// Get the authenticated user's open positions across all subPools of a pool
// (one entry per (subPool, side) the user still holds; excludes claimed / losing sides)
const open = await rain.getOpenPositions({ poolId: '...' }, accessToken);

// Get user's open share positions per side with price move vs avg buy price
const sharePositions = await rain.getUserSharePositions({ poolId: '...' }, accessToken);

// Search user's invested pools by keyword (matched against question and tags)
const results = await rain.searchInvestedPools({
  question: 'world cup',       // required — keyword to match
  status: 'active',            // optional — 'all' | 'active' | 'closed' | granular status
  sortBy: 'value',             // optional — 'value' (net amount) | 'recent' (creation time)
  limit: 10,                   // optional — max results (default 10)
}, accessToken);

Orders

// Get user's orders
const orders = await rain.getUserOrders({ limit: 10, offset: 1, filter: 'pending' }, accessToken);

// Get order book for a pool
const book = await rain.getOrderBook({ pool: '...' });

// Get user's orders for a specific pool
const orders = await rain.getUserOrderByPoolId({ poolId: '...' }, accessToken);

// Get all orders for a pool
const orders = await rain.getOrdersListingByPool({ pool: '...', limit: 10, offset: 1 });

// Get order by ID
const order = await rain.getOrderById({ orderId: '...' });

Points

// Get user points
const points = await rain.getUserPoints(accessToken);

// Get user points graph
const graph = await rain.getUserPointsGraph(accessToken);

// Add user points
await rain.addUserPoints({ points: 100, reason: '...' }, accessToken);

// User successful onboarding
await rain.userSuccessfulOnboarding({ step: '...' }, accessToken);

Notifications

// Get notifications
const notifs = await rain.getNotifications({ limit: 20, offset: 0 }, accessToken);

// Mark all as read
await rain.markAllNotificationsAsRead(accessToken);

// Mark single notification as read
await rain.markNotificationAsRead({ notificationId: '...' }, accessToken);

Price Data

// Get price data for a pool
const prices = await rain.getPriceData({
  contractAddress: '0x...', // market contract address
  side: 1, // 1 = YES, 2 = NO
  filter: '1D', // '1H' | '6H' | '1D' | '1W' | '1M' | 'ALL'
});

Pool Reviews

// Add review
await rain.addReview({ poolId: '...', rating: 5, review: 'Great!' }, accessToken);

// Get user's reviews
const reviews = await rain.getUserReviews(accessToken);

// Get pools by creator with reviews
const pools = await rain.getPoolsByCreatorReviews(accessToken);

Follow

// Toggle follow a user
await rain.toggleFollow({ userId: '...' }, accessToken);

// Check if following
const status = await rain.checkFollow({ userId: '...' }, accessToken);

// Get followers
const followers = await rain.getFollowers({ userId: '...', limit: 20, offset: 0 });

// Get following
const following = await rain.getFollowing({ userId: '...', limit: 20, offset: 0 });

// Get follow stats
const stats = await rain.getFollowStats({ userId: '...' });

Rain Burn

// Get total RAIN burned
const burned = await rain.getTotalBurned();

// Get burn per pool
const burn = await rain.getBurnPerPool({ poolId: '...' });

Dispute

// Create dispute message (text)
await rain.createDisputeMessage({
  pool: '...',          // Parent pool document ID
  subPool: '...',       // SubPool document ID
  role: 'disputer',     // 'creator' | 'disputer' | 'proposer'
  messageType: 'text',  // 'text' | 'image' | 'video' | 'file' | 'youtube' | 'mixed'
  evidence: {
    options: ['Yes', 'No'],                // required, at least 2
    evidenceType: 'photo',                 // 'photo' | 'video' | 'pdf' | 'youtube' | 'mixed'
    question: 'Did the event happen?',     // optional
    description: 'Screenshot of result',   // optional
    source: 'https://espn.com/result',     // optional
    youtubeUrls: ['https://youtube.com/watch?v=abc123'], // optional
    urls: ['https://example.com/proof'],   // optional
  },
}, accessToken);

// Create dispute message with file attachments (image/video/pdf)
await rain.createDisputeMessage({
  pool: '...',
  subPool: '...',
  role: 'creator',
  messageType: 'image',     // set to 'file' for PDF
  files: [fileObject],      // File[] — required for image/video/file/mixed types, max 25MB each
  evidence: {
    options: ['Yes', 'No'],
    evidenceType: 'photo',  // use 'pdf' for PDF files
  },
}, accessToken);

// Get dispute conversation for a sub-pool
const convo = await rain.getPoolDisputeConvo({ poolId: '...', subPool: '...', limit: 50, offset: 0 }, accessToken);

Bookmarks

// Toggle bookmark (add/remove) for a market
await rain.toggleBookmark({ poolId: '...' }, accessToken);

// Get user's bookmarked markets (paginated)
const bookmarks = await rain.getBookmarks({ page: 1, limit: 20 }, accessToken);

// Check if a market is bookmarked
const status = await rain.checkBookmark({ poolId: '...' }, accessToken);

Whitelisted Tokens

// Get token price in USD
const priceData = await rain.getTokenPrice('0x...'); // token contract address
// priceData.data = { price: 0.05 } — USD price per token

Leaderboard

Public endpoints — no access token required. Boards are served from a materialized document rebuilt by a cron (~20 min), so changes appear after the next tick rather than on the next request.

// Get a ranked leaderboard — one board per (board, window, category)
const board = await rain.getLeaderboard({
  board: 'profit',        // 'profit' (net realized PnL) or 'correct_calls' (sum of 1 − entryPrice over winning positions)
  window: 'all_time',     // 'all_time' (default) or 'monthly' (current UTC calendar month)
  category: 'Crypto',     // optional, case-insensitive; omit for the global board
  limit: 10,              // up to the stored cap of 100 (default 10)
});
// board.data = { board, window, category, categoryLabel, period, limit, totalRanked, priceBasis, entries, lastUpdatedAt }
// Each entry carries all four table columns — profit, correctCalls, winRate, trades —
// whichever board is sorting, so switching the sorted column needs no second request.

// Search traders on a board — the leaderboard's trader search box
const found = await rain.searchLeaderboard({
  q: '0x742d',            // wallet address, address fragment (with/without 0x, prefix/middle/tail), or user id
  board: 'profit',        // required — searches the stored board for this (board, window, category)
  window: 'all_time',     // optional, default 'all_time'
  category: 'Crypto',     // optional; omit for the global board
  limit: 10,              // up to 50 (default 10)
});
// found.data = { board, window, category, period, query, matched, searchedTop, totalRanked, lastUpdatedAt, results }
// `results` are the same entry objects getLeaderboard returns, in rank order.
// Scoped to the stored board — a trader outside the top N is simply not found.
// Empty `results` means "not on this board"; `searchedTop` + `totalRanked` let you
// say "not in the top 100" precisely. Queries under 2 characters return empty, not a 400.

// Get a trader's most recent trades — the rows inside an expanded leaderboard row
const recent = await rain.getTraderRecentTrades({
  userId: '...',          // from a leaderboard entry's `userId`
  limit: 5,               // rendered rows (a multi-side trade flattens into more lines), up to 25 (default 5)
});
// recent.data = { userId, limit, trades }
// Each trade: { action ('Buy'|'Sell'), origin, transactionHash, tradedAt, poolId, question,
//               poolImage, subPoolId, subQuestion, side (1=YES, 2=NO), optionName, shares,
//               amountUSD, pricePerShare, priceCents, status ('open'|'pending'|'settled'),
//               pnlScope, pnlUSD }
// Directional trades only (origin ∈ {enter, orderFill}), newest first — and unlike the
// track-record endpoint, OPEN positions are included (status 'open').
// `pnlUSD` is POSITION-level, not per-fill: several trades in the same market report the
// same number. Null until settled; `amountUSD`/`pnlUSD` are dollars × 1e6.

// Get categories that currently have a ranked board (feeds the category filter)
const categories = await rain.getLeaderboardCategories();
// categories.data = [{ category: 'CRYPTO', label: 'Crypto' }, ...]
// Pass `category` back as the `category` param of getLeaderboard.

Notes:

  • Units: every *USD number (including profit and score on the profit board) is dollars × 1e6 — divide by 1,000,000 to display. E.g. profit: 887505347 is $887.51.
  • Monthly window buckets on when PnL settled, not when the position was opened; window: 'monthly' always means the current UTC month (period reports it as YYYY-MM).
  • Only positive scores are ranked. An unknown category returns an empty board, not a 400.
  • Top-N only — there is no rank-of-user lookup. Empty entries with lastUpdatedAt: null means the cron hasn't built the board yet; empty with a timestamp means nobody qualifies.
  • The client can highlight the viewer's own row by matching userId in entries.

WebSocket Events (Socket.IO)

Real-time event subscriptions via Socket.IO.

Setup

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

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

Pool-Scoped Events

Subscribe to events for a specific pool using poolId:

// Trading events
const unsub = socket.onEnterOption(poolId, (data) => {
  console.log(data.enterOption, data.pool, data.subPool);
});

socket.onExitOption(poolId, (data) => { /* data.exitOption, pool, subPool */ });
socket.onLiquidity(poolId, (data) => { /* data.enterLiquidity, pool, subPool */ });
socket.onSplit(poolId, (data) => { /* data.split, pool, subPool */ });
socket.onMerge(poolId, (data) => { /* data.merge, pool, subPool */ });
socket.onRemoveLiquidity(poolId, (data) => { /* data.removeLiquidity, pool, subPool */ });

// Price updates
socket.onSyncPrice(poolId, (data) => {
  // data.prices = [{ side: 1, price: 0.62, percentage: 62, subPoolIndex: 1 }, ...]
  // data.pool, data.subPool
});

// Order book events
socket.onOrderCreated(poolId, (data) => { /* data.order, pool, subPool */ });
socket.onOrderCancelled(poolId, (data) => { /* data.order, pool, subPool */ });
socket.onOrderFilled(poolId, (data) => { /* data.filledOrder, data.pendingOrder?, pool, subPool */ });

// Pool lifecycle
socket.onPoolClosed(poolId, (data) => { /* data.pool, data.subPool? */ });
socket.onPoolReverted(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onPoolTokenSet(poolId, (data) => { /* data.pool */ });
socket.onStreamingStatusChanged(poolId, (data) => { /* data.pool */ });

// Resolution events
socket.onWinner(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onWinnerProposer(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onRevealWinnerAvailable(poolId, (data) => { /* data.subPoolId */ });

// Dispute & Appeal events
socket.onDisputeOpened(poolId, (data) => { /* data.subPool, data.eventType */ });
socket.onOracleCreated(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onDisputeTimeExtended(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onAppealOpened(poolId, (data) => { /* data.pool, data.subPool */ });
socket.onAppealWinnerCalculated(poolId, (data) => { /* data.subPool */ });
socket.onDisputeWinner(poolId, (data) => { /* data.subPool, data.eventType */ });
socket.onAppealWinner(poolId, (data) => { /* data.subPool, data.eventType, data.winnerFinalized */ });

// Claim events (pool-scoped broadcast)
socket.onClaimReward(poolId, (data) => { /* data.claimReward, pool, subPool */ });

User-Scoped Events

Events targeted to a specific user (require userId):

// Personal claim reward
socket.onUserClaimReward(poolId, userId, (data) => { /* data.claimReward, pool, subPool */ });

// Dispute bond refunded (disputer won)
socket.onDisputeRefund(poolId, userId, (data) => { /* data.claimReward, pool, subPool */ });

// Appeal fee refunded (appealer won)
socket.onAppealRefund(poolId, userId, (data) => { /* data.claimReward, pool, subPool */ });

// Resolution bond refunded (proposer was correct)
socket.onResolutionRefund(poolId, userId, (data) => { /* data.claimReward, pool, subPool */ });

// Resolver closing share reward
socket.onResolverReward(poolId, userId, (data) => { /* data.claimReward, pool, subPool */ });

// Personal notifications
socket.onNotifications(userId, (data) => {
  // data._id, userId, type, message, pool, subPool, createdAt, read
});

Global Events

// New pool created
socket.onNewPool((data) => { /* data.pool, data.subMarkets */ });

Generic Subscription

// Subscribe to any channel directly
const unsub = socket.onChannel('custom-event/123', (data) => { ... });

// Unsubscribe
unsub();

Cleanup

socket.disconnect();

All Socket Event Channels

| Channel | Description | |---------|-------------| | pool | New pool created | | pool-token-set/{poolId} | Token set for pool | | pool-closed/{poolId} | Pool/SubPool finalized | | pool-reverted/{poolId} | Resolution reverted | | streamingStatusChanged/{poolId} | Streaming toggled | | enter-option/{poolId} | Shares bought (AMM or order match) | | exit-option/{poolId} | Shares sold (order match) | | liquidity/{poolId} | Liquidity added | | split/{poolId} | Tokens split into Yes+No | | merge/{poolId} | Yes+No merged back | | remove-liquidity/{poolId} | Liquidity removed | | sync-price/{poolId} | Price update | | order-created/{poolId} | New order placed | | order-cancelled/{poolId} | Order cancelled | | order-filled/{poolId} | Order filled (full/partial) | | winner/{poolId} | Winner proposed | | winner-proposer/{poolId} | Proposer recorded | | reveal-winner-available/{poolId} | Appeal winner ready | | dispute-opened/{poolId} | Dispute opened | | oracle-created/{poolId} | Dispute oracle deployed | | dispute-time-extented/{poolId} | Deadline extended | | appeal-opened/{poolId} | Appeal filed | | appeal-winner-calculated/{poolId} | Oracle calculated winner | | dispute-winner/{poolId} | Dispute decided (LEX) | | appeal-winner/{poolId} | Final appeal winner | | claim-reward/{poolId} | Reward claimed (broadcast) | | claim-reward/{poolId}/{userId} | Reward claimed (personal) | | dispute-refund/{poolId}/{userId} | Dispute bond refunded | | appeal-refund/{poolId}/{userId} | Appeal fee refunded | | resolution-refund/{poolId}/{userId} | Resolution bond refunded | | resolver-reward/{poolId}/{userId} | Resolver reward | | notifications/{userId} | Personal notifications |


Helpers & Execution

Executing transactions

Builders return unsigned RawTransaction[] where index 0 may be an ERC-20 approval. rain.execute() sends them in order and waits for each receipt before the next — so the main transaction never front-runs its approval:

const txs = await rain.buildEnterOptionTx({ /* ... */ });

await rain.execute(txs, {
  send: (tx) => walletClient.sendTransaction({ to: tx.to, data: tx.data, value: tx.value ?? 0n }),
  wait: (hash) => publicClient.waitForTransactionReceipt({ hash }),
});

The executor is pluggable — adapt { send, wait? } to viem, ethers, RainAA, or your own KMS. Also available as the standalone executeTxs(txs, executor).

Amount conversion

Convert between human-readable amounts and base units using the token's configured decimals — no need to track that USDT is 6 and RAIN is 18:

rain.parseAmount('5', usdtAddress);        // 5_000_000n
rain.formatAmount(5_000_000n, usdtAddress); // '5'

Typed pool data

getPublicPools / getPrivatePools return ApiResponse<PaginatedPools>, and socket event payloads type their pool / subPool fields as Pool / SubPool. Import the DTOs to annotate your own code:

import type { Pool, SubPool, PaginatedPools } from 'rain-sdk-v2';

Socket event names

Use RAIN_SOCKET_EVENTS for correctly-spelled event identifiers with the low-level .on() API (some raw wire names are misspelled upstream):

import { RAIN_SOCKET_EVENTS } from 'rain-sdk-v2';
socket.on(RAIN_SOCKET_EVENTS.disputeTimeExtended, poolId, handler);

Token expiry

login() returns expiresAt (unix seconds, decoded from the JWT) so you can renew before it lapses:

const { accessToken, expiresAt } = await rain.login({ /* ... */ });

Error Handling & Validation

Build-time validation

Transaction builders validate their inputs synchronously — before any encoding or broadcast — and throw RainValidationError on the common footguns:

  • deadline must be an absolute unix timestamp (seconds), not a duration. deadline: 600n is rejected; use BigInt(Math.floor(Date.now()/1000) + 600).
  • slippageTolerance is a whole percent (5n = 5%), and must be 0–100.
  • option / selectedOption are 1-based (the first option is 1, never 0).
import { RainValidationError } from 'rain-sdk-v2';

try {
  await rain.buildEnterOptionTx({ /* ... */, deadline: 600n });
} catch (err) {
  if (err instanceof RainValidationError) {
    console.error(err.field, err.message); // 'deadline', 'Invalid deadline: ...'
  }
}

API errors

REST methods throw RainApiError (a subclass of Error) on non-2xx responses, carrying structured fields so you can branch on code instead of parsing strings:

import { RainApiError } from 'rain-sdk-v2';

try {
  await rain.getSomeData(/* ... */);
} catch (err) {
  if (err instanceof RainApiError) {
    console.error(err.status, err.code, err.endpoint, err.body);
    // code: 'NOT_FOUND' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'UNKNOWN'
  }
}

Legitimate empty states are not errors: getPnlByPoolId (no position) and getTokenPrice (non-whitelisted token) return { statusCode: 200, data: null } rather than throwing on a 404.


License

MIT