rain-sdk-v2 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,44 @@ APIs slated for removal are marked `@deprecated` in the type declarations for at
9
9
  least one minor release before they are removed, with the replacement named in
10
10
  the deprecation notice. Breaking removals land only in a major version.
11
11
 
12
+ ## [2.5.1] - Add LICENSE
13
+
14
+ ### Added
15
+ - MIT `LICENSE` file at the repo root so GitHub detects the license (npm already
16
+ declared MIT). No code changes.
17
+
18
+ ## [2.5.0] - Sell slippage + create-market validation
19
+
20
+ ### Fixed
21
+ - **Unprotected sells (critical):** `buildSellOptionTx` hard-coded `minAmountOut = 0`
22
+ and silently ignored slippage — every SDK sell shipped with zero MEV/slippage
23
+ protection despite the docs. It now quotes `getSellProceeds` on-chain and applies
24
+ `slippageTolerance` (default 5%) when `minAmountOut` is omitted. Verified against
25
+ live Arbitrum: the quote is the correct (slightly conservative) `minAmountOut`
26
+ basis. Pass `minAmountOut: 0n` to opt out explicitly.
27
+ - **Silent market-odds corruption:** `createMarket` now validates bar values and
28
+ option arrays and **throws** instead of silently redistributing the rounding
29
+ remainder onto the last option (which turned `[60,60]` into a 60/40 market).
30
+ Enforced: `no_of_options ≥ 2`; `marketOptions` / `barValues` / `initialYesPrices`
31
+ lengths equal `no_of_options`; each bar value in `0–100`; bar values sum to 100;
32
+ each `initialYesPrice` in `(0, 1e18)`.
33
+ - **`split` / `merge` option guard:** both now reject `option: 0n` (options are
34
+ 1-based) before any network call — the last two builders missing the guard.
35
+
36
+ ### Changed (breaking)
37
+ - **`buildSellOptionTx` is now async** (`Promise<RawTransaction>`) because it makes
38
+ an on-chain quote — this matches what the README already documented. Callers must
39
+ `await` it. `SellOptionTxParams` gains `slippageTolerance?`.
40
+
41
+ ### Added
42
+ - Test coverage grew to cover sell slippage derivation (mocked quote), create-market
43
+ validation, and the split/merge option guards.
44
+
45
+ ### Note for integrators
46
+ - Confirm the fee treatment of `getSellProceeds` vs. `sellOption` with the contract
47
+ team; the SDK uses it as the contract's own sell quote and applies the slippage
48
+ discount to it (empirically a conservative, safe floor).
49
+
12
50
  ## [2.4.0] - Production readiness
13
51
 
