Getting Started

From zero to your first on-chain prediction market with rain-sdk-v2 — the official TypeScript SDK for the Rain protocol on Arbitrum One.

Installation

bash
npm install rain-sdk-v2 viem

Optional — for smart account (Account Abstraction) support with gas sponsorship:

bash
npm install @alchemy/aa-core @account-kit/infra @account-kit/wallet-client
Requirements: Node 18+, a wallet on Arbitrum One, and USDT or RAIN tokens for market liquidity and trading.

Initialize the SDK

The Rain class is stateless: it builds raw transactions and runs on-chain reads, and never needs your private key. You sign and send the returned transactions with your own wallet client (viem, ethers, or a smart account).

TypeScript
import { Rain, TradingModel, OptionSide } from 'rain-sdk-v2';

const rain = new Rain({
  environment: 'development', // 'development' | 'stage' | 'production'
  rpcUrl: 'https://arb1.arbitrum.io/rpc', // optional — random public RPC if omitted
});

// Environment config: factory address, tokens, API URL
const config = rain.getEnvironmentConfig();
console.log(config.market_factory_address);
console.log(config.tokens.usdt.address, config.tokens.usdt.decimals); // 6
console.log(config.tokens.rain.address, config.tokens.rain.decimals); // 18

Every transaction builder returns a RawTransaction (or an array, when an ERC-20 approval is required first):

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

Environments

Rain runs three parallel environments. All of them live on Arbitrum One — they differ in factory contract, backend API, and token deployments.

EnvironmentAPI URLUse for
developmenthttps://dev2-api.rain.oneBuilding & integration testing (default)
stagehttps://stg2-api.rain.onePre-production validation
productionhttps://prod2-api.rain.oneLive markets, real value

Each environment exposes two base tokens via getEnvironmentConfig():

TokenDecimalsNotes
USDT6Use parseUnits(amount, 6). Symbol: USDTm (dev) / USD₮0 (stage, production)
RAIN18Use parseUnits(amount, 18) (or parseEther)
Decimals matter. USDT amounts use 6 decimals, RAIN uses 18, and all prices are expressed on a 1e18 scale where parseEther('0.5') = 50% probability. Mixing these up is the #1 integration bug.

Core concepts

ConceptDescription
Pool (market)A prediction market with a question and 2–26 options
OptionOne outcome inside a market. Indexes are 1-based (1n is the first option)
Option sideEvery option has a Yes side and a No side: OptionSide.Yes = 1, OptionSide.No = 2
Trading modelTradingModel.AMM = 0 (continuous pricing) or TradingModel.OrderBook = 1 (limit orders)
Price scalePrices are probabilities in 1e18 scale: 500000000000000000n (= parseEther('0.5')) is 50%
Lifecyclecreate → startTime → trade → endTime → close pool → calculate winner → claim (disputes optional)

Your first market in 5 minutes

This walkthrough creates a USDT-denominated Yes/No market, signs with a viem wallet, and confirms on Arbitrum One.

1. Set up a wallet client

TypeScript
import { createWalletClient, http, parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum } from 'viem/chains';
import { Rain, TradingModel } from 'rain-sdk-v2';

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: arbitrum, transport: http() });

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

2. Build the create-market transactions

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

const txs = await rain.buildCreateMarketTx({
  marketQuestion: 'Will ETH close above $5,000 this year?',
  marketOptions: ['Yes', 'No'],
  marketTags: ['crypto'],
  marketDescription: 'Resolves Yes if ETH/USD closes above $5,000 on Dec 31.',
  isPublic: true,
  isPublicPoolResolverAi: true,       // AI resolver (false = manual)
  creator: account.address,
  startTime: BigInt(now + 120),       // trading opens in 2 minutes
  endTime: BigInt(now + 7 * 86400),   // trading ends in 7 days
  no_of_options: 2n,
  disputeTimer: 259200,               // 3-day oracle window
  inputAmountWei: parseUnits('10', 6),// 10 USDT initial liquidity
  barValues: [50, 50],                // initial probabilities (sum to 100)
  baseToken: config.tokens.usdt.address,
  tradingModel: TradingModel.AMM,
  marketImage: 'https://cdn.example.com/market-image.png',
});
// txs = [approveTx?, createPoolTx] — approval included only if allowance is too low
Approval math: for USDT the SDK approves liquidity + oracleFixedFeePerOption × nOptions; for RAIN it approves liquidity + nOptions × 20% of liquidity. This is handled automatically.

3. Sign and send

TypeScript
import { createPublicClient } from 'viem';

const publicClient = createPublicClient({ chain: arbitrum, transport: http() });

for (const tx of txs) {
  const hash = await wallet.sendTransaction({
    to: tx.to,
    data: tx.data,
    value: tx.value ?? 0n,
  });
  await publicClient.waitForTransactionReceipt({ hash });
  console.log('confirmed:', hash);
}

4. (Optional) go gasless with RainAA

Wrap the same raw transactions in a smart account with Alchemy gas sponsorship:

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

const rainAA = new RainAA({
  walletClient: wallet,
  alchemyApiKey: process.env.ALCHEMY_API_KEY!,
  paymasterPolicyId: process.env.PAYMASTER_POLICY_ID!,
  chain: arbitrum,
});

const smartAccountAddress = await rainAA.connect();
for (const tx of txs) {
  const hash = await rainAA.sendTransaction(tx); // gas-sponsored
  console.log('sponsored tx:', hash);
}

5. Trade it

Once startTime passes, anyone can buy shares:

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

const buyTxs = await rain.buildEnterOptionTx({
  marketContractAddress: '0x...',     // market contract from the create receipt / API
  selectedOption: 1n,                 // 1-based option index
  optionSide: OptionSide.Yes,
  buyAmountInWei: parseUnits('5', 6), // 5 USDT
  walletAddress: account.address,
  slippageTolerance: 5n,              // 5% (default)
});
for (const tx of buyTxs) { /* sign & send as above */ }

Next steps