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 +38 -0
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/Rain.d.ts +1 -1
- package/dist/Rain.js +2 -2
- package/dist/tx/CreateMarket/createMarketValidation.js +29 -1
- package/dist/tx/buildMergeRawTx.js +2 -0
- package/dist/tx/buildSellOptionRawTx.d.ts +3 -1
- package/dist/tx/buildSellOptionRawTx.js +22 -4
- package/dist/tx/buildSplitRawTx.js +2 -0
- package/dist/tx/types.d.ts +1 -0
- package/package.json +1 -1
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 `
|
|
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 `
|
|
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
|
|
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,
|
|
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) {
|
package/dist/tx/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rain-sdk-v2",
|
|
3
|
-
"version": "2.
|
|
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",
|