14
52
  No breaking changes to the public API.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rain Protocol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -199,6 +199,8 @@ const txsRain = await rain.buildCreateMarketTx({
199
199
  | `tradingModel` | `TradingModel` | `AMM (0)` or `OrderBook (1)` |
200
200
  | `marketImage` | `string` | Market image URL (required) |
201
201
 
202
+ > **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.
203
+
202
204
  ---
203
205
 
204
206
  ### Trading
@@ -237,7 +239,7 @@ const txs = await rain.buildEnterOptionTx({
237
239
 
238
240
  #### `buildSellOptionTx(params): Promise<RawTransaction>`
239
241
 
240
- 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 `getCurrentPrice`.
242
+ 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.
241
243
 
242
244
  ```typescript
243
245
  const tx = await rain.buildSellOptionTx({
@@ -257,7 +259,7 @@ const tx = await rain.buildSellOptionTx({
257
259
  | `selectedOption` | `bigint` | Option index (1-based) |
258
260
  | `optionSide` | `OptionSide` | `Yes (1)` or `No (2)` |
259
261
  | `sharesAmount` | `bigint` | Number of shares to sell |
260
- | `minAmountOut` | `bigint` | *(Optional)* Minimum base tokens to receive. Auto-calculated from `getCurrentPrice` with slippage if not set |
262
+ | `minAmountOut` | `bigint` | *(Optional)* Minimum base tokens to receive. Auto-calculated from `getSellProceeds` with slippage if not set. Pass `0n` to opt out |
261
263
  | `slippageTolerance` | `bigint` | *(Optional)* Slippage percentage (e.g. `5n` = 5%). Default: 5% |
262
264
  | `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`). |
263
265
 
package/dist/Rain.d.ts CHANGED
@@ -56,7 +56,7 @@ export declare class Rain {
56
56
  buildEnterOptionTx(params: EnterOptionTxParams & {
57
57
  walletAddress: `0x${string}`;
58
58
  }): Promise<RawTransaction[]>;
59
- buildSellOptionTx(params: SellOptionTxParams): RawTransaction;
59
+ buildSellOptionTx(params: SellOptionTxParams): Promise<RawTransaction>;
60
60
  buildAddLiquidityTx(params: AddLiquidityTxParams & {
61
61
  walletAddress: `0x${string}`;
62
62
  }): Promise<RawTransaction[]>;
package/dist/Rain.js CHANGED
@@ -164,8 +164,8 @@ export class Rain {
164
164
  async buildEnterOptionTx(params) {
165
165
  return buildEnterOptionRawTx({ ...params, rpcUrl: this.rpcUrl });
166
166
  }
167
- buildSellOptionTx(params) {
168
- return buildSellOptionRawTx(params);
167
+ async buildSellOptionTx(params) {
168
+ return buildSellOptionRawTx({ ...params, rpcUrl: this.rpcUrl });
169
169
  }
170
170
  async buildAddLiquidityTx(params) {
171
171
  return buildAddLiquidityRawTx({ ...params, rpcUrl: this.rpcUrl });
@@ -1,5 +1,5 @@
1
1
  export function validateCreateMarketParams(params) {
2
- const { marketQuestion, marketOptions, marketTags, marketDescription, isPublic, isPublicPoolResolverAi, creator, startTime, endTime, no_of_options, inputAmountWei, barValues, baseToken, factoryContractAddress, tokenDecimals, } = params;
2
+ const { marketQuestion, marketOptions, marketTags, marketDescription, isPublic, isPublicPoolResolverAi, creator, startTime, endTime, no_of_options, inputAmountWei, barValues, baseToken, factoryContractAddress, tokenDecimals, initialYesPrices, } = params;
3
3
  // Required field validations
4
4
  if (typeof isPublic !== "boolean")
5
5
  throw new Error("isPublic is required and must be a boolean");
@@ -48,5 +48,33 @@ export function validateCreateMarketParams(params) {
48
48
  }
49
49
  if (startTime >= endTime)
50
50
  throw new Error("startTime must be earlier than endTime");
51
+ // Cross-field validation — these previously slipped through and were silently
52
+ // "fixed" by normalizeBarValues (which dumped the rounding remainder onto the
53
+ // last option, producing odds the caller never asked for). Reject loudly instead.
54
+ const optionCount = Number(no_of_options);
55
+ if (optionCount < 2)
56
+ throw new Error("no_of_options must be at least 2");
57
+ if (marketOptions.length !== optionCount) {
58
+ throw new Error(`marketOptions length (${marketOptions.length}) must equal no_of_options (${optionCount})`);
59
+ }
60
+ if (barValues.length !== optionCount) {
61
+ throw new Error(`barValues length (${barValues.length}) must equal no_of_options (${optionCount})`);
62
+ }
63
+ if (barValues.some(v => typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 100)) {
64
+ throw new Error("each barValue must be a number between 0 and 100");
65
+ }
66
+ const barSum = barValues.reduce((s, v) => s + v, 0);
67
+ if (Math.abs(barSum - 100) > 0.01) {
68
+ throw new Error(`barValues must sum to 100 (got ${barSum})`);
69
+ }
70
+ if (initialYesPrices !== undefined) {
71
+ if (initialYesPrices.length !== optionCount) {
72
+ throw new Error(`initialYesPrices length (${initialYesPrices.length}) must equal no_of_options (${optionCount})`);
73
+ }
74
+ const ONE = 10n ** 18n;
75
+ if (initialYesPrices.some(p => p <= 0n || p >= ONE)) {
76
+ throw new Error("each initialYesPrice must be within (0, 1e18) exclusive");
77
+ }
78
+ }
51
79
  return true;
52
80
  }
@@ -1,6 +1,7 @@
1
1
  import { encodeFunctionData } from "viem";
2
2
  import { MarketsAbi } from "../abi/MarketsAbi.js";
3
3
  import { MERGE } from "../constants/contractmethods.js";
4
+ import { assertOption } from "./validation.js";
4
5
  export function buildMergeRawTx(params) {
5
6
  const { marketContractAddress, option, amount } = params;
6
7
  if (!marketContractAddress)
@@ -9,6 +10,7 @@ export function buildMergeRawTx(params) {
9
10
  throw new Error("option is required");
10
11
  if (!amount || amount <= 0n)
11
12
  throw new Error("amount must be greater than 0");
13
+ assertOption(option);
12
14
  return {
13
15
  to: marketContractAddress,
14
16
  data: encodeFunctionData({
@@ -1,2 +1,4 @@
1
1
  import { SellOptionTxParams, RawTransaction } from "./types.js";
2
- export declare function buildSellOptionRawTx(params: SellOptionTxParams): RawTransaction;
2
+ export declare function buildSellOptionRawTx(params: SellOptionTxParams & {
3
+ rpcUrl: string;
4
+ }): Promise<RawTransaction>;
@@ -1,10 +1,12 @@
1
1
  import { encodeFunctionData } from "viem";
2
2
  import { MarketsAbi } from "../abi/MarketsAbi.js";
3
3
  import { SELL_OPTION } from "../constants/contractmethods.js";
4
- import { assertOption, assertDeadline } from "./validation.js";
4
+ import { assertOption, assertSlippage, assertDeadline } from "./validation.js";
5
+ import { getSellProceeds } from "../markets/getSellProceeds.js";
6
+ const DEFAULT_SLIPPAGE = 5n; // 5%
5
7
  const DEFAULT_DEADLINE_DURATION = 600; // 10 minutes
6
- export function buildSellOptionRawTx(params) {
7
- const { marketContractAddress, selectedOption, optionSide, sharesAmount, minAmountOut, deadline } = params;
8
+ export async function buildSellOptionRawTx(params) {
9
+ const { marketContractAddress, selectedOption, optionSide, sharesAmount, minAmountOut, slippageTolerance, deadline, rpcUrl } = params;
8
10
  const effectiveDeadline = deadline ?? BigInt(Math.floor(Date.now() / 1000) + DEFAULT_DEADLINE_DURATION);
9
11
  if (!marketContractAddress)
10
12
  throw new Error("marketContractAddress is required");
@@ -17,13 +19,29 @@ export function buildSellOptionRawTx(params) {
17
19
  if (sharesAmount <= 0n)
18
20
  throw new Error("sharesAmount must be greater than 0");
19
21
  assertOption(selectedOption, "selectedOption");
22
+ assertSlippage(slippageTolerance);
20
23
  assertDeadline(deadline);
24
+ // Slippage protection: when the caller does not pin minAmountOut, quote the
25
+ // expected proceeds on-chain (getSellProceeds — a conservative quote) and
26
+ // apply the slippage tolerance. Pass minAmountOut: 0n to opt out explicitly.
27
+ let effectiveMinAmountOut = minAmountOut;
28
+ if (effectiveMinAmountOut === undefined || effectiveMinAmountOut === null) {
29
+ const { proceeds } = await getSellProceeds({
30
+ marketContractAddress,
31
+ option: selectedOption,
32
+ optionSide,
33
+ shares: sharesAmount,
34
+ rpcUrl,
35
+ });
36
+ const slippage = slippageTolerance ?? DEFAULT_SLIPPAGE;
37
+ effectiveMinAmountOut = proceeds * (100n - slippage) / 100n;
38
+ }
21
39
  return {
22
40
  to: marketContractAddress,
23
41
  data: encodeFunctionData({
24
42
  abi: MarketsAbi,
25
43
  functionName: SELL_OPTION,
26
- args: [selectedOption, optionSide, sharesAmount, minAmountOut ?? 0n, effectiveDeadline],
44
+ args: [selectedOption, optionSide, sharesAmount, effectiveMinAmountOut, effectiveDeadline],
27
45
  }),
28
46
  value: 0n,
29
47
  };
@@ -3,6 +3,7 @@ import { MarketsAbi } from "../abi/MarketsAbi.js";
3
3
  import { SPLIT } from "../constants/contractmethods.js";
4
4
  import { checkMarketTokenAllowance } from "../utils/helpers.js";
5
5
  import { buildApproveRawTx } from "./buildApprovalRawTx.js";
6
+ import { assertOption } from "./validation.js";
6
7
  export async function buildSplitRawTx(params) {
7
8
  const { marketContractAddress, option, amount, walletAddress, rpcUrl } = params;
8
9
  if (!marketContractAddress)
@@ -11,6 +12,7 @@ export async function buildSplitRawTx(params) {
11
12
  throw new Error("option is required");
12
13
  if (!amount || amount <= 0n)
13
14
  throw new Error("amount must be greater than 0");
15
+ assertOption(option);
14
16
  const { allowance, baseToken } = await checkMarketTokenAllowance({ marketContractAddress, owner: walletAddress, rpcUrl });
15
17
  const txs = [];
16
18
  if (allowance < amount) {
@@ -31,6 +31,7 @@ export interface SellOptionTxParams {
31
31
  optionSide: OptionSide;
32
32
  sharesAmount: bigint;
33
33
  minAmountOut?: bigint;
34
+ slippageTolerance?: bigint;
34
35
  deadline?: bigint;
35
36
  }
36
37
  export interface PlaceBuyOrderTxParams {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rain-sdk-v2",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "description": "Rain SDK V2 — TypeScript SDK for Rain prediction markets on Arbitrum. Market creation, trading, liquidity, order book, split/merge, dispute, and smart account support.",
6
6
  "main": "dist/index.js",