rain-sdk-v2 2.3.0 → 2.4.0

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,36 @@ 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.4.0] - Production readiness
13
+
14
+ No breaking changes to the public API.
15
+
16
+ ### Fixed
17
+ - **Docs correctness:** the `deadline` examples and parameter tables in the README
18
+ no longer show `deadline: 600n` (a duration), which the builders reject since
19
+ 2.2.0. They now show an absolute unix timestamp and note the default 10-min window.
20
+
21
+ ### Changed
22
+ - **viem-only (dropped `ethers`):** `utils/helpers.ts` (allowance + decimals reads,
23
+ `parseUnits`, RPC liveness) is ported to viem. `ethers` is removed from
24
+ dependencies — one web3 stack, smaller install/bundle. The redundant
25
+ per-call `getNetwork()` RPC round-trip before allowance checks is gone.
26
+ - **`RainAA` browser-only, documented + guarded:** session methods now throw a
27
+ clear error when `indexedDB` is unavailable (Node/SSR) instead of a cryptic
28
+ `ReferenceError`. Documented in the README and class JSDoc. Use `Rain` for
29
+ server-side flows.
30
+
31
+ ### Added
32
+ - **Test suite (vitest):** unit tests for the validators and encoders, plus a
33
+ gated Arbitrum integration test (`npm run test:integration`) exercising the
34
+ on-chain read path. Scripts: `test`, `test:watch`, `test:integration`.
35
+ - **CI:** GitHub Actions — build + test on push/PR, and publish-on-release
36
+ (`.github/workflows/`).
37
+
38
+ ### Removed
39
+ - Dead `getRandomRpc` export (superseded by `getDefaultRpc` in 2.3.0).
40
+ - Committed `*.tgz` tarball removed from the repo and gitignored.
41
+
12
42
  ## [2.3.0] - Developer experience
13
43
 
14
44
  Additive, non-breaking. Fills in the untyped core data model and adds the
package/README.md CHANGED
@@ -71,6 +71,8 @@ const rain = new Rain(config?: RainCoreConfig);
71
71
 
72
72
  Stateful class for smart account (Account Abstraction) management with Alchemy gas sponsorship.
73
73
 
74
+ > ⚠️ **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.
75
+
74
76
  #### Constructor
75
77
 
76
78
  ```typescript
@@ -213,7 +215,7 @@ const txs = await rain.buildEnterOptionTx({
213
215
  buyAmountInWei: parseUnits('5', 6), // 5 USDT (or parseUnits('5', 18) for RAIN)
214
216
  walletAddress: '0x...', // user's wallet address
215
217
  slippageTolerance: 5n, // optional, default 5% — percentage tolerance for minSharesOut
216
- deadline: 600n, // optional, default 600 (10 min) — duration in seconds
218
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
217
219
  });
218
220
  // Returns [approveTx?, enterOptionTx]
219
221
  ```
@@ -227,7 +229,7 @@ const txs = await rain.buildEnterOptionTx({
227
229
  | `walletAddress` | `0x${string}` | User's wallet address (for allowance check) |
228
230
  | `minSharesOut` | `bigint` | *(Optional)* Minimum shares to receive. Auto-calculated from `getEntryShares` with slippage if not set |
229
231
  | `slippageTolerance` | `bigint` | *(Optional)* Slippage percentage (e.g. `5n` = 5%). Default: 5% |
230
- | `deadline` | `bigint` | *(Optional)* Duration in seconds (e.g. `600n` = 10 min). Default: 600 |
232
+ | `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`). |
231
233
 
232
234
  > **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.
233
235
 
@@ -244,7 +246,7 @@ const tx = await rain.buildSellOptionTx({
244
246
  optionSide: OptionSide.Yes, // Yes = 1, No = 2
245
247
  sharesAmount: parseUnits('10', 6), // shares to sell
246
248
  slippageTolerance: 5n, // optional, default 5%
247
- deadline: 600n, // optional, default 600 (10 min) — duration in seconds
249
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
248
250
  });
249
251
  // Returns a single RawTransaction (no approval needed)
250
252
  ```
@@ -257,7 +259,7 @@ const tx = await rain.buildSellOptionTx({
257
259
  | `sharesAmount` | `bigint` | Number of shares to sell |
258
260
  | `minAmountOut` | `bigint` | *(Optional)* Minimum base tokens to receive. Auto-calculated from `getCurrentPrice` with slippage if not set |
259
261
  | `slippageTolerance` | `bigint` | *(Optional)* Slippage percentage (e.g. `5n` = 5%). Default: 5% |
260
- | `deadline` | `bigint` | *(Optional)* Duration in seconds (e.g. `600n` = 10 min). Default: 600 |
262
+ | `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`). |
261
263
 
262
264
  ---
263
265
 
@@ -308,7 +310,7 @@ const txs = await rain.buildAddLiquidityTx({
308
310
  totalAmountInWei: parseUnits('10', 6), // 10 USDT
309
311
  walletAddress: '0x...', // user's wallet address
310
312
  slippageTolerance: 5n, // optional, default 5%
311
- deadline: 600n, // optional, default 600 (10 min) — duration in seconds
313
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
312
314
  });
313
315
  // Returns [approveTx?, addLiquidityTx]
314
316
  ```
@@ -322,7 +324,7 @@ const txs = await rain.buildAddLiquidityTx({
322
324
  | `minYesToDeposit` | `bigint` | *(Optional)* Min yes tokens to deposit. Auto-calculated from reserves if not set |
323
325
  | `minNoToDeposit` | `bigint` | *(Optional)* Min no tokens to deposit. Auto-calculated from reserves if not set |
324
326
  | `slippageTolerance` | `bigint` | *(Optional)* Slippage percentage (e.g. `5n` = 5%). Default: 5% |
325
- | `deadline` | `bigint` | *(Optional)* Duration in seconds (e.g. `600n` = 10 min). Default: 600 |
327
+ | `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`). |
326
328
 
327
329
  > **Note:** Approval is handled automatically. Slippage protection is auto-calculated from `ammYesReserve`/`ammNoReserve` proportionally.
328
330
 
@@ -342,7 +344,7 @@ const tx = await rain.buildRemoveLiquidityTx({
342
344
  option: 1n,
343
345
  lpShares, // raw LP shares amount
344
346
  slippageTolerance: 5n, // optional, default 5%
345
- deadline: 600n, // optional, default 600 (10 min) — duration in seconds
347
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 600), // optional — absolute unix timestamp; omit for a default 10-min window
346
348
  });
347
349
  ```
348
350
 
@@ -354,7 +356,7 @@ const tx = await rain.buildRemoveLiquidityTx({
354
356
  | `minYesOut` | `bigint` | *(Optional)* Min yes tokens to receive. Auto-calculated from `getRemovedLiquidity` if not set |
355
357
  | `minNoOut` | `bigint` | *(Optional)* Min no tokens to receive. Auto-calculated if not set |
356
358
  | `slippageTolerance` | `bigint` | *(Optional)* Slippage percentage (e.g. `5n` = 5%). Default: 5% |
357
- | `deadline` | `bigint` | *(Optional)* Duration in seconds (e.g. `600n` = 10 min). Default: 600 |
359
+ | `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`). |
358
360
 
359
361
  ---
360
362
 
package/dist/RainAA.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { RainConfig } from './types.js';
2
2
  import { RawTransaction } from './tx/types.js';
3
+ /**
4
+ * Smart-account (Account Abstraction) manager with Alchemy gas sponsorship.
5
+ *
6
+ * ⚠️ **Browser-only.** Session persistence uses `indexedDB`, so `RainAA` is not
7
+ * supported in Node.js or SSR — session methods throw a descriptive error there.
8
+ * The stateless `Rain` class works in any environment; use it for server-side flows.
9
+ */
3
10
  export declare class RainAA {
4
11
  private config;
5
12
  private _client;
package/dist/RainAA.js CHANGED
@@ -3,6 +3,10 @@ const SESSION_DURATION_SEC = 60 * 60 * 24; // 24 hours
3
3
  const DB_NAME = 'RainSDKV2';
4
4
  const STORE_NAME = 'sessions';
5
5
  function openSessionDB() {
6
+ if (typeof indexedDB === 'undefined') {
7
+ return Promise.reject(new Error('RainAA requires a browser environment: session persistence uses indexedDB, ' +
8
+ 'which is unavailable in Node.js/SSR. RainAA is browser-only.'));
9
+ }
6
10
  return new Promise((resolve, reject) => {
7
11
  const req = indexedDB.open(DB_NAME, 1);
8
12
  req.onupgradeneeded = () => {
@@ -42,6 +46,13 @@ async function deleteSession(key) {
42
46
  tx.onerror = () => reject(tx.error);
43
47
  });
44
48
  }
49
+ /**
50
+ * Smart-account (Account Abstraction) manager with Alchemy gas sponsorship.
51
+ *
52
+ * ⚠️ **Browser-only.** Session persistence uses `indexedDB`, so `RainAA` is not
53
+ * supported in Node.js or SSR — session methods throw a descriptive error there.
54
+ * The stateless `Rain` class works in any environment; use it for server-side flows.
55
+ */
45
56
  export class RainAA {
46
57
  config;
47
58
  _client = null; // EOA-signed client (for grantPermissions)
@@ -1,15 +1,12 @@
1
- /** Decode the `exp` (unix seconds) claim from a JWT without verifying it. Cross-environment (browser + Node). */
1
+ /** Decode the `exp` (unix seconds) claim from a JWT without verifying it. Uses `atob` (browser + Node 18+). */
2
2
  function decodeJwtExp(token) {
3
3
  try {
4
4
  const part = token.split('.')[1];
5
- if (!part)
5
+ if (!part || typeof atob !== 'function')
6
6
  return undefined;
7
7
  const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
8
8
  const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
9
- const json = typeof atob === 'function'
10
- ? atob(padded)
11
- : Buffer.from(padded, 'base64').toString('binary');
12
- const payload = JSON.parse(json);
9
+ const payload = JSON.parse(atob(padded));
13
10
  return typeof payload?.exp === 'number' ? payload.exp : undefined;
14
11
  }
15
12
  catch {
@@ -1,6 +1,5 @@
1
1
  export declare const ALLOWED_ENVIRONMENTS: readonly ["development", "stage", "production"];
2
2
  export declare const DEFAULT_RPCS: string[];
3
- export declare function getRandomRpc(): string;
4
3
  /** Deterministic default RPC — every instance resolves to the same endpoint. */
5
4
  export declare function getDefaultRpc(): string;
6
5
  export declare const USDT_SYMBOL_DEV = "USDTm";
@@ -4,10 +4,6 @@ export const DEFAULT_RPCS = [
4
4
  "https://arbitrum-one.publicnode.com",
5
5
  "https://rpc.sentio.xyz/arbitrum-one"
6
6
  ];
7
- export function getRandomRpc() {
8
- const index = Math.floor(Math.random() * DEFAULT_RPCS.length);
9
- return DEFAULT_RPCS[index];
10
- }
11
7
  /** Deterministic default RPC — every instance resolves to the same endpoint. */
12
8
  export function getDefaultRpc() {
13
9
  return DEFAULT_RPCS[0];
@@ -1,7 +1,8 @@
1
1
  import { CreateMarketTxParams } from "../tx/types.js";
2
2
  export declare const convertToWeiEthers: (value: string | bigint, decimals: number) => bigint;
3
+ /** Lightweight RPC liveness check via viem `getChainId`. Kept for callers that want a pre-flight probe. */
3
4
  export declare function isRpcValid(rpcUrl: string | undefined): Promise<boolean>;
4
- export declare function getUserAllowance(params: CreateMarketTxParams): Promise<number>;
5
+ export declare function getUserAllowance(params: CreateMarketTxParams): Promise<bigint>;
5
6
  /**
6
7
  * Checks allowance for a market's base token.
7
8
  * Reads baseToken from the market contract, then checks the ERC20 allowance.
@@ -1,31 +1,36 @@
1
- import { ethers, JsonRpcProvider, Contract } from "ethers";
1
+ import { parseUnits, createPublicClient, http } from "viem";
2
+ import { arbitrum } from "viem/chains";
2
3
  import { ERC20Abi } from "../abi/ERC20Abi.js";
3
4
  import { getMarketBaseToken } from "../markets/getResolverBondAmount.js";
5
+ const erc20Abi = ERC20Abi;
4
6
  export const convertToWeiEthers = (value, decimals) => {
5
- return ethers.parseUnits(value.toString(), decimals);
7
+ return parseUnits(value.toString(), decimals);
6
8
  };
9
+ /** Lightweight RPC liveness check via viem `getChainId`. Kept for callers that want a pre-flight probe. */
7
10
  export async function isRpcValid(rpcUrl) {
8
11
  if (!rpcUrl)
9
12
  return false;
10
- const provider = new JsonRpcProvider(rpcUrl);
11
13
  try {
12
- await provider.getNetwork();
14
+ const client = createPublicClient({ chain: arbitrum, transport: http(rpcUrl) });
15
+ await client.getChainId();
13
16
  return true;
14
17
  }
15
- catch (error) {
18
+ catch {
16
19
  return false;
17
20
  }
18
21
  }
19
22
  export async function getUserAllowance(params) {
20
23
  const { factoryContractAddress, baseToken, creator, rpcUrl } = params;
21
- const isRpcWorking = await isRpcValid(rpcUrl);
22
- if (!rpcUrl || !isRpcWorking) {
24
+ if (!rpcUrl)
23
25
  throw new Error("Provided RPC URL is not valid or not working");
24
- }
25
- const provider = new JsonRpcProvider(rpcUrl);
26
- const ERC20ApprovalContract = new Contract(baseToken, ERC20Abi, provider);
27
- const userAllowance = await ERC20ApprovalContract.allowance(creator, factoryContractAddress);
28
- return userAllowance;
26
+ const client = createPublicClient({ chain: arbitrum, transport: http(rpcUrl) });
27
+ const allowance = await client.readContract({
28
+ address: baseToken,
29
+ abi: erc20Abi,
30
+ functionName: 'allowance',
31
+ args: [creator, factoryContractAddress],
32
+ });
33
+ return allowance;
29
34
  }
30
35
  /**
31
36
  * Checks allowance for a market's base token.
@@ -34,19 +39,16 @@ export async function getUserAllowance(params) {
34
39
  */
35
40
  export async function checkMarketTokenAllowance(params) {
36
41
  const { marketContractAddress, owner, rpcUrl } = params;
37
- const isRpcWorking = await isRpcValid(rpcUrl);
38
- if (!rpcUrl || !isRpcWorking) {
42
+ if (!rpcUrl)
39
43
  throw new Error("Provided RPC URL is not valid or not working");
40
- }
41
44
  const baseToken = await getMarketBaseToken({ marketContractAddress, rpcUrl });
42
- const provider = new JsonRpcProvider(rpcUrl);
43
- const tokenContract = new Contract(baseToken, ERC20Abi, provider);
45
+ const client = createPublicClient({ chain: arbitrum, transport: http(rpcUrl) });
44
46
  const [userAllowance, tokenDecimals] = await Promise.all([
45
- tokenContract.allowance(owner, marketContractAddress),
46
- tokenContract.decimals(),
47
+ client.readContract({ address: baseToken, abi: erc20Abi, functionName: 'allowance', args: [owner, marketContractAddress] }),
48
+ client.readContract({ address: baseToken, abi: erc20Abi, functionName: 'decimals' }),
47
49
  ]);
48
50
  return {
49
- allowance: BigInt(userAllowance),
51
+ allowance: userAllowance,
50
52
  baseToken,
51
53
  decimals: Number(tokenDecimals),
52
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rain-sdk-v2",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
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",
@@ -19,6 +19,9 @@
19
19
  "scripts": {
20
20
  "build": "tsc",
21
21
  "dev": "tsc -w",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:integration": "RUN_INTEGRATION=1 vitest run test/integration.test.ts",
22
25
  "prepublishOnly": "npm run build"
23
26
  },
24
27
  "repository": {
@@ -57,11 +60,11 @@
57
60
  }
58
61
  },
59
62
  "dependencies": {
60
- "ethers": "^6.16.0",
61
63
  "socket.io-client": "^4.8.3"
62
64
  },
63
65
  "devDependencies": {
64
66
  "typescript": "^5.9.3",
65
- "viem": "^2.48.8"
67
+ "viem": "^2.48.8",
68
+ "vitest": "^5.0.1"
66
69
  }
67
70
  }