@pulsepairs/sdk 0.2.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 ADDED
@@ -0,0 +1,264 @@
1
+ # @pulsepairs/sdk
2
+
3
+ Standalone SDK for the **UpDown** (PulsePairs) up/down prediction markets —
4
+ BTC-USD / ETH-USD, UP or DOWN, on 5-minute / 15-minute / 1-hour cycles.
5
+
6
+ This is the package [rain.trade](https://rain.trade) integrates to surface
7
+ UpDown markets alongside Rain's own markets ("satellite" integration, Option A).
8
+ It is deliberately **segregated** from `rain-sdk-v2`: bump this package's version
9
+ independently, no release coupling.
10
+
11
+ It gives you three things:
12
+
13
+ 1. **Matcher client** — typed REST + WebSocket client for the off-chain
14
+ order book (`UpDownHttpClient`, `UpDownWsClient`).
15
+ 2. **EIP-712 + trade-math** — `buildOrderTypedData` / `buildCancelTypedData` /
16
+ `buildWsAuthTypedData`, plus fee/stake/price helpers that mirror the backend
17
+ and contract exactly.
18
+ 3. **Account Kit signing** — `UpDownAccountKitSigner`: sign orders with an
19
+ Alchemy smart-contract account (the SAME wallet rain.trade uses), plus
20
+ gasless onboarding / approve / withdraw.
21
+
22
+ > **Live integration environment:** a public demo of the full stack (markets
23
+ > cycling 24/7 on Arbitrum One, mock oracle, mintable test USDT) runs at
24
+ > **https://demo-pulsepairs.rainwins.com** with the matcher API/WS at
25
+ > **`https://api.demo-pulsepairs.rainwins.com`** (`wss://…/stream`). Every
26
+ > example below targets it by default. Test funds: `POST /test/devmint`
27
+ > (10k cap, 1 mint per address per 5 min — mint to the **smart-account**
28
+ > address, not the owner EOA; it also seeds a little gas ETH).
29
+
30
+ ---
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ npm install @pulsepairs/sdk viem
36
+ # For Account Kit (smart-account) signing, also install the peer deps rain.trade
37
+ # already ships (skip if you only use the EOA / private-key path):
38
+ npm install @account-kit/wallet-client@^4.88 @account-kit/infra@^4.88 \
39
+ @account-kit/smart-contracts@^4.88 @aa-sdk/core@^4.88
40
+ ```
41
+
42
+ `viem` is a required peer. The four `@account-kit/*`/`@aa-sdk/*` packages are
43
+ **optional** peers — they are lazy-imported and only needed if you construct
44
+ `UpDownAccountKitSigner` (`smart-contracts` specifically backs the popup-less
45
+ order-session signing below). A pure-EOA bot never pulls any of them.
46
+
47
+ ---
48
+
49
+ ## Two signing paths
50
+
51
+ The matcher and contract are signer-agnostic (on-chain verification is OZ
52
+ `SignatureChecker`, which accepts EOAs **and** ERC-1271 smart accounts). Pick:
53
+
54
+ | Path | `order.maker` | Sign with | Use for |
55
+ |---|---|---|---|
56
+ | **EOA** (`examples/simple-*`) | your EOA | `viem` `account.signTypedData` | bots, scripts, testing |
57
+ | **Account Kit** (`examples/account-kit-taker.ts`) | your **SCA** | `UpDownAccountKitSigner` | rain.trade / any smart-wallet UX |
58
+
59
+ ---
60
+
61
+ ## Quickstart — EOA (bots)
62
+
63
+ ```ts
64
+ import { createPublicClient, createWalletClient, http } from "viem";
65
+ import { privateKeyToAccount } from "viem/accounts";
66
+ import { arbitrum } from "viem/chains";
67
+ import {
68
+ UpDownHttpClient, buildOrderTypedData, ensureSettlementAllowance,
69
+ parseCompositeMarketKey, parseStake, OrderType, OrderSide, Option,
70
+ } from "@pulsepairs/sdk";
71
+
72
+ const api = new UpDownHttpClient("https://api.demo-pulsepairs.rainwins.com");
73
+ const cfg = await api.getConfig(); // never hardcode addresses
74
+ const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
75
+ const [live] = (await api.getMarkets({ pair: "BTC-USD", timeframe: 300 }))
76
+ .filter((m) => m.status === "ACTIVE");
77
+ const { settlementAddress, marketId } = parseCompositeMarketKey(live.address)!;
78
+
79
+ const amount = parseStake("5");
80
+ const maxFee = (amount * BigInt(cfg.platformFeeBps + cfg.makerFeeBps)) / 10000n; // peak fee
81
+ const nonce = BigInt(Math.floor(Math.random() * 1e12));
82
+ const typedData = buildOrderTypedData({
83
+ cfg, settlementAddress,
84
+ message: { maker: account.address, market: BigInt(marketId), option: BigInt(Option.UP),
85
+ side: OrderSide.BUY, type: OrderType.MARKET, price: 0n, amount, maxFee, nonce,
86
+ expiry: BigInt(live.endTime) },
87
+ });
88
+ const signature = await account.signTypedData(typedData);
89
+ await api.postOrder({ maker: account.address, market: live.address, option: Option.UP,
90
+ side: OrderSide.BUY, type: OrderType.MARKET, price: 0, amount: amount.toString(),
91
+ maxFee: maxFee.toString(), nonce: Number(nonce), expiry: live.endTime, signature });
92
+ ```
93
+
94
+ Full runnable scripts: `examples/simple-taker.ts` (MARKET), `examples/simple-maker.ts`
95
+ (LIMIT + authed WS), `examples/full-dmm-bot.ts` (two-sided quoting).
96
+
97
+ ---
98
+
99
+ ## Quickstart — Account Kit (rain.trade / smart wallets)
100
+
101
+ `UpDownAccountKitSigner` builds its own Alchemy smart-wallet client using the
102
+ **same config rain.trade's `RainAA` uses**, so the same owner EOA resolves to
103
+ the **same SCA address** → one wallet across both products.
104
+
105
+ ```ts
106
+ import { arbitrum } from "viem/chains";
107
+ import { UpDownAccountKitSigner, buildOrderTypedData, UpDownHttpClient } from "@pulsepairs/sdk";
108
+
109
+ const ak = new UpDownAccountKitSigner({
110
+ walletClient: window.ethereum, // owner EOA provider (or connector.getProvider())
111
+ alchemyApiKey: import.meta.env.VITE_ALCHEMY_API_KEY, // SAME as rain.trade
112
+ paymasterPolicyId: import.meta.env.VITE_PAYMASTER_POLICY_ID, // SAME as rain.trade
113
+ chain: arbitrum, // SAME as rain.trade (42161)
114
+ });
115
+ const sca = await ak.connect(); // deterministic; == rain.trade's SCA
116
+
117
+ // Deploy-before-fill: ONE UserOp deploys the SCA + approves USDT + installs
118
+ // the popup-less order-session key (see "Popup-less order signing" below).
119
+ await ak.onboard({ usdt: cfg.usdtAddress, settlement });
120
+
121
+ // Owner-key ERC-1271 order signature (6492 wrapper stripped automatically).
122
+ const signature = await ak.signTypedDataBare(buildOrderTypedData({ cfg, settlementAddress, message }));
123
+
124
+ // WS auth for orders:/balance: channels (keyed by the SCA):
125
+ ws.connectAuthed({ channels: [`orders:${sca.toLowerCase()}`], signAuth: () => ak.signWsAuth(cfg.chainId) });
126
+ ```
127
+
128
+ See `examples/account-kit-taker.ts` for the end-to-end flow.
129
+
130
+ ### Already have rain.trade's smart-wallet client?
131
+
132
+ If the host app owns an Alchemy client (e.g. rain.trade's shared session), skip
133
+ `UpDownAccountKitSigner` and just make its output UpDown-correct:
134
+
135
+ ```ts
136
+ import { bareErc1271Signer } from "@pulsepairs/sdk";
137
+ const signOrder = bareErc1271Signer((td) => rainSmartWalletClient.signTypedData(td));
138
+ const signature = await signOrder(buildOrderTypedData({ cfg, settlementAddress, message }));
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Account Kit: the two rules you cannot break
144
+
145
+ Both come from the on-chain verifier (OZ `SignatureChecker` v5.6.1, no EIP-6492):
146
+
147
+ 1. **Deploy the SCA before its first fill.** A counterfactual (undeployed)
148
+ account cannot be verified via ERC-1271 → the fill reverts. `onboard()` does
149
+ this (deploy + approve in one UserOp); `isDeployed(publicClient)` checks it.
150
+ 2. **Bare ERC-1271, not 6492.** `signTypedData` on an undeployed account can
151
+ return a 6492-wrapped blob the settlement can't parse. Every signature this
152
+ SDK produces is run through `stripErc6492Wrapper(...)`, so a wrapped blob
153
+ never reaches the matcher. (Exposed standalone too, for the callback path.)
154
+
155
+ > **Confirmed live on Arbitrum One (2026-07-05/06).** A real SCA was deployed,
156
+ > `isValidSignature` over the full Order typed-data returned the `0x1626ba7e`
157
+ > magic on-chain, a real `enterPosition` fill settled with `maker = SCA`, and
158
+ > the negative control (undeployed SCA) reverted as expected. The MA-v2 /
159
+ > ERC-7739 question is resolved: sign the FULL typed-data through the account
160
+ > client (which this SDK does); a bare-`bytes32` flow does not validate.
161
+
162
+ ---
163
+
164
+ ## Popup-less order signing (order sessions) — ON by default
165
+
166
+ `UpDownAccountKitSigner` distinguishes **two different session-key mechanisms**
167
+ — do not conflate them:
168
+
169
+ 1. **Send session** (`grantSession()`, Alchemy `grantPermissions` root) —
170
+ authorizes gasless UserOp *execution* (`onboard`/`approve`/`withdraw`).
171
+ Its signatures pack the OWNER validation entity, so it is verified
172
+ **incapable** of ERC-1271 order signing.
173
+ 2. **Order session** (default ON) — a locally-generated key installed on the
174
+ MA-v2 SCA as a **signature-validation-ONLY** entity
175
+ (`isSignatureValidation: true`, `isUserOpValidation: false`). It can only
176
+ answer `isValidSignature` for orders / cancels / WS-auth — it can **never
177
+ move funds or execute a UserOp**. `onboard()` batches the install into the
178
+ same single UserOp as deploy + approve (still exactly one owner signature),
179
+ after which orders sign with zero wallet popups.
180
+
181
+ Behavior and knobs:
182
+
183
+ - `signTypedDataBare` prefers the order-session key when one is active and
184
+ **falls back to the owner key automatically** on any failure — enabling this
185
+ feature can never break signing.
186
+ - `ensureOrderSession()` installs a session for already-onboarded accounts
187
+ (one owner-signed UserOp); `revokeOrderSession()` drops the local key;
188
+ `hasOrderSession` inspects state.
189
+ - Opt out entirely with `orderSessions: false` in the constructor — every
190
+ order then prompts the owner wallet (the pre-session behavior).
191
+ - Storage: browser `localStorage` (`updown:oskey:<sca>`) by default, an
192
+ in-memory map in Node, or bring your own via `orderSessionStorage`.
193
+ - **Known limitations** (fine for integration/demo, review before launch):
194
+ expiry is client-side only (24 h — not enforced on-chain), and
195
+ `revokeOrderSession()` does not uninstall the validation entity on-chain.
196
+
197
+ Validated in 4 live PoC stages before shipping (on-chain `0x1626ba7e` +
198
+ negative controls, live matcher order 201 + cancel 200, batched-install flow).
199
+
200
+ ---
201
+
202
+ ## `maxFee` (F-2026-17731) — REQUIRED on every order
203
+
204
+ The signed `Order` includes `maxFee` (between `amount` and `nonce`). The
205
+ contract's `ORDER_TYPEHASH` includes it, so **omitting it produces a signature
206
+ the on-chain `SignatureChecker` rejects** — the fill reverts. Compute the peak
207
+ fee and pass the same value into both the typed-data and the POST body:
208
+
209
+ ```ts
210
+ const maxFee = (amount * BigInt(cfg.platformFeeBps + cfg.makerFeeBps)) / 10000n;
211
+ ```
212
+
213
+ Signing the peak means legitimate (probability-weighted) fills never revert with
214
+ `FeeExceedsTakerCap`; the contract enforces the *actual* cumulative fee under
215
+ this cap across partial fills.
216
+
217
+ ---
218
+
219
+ ## API surface
220
+
221
+ ```
222
+ Clients UpDownHttpClient, UpDownWsClient, wsUrlFromHttpBase
223
+ EIP-712 ORDER_TYPES, CANCEL_TYPES, WS_AUTH_TYPES,
224
+ buildOrderTypedData, buildCancelTypedData, buildWsAuthTypedData,
225
+ freshSessionId, domainForSettlement, findPairBySettlement,
226
+ parseCompositeMarketKey
227
+ Trade-math centsToBps, bpsToCents, parseStake, assertStakeBounds, feeAtomic,
228
+ MIN_STAKE_ATOMIC, MAX_STAKE_ATOMIC
229
+ Approve (EOA) ensureSettlementAllowance, MAX_UINT256
230
+ Account Kit UpDownAccountKitSigner (connect, onboard, approve, withdraw,
231
+ signTypedDataBare, signWsAuth, isDeployed, grantSession,
232
+ ensureOrderSession, revokeOrderSession, hasOrderSession),
233
+ bareErc1271Signer, stripErc6492Wrapper, isErc6492Signature
234
+ L2 HMAC auth CLOB_AUTH_TYPES, buildClobAuthTypedData, buildHmacSignature, HMAC_HEADERS
235
+ Enums/types OrderType, OrderSide, Option, ApiConfig, PairConfig, PostOrderBody, …
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Field-name gotchas (live with them)
241
+
242
+ - `OrderRow.nonce` / `OrderRow.expiry` are JSON **strings** (uint256 precision).
243
+ - WS `order_update` uses `orderType`; REST `OrderRow` uses `type`. Same value.
244
+ - `Option` is `1 = UP`, `2 = DOWN` — not `0/1`.
245
+ - Market addresses are composite `<settlementAddress>-<marketId>`. The signed
246
+ `Order.market` is the **bare uint256 marketId**; the composite goes in the
247
+ POST body's `market` field for routing only.
248
+ - EIP-712 domain name is **`"UpDown Exchange"`**, `verifyingContract` = the
249
+ settlement contract, price in **bps** (not 1e18). WS-auth uses a distinct
250
+ domain (`"PulsePairs WebSocket Auth"`, `verifyingContract` = zero) so an order
251
+ signature can never replay as a WS-auth signature.
252
+
253
+ ---
254
+
255
+ ## Reference
256
+
257
+ - **Wire shapes / REST / WS**: `docs/api.md` in the `updown-backend` repo
258
+ (branch `audit/hacken-remediation-v2`) — auth model, WS handshake, all
259
+ endpoints, error strings, rate limits.
260
+ - **On-chain spec**: `src/UpDownSettlement.sol` in the `updown-contracts` repo
261
+ (same branch) — `ORDER_TYPEHASH`, `enterPosition`, fee cap enforcement.
262
+ - **Schema drift guard**: `npm test` runs an EIP-712 golden-vector test whose
263
+ expected digest/signature were derived independently from the backend and
264
+ contract definitions.
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Account Kit (Alchemy smart-account) signing for the UpDown satellite.
3
+ *
4
+ * ─── Why this exists ─────────────────────────────────────────────────────
5
+ * rain.trade custody is Alchemy Account Kit (a smart-contract account, "SCA").
6
+ * The UpDown settlement verifies order signatures on-chain with OpenZeppelin
7
+ * `SignatureChecker.isValidSignatureNow(maker, digest, sig)` (OZ 5.6.1). When
8
+ * `maker` is an SCA, that routes to `IERC1271(maker).isValidSignature(...)`.
9
+ *
10
+ * To make one wallet span BOTH products, this module builds its own Alchemy
11
+ * smart-wallet client using the EXACT same config rain.trade's `RainAA` uses
12
+ * (same `chain` + `alchemyApiKey` + `paymasterPolicyId` + owner EOA). Account
13
+ * Kit derives the SCA address deterministically from (owner, factory), so the
14
+ * same owner + same config ⇒ the SAME account address ⇒ one shared wallet.
15
+ * We deliberately do NOT import `RainAA` — it is a published, unpatchable
16
+ * package that exposes no `signTypedData`, and the meeting locked UpDown as a
17
+ * *segregated* package. We mirror its config, not its code.
18
+ *
19
+ * ─── The two hard constraints (see UPDOWN_SDK_MEETING_PREP_OPTION_A) ─────
20
+ * 1. BARE ERC-1271, not EIP-6492. OZ `SignatureChecker` v5.6.1 is NOT
21
+ * 6492-aware. `signTypedData` on an *undeployed* SCA can return a
22
+ * 6492-wrapped blob that the settlement cannot parse → `enterPosition`
23
+ * reverts. We therefore (a) enforce deploy-before-fill and (b) defensively
24
+ * `stripErc6492Wrapper(...)` every signature so a wrapped blob never
25
+ * reaches the matcher.
26
+ * 2. Two DIFFERENT session-key mechanisms — don't conflate them:
27
+ * a. RainAA-style "send session" (`grantSession`, Alchemy
28
+ * `grantPermissions({type:'root'})`): authorizes UserOp EXECUTION only.
29
+ * Its signatures pack the OWNER validation entity, so it is verified
30
+ * INCAPABLE of ERC-1271 order signing (isValidSignature → 0xffffffff;
31
+ * see updown-demo/POC_SESSION_KEY_ORDERS_2026-07-06.md). Used only for
32
+ * gasless *sends* (onboard / approve / withdraw), mirroring RainAA.
33
+ * b. "ORDER session" (2026-07-06, PoC-validated on-chain + live API): a
34
+ * locally-generated key installed on the MA-v2 SCA as a
35
+ * SingleSignerValidationModule entity with isSignatureValidation=true
36
+ * and isUserOpValidation=false — it can ONLY answer `isValidSignature`
37
+ * (orders / cancels / ws-auth, popup-less) and can never execute a
38
+ * UserOp. Installed inside the onboarding UserOp (one owner popup
39
+ * total) or via `ensureOrderSession()`. Orders sign with it when
40
+ * active; the OWNER client is the automatic fallback. Client-side 24h
41
+ * expiry only (on-chain expiry needs a time-range hook — follow-up);
42
+ * `revokeOrderSession()` drops the local key.
43
+ *
44
+ * ─── MUST be confirmed with a fork test before trusting on mainnet ───────
45
+ * deploy a real Alchemy SCA on the target chain → `approve(settlement,USDT)`
46
+ * → sign a real `Order` via `signTypedDataBare(...)` → call `enterPosition`
47
+ * as the relayer → assert NO revert. Negative control: an *undeployed* SCA
48
+ * and/or a 6492-wrapped signature MUST revert. This resolves the open
49
+ * Light-Account-v2-vs-Modular-Account-v2 (ERC-7739) question: for MA v2 only
50
+ * the account-level `signTypedData` over the FULL typed-data validates 1271
51
+ * (handing the SCA a bare `bytes32` silently fails). `signTypedDataBare`
52
+ * below signs the full typed-data, which is the correct path for both.
53
+ */
54
+ import { type Address, type Chain, type Hex, type PublicClient, type TypedDataDefinition } from "viem";
55
+ import type { WsAuthCredentials } from "./ws.js";
56
+ /** True if `signature` is an EIP-6492-wrapped blob (ends with the magic suffix). */
57
+ export declare function isErc6492Signature(signature: Hex): boolean;
58
+ /**
59
+ * Return the BARE inner signature from an EIP-6492-wrapped blob, or the input
60
+ * unchanged if it is not wrapped. The 6492 layout is
61
+ * `abi.encode(address factory, bytes factoryCalldata, bytes signature)` with
62
+ * the 32-byte magic suffix appended. Once the SCA is deployed the bare inner
63
+ * signature is exactly what OZ `SignatureChecker` needs.
64
+ */
65
+ export declare function stripErc6492Wrapper(signature: Hex): Hex;
66
+ export type RawTypedDataSigner = (typedData: TypedDataDefinition) => Promise<Hex>;
67
+ /**
68
+ * Wrap ANY raw typed-data signer so its output is a BARE ERC-1271 signature
69
+ * the UpDown settlement can verify. Use this when the host app already owns an
70
+ * Alchemy smart-wallet client (e.g. rain.trade's shared session) and just
71
+ * wants UpDown-correct order signatures without a second signer:
72
+ *
73
+ * const signOrder = bareErc1271Signer((td) => rainSmartWalletClient.signTypedData(td));
74
+ * const sig = await signOrder(buildOrderTypedData({ cfg, settlementAddress, message }));
75
+ *
76
+ * The SCA MUST be deployed on-chain before the signature is used in a fill.
77
+ */
78
+ export declare function bareErc1271Signer(raw: RawTypedDataSigner): RawTypedDataSigner;
79
+ /** EIP-1193 provider for the OWNER EOA (e.g. `window.ethereum`, or the
80
+ * provider from a wagmi connector via `connector.getProvider()`). */
81
+ export type Eip1193Provider = {
82
+ request: (args: {
83
+ method: string;
84
+ params?: unknown[] | object;
85
+ }) => Promise<unknown>;
86
+ };
87
+ export type UpDownAccountKitConfig = {
88
+ /** Provider for the owner EOA. Signs the owner ERC-1271 order signatures and
89
+ * the one-time `grantSession` permission. */
90
+ walletClient: Eip1193Provider;
91
+ /** Alchemy API key (same one rain.trade uses — `VITE_ALCHEMY_API_KEY`). */
92
+ alchemyApiKey: string;
93
+ /**
94
+ * Alchemy Gas Manager policy id (`VITE_PAYMASTER_POLICY_ID`).
95
+ * - **Sponsorship-type** policy: pass the id alone — sends are app-sponsored.
96
+ * - **ERC-20-type** policy: also pass `gasToken` (the token the USER pays gas
97
+ * in; must be on the policy's allowlist and held by the SCA).
98
+ * - **Omitted**: UserOps are SELF-PAID — the SCA must hold ETH for gas.
99
+ */
100
+ paymasterPolicyId?: string;
101
+ /** Gas token for an ERC-20-type Gas Manager policy (e.g. USDC). Ignored when
102
+ * `paymasterPolicyId` is unset. */
103
+ gasToken?: {
104
+ tokenAddress: Address;
105
+ };
106
+ /** viem `Chain`. MUST equal rain.trade's RainAA chain (Arbitrum One, 42161)
107
+ * to derive the SAME SCA address and share one wallet. */
108
+ chain: Chain;
109
+ /** Optional node RPC override; defaults to Alchemy's RPC for `chain`. */
110
+ rpcUrl?: string;
111
+ /** Popup-less ORDER-session signing (header §2b). Default true; set false to
112
+ * force owner-key signing per order (the pre-2026-07-06 behavior). */
113
+ orderSessions?: boolean;
114
+ /** Where the order-session key persists. Defaults to `globalThis.localStorage`
115
+ * when available (browser), else an in-memory map (Node — session lives for
116
+ * the process). Keys are namespaced `updown:oskey:<sca>`. */
117
+ orderSessionStorage?: {
118
+ getItem: (k: string) => string | null;
119
+ setItem: (k: string, v: string) => void;
120
+ removeItem: (k: string) => void;
121
+ };
122
+ };
123
+ export type GrantSessionResult = {
124
+ privateKey: Hex;
125
+ sessionKeyAddress: Address;
126
+ context: Hex;
127
+ expirySec: number;
128
+ };
129
+ /**
130
+ * Owner-key ERC-1271 order signer + gasless custody sends on an Alchemy SCA.
131
+ *
132
+ * Lifecycle:
133
+ * const ak = new UpDownAccountKitSigner({ walletClient, alchemyApiKey, paymasterPolicyId, chain });
134
+ * const sca = await ak.connect(); // SCA address (order.maker)
135
+ * await ak.onboard({ usdt, settlement }); // deploy SCA + approve USDT (one UserOp)
136
+ * const sig = await ak.signTypedDataBare(orderTypedData); // owner ERC-1271, bare
137
+ *
138
+ * All Account Kit packages are lazy-imported peer deps (mirrors RainAA), so
139
+ * importing this module never pulls `@account-kit/*` unless you call `connect`.
140
+ */
141
+ export declare class UpDownAccountKitSigner {
142
+ private readonly config;
143
+ private _client;
144
+ private _sessionClient;
145
+ private _account;
146
+ private _address;
147
+ private _ownerEoa;
148
+ private _sessionContext;
149
+ private _sessionKeyAddress;
150
+ private _sessionPrivateKey;
151
+ private _sessionExpirySec;
152
+ private _orderSession;
153
+ private _orderSessionClient;
154
+ private _mods;
155
+ constructor(config: UpDownAccountKitConfig);
156
+ /**
157
+ * Create the owner smart-wallet client and resolve the SCA address. Does NOT
158
+ * prompt for a session — call `grantSession()` (or `onboard`, which grants
159
+ * as needed) before gasless sends. Order signing works immediately (owner key).
160
+ */
161
+ connect(): Promise<Address>;
162
+ /** The SCA address — this is `order.maker`, the deposit address, and the
163
+ * ERC-1271 signer. Identical to rain.trade's SCA for the same owner. */
164
+ get address(): Address;
165
+ /** Owner EOA address (signs the ERC-1271 order signatures under the hood). */
166
+ get ownerEoa(): Address;
167
+ /** The owner smart-wallet client. Exposes `signTypedData`, `sendCalls`, etc. */
168
+ get ownerClient(): any;
169
+ get hasActiveSession(): boolean;
170
+ /**
171
+ * EIP-712 typed data as a BARE ERC-1271 signature — the ONLY signing path
172
+ * for orders, cancels, and ws-auth. ORDER-session-signed (popup-less) when
173
+ * an order session is active (header §2b); owner-signed with the 6492
174
+ * wrapper stripped otherwise. Requires `connect()`; the SCA should be
175
+ * deployed (`onboard`) before the signature is used in a fill.
176
+ */
177
+ signTypedDataBare(typedData: TypedDataDefinition): Promise<Hex>;
178
+ private get orderSessionsEnabled();
179
+ private get orderSessionStore();
180
+ /** True iff an unexpired order session exists for the connected SCA. */
181
+ get hasOrderSession(): boolean;
182
+ private readOrderSession;
183
+ private persistOrderSession;
184
+ /** Drop the local order-session key (no on-chain uninstall — follow-up). */
185
+ revokeOrderSession(): void;
186
+ /**
187
+ * Sign typed data with the order-session key (MA-v2 entity signature, bare
188
+ * by construction). Returns null when no session is active or anything
189
+ * fails — `signTypedDataBare` falls back to the owner path.
190
+ */
191
+ private orderSessionSign;
192
+ /** Lazily build the MA-v2 client bound to the order-session key's entity. */
193
+ private orderSessionAccount;
194
+ /**
195
+ * Make sure an order session exists: no-op when one is active (or the
196
+ * feature is off), otherwise install a fresh session key via a one-time
197
+ * owner UserOp. Fresh users get the install batched into `onboard()` and
198
+ * never hit the UserOp here. Throws if the owner rejects; callers should
199
+ * treat that as non-fatal (owner-key signing keeps working).
200
+ */
201
+ ensureOrderSession(): Promise<"disabled" | "active" | "installed">;
202
+ /**
203
+ * Build the `installValidation` self-call that registers a fresh session
204
+ * key on the SCA as a signature-validation-ONLY entity (validated flow:
205
+ * updown-frontend/scripts/poc-session-key-fe-flow.mjs).
206
+ */
207
+ private buildOrderSessionInstallCall;
208
+ /** The @account-kit/infra chain (Alchemy RPC config baked in) for MA-v2 clients. */
209
+ private infraChain;
210
+ /**
211
+ * Build + owner-sign a WS-auth handshake and package it as `WsAuthCredentials`
212
+ * for `UpDownWsClient.connectAuthed({ signAuth })`. `wallet` is the SCA
213
+ * address (private channels are keyed by the SCA, the trading identity).
214
+ */
215
+ signWsAuth(chainId: number): Promise<WsAuthCredentials>;
216
+ /** True iff the SCA has bytecode on-chain (deploy-before-fill precondition). */
217
+ isDeployed(publicClient: PublicClient): Promise<boolean>;
218
+ /**
219
+ * Grant a 24h session key (one MetaMask popup) so subsequent sends are
220
+ * gasless + popup-less. Mirrors RainAA's `grantSession`; safe to call when a
221
+ * session is already active (returns the existing one without prompting).
222
+ */
223
+ grantSession(): Promise<GrantSessionResult>;
224
+ /**
225
+ * One-time onboarding: DEPLOY the SCA, `approve(settlement, USDT, MAX)` AND
226
+ * install the order-session key (header §2b), all in a single UserOp —
227
+ * still exactly one owner signature. Satisfies deploy-before-fill + the
228
+ * allowance `enterPosition` needs, and makes subsequent order signatures
229
+ * popup-less. Order-session prep failures degrade to plain deploy+approve
230
+ * (owner-key signing per order). Uses the owner client so it works before
231
+ * any session exists; deployment is carried by the account init-code.
232
+ */
233
+ onboard(args: {
234
+ usdt: Address;
235
+ settlement: Address;
236
+ }): Promise<Hex>;
237
+ /** Idempotent USDT approve to the settlement (gasless UserOp). */
238
+ approve(args: {
239
+ usdt: Address;
240
+ settlement: Address;
241
+ }): Promise<Hex>;
242
+ /** Transfer USDT out of the SCA to `to` (gasless UserOp). */
243
+ withdraw(args: {
244
+ usdt: Address;
245
+ to: Address;
246
+ amount: bigint;
247
+ }): Promise<Hex>;
248
+ /**
249
+ * Send a single call from the SCA as a UserOp. Gas is paid per the config:
250
+ * sponsorship policy → app-sponsored; ERC-20 policy + `gasToken` → user pays
251
+ * in that token; no policy → SELF-PAID (SCA must hold ETH). Prefers the
252
+ * session client (no popup) when a session is active; otherwise falls back
253
+ * to the owner client (one signature). Returns the on-chain tx hash.
254
+ */
255
+ sendCall(call: {
256
+ to: Address;
257
+ data: Hex;
258
+ value?: bigint;
259
+ }): Promise<Hex>;
260
+ /** Send one UserOp batching `calls` in order (same gas/session semantics as
261
+ * `sendCall`); returns the on-chain tx hash. */
262
+ private sendCalls;
263
+ /** Clear in-memory state (persisted sessions, if any, are left intact). */
264
+ disconnect(): void;
265
+ }