@pulsepairs/sdk 0.3.0 → 0.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/README.md +66 -7
- package/dist/accountKit.d.ts +8 -1
- package/dist/accountKit.js +27 -4
- package/dist/approve.d.ts +37 -5
- package/dist/approve.js +36 -9
- package/dist/eip712.d.ts +23 -0
- package/dist/eip712.js +34 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -2
- package/dist/reconcile.d.ts +103 -0
- package/dist/reconcile.js +129 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -65,11 +65,14 @@ import { createPublicClient, createWalletClient, http } from "viem";
|
|
|
65
65
|
import { privateKeyToAccount } from "viem/accounts";
|
|
66
66
|
import { arbitrum } from "viem/chains";
|
|
67
67
|
import {
|
|
68
|
-
UpDownHttpClient, buildOrderTypedData, ensureSettlementAllowance,
|
|
68
|
+
UpDownHttpClient, buildOrderTypedData, ensureSettlementAllowance, freshNonce,
|
|
69
69
|
parseCompositeMarketKey, parseStake, OrderType, OrderSide, Option,
|
|
70
70
|
} from "@pulsepairs/sdk";
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// Point the bot at a matcher YOU chose. Don't hardcode ours into a process
|
|
73
|
+
// that holds a funded key — and assert the chain matches your RPC's chain
|
|
74
|
+
// before you approve anything (see "Handling a funded key" below).
|
|
75
|
+
const api = new UpDownHttpClient(process.env.UPND_API!);
|
|
73
76
|
const cfg = await api.getConfig(); // never hardcode addresses
|
|
74
77
|
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
|
|
75
78
|
const [live] = (await api.getMarkets({ pair: "BTC-USD", timeframe: 300 }))
|
|
@@ -77,8 +80,18 @@ const [live] = (await api.getMarkets({ pair: "BTC-USD", timeframe: 300 }))
|
|
|
77
80
|
const { settlementAddress, marketId } = parseCompositeMarketKey(live.address)!;
|
|
78
81
|
|
|
79
82
|
const amount = parseStake("5");
|
|
83
|
+
|
|
84
|
+
// BOUNDED approve. `settlementAddress` is server-supplied (it came from the
|
|
85
|
+
// matcher's market list), so cap its reach at what you actually intend to
|
|
86
|
+
// spend. `amount` = how much to approve; `threshold` = when to top back up.
|
|
87
|
+
// Omitting `amount` still approves MAX_UINT256 — that default is legacy.
|
|
88
|
+
await ensureSettlementAllowance({
|
|
89
|
+
publicClient, walletClient, usdt: cfg.usdtAddress, settlement: settlementAddress,
|
|
90
|
+
amount: amount * 20n, threshold: amount,
|
|
91
|
+
});
|
|
92
|
+
|
|
80
93
|
const maxFee = (amount * BigInt(cfg.platformFeeBps + cfg.makerFeeBps)) / 10000n; // peak fee
|
|
81
|
-
const nonce =
|
|
94
|
+
const nonce = freshNonce(); // CSPRNG; never Math.random/Date.now
|
|
82
95
|
const typedData = buildOrderTypedData({
|
|
83
96
|
cfg, settlementAddress,
|
|
84
97
|
message: { maker: account.address, market: BigInt(marketId), option: BigInt(Option.UP),
|
|
@@ -91,8 +104,54 @@ await api.postOrder({ maker: account.address, market: live.address, option: Opti
|
|
|
91
104
|
maxFee: maxFee.toString(), nonce: Number(nonce), expiry: live.endTime, signature });
|
|
92
105
|
```
|
|
93
106
|
|
|
94
|
-
Full runnable scripts
|
|
95
|
-
(LIMIT + authed WS), `
|
|
107
|
+
Full runnable scripts live in the **repo** under `examples/` — `simple-taker.ts`
|
|
108
|
+
(MARKET), `simple-maker.ts` (LIMIT + authed WS), `full-dmm-bot.ts` (two-sided
|
|
109
|
+
quoting). They are **not in the npm tarball** (`files: ["dist","README.md"]`), so
|
|
110
|
+
if you installed from npm without repo access, this README is your reference —
|
|
111
|
+
the safe patterns are inlined here on purpose.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Handling a funded key
|
|
116
|
+
|
|
117
|
+
This SDK is normally driven by a hot key that holds real USDT. Three rules,
|
|
118
|
+
each of which exists because the failure is silent:
|
|
119
|
+
|
|
120
|
+
**1. Never default your endpoints.** A bot signs orders for whatever matcher you
|
|
121
|
+
point it at, and then grants that matcher's *server-supplied* settlement address
|
|
122
|
+
a USDT allowance. A defaulted `UPND_API` plus a defaulted mainnet RPC means a
|
|
123
|
+
real funded key trading against a box you never chose. Require both explicitly:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const API = process.env.UPND_API;
|
|
127
|
+
if (!API) throw new Error("Set UPND_API — refusing to default a funded key to a third-party endpoint");
|
|
128
|
+
const RPC = process.env.ARBITRUM_RPC_URL;
|
|
129
|
+
if (!RPC) throw new Error("Set ARBITRUM_RPC_URL");
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**2. Assert the matcher and the RPC agree on the chain**, before the approve.
|
|
133
|
+
This one check catches the whole class — a testnet/demo matcher paired with a
|
|
134
|
+
mainnet key can't survive it:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const cfg = await api.getConfig();
|
|
138
|
+
const rpcChainId = await publicClient.getChainId();
|
|
139
|
+
if (cfg.chainId !== rpcChainId) {
|
|
140
|
+
throw new Error(`chain mismatch: matcher says ${cfg.chainId}, RPC says ${rpcChainId}`);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**3. Bound the allowance.** Pass `amount` to `ensureSettlementAllowance` (EOA) /
|
|
145
|
+
`onboard`/`approve` (Account Kit) / `buildApproveSettlementTx` (raw-tx tier).
|
|
146
|
+
All four default to `MAX_UINT256` for backwards compatibility; that default is
|
|
147
|
+
an unbounded claim on your balance by an address the server named. A bounded
|
|
148
|
+
allowance is consumed by fills, so re-run the helper on a timer — if it runs dry
|
|
149
|
+
mid-session your fills revert with `insufficient allowance`.
|
|
150
|
+
|
|
151
|
+
Use `freshNonce()` for order/cancel nonces. It draws from `crypto.getRandomValues`
|
|
152
|
+
and stays under 2^53 so `Number(nonce)` on the wire matches the value you signed.
|
|
153
|
+
`Math.random()`/`Date.now()` nonces are guessable, which lets anyone pre-burn your
|
|
154
|
+
next nonce against the replay store and block your order flow.
|
|
96
155
|
|
|
97
156
|
---
|
|
98
157
|
|
|
@@ -261,8 +320,8 @@ this cap across partial fills.
|
|
|
261
320
|
Clients UpDownHttpClient, UpDownWsClient, wsUrlFromHttpBase
|
|
262
321
|
EIP-712 ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES,
|
|
263
322
|
buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData,
|
|
264
|
-
freshSessionId,
|
|
265
|
-
parseCompositeMarketKey
|
|
323
|
+
freshSessionId, freshNonce, domainForSettlement,
|
|
324
|
+
findPairBySettlement, parseCompositeMarketKey
|
|
266
325
|
Trade-math centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic,
|
|
267
326
|
MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC
|
|
268
327
|
Approve (EOA) ensureSettlementAllowance, MAX_UINT256
|
package/dist/accountKit.d.ts
CHANGED
|
@@ -253,15 +253,22 @@ export declare class UpDownAccountKitSigner {
|
|
|
253
253
|
* popup-less. Order-session prep failures degrade to plain deploy+approve
|
|
254
254
|
* (owner-key signing per order). Uses the owner client so it works before
|
|
255
255
|
* any session exists; deployment is carried by the account init-code.
|
|
256
|
+
*
|
|
257
|
+
* `amount` bounds the allowance (default unlimited) — see
|
|
258
|
+
* `ensureSettlementAllowance`'s notes; the same argument applies here,
|
|
259
|
+
* since `settlement` comes from the matcher's own config.
|
|
256
260
|
*/
|
|
257
261
|
onboard(args: {
|
|
258
262
|
usdt: Address;
|
|
259
263
|
settlement: Address;
|
|
264
|
+
amount?: bigint;
|
|
260
265
|
}): Promise<Hex>;
|
|
261
|
-
/** Idempotent USDT approve to the settlement (gasless UserOp).
|
|
266
|
+
/** Idempotent USDT approve to the settlement (gasless UserOp). `amount`
|
|
267
|
+
* bounds the allowance; defaults to unlimited (MAX_UINT256). */
|
|
262
268
|
approve(args: {
|
|
263
269
|
usdt: Address;
|
|
264
270
|
settlement: Address;
|
|
271
|
+
amount?: bigint;
|
|
265
272
|
}): Promise<Hex>;
|
|
266
273
|
/** Transfer USDT out of the SCA to `to` (gasless UserOp). */
|
|
267
274
|
withdraw(args: {
|
package/dist/accountKit.js
CHANGED
|
@@ -483,10 +483,14 @@ export class UpDownAccountKitSigner {
|
|
|
483
483
|
* popup-less. Order-session prep failures degrade to plain deploy+approve
|
|
484
484
|
* (owner-key signing per order). Uses the owner client so it works before
|
|
485
485
|
* any session exists; deployment is carried by the account init-code.
|
|
486
|
+
*
|
|
487
|
+
* `amount` bounds the allowance (default unlimited) — see
|
|
488
|
+
* `ensureSettlementAllowance`'s notes; the same argument applies here,
|
|
489
|
+
* since `settlement` comes from the matcher's own config.
|
|
486
490
|
*/
|
|
487
491
|
async onboard(args) {
|
|
488
492
|
const calls = [
|
|
489
|
-
{ to: args.usdt, data: encodeApprove(args.settlement) },
|
|
493
|
+
{ to: args.usdt, data: encodeApprove(args.settlement, args.amount ?? MAX_UINT256) },
|
|
490
494
|
];
|
|
491
495
|
let record = null;
|
|
492
496
|
if (this.orderSessionsEnabled && !(await this.orderSessionAccount().catch(() => null))) {
|
|
@@ -504,9 +508,13 @@ export class UpDownAccountKitSigner {
|
|
|
504
508
|
this.persistOrderSession(record);
|
|
505
509
|
return txHash;
|
|
506
510
|
}
|
|
507
|
-
/** Idempotent USDT approve to the settlement (gasless UserOp).
|
|
511
|
+
/** Idempotent USDT approve to the settlement (gasless UserOp). `amount`
|
|
512
|
+
* bounds the allowance; defaults to unlimited (MAX_UINT256). */
|
|
508
513
|
async approve(args) {
|
|
509
|
-
return this.sendCall({
|
|
514
|
+
return this.sendCall({
|
|
515
|
+
to: args.usdt,
|
|
516
|
+
data: encodeApprove(args.settlement, args.amount ?? MAX_UINT256),
|
|
517
|
+
});
|
|
510
518
|
}
|
|
511
519
|
/** Transfer USDT out of the SCA to `to` (gasless UserOp). */
|
|
512
520
|
async withdraw(args) {
|
|
@@ -617,7 +625,10 @@ async function buildOrderSessionInstall(sca, moduleChain) {
|
|
|
617
625
|
// Random 4-byte entity id (≥2): 0 is the owner entity, and installing an
|
|
618
626
|
// id that already exists on the account reverts — random keeps collisions
|
|
619
627
|
// with prior sessions (lost storage, other hosts) vanishingly unlikely.
|
|
620
|
-
|
|
628
|
+
// CSPRNG-drawn: a predictable id lets an observer front-run the install
|
|
629
|
+
// and brick onboarding by burning the id we're about to claim (the revert
|
|
630
|
+
// is the whole failure mode this randomness exists to avoid).
|
|
631
|
+
const entityId = 2 + randomUint31();
|
|
621
632
|
const data = viemMod.encodeFunctionData({
|
|
622
633
|
abi: semiModularAccountBytecodeAbi,
|
|
623
634
|
functionName: "installValidation",
|
|
@@ -708,6 +719,18 @@ export async function signWithOrderSession(args) {
|
|
|
708
719
|
}
|
|
709
720
|
/* ───────────────────────── calldata encoders ───────────────────────── */
|
|
710
721
|
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
722
|
+
/** Uniform CSPRNG draw in [0, 0x7ffffff0). Mirrors `freshSessionId`'s
|
|
723
|
+
* contract: throws rather than silently degrading to a weak source. */
|
|
724
|
+
function randomUint31() {
|
|
725
|
+
const bytes = new Uint8Array(4);
|
|
726
|
+
const g = globalThis;
|
|
727
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") {
|
|
728
|
+
throw new Error("globalThis.crypto.getRandomValues unavailable; use Node 18+ or a modern browser");
|
|
729
|
+
}
|
|
730
|
+
g.crypto.getRandomValues(bytes);
|
|
731
|
+
const raw = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
732
|
+
return raw % 0x7ffffff0;
|
|
733
|
+
}
|
|
711
734
|
function encodeApprove(spender, amount = MAX_UINT256) {
|
|
712
735
|
// approve(address,uint256) selector 0x095ea7b3
|
|
713
736
|
return ("0x095ea7b3" + pad(spender) + pad(amount));
|
package/dist/approve.d.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* USDT.approve(settlement, …) helper.
|
|
3
3
|
*
|
|
4
4
|
* Path-1 settlement pulls USDT directly from the maker via `transferFrom`
|
|
5
5
|
* inside `enterPosition`. Without this approval the first BUY reverts
|
|
6
6
|
* with `ERC20: insufficient allowance`. Idempotent: reads current
|
|
7
7
|
* allowance and only submits a tx when below threshold.
|
|
8
8
|
*
|
|
9
|
+
* `amount` defaults to a BOUNDED amount (`DEFAULT_APPROVAL_AMOUNT`, 100k USDT),
|
|
10
|
+
* not MAX_UINT256 (S2): `settlement` is routinely sourced from a server-supplied
|
|
11
|
+
* `/config` or market list, so an unlimited approval would grant a server-named
|
|
12
|
+
* spender unbounded reach into a funded hot wallet. A hot bot key SHOULD still
|
|
13
|
+
* pass its own inventory-sized bound; `MAX_UINT256` is exported for the rare
|
|
14
|
+
* caller that explicitly opts into unlimited.
|
|
15
|
+
*
|
|
9
16
|
* `viem` is a peer dependency — pass in the public + wallet clients you
|
|
10
17
|
* already have so this helper doesn't bake in a transport choice.
|
|
11
18
|
*/
|
|
12
19
|
import type { Address, PublicClient, WalletClient } from "viem";
|
|
13
20
|
export declare const MAX_UINT256: bigint;
|
|
21
|
+
/**
|
|
22
|
+
* S2: bounded default approval (100k USDT, atomic). Caps the blast radius of a
|
|
23
|
+
* settlement-contract bug (or a hostile server-supplied spender) to one
|
|
24
|
+
* inventory bound instead of the wallet's entire USDT balance, while sitting
|
|
25
|
+
* comfortably above `DEFAULT_THRESHOLD` so a continuously-quoting bot isn't
|
|
26
|
+
* re-approving on every call. Bots with larger inventory should pass their own
|
|
27
|
+
* `amount`. This is 6-decimal USDT, so 100k * 1e6.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_APPROVAL_AMOUNT: bigint;
|
|
14
30
|
export type EnsureAllowanceResult = {
|
|
15
31
|
status: "already_ok";
|
|
16
32
|
allowance: bigint;
|
|
@@ -30,12 +46,27 @@ export type EnsureAllowanceResult = {
|
|
|
30
46
|
* `cfg.pairs[0].settlementAddress` for today's
|
|
31
47
|
* single-settlement deploy).
|
|
32
48
|
* @param args.threshold — Re-approve when current allowance is below this
|
|
33
|
-
* atomic-USDT amount. Default 10,000 USDT.
|
|
49
|
+
* atomic-USDT amount. Default 10,000 USDT. This is
|
|
50
|
+
* WHEN to top up, not HOW MUCH — see `amount`.
|
|
51
|
+
* @param args.amount — How much to approve when a top-up is sent. Defaults
|
|
52
|
+
* to `DEFAULT_APPROVAL_AMOUNT` (100k USDT, BOUNDED — S2),
|
|
53
|
+
* not `MAX_UINT256`. `settlement` is routinely sourced
|
|
54
|
+
* from the matcher's own `/config` or market list, so an
|
|
55
|
+
* unlimited approval would grant a server-supplied spender
|
|
56
|
+
* unbounded reach into a funded hot wallet — size this to
|
|
57
|
+
* the inventory the bot actually needs between top-ups.
|
|
58
|
+
* It must exceed `threshold`, or every call re-approves.
|
|
59
|
+
* Pass `MAX_UINT256` to explicitly opt into unlimited.
|
|
34
60
|
*
|
|
35
61
|
* Returns `already_ok` if nothing needed to be done, `approved` with the
|
|
36
|
-
* tx hash if a new approval was sent. Caller can
|
|
37
|
-
* publicClient.waitForTransactionReceipt({ hash })` if it wants to
|
|
38
|
-
* before placing orders.
|
|
62
|
+
* tx hash and the amount approved if a new approval was sent. Caller can
|
|
63
|
+
* `await publicClient.waitForTransactionReceipt({ hash })` if it wants to
|
|
64
|
+
* confirm before placing orders.
|
|
65
|
+
*
|
|
66
|
+
* Operational note for bounded approvals: the allowance is consumed by
|
|
67
|
+
* fills, so a bot that quotes continuously WILL run it down. This helper
|
|
68
|
+
* only tops up when called — call it on your quoting loop, not just at
|
|
69
|
+
* startup, or fills start reverting with `insufficient allowance` mid-session.
|
|
39
70
|
*/
|
|
40
71
|
export declare function ensureSettlementAllowance(args: {
|
|
41
72
|
publicClient: PublicClient;
|
|
@@ -43,4 +74,5 @@ export declare function ensureSettlementAllowance(args: {
|
|
|
43
74
|
usdt: Address;
|
|
44
75
|
settlement: Address;
|
|
45
76
|
threshold?: bigint;
|
|
77
|
+
amount?: bigint;
|
|
46
78
|
}): Promise<EnsureAllowanceResult>;
|
package/dist/approve.js
CHANGED
|
@@ -21,10 +21,17 @@ const ERC20_ABI = [
|
|
|
21
21
|
},
|
|
22
22
|
];
|
|
23
23
|
export const MAX_UINT256 = (1n << 256n) - 1n;
|
|
24
|
-
/** Default refresh threshold — re-approve if allowance falls below 10k USDT.
|
|
25
|
-
* MaxUint256 effectively never decreases with USDT, but if a partial
|
|
26
|
-
* approval was set in some flow this guards against drift. */
|
|
24
|
+
/** Default refresh threshold — re-approve if allowance falls below 10k USDT. */
|
|
27
25
|
const DEFAULT_THRESHOLD = 10000n * 1000000n;
|
|
26
|
+
/**
|
|
27
|
+
* S2: bounded default approval (100k USDT, atomic). Caps the blast radius of a
|
|
28
|
+
* settlement-contract bug (or a hostile server-supplied spender) to one
|
|
29
|
+
* inventory bound instead of the wallet's entire USDT balance, while sitting
|
|
30
|
+
* comfortably above `DEFAULT_THRESHOLD` so a continuously-quoting bot isn't
|
|
31
|
+
* re-approving on every call. Bots with larger inventory should pass their own
|
|
32
|
+
* `amount`. This is 6-decimal USDT, so 100k * 1e6.
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_APPROVAL_AMOUNT = 100000n * 1000000n;
|
|
28
35
|
/**
|
|
29
36
|
* Ensure the maker has enough USDT allowance on the settlement contract.
|
|
30
37
|
*
|
|
@@ -36,12 +43,27 @@ const DEFAULT_THRESHOLD = 10000n * 1000000n;
|
|
|
36
43
|
* `cfg.pairs[0].settlementAddress` for today's
|
|
37
44
|
* single-settlement deploy).
|
|
38
45
|
* @param args.threshold — Re-approve when current allowance is below this
|
|
39
|
-
* atomic-USDT amount. Default 10,000 USDT.
|
|
46
|
+
* atomic-USDT amount. Default 10,000 USDT. This is
|
|
47
|
+
* WHEN to top up, not HOW MUCH — see `amount`.
|
|
48
|
+
* @param args.amount — How much to approve when a top-up is sent. Defaults
|
|
49
|
+
* to `DEFAULT_APPROVAL_AMOUNT` (100k USDT, BOUNDED — S2),
|
|
50
|
+
* not `MAX_UINT256`. `settlement` is routinely sourced
|
|
51
|
+
* from the matcher's own `/config` or market list, so an
|
|
52
|
+
* unlimited approval would grant a server-supplied spender
|
|
53
|
+
* unbounded reach into a funded hot wallet — size this to
|
|
54
|
+
* the inventory the bot actually needs between top-ups.
|
|
55
|
+
* It must exceed `threshold`, or every call re-approves.
|
|
56
|
+
* Pass `MAX_UINT256` to explicitly opt into unlimited.
|
|
40
57
|
*
|
|
41
58
|
* Returns `already_ok` if nothing needed to be done, `approved` with the
|
|
42
|
-
* tx hash if a new approval was sent. Caller can
|
|
43
|
-
* publicClient.waitForTransactionReceipt({ hash })` if it wants to
|
|
44
|
-
* before placing orders.
|
|
59
|
+
* tx hash and the amount approved if a new approval was sent. Caller can
|
|
60
|
+
* `await publicClient.waitForTransactionReceipt({ hash })` if it wants to
|
|
61
|
+
* confirm before placing orders.
|
|
62
|
+
*
|
|
63
|
+
* Operational note for bounded approvals: the allowance is consumed by
|
|
64
|
+
* fills, so a bot that quotes continuously WILL run it down. This helper
|
|
65
|
+
* only tops up when called — call it on your quoting loop, not just at
|
|
66
|
+
* startup, or fills start reverting with `insufficient allowance` mid-session.
|
|
45
67
|
*/
|
|
46
68
|
export async function ensureSettlementAllowance(args) {
|
|
47
69
|
const account = args.walletClient.account;
|
|
@@ -49,6 +71,11 @@ export async function ensureSettlementAllowance(args) {
|
|
|
49
71
|
throw new Error("walletClient has no account");
|
|
50
72
|
const owner = account.address;
|
|
51
73
|
const threshold = args.threshold ?? DEFAULT_THRESHOLD;
|
|
74
|
+
const amount = args.amount ?? DEFAULT_APPROVAL_AMOUNT;
|
|
75
|
+
if (amount < threshold) {
|
|
76
|
+
throw new Error(`approve amount (${amount}) is below the re-approve threshold (${threshold}) — ` +
|
|
77
|
+
"every call would send a redundant approve tx; raise amount or lower threshold");
|
|
78
|
+
}
|
|
52
79
|
const current = (await args.publicClient.readContract({
|
|
53
80
|
address: args.usdt,
|
|
54
81
|
abi: ERC20_ABI,
|
|
@@ -63,7 +90,7 @@ export async function ensureSettlementAllowance(args) {
|
|
|
63
90
|
address: args.usdt,
|
|
64
91
|
abi: ERC20_ABI,
|
|
65
92
|
functionName: "approve",
|
|
66
|
-
args: [args.settlement,
|
|
93
|
+
args: [args.settlement, amount],
|
|
67
94
|
});
|
|
68
|
-
return { status: "approved", txHash, allowance:
|
|
95
|
+
return { status: "approved", txHash, allowance: amount };
|
|
69
96
|
}
|
package/dist/eip712.d.ts
CHANGED
|
@@ -60,6 +60,29 @@ export declare function buildWsAuthTypedData(args: {
|
|
|
60
60
|
* no sessionId).
|
|
61
61
|
*/
|
|
62
62
|
export declare function freshSessionId(): `0x${string}`;
|
|
63
|
+
/**
|
|
64
|
+
* Generate a fresh order/cancel `nonce` from a CSPRNG.
|
|
65
|
+
*
|
|
66
|
+
* The nonce is the only thing standing between an order and the backend's
|
|
67
|
+
* replay store: a PREDICTABLE nonce lets anyone pre-burn a maker's next
|
|
68
|
+
* nonce and grief their order flow (a liveness attack — the signature
|
|
69
|
+
* itself still can't be forged). `Math.random()` is not a CSPRNG and
|
|
70
|
+
* `Date.now()` is public knowledge; neither is acceptable here.
|
|
71
|
+
*
|
|
72
|
+
* Deliberately 48 bits, NOT the full uint256: `PostOrderBody.nonce` is a
|
|
73
|
+
* JSON `number`, so the value must survive `Number(nonce)` losslessly —
|
|
74
|
+
* anything above 2^53 would silently round and the posted nonce would stop
|
|
75
|
+
* matching the signed one, failing signature recovery. Across 2^48 values the
|
|
76
|
+
* CUMULATIVE (birthday-bound) chance that ANY two of one maker's ~750k orders
|
|
77
|
+
* collide is ~1e-3 (~k²/2·2^48) — about one in a thousand, not vanishing; the
|
|
78
|
+
* ~1-in-10^9 figure is only the MARGINAL odds that a single new draw hits an
|
|
79
|
+
* existing nonce at that point. Either way a collision is a rejected order
|
|
80
|
+
* (retry with a fresh nonce), not a loss.
|
|
81
|
+
*
|
|
82
|
+
* Single-writer bots may prefer a monotonic counter (no birthday bound at
|
|
83
|
+
* all) — seed it from this rather than from the clock.
|
|
84
|
+
*/
|
|
85
|
+
export declare function freshNonce(): bigint;
|
|
63
86
|
/**
|
|
64
87
|
* EIP-712 type schemas. Mirrors the backend's `EIP712_ORDER_TYPES` /
|
|
65
88
|
* `EIP712_CANCEL_TYPES` exactly — if these drift, signatures stop being
|
package/dist/eip712.js
CHANGED
|
@@ -66,6 +66,40 @@ export function freshSessionId() {
|
|
|
66
66
|
}
|
|
67
67
|
return hex;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Generate a fresh order/cancel `nonce` from a CSPRNG.
|
|
71
|
+
*
|
|
72
|
+
* The nonce is the only thing standing between an order and the backend's
|
|
73
|
+
* replay store: a PREDICTABLE nonce lets anyone pre-burn a maker's next
|
|
74
|
+
* nonce and grief their order flow (a liveness attack — the signature
|
|
75
|
+
* itself still can't be forged). `Math.random()` is not a CSPRNG and
|
|
76
|
+
* `Date.now()` is public knowledge; neither is acceptable here.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately 48 bits, NOT the full uint256: `PostOrderBody.nonce` is a
|
|
79
|
+
* JSON `number`, so the value must survive `Number(nonce)` losslessly —
|
|
80
|
+
* anything above 2^53 would silently round and the posted nonce would stop
|
|
81
|
+
* matching the signed one, failing signature recovery. Across 2^48 values the
|
|
82
|
+
* CUMULATIVE (birthday-bound) chance that ANY two of one maker's ~750k orders
|
|
83
|
+
* collide is ~1e-3 (~k²/2·2^48) — about one in a thousand, not vanishing; the
|
|
84
|
+
* ~1-in-10^9 figure is only the MARGINAL odds that a single new draw hits an
|
|
85
|
+
* existing nonce at that point. Either way a collision is a rejected order
|
|
86
|
+
* (retry with a fresh nonce), not a loss.
|
|
87
|
+
*
|
|
88
|
+
* Single-writer bots may prefer a monotonic counter (no birthday bound at
|
|
89
|
+
* all) — seed it from this rather than from the clock.
|
|
90
|
+
*/
|
|
91
|
+
export function freshNonce() {
|
|
92
|
+
const bytes = new Uint8Array(6);
|
|
93
|
+
const g = globalThis;
|
|
94
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") {
|
|
95
|
+
throw new Error("globalThis.crypto.getRandomValues unavailable; use Node 18+ or a modern browser");
|
|
96
|
+
}
|
|
97
|
+
g.crypto.getRandomValues(bytes);
|
|
98
|
+
let n = 0n;
|
|
99
|
+
for (let i = 0; i < bytes.length; i++)
|
|
100
|
+
n = (n << 8n) | BigInt(bytes[i]);
|
|
101
|
+
return n;
|
|
102
|
+
}
|
|
69
103
|
/**
|
|
70
104
|
* EIP-712 type schemas. Mirrors the backend's `EIP712_ORDER_TYPES` /
|
|
71
105
|
* `EIP712_CANCEL_TYPES` exactly — if these drift, signatures stop being
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { UpDownHttpClient, wsUrlFromHttpBase } from "./http.js";
|
|
2
2
|
export { UpDownWsClient, type UpDownWsMessage, type SubscribePayload, type WsAuthCredentials, type ConnectAuthedOptions, } from "./ws.js";
|
|
3
|
-
export { ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES, buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData, freshSessionId, domainForSettlement, findPairBySettlement, parseCompositeMarketKey, centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic, MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC, type OrderSignMessage, type CancelSignMessage, type WsAuthMessage, type ParsedComposite, } from "./eip712.js";
|
|
3
|
+
export { ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES, buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData, freshSessionId, freshNonce, domainForSettlement, findPairBySettlement, parseCompositeMarketKey, centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic, MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC, type OrderSignMessage, type CancelSignMessage, type WsAuthMessage, type ParsedComposite, } from "./eip712.js";
|
|
4
4
|
export { CLOB_AUTH_TYPES, CLOB_AUTH_MESSAGE, buildClobAuthTypedData, buildHmacSignature, HMAC_HEADERS, type ClobAuthDomain, type ClobAuthMessage, } from "./auth.js";
|
|
5
|
-
export { ensureSettlementAllowance, MAX_UINT256, type EnsureAllowanceResult, } from "./approve.js";
|
|
5
|
+
export { ensureSettlementAllowance, MAX_UINT256, DEFAULT_APPROVAL_AMOUNT, type EnsureAllowanceResult, } from "./approve.js";
|
|
6
|
+
export { readOnChainHolderShares, reconcileFills, reportedFromPositions, type OnChainHolderShares, type ShareReconciliation, type ReconcileStatus, type FillReconciliationReport, } from "./reconcile.js";
|
|
6
7
|
export { UpDownAccountKitSigner, bareErc1271Signer, stripErc6492Wrapper, isErc6492Signature, type UpDownAccountKitConfig, type Eip1193Provider, type GrantSessionResult, type RawTypedDataSigner, } from "./accountKit.js";
|
|
7
8
|
export { buildApproveSettlementTx, buildInstallOrderSessionTx, signWithOrderSession, type RawTransaction, type OrderSessionRecord, } from "./accountKit.js";
|
|
8
9
|
export { OrderType, OrderSide, Option, type ApiConfig, type Balance, type CancelOrderBody, type Eip712Domain, type MarketDetail, type MarketListItem, type OptionValue, type OrderBookFull, type OrderBookLevel, type OrderBookSide, type OrderRow, type OrderSideKey, type OrderSideValue, type OrderStatus, type OrderTypeKey, type OrderTypeValue, type OrdersResponse, type PairConfig, type PairSymbol, type PostOrderBody, type Position, type Stats, type Trade, type Version, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
export { UpDownHttpClient, wsUrlFromHttpBase } from "./http.js";
|
|
3
3
|
export { UpDownWsClient, } from "./ws.js";
|
|
4
4
|
// EIP-712 helpers
|
|
5
|
-
export { ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES, buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData, freshSessionId, domainForSettlement, findPairBySettlement, parseCompositeMarketKey, centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic, MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC, } from "./eip712.js";
|
|
5
|
+
export { ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES, buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData, freshSessionId, freshNonce, domainForSettlement, findPairBySettlement, parseCompositeMarketKey, centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic, MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC, } from "./eip712.js";
|
|
6
6
|
// Phase 3 / Gate 1 — L2 HMAC auth helpers
|
|
7
7
|
export { CLOB_AUTH_TYPES, CLOB_AUTH_MESSAGE, buildClobAuthTypedData, buildHmacSignature, HMAC_HEADERS, } from "./auth.js";
|
|
8
8
|
// Approve helper
|
|
9
|
-
export { ensureSettlementAllowance, MAX_UINT256, } from "./approve.js";
|
|
9
|
+
export { ensureSettlementAllowance, MAX_UINT256, DEFAULT_APPROVAL_AMOUNT, } from "./approve.js";
|
|
10
|
+
// S5 — on-chain fill reconciliation (independent detector for the B1/B6
|
|
11
|
+
// off-chain ledger bugs: diffs authoritative on-chain `userShares` against
|
|
12
|
+
// what the matcher reported).
|
|
13
|
+
export { readOnChainHolderShares, reconcileFills, reportedFromPositions, } from "./reconcile.js";
|
|
10
14
|
// Account Kit (Alchemy SCA) — owner-key ERC-1271 order signing + gasless custody.
|
|
11
15
|
// Peer deps (@account-kit/*, @aa-sdk/core) are lazy-imported; importing this
|
|
12
16
|
// module does NOT require them unless you construct/connect the signer.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S5 — on-chain fill reconciliation.
|
|
3
|
+
*
|
|
4
|
+
* The matcher reports an MM's positions off-chain (`GET /positions/:wallet`,
|
|
5
|
+
* `GET /markets/:address/holders`). On-chain, `UpDownSettlement.userShares`
|
|
6
|
+
* (a public mapping) is the AUTHORITATIVE record of what each holder actually
|
|
7
|
+
* owns — it is the number `redeemFor`/`redeem` pays against. A bot that trusts
|
|
8
|
+
* only the matcher cannot see the off-chain ledger-integrity failures in the
|
|
9
|
+
* pre-prod review (B1 collateral double-commit, B6 phantom positions): both
|
|
10
|
+
* manifest as the matcher REPORTING shares (or a fill) that on-chain state does
|
|
11
|
+
* not back. This helper closes that gap — it reads `userShares` directly and
|
|
12
|
+
* diffs it against what the matcher reported, so an MM has an independent
|
|
13
|
+
* detector rather than taking the API's word for its own book.
|
|
14
|
+
*
|
|
15
|
+
* `viem` is a peer dependency — pass the `PublicClient` you already have.
|
|
16
|
+
*/
|
|
17
|
+
import type { Address, PublicClient } from "viem";
|
|
18
|
+
import { type OptionValue } from "./types.js";
|
|
19
|
+
/** Authoritative on-chain shares for one holder in one market. */
|
|
20
|
+
export interface OnChainHolderShares {
|
|
21
|
+
marketId: bigint;
|
|
22
|
+
holder: Address;
|
|
23
|
+
/** `userShares[marketId][holder][UP]` (atomic). */
|
|
24
|
+
up: bigint;
|
|
25
|
+
/** `userShares[marketId][holder][DOWN]` (atomic). */
|
|
26
|
+
down: bigint;
|
|
27
|
+
resolved: boolean;
|
|
28
|
+
/** Winning option once resolved: `Option.UP`/`Option.DOWN`, or 0 if unresolved. */
|
|
29
|
+
winner: OptionValue | 0;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read a holder's authoritative on-chain shares (both options) and the market's
|
|
33
|
+
* resolution state in one batch of `readContract` calls.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readOnChainHolderShares(args: {
|
|
36
|
+
publicClient: PublicClient;
|
|
37
|
+
settlement: Address;
|
|
38
|
+
marketId: bigint;
|
|
39
|
+
holder: Address;
|
|
40
|
+
}): Promise<OnChainHolderShares>;
|
|
41
|
+
export type ReconcileStatus = "match" | "reported_over" | "reported_under";
|
|
42
|
+
/** Per-option reconciliation of matcher-reported vs on-chain shares. */
|
|
43
|
+
export interface ShareReconciliation {
|
|
44
|
+
option: OptionValue;
|
|
45
|
+
optionLabel: "UP" | "DOWN";
|
|
46
|
+
/** Authoritative on-chain `userShares`. */
|
|
47
|
+
onChain: bigint;
|
|
48
|
+
/** What the matcher reported off-chain. */
|
|
49
|
+
reported: bigint;
|
|
50
|
+
/** `reported - onChain`. Positive = matcher over-reports (phantom risk). */
|
|
51
|
+
drift: bigint;
|
|
52
|
+
status: ReconcileStatus;
|
|
53
|
+
}
|
|
54
|
+
export interface FillReconciliationReport {
|
|
55
|
+
marketId: bigint;
|
|
56
|
+
holder: Address;
|
|
57
|
+
resolved: boolean;
|
|
58
|
+
winner: OptionValue | 0;
|
|
59
|
+
lines: ShareReconciliation[];
|
|
60
|
+
/** True when every option reconciles exactly. */
|
|
61
|
+
ok: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* True when the matcher reports MORE shares than the chain backs on some
|
|
64
|
+
* option (`reported_over`) — the dangerous direction: a phantom position the
|
|
65
|
+
* MM would price against but cannot redeem. Post-redemption a winner-side
|
|
66
|
+
* `reported_over` is benign (the holder was already paid and `userShares`
|
|
67
|
+
* zeroed); check `resolved`/`winner` before alerting.
|
|
68
|
+
*/
|
|
69
|
+
hasPhantom: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reconcile matcher-reported net shares against on-chain `userShares` for one
|
|
73
|
+
* holder in one market. `reported` is the net shares the matcher attributes to
|
|
74
|
+
* the holder per option (atomic units), e.g. folded from `GET /positions/:wallet`
|
|
75
|
+
* — see {@link reportedFromPositions}.
|
|
76
|
+
*
|
|
77
|
+
* Returns a per-option diff plus `ok` / `hasPhantom` flags an MM can gate on.
|
|
78
|
+
* A healthy market reconciles exactly; a divergence is the on-chain signature of
|
|
79
|
+
* the B1/B6 ledger bugs (or of a redemption the off-chain ledger hasn't caught
|
|
80
|
+
* up to yet — hence the `resolved`/`winner` context).
|
|
81
|
+
*/
|
|
82
|
+
export declare function reconcileFills(args: {
|
|
83
|
+
publicClient: PublicClient;
|
|
84
|
+
settlement: Address;
|
|
85
|
+
marketId: bigint;
|
|
86
|
+
holder: Address;
|
|
87
|
+
reported: {
|
|
88
|
+
up: bigint;
|
|
89
|
+
down: bigint;
|
|
90
|
+
};
|
|
91
|
+
}): Promise<FillReconciliationReport>;
|
|
92
|
+
/**
|
|
93
|
+
* Fold a matcher `Position[]` list (from `GET /positions/:wallet`, already
|
|
94
|
+
* filtered to one market) into the `{ up, down }` atomic-share shape
|
|
95
|
+
* {@link reconcileFills} expects. Unknown option labels are ignored.
|
|
96
|
+
*/
|
|
97
|
+
export declare function reportedFromPositions(positions: ReadonlyArray<{
|
|
98
|
+
optionLabel: "UP" | "DOWN";
|
|
99
|
+
shares: string;
|
|
100
|
+
}>): {
|
|
101
|
+
up: bigint;
|
|
102
|
+
down: bigint;
|
|
103
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Option } from "./types.js";
|
|
2
|
+
/** Minimal read-only slice of the settlement ABI — no write surface. */
|
|
3
|
+
const SETTLEMENT_RECON_ABI = [
|
|
4
|
+
{
|
|
5
|
+
type: "function",
|
|
6
|
+
name: "userShares",
|
|
7
|
+
stateMutability: "view",
|
|
8
|
+
inputs: [
|
|
9
|
+
{ name: "marketId", type: "uint256" },
|
|
10
|
+
{ name: "holder", type: "address" },
|
|
11
|
+
{ name: "option", type: "uint8" },
|
|
12
|
+
],
|
|
13
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: "function",
|
|
17
|
+
name: "getMarket",
|
|
18
|
+
stateMutability: "view",
|
|
19
|
+
inputs: [{ name: "marketId", type: "uint256" }],
|
|
20
|
+
outputs: [
|
|
21
|
+
{
|
|
22
|
+
name: "",
|
|
23
|
+
type: "tuple",
|
|
24
|
+
components: [
|
|
25
|
+
{ name: "pairId", type: "bytes32" },
|
|
26
|
+
{ name: "cashUpFlow", type: "uint128" },
|
|
27
|
+
{ name: "cashDownFlow", type: "uint128" },
|
|
28
|
+
{ name: "startTime", type: "uint64" },
|
|
29
|
+
{ name: "endTime", type: "uint64" },
|
|
30
|
+
{ name: "duration", type: "uint32" },
|
|
31
|
+
{ name: "winner", type: "uint8" },
|
|
32
|
+
{ name: "resolved", type: "bool" },
|
|
33
|
+
{ name: "settled", type: "bool" },
|
|
34
|
+
{ name: "strikePrice", type: "int128" },
|
|
35
|
+
{ name: "settlementPrice", type: "int128" },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Read a holder's authoritative on-chain shares (both options) and the market's
|
|
43
|
+
* resolution state in one batch of `readContract` calls.
|
|
44
|
+
*/
|
|
45
|
+
export async function readOnChainHolderShares(args) {
|
|
46
|
+
const { publicClient, settlement, marketId, holder } = args;
|
|
47
|
+
const read = (option) => publicClient.readContract({
|
|
48
|
+
address: settlement,
|
|
49
|
+
abi: SETTLEMENT_RECON_ABI,
|
|
50
|
+
functionName: "userShares",
|
|
51
|
+
args: [marketId, holder, option],
|
|
52
|
+
});
|
|
53
|
+
const [up, down, market] = await Promise.all([
|
|
54
|
+
read(Option.UP),
|
|
55
|
+
read(Option.DOWN),
|
|
56
|
+
publicClient.readContract({
|
|
57
|
+
address: settlement,
|
|
58
|
+
abi: SETTLEMENT_RECON_ABI,
|
|
59
|
+
functionName: "getMarket",
|
|
60
|
+
args: [marketId],
|
|
61
|
+
}),
|
|
62
|
+
]);
|
|
63
|
+
const winnerNum = Number(market.winner);
|
|
64
|
+
const winner = winnerNum === Option.UP ? Option.UP : winnerNum === Option.DOWN ? Option.DOWN : 0;
|
|
65
|
+
return { marketId, holder, up, down, resolved: market.resolved, winner };
|
|
66
|
+
}
|
|
67
|
+
function classify(onChain, reported) {
|
|
68
|
+
if (reported === onChain)
|
|
69
|
+
return "match";
|
|
70
|
+
return reported > onChain ? "reported_over" : "reported_under";
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Reconcile matcher-reported net shares against on-chain `userShares` for one
|
|
74
|
+
* holder in one market. `reported` is the net shares the matcher attributes to
|
|
75
|
+
* the holder per option (atomic units), e.g. folded from `GET /positions/:wallet`
|
|
76
|
+
* — see {@link reportedFromPositions}.
|
|
77
|
+
*
|
|
78
|
+
* Returns a per-option diff plus `ok` / `hasPhantom` flags an MM can gate on.
|
|
79
|
+
* A healthy market reconciles exactly; a divergence is the on-chain signature of
|
|
80
|
+
* the B1/B6 ledger bugs (or of a redemption the off-chain ledger hasn't caught
|
|
81
|
+
* up to yet — hence the `resolved`/`winner` context).
|
|
82
|
+
*/
|
|
83
|
+
export async function reconcileFills(args) {
|
|
84
|
+
const chain = await readOnChainHolderShares(args);
|
|
85
|
+
const lines = [
|
|
86
|
+
{
|
|
87
|
+
option: Option.UP,
|
|
88
|
+
optionLabel: "UP",
|
|
89
|
+
onChain: chain.up,
|
|
90
|
+
reported: args.reported.up,
|
|
91
|
+
drift: args.reported.up - chain.up,
|
|
92
|
+
status: classify(chain.up, args.reported.up),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
option: Option.DOWN,
|
|
96
|
+
optionLabel: "DOWN",
|
|
97
|
+
onChain: chain.down,
|
|
98
|
+
reported: args.reported.down,
|
|
99
|
+
drift: args.reported.down - chain.down,
|
|
100
|
+
status: classify(chain.down, args.reported.down),
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
return {
|
|
104
|
+
marketId: args.marketId,
|
|
105
|
+
holder: args.holder,
|
|
106
|
+
resolved: chain.resolved,
|
|
107
|
+
winner: chain.winner,
|
|
108
|
+
lines,
|
|
109
|
+
ok: lines.every((l) => l.status === "match"),
|
|
110
|
+
hasPhantom: lines.some((l) => l.status === "reported_over"),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Fold a matcher `Position[]` list (from `GET /positions/:wallet`, already
|
|
115
|
+
* filtered to one market) into the `{ up, down }` atomic-share shape
|
|
116
|
+
* {@link reconcileFills} expects. Unknown option labels are ignored.
|
|
117
|
+
*/
|
|
118
|
+
export function reportedFromPositions(positions) {
|
|
119
|
+
let up = 0n;
|
|
120
|
+
let down = 0n;
|
|
121
|
+
for (const p of positions) {
|
|
122
|
+
const s = BigInt(p.shares);
|
|
123
|
+
if (p.optionLabel === "UP")
|
|
124
|
+
up += s;
|
|
125
|
+
else if (p.optionLabel === "DOWN")
|
|
126
|
+
down += s;
|
|
127
|
+
}
|
|
128
|
+
return { up, down };
|
|
129
|
+
}
|
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.4.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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
23
23
|
"build": "npm run clean && tsc",
|
|
24
|
-
"test": "node scripts/eip712-golden.test.mjs && node scripts/rawtx-tier.test.mjs",
|
|
24
|
+
"test": "node scripts/eip712-golden.test.mjs && node scripts/rawtx-tier.test.mjs && node scripts/hot-key-safety.test.mjs && node scripts/reconcile.test.mjs",
|
|
25
25
|
"prepublishOnly": "npm run build && npm test",
|
|
26
26
|
"example:taker": "npx tsx examples/simple-taker.ts",
|
|
27
27
|
"example:maker": "npx tsx examples/simple-maker.ts",
|