@pulsepairs/sdk 0.5.2 → 0.7.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/DOCUMENTATION.md +1500 -0
- package/README.md +4 -0
- package/dist/eip712.d.ts +16 -8
- package/dist/eip712.js +17 -9
- package/dist/http.d.ts +12 -0
- package/dist/http.js +24 -2
- package/dist/pnl.d.ts +18 -1
- package/dist/pnl.js +5 -1
- package/dist/types.d.ts +25 -2
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@ It gives you three things:
|
|
|
19
19
|
Alchemy smart-contract account (the SAME wallet rain.trade uses), plus
|
|
20
20
|
gasless onboarding / approve / withdraw.
|
|
21
21
|
|
|
22
|
+
> This README is the quickstart. For the exhaustive reference — every export,
|
|
23
|
+
> its units, its failure modes, the WS protocol, the full type surface and a
|
|
24
|
+
> troubleshooting table — see **[`DOCUMENTATION.md`](./DOCUMENTATION.md)**.
|
|
25
|
+
|
|
22
26
|
> **Live integration environment:** a public demo of the full stack (markets
|
|
23
27
|
> cycling 24/7 on Arbitrum One, mock oracle, mintable test USDT) runs at
|
|
24
28
|
> **https://demo-pulsepairs.rainwins.com** with the matcher API/WS at
|
package/dist/eip712.d.ts
CHANGED
|
@@ -218,20 +218,28 @@ export declare function parseCompositeMarketKey(market: string): ParsedComposite
|
|
|
218
218
|
export declare function centsToBps(cents: number): number;
|
|
219
219
|
/** Bps → cents (5500 → 55). Inverse of `centsToBps`. */
|
|
220
220
|
export declare function bpsToCents(bps: number): number;
|
|
221
|
-
/**
|
|
222
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Atomic denomination of the documented stake window, in SHARE FACE VALUE
|
|
223
|
+
* — `Order.amount` is a share count, not the cash spent (the buyer pays
|
|
224
|
+
* `amount × price / 10000`). A $1 floor is $1 of face: ~$0.51 of cash at
|
|
225
|
+
* 51¢. Lowered from $5 → $1 on 2026-07-23 (Polymarket parity).
|
|
226
|
+
*/
|
|
227
|
+
export declare const MIN_STAKE_ATOMIC = 1000000n;
|
|
223
228
|
export declare const MAX_STAKE_ATOMIC = 500000000n;
|
|
224
229
|
/** Parse a USD string (e.g. "5", "5.50", "12.345") into atomic USDT.
|
|
225
230
|
* Rejects negatives + non-finite. */
|
|
226
231
|
export declare function parseStake(usd: string | number): bigint;
|
|
227
232
|
/**
|
|
228
|
-
* Defense-in-depth stake clamp. Throws if `amountAtomic`
|
|
229
|
-
* documented `$
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
+
* Defense-in-depth stake clamp. Throws if `amountAtomic` (share face value)
|
|
234
|
+
* is outside the documented `$1 ≤ stake ≤ $500` window; the backend applies
|
|
235
|
+
* the same window at the API boundary. SDK callers should validate before
|
|
236
|
+
* signing so a bad stake never produces a signed payload at all.
|
|
237
|
+
*
|
|
238
|
+
* Pass `side: "SELL"` for exits: the minimum is ENTRY-ONLY, so a holder left
|
|
239
|
+
* with a sub-$1 position (routine after a partial fill) can always sell it.
|
|
240
|
+
* The default stays "BUY" so existing callers keep the strict branch.
|
|
233
241
|
*/
|
|
234
|
-
export declare function assertStakeBounds(amountAtomic: bigint): void;
|
|
242
|
+
export declare function assertStakeBounds(amountAtomic: bigint, side?: "BUY" | "SELL"): void;
|
|
235
243
|
/**
|
|
236
244
|
* Probability-weighted fee in atomic USDT. Mirrors the backend / contract
|
|
237
245
|
* formula. Returns the fee for `notionalAtomic` filled at `priceBps`.
|
package/dist/eip712.js
CHANGED
|
@@ -220,8 +220,13 @@ export function bpsToCents(bps) {
|
|
|
220
220
|
}
|
|
221
221
|
return bps / 100;
|
|
222
222
|
}
|
|
223
|
-
/**
|
|
224
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Atomic denomination of the documented stake window, in SHARE FACE VALUE
|
|
225
|
+
* — `Order.amount` is a share count, not the cash spent (the buyer pays
|
|
226
|
+
* `amount × price / 10000`). A $1 floor is $1 of face: ~$0.51 of cash at
|
|
227
|
+
* 51¢. Lowered from $5 → $1 on 2026-07-23 (Polymarket parity).
|
|
228
|
+
*/
|
|
229
|
+
export const MIN_STAKE_ATOMIC = 1000000n;
|
|
225
230
|
export const MAX_STAKE_ATOMIC = 500000000n;
|
|
226
231
|
/** Parse a USD string (e.g. "5", "5.50", "12.345") into atomic USDT.
|
|
227
232
|
* Rejects negatives + non-finite. */
|
|
@@ -233,14 +238,17 @@ export function parseStake(usd) {
|
|
|
233
238
|
return BigInt(Math.round(n * 1e6));
|
|
234
239
|
}
|
|
235
240
|
/**
|
|
236
|
-
* Defense-in-depth stake clamp. Throws if `amountAtomic`
|
|
237
|
-
* documented `$
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
+
* Defense-in-depth stake clamp. Throws if `amountAtomic` (share face value)
|
|
242
|
+
* is outside the documented `$1 ≤ stake ≤ $500` window; the backend applies
|
|
243
|
+
* the same window at the API boundary. SDK callers should validate before
|
|
244
|
+
* signing so a bad stake never produces a signed payload at all.
|
|
245
|
+
*
|
|
246
|
+
* Pass `side: "SELL"` for exits: the minimum is ENTRY-ONLY, so a holder left
|
|
247
|
+
* with a sub-$1 position (routine after a partial fill) can always sell it.
|
|
248
|
+
* The default stays "BUY" so existing callers keep the strict branch.
|
|
241
249
|
*/
|
|
242
|
-
export function assertStakeBounds(amountAtomic) {
|
|
243
|
-
if (amountAtomic < MIN_STAKE_ATOMIC) {
|
|
250
|
+
export function assertStakeBounds(amountAtomic, side = "BUY") {
|
|
251
|
+
if (side === "BUY" && amountAtomic < MIN_STAKE_ATOMIC) {
|
|
244
252
|
throw new Error(`stake below $${Number(MIN_STAKE_ATOMIC) / 1e6} minimum`);
|
|
245
253
|
}
|
|
246
254
|
if (amountAtomic > MAX_STAKE_ATOMIC) {
|
package/dist/http.d.ts
CHANGED
|
@@ -18,6 +18,18 @@ export declare class UpDownHttpClient {
|
|
|
18
18
|
getOrderbook(marketAddress: string): Promise<OrderBookFull>;
|
|
19
19
|
getBalance(wallet: string): Promise<Balance>;
|
|
20
20
|
getPositions(wallet: string): Promise<Position[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Open positions PLUS the wallet-level realized-from-sells scalar (atomic
|
|
23
|
+
* USDT, signed). Uses `?includeRealized=1`; `getPositions` keeps the plain
|
|
24
|
+
* bare-array response for callers that don't need the realized term.
|
|
25
|
+
*
|
|
26
|
+
* Tolerant of a backend that predates the envelope: if the body comes back a
|
|
27
|
+
* bare array, the realized term is simply reported as "0" rather than failing.
|
|
28
|
+
*/
|
|
29
|
+
getPositionsWithRealized(wallet: string): Promise<{
|
|
30
|
+
positions: Position[];
|
|
31
|
+
realizedFromSells: string;
|
|
32
|
+
}>;
|
|
21
33
|
getTrades(wallet: string, opts?: {
|
|
22
34
|
limit?: number;
|
|
23
35
|
offset?: number;
|
package/dist/http.js
CHANGED
|
@@ -69,6 +69,21 @@ export class UpDownHttpClient {
|
|
|
69
69
|
const res = await fetch(buildUrl(this.baseUrl, `/positions/${wallet}`));
|
|
70
70
|
return parseJson(res);
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Open positions PLUS the wallet-level realized-from-sells scalar (atomic
|
|
74
|
+
* USDT, signed). Uses `?includeRealized=1`; `getPositions` keeps the plain
|
|
75
|
+
* bare-array response for callers that don't need the realized term.
|
|
76
|
+
*
|
|
77
|
+
* Tolerant of a backend that predates the envelope: if the body comes back a
|
|
78
|
+
* bare array, the realized term is simply reported as "0" rather than failing.
|
|
79
|
+
*/
|
|
80
|
+
async getPositionsWithRealized(wallet) {
|
|
81
|
+
const res = await fetch(buildUrl(this.baseUrl, `/positions/${wallet}`, { includeRealized: 1 }));
|
|
82
|
+
const body = await parseJson(res);
|
|
83
|
+
if (Array.isArray(body))
|
|
84
|
+
return { positions: body, realizedFromSells: "0" };
|
|
85
|
+
return { positions: body.positions ?? [], realizedFromSells: body.realizedFromSells ?? "0" };
|
|
86
|
+
}
|
|
72
87
|
async getTrades(wallet, opts) {
|
|
73
88
|
const res = await fetch(buildUrl(this.baseUrl, `/trades/${wallet}`, {
|
|
74
89
|
limit: opts?.limit,
|
|
@@ -91,9 +106,15 @@ export class UpDownHttpClient {
|
|
|
91
106
|
* @param opts.usdtDecimals display-float decimals (default 6).
|
|
92
107
|
*/
|
|
93
108
|
async getPnl(wallet, opts) {
|
|
94
|
-
|
|
109
|
+
// Fetch the realized-from-sells scalar alongside the positions so the
|
|
110
|
+
// rolled-up PnL carries realized (from manual sells) as well as unrealized.
|
|
111
|
+
// Backward-tolerant: an old backend yields "0" here, not an error.
|
|
112
|
+
const { positions, realizedFromSells } = await this.getPositionsWithRealized(wallet);
|
|
95
113
|
if (positions.length === 0) {
|
|
96
|
-
return computePortfolioPnl([], new Map(), {
|
|
114
|
+
return computePortfolioPnl([], new Map(), {
|
|
115
|
+
usdtDecimals: opts?.usdtDecimals,
|
|
116
|
+
realizedFromSells,
|
|
117
|
+
});
|
|
97
118
|
}
|
|
98
119
|
const marketKeys = Array.from(new Set(positions.map((p) => p.market)));
|
|
99
120
|
const details = await Promise.all(marketKeys.map((key) => this.getMarket(key).catch(() => null)));
|
|
@@ -111,6 +132,7 @@ export class UpDownHttpClient {
|
|
|
111
132
|
return computePortfolioPnl(positions, marketsByKey, {
|
|
112
133
|
fees,
|
|
113
134
|
usdtDecimals: opts?.usdtDecimals,
|
|
135
|
+
realizedFromSells,
|
|
114
136
|
});
|
|
115
137
|
}
|
|
116
138
|
async getOrders(wallet, opts) {
|
package/dist/pnl.d.ts
CHANGED
|
@@ -82,6 +82,18 @@ export type PortfolioPnl = {
|
|
|
82
82
|
* (see `feesPaidByWallet`); "0" otherwise. Not netted into `unrealizedPnl`
|
|
83
83
|
* — cost basis is fee-exclusive, so fees stay a separate, explicit line. */
|
|
84
84
|
fees: string;
|
|
85
|
+
/**
|
|
86
|
+
* Atomic USDT (signed) realized P&L booked from manual market-SELLs, across
|
|
87
|
+
* ALL of the wallet's positions — including ones sold to zero net shares,
|
|
88
|
+
* which never appear in `positions`. "0" when the caller did not supply it
|
|
89
|
+
* (or the backend predates the `?includeRealized=1` envelope).
|
|
90
|
+
*
|
|
91
|
+
* Report it ALONGSIDE `unrealizedPnl`, which already carries settled
|
|
92
|
+
* survivors at full-face/zero (`resolved: true`); the two together are the
|
|
93
|
+
* wallet's complete realized+unrealized P&L. It is average-cost per sell, so
|
|
94
|
+
* only the combined figure is order-invariant — never surface it standalone.
|
|
95
|
+
*/
|
|
96
|
+
realizedFromSells: string;
|
|
85
97
|
};
|
|
86
98
|
/** Options controlling how one position is valued. */
|
|
87
99
|
export type PositionPnlOptions = {
|
|
@@ -130,9 +142,13 @@ export declare function positionPnl(position: Position, opts?: PositionPnlOption
|
|
|
130
142
|
* so treat it as an upper bound on the wallet's own fee drag. */
|
|
131
143
|
export declare function feesPaidByWallet(wallet: string, trades: Trade[]): string;
|
|
132
144
|
/** Roll individual position PnLs up into a portfolio total. Pass `fees` (from
|
|
133
|
-
* `feesPaidByWallet`) to carry a fee line through; it is reported, not netted.
|
|
145
|
+
* `feesPaidByWallet`) to carry a fee line through; it is reported, not netted.
|
|
146
|
+
* Pass `realizedFromSells` (atomic USDT string, from `GET
|
|
147
|
+
* /positions/:wallet?includeRealized=1`) to carry the manual-sell realized
|
|
148
|
+
* term; likewise reported, never netted into `unrealizedPnl`. */
|
|
134
149
|
export declare function portfolioPnl(items: PositionPnl[], opts?: {
|
|
135
150
|
fees?: string;
|
|
151
|
+
realizedFromSells?: string;
|
|
136
152
|
usdtDecimals?: number;
|
|
137
153
|
}): PortfolioPnl;
|
|
138
154
|
/**
|
|
@@ -146,5 +162,6 @@ export declare function portfolioPnl(items: PositionPnl[], opts?: {
|
|
|
146
162
|
*/
|
|
147
163
|
export declare function computePortfolioPnl(positions: Position[], marketsByKey: Map<string, Pick<MarketDetail, "winner" | "orderBook">>, opts?: {
|
|
148
164
|
fees?: string;
|
|
165
|
+
realizedFromSells?: string;
|
|
149
166
|
usdtDecimals?: number;
|
|
150
167
|
}): PortfolioPnl;
|
package/dist/pnl.js
CHANGED
|
@@ -113,7 +113,10 @@ export function feesPaidByWallet(wallet, trades) {
|
|
|
113
113
|
return total.toString();
|
|
114
114
|
}
|
|
115
115
|
/** Roll individual position PnLs up into a portfolio total. Pass `fees` (from
|
|
116
|
-
* `feesPaidByWallet`) to carry a fee line through; it is reported, not netted.
|
|
116
|
+
* `feesPaidByWallet`) to carry a fee line through; it is reported, not netted.
|
|
117
|
+
* Pass `realizedFromSells` (atomic USDT string, from `GET
|
|
118
|
+
* /positions/:wallet?includeRealized=1`) to carry the manual-sell realized
|
|
119
|
+
* term; likewise reported, never netted into `unrealizedPnl`. */
|
|
117
120
|
export function portfolioPnl(items, opts = {}) {
|
|
118
121
|
const decimals = opts.usdtDecimals ?? DEFAULT_USDT_DECIMALS;
|
|
119
122
|
let costBasis = 0n;
|
|
@@ -131,6 +134,7 @@ export function portfolioPnl(items, opts = {}) {
|
|
|
131
134
|
unrealizedPnlUsdt: atomicToUsdt(unrealizedPnl, decimals),
|
|
132
135
|
roiPct: pctOf(unrealizedPnl, costBasis),
|
|
133
136
|
fees: opts.fees ?? "0",
|
|
137
|
+
realizedFromSells: opts.realizedFromSells ?? "0",
|
|
134
138
|
};
|
|
135
139
|
}
|
|
136
140
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -81,8 +81,24 @@ export type MarketListItem = {
|
|
|
81
81
|
duration: number;
|
|
82
82
|
status: "ACTIVE" | "TRADING_ENDED" | "RESOLVED" | "CLAIMED" | string;
|
|
83
83
|
winner: number | null;
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Odds in basis points of $1 (5000 = 50%), from the mid of the live book.
|
|
86
|
+
* `null` when no price is defined (empty / one-sided book, or trading not
|
|
87
|
+
* open) — render "—", never 0%. `0` / `10000` occur only on a settled
|
|
88
|
+
* market, where they are the outcome. When non-null,
|
|
89
|
+
* `upPrice + downPrice === 10000`.
|
|
90
|
+
*
|
|
91
|
+
* BREAKING 2026-07-26: previously `string`, and previously carried the
|
|
92
|
+
* on-chain cumulative collateral flow rather than any price.
|
|
93
|
+
*/
|
|
94
|
+
upPrice: number | null;
|
|
95
|
+
downPrice: number | null;
|
|
96
|
+
/**
|
|
97
|
+
* Why `upPrice`/`downPrice` are what they are, so a `null` is explained
|
|
98
|
+
* rather than merely absent. Widened to `string` deliberately: this is an
|
|
99
|
+
* OPEN set and new values may be added — do not switch exhaustively.
|
|
100
|
+
*/
|
|
101
|
+
priceSource?: "settled" | "pending_resolution" | "not_trading" | "book_mid" | "none" | string;
|
|
86
102
|
strikePrice?: string;
|
|
87
103
|
settlementPrice?: string;
|
|
88
104
|
volume: string;
|
|
@@ -159,6 +175,13 @@ export type Trade = {
|
|
|
159
175
|
platformFee: string;
|
|
160
176
|
makerFee: string;
|
|
161
177
|
settlementStatus: string;
|
|
178
|
+
/** Hash of the on-chain settlement tx, for linking a fill to the explorer.
|
|
179
|
+
* Null until the tx is broadcast — and reset to null if a reconcile drops
|
|
180
|
+
* it — so treat absence as "pending", not "missing". Complementary
|
|
181
|
+
* (MINT/MERGE) fills settle in batches, so the same hash legitimately
|
|
182
|
+
* repeats across several trades. Optional for back-compat with a
|
|
183
|
+
* pre-2026-07-21 backend. */
|
|
184
|
+
settlementTxHash?: string | null;
|
|
162
185
|
createdAt: string;
|
|
163
186
|
/** Match geometry. NORMAL = same-option fill. MINT/MERGE = complementary
|
|
164
187
|
* cross (opposite-option legs). Optional so a pre-2026-07-17 backend
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulsepairs/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Standalone SDK for the UpDown (PulsePairs) up/down prediction markets
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Standalone SDK for the UpDown (PulsePairs) up/down prediction markets \u2014 matcher REST/WS client, EIP-712 order signing, trade-math, and Alchemy Account Kit (smart-account) order signing for rain.trade integration.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"README.md"
|
|
30
|
+
"README.md",
|
|
31
|
+
"DOCUMENTATION.md"
|
|
31
32
|
],
|
|
32
33
|
"sideEffects": false,
|
|
33
34
|
"scripts": {
|