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
npm install rain-sdk-v2 viemOptional — for smart account (Account Abstraction) support with gas sponsorship:
npm install @alchemy/aa-core @account-kit/infra @account-kit/wallet-clientInitialize 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).
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); // 18Every transaction builder returns a RawTransaction (or an array, when an ERC-20 approval is required first):
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.
| Environment | API URL | Use for |
|---|---|---|
development | https://dev2-api.rain.one | Building & integration testing (default) |
stage | https://stg2-api.rain.one | Pre-production validation |
production | https://prod2-api.rain.one | Live markets, real value |
Each environment exposes two base tokens via getEnvironmentConfig():
| Token | Decimals | Notes |
|---|---|---|
| USDT | 6 | Use parseUnits(amount, 6). Symbol: USDTm (dev) / USD₮0 (stage, production) |
| RAIN | 18 | Use parseUnits(amount, 18) (or parseEther) |
parseEther('0.5') = 50% probability. Mixing these up is the #1 integration bug.Core concepts
| Concept | Description |
|---|---|
| Pool (market) | A prediction market with a question and 2–26 options |
| Option | One outcome inside a market. Indexes are 1-based (1n is the first option) |
| Option side | Every option has a Yes side and a No side: OptionSide.Yes = 1, OptionSide.No = 2 |
| Trading model | TradingModel.AMM = 0 (continuous pricing) or TradingModel.OrderBook = 1 (limit orders) |
| Price scale | Prices are probabilities in 1e18 scale: 500000000000000000n (= parseEther('0.5')) is 50% |
| Lifecycle | create → 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
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
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 lowliquidity + oracleFixedFeePerOption × nOptions; for RAIN it approves liquidity + nOptions × 20% of liquidity. This is handled automatically.3. Sign and send
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:
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:
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 */ }