@lightconexyz/lightcone-sdk 0.8.1-rc.8 → 0.8.2

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.
Files changed (187) hide show
  1. package/README.md +198 -10
  2. package/dist/auth/client.d.ts +27 -1
  3. package/dist/auth/client.d.ts.map +1 -1
  4. package/dist/auth/client.js +94 -4
  5. package/dist/auth/client.js.map +1 -1
  6. package/dist/auth/index.d.ts +59 -5
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +31 -5
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/client.d.ts +49 -3
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +70 -2
  13. package/dist/client.js.map +1 -1
  14. package/dist/context.d.ts +59 -3
  15. package/dist/context.d.ts.map +1 -1
  16. package/dist/context.js +303 -6
  17. package/dist/context.js.map +1 -1
  18. package/dist/domain/market/client.d.ts +16 -0
  19. package/dist/domain/market/client.d.ts.map +1 -1
  20. package/dist/domain/market/client.js +33 -0
  21. package/dist/domain/market/client.js.map +1 -1
  22. package/dist/domain/market/convert.d.ts.map +1 -1
  23. package/dist/domain/market/convert.js +24 -10
  24. package/dist/domain/market/convert.js.map +1 -1
  25. package/dist/domain/market/index.d.ts +18 -4
  26. package/dist/domain/market/index.d.ts.map +1 -1
  27. package/dist/domain/market/index.js.map +1 -1
  28. package/dist/domain/market/outcome.d.ts +9 -3
  29. package/dist/domain/market/outcome.d.ts.map +1 -1
  30. package/dist/domain/market/outcome.js +10 -7
  31. package/dist/domain/market/outcome.js.map +1 -1
  32. package/dist/domain/market/tokens.js +8 -2
  33. package/dist/domain/market/tokens.js.map +1 -1
  34. package/dist/domain/market/wire.d.ts +20 -3
  35. package/dist/domain/market/wire.d.ts.map +1 -1
  36. package/dist/domain/market/wire.js.map +1 -1
  37. package/dist/domain/order/client.d.ts +8 -7
  38. package/dist/domain/order/client.d.ts.map +1 -1
  39. package/dist/domain/order/client.js +23 -8
  40. package/dist/domain/order/client.js.map +1 -1
  41. package/dist/domain/orderbook/client.d.ts +7 -1
  42. package/dist/domain/orderbook/client.d.ts.map +1 -1
  43. package/dist/domain/orderbook/client.js +54 -1
  44. package/dist/domain/orderbook/client.js.map +1 -1
  45. package/dist/domain/orderbook/index.d.ts +7 -9
  46. package/dist/domain/orderbook/index.d.ts.map +1 -1
  47. package/dist/domain/orderbook/index.js +28 -24
  48. package/dist/domain/orderbook/index.js.map +1 -1
  49. package/dist/domain/orderbook/state.d.ts +21 -16
  50. package/dist/domain/orderbook/state.d.ts.map +1 -1
  51. package/dist/domain/orderbook/state.js +36 -15
  52. package/dist/domain/orderbook/state.js.map +1 -1
  53. package/dist/domain/orderbook/ticker.d.ts +1 -0
  54. package/dist/domain/orderbook/ticker.d.ts.map +1 -1
  55. package/dist/domain/orderbook/ticker.js.map +1 -1
  56. package/dist/domain/orderbook/wire.d.ts +34 -10
  57. package/dist/domain/orderbook/wire.d.ts.map +1 -1
  58. package/dist/domain/orderbook/wire.js +47 -0
  59. package/dist/domain/orderbook/wire.js.map +1 -1
  60. package/dist/domain/position/builders.d.ts +6 -2
  61. package/dist/domain/position/builders.d.ts.map +1 -1
  62. package/dist/domain/position/builders.js +24 -8
  63. package/dist/domain/position/builders.js.map +1 -1
  64. package/dist/domain/position/client.d.ts +166 -12
  65. package/dist/domain/position/client.d.ts.map +1 -1
  66. package/dist/domain/position/client.js +614 -15
  67. package/dist/domain/position/client.js.map +1 -1
  68. package/dist/domain/position/index.d.ts +58 -3
  69. package/dist/domain/position/index.d.ts.map +1 -1
  70. package/dist/domain/position/index.js +1 -0
  71. package/dist/domain/position/index.js.map +1 -1
  72. package/dist/domain/position/state.d.ts +118 -0
  73. package/dist/domain/position/state.d.ts.map +1 -0
  74. package/dist/domain/position/state.js +262 -0
  75. package/dist/domain/position/state.js.map +1 -0
  76. package/dist/env.d.ts.map +1 -1
  77. package/dist/env.js +2 -2
  78. package/dist/env.js.map +1 -1
  79. package/dist/error.d.ts +32 -2
  80. package/dist/error.d.ts.map +1 -1
  81. package/dist/error.js +59 -1
  82. package/dist/error.js.map +1 -1
  83. package/dist/http/client.d.ts +76 -1
  84. package/dist/http/client.d.ts.map +1 -1
  85. package/dist/http/client.js +259 -39
  86. package/dist/http/client.js.map +1 -1
  87. package/dist/http/credentialRestorer.d.ts +49 -0
  88. package/dist/http/credentialRestorer.d.ts.map +1 -0
  89. package/dist/http/credentialRestorer.js +3 -0
  90. package/dist/http/credentialRestorer.js.map +1 -0
  91. package/dist/http/index.d.ts +1 -0
  92. package/dist/http/index.d.ts.map +1 -1
  93. package/dist/http/index.js.map +1 -1
  94. package/dist/index.d.ts +6 -5
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +6 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/prelude.d.ts +10 -7
  99. package/dist/prelude.d.ts.map +1 -1
  100. package/dist/prelude.js +12 -2
  101. package/dist/prelude.js.map +1 -1
  102. package/dist/privy/client.d.ts +4 -0
  103. package/dist/privy/client.d.ts.map +1 -1
  104. package/dist/privy/client.js +4 -0
  105. package/dist/privy/client.js.map +1 -1
  106. package/dist/privy/index.d.ts +4 -4
  107. package/dist/privy/index.d.ts.map +1 -1
  108. package/dist/privy/index.js +11 -16
  109. package/dist/privy/index.js.map +1 -1
  110. package/dist/program/builder.d.ts +13 -11
  111. package/dist/program/builder.d.ts.map +1 -1
  112. package/dist/program/builder.js +31 -16
  113. package/dist/program/builder.js.map +1 -1
  114. package/dist/program/constants.d.ts +1 -0
  115. package/dist/program/constants.d.ts.map +1 -1
  116. package/dist/program/constants.js +1 -0
  117. package/dist/program/constants.js.map +1 -1
  118. package/dist/program/envelope.d.ts +14 -16
  119. package/dist/program/envelope.d.ts.map +1 -1
  120. package/dist/program/envelope.js +48 -39
  121. package/dist/program/envelope.js.map +1 -1
  122. package/dist/program/index.d.ts +2 -2
  123. package/dist/program/index.d.ts.map +1 -1
  124. package/dist/program/index.js +6 -4
  125. package/dist/program/index.js.map +1 -1
  126. package/dist/program/instructions.d.ts +17 -9
  127. package/dist/program/instructions.d.ts.map +1 -1
  128. package/dist/program/instructions.js +40 -19
  129. package/dist/program/instructions.js.map +1 -1
  130. package/dist/program/orders.d.ts +7 -8
  131. package/dist/program/orders.d.ts.map +1 -1
  132. package/dist/program/orders.js +20 -22
  133. package/dist/program/orders.js.map +1 -1
  134. package/dist/program/types.d.ts +4 -3
  135. package/dist/program/types.d.ts.map +1 -1
  136. package/dist/program/types.js.map +1 -1
  137. package/dist/rpc.d.ts +89 -1
  138. package/dist/rpc.d.ts.map +1 -1
  139. package/dist/rpc.js +285 -4
  140. package/dist/rpc.js.map +1 -1
  141. package/dist/shared/api_response.d.ts +16 -1
  142. package/dist/shared/api_response.d.ts.map +1 -1
  143. package/dist/shared/api_response.js +26 -1
  144. package/dist/shared/api_response.js.map +1 -1
  145. package/dist/shared/index.d.ts +3 -3
  146. package/dist/shared/index.d.ts.map +1 -1
  147. package/dist/shared/index.js +13 -6
  148. package/dist/shared/index.js.map +1 -1
  149. package/dist/shared/json.d.ts +5 -0
  150. package/dist/shared/json.d.ts.map +1 -0
  151. package/dist/shared/json.js +38 -0
  152. package/dist/shared/json.js.map +1 -0
  153. package/dist/shared/rejection.d.ts.map +1 -1
  154. package/dist/shared/rejection.js +8 -0
  155. package/dist/shared/rejection.js.map +1 -1
  156. package/dist/shared/scaling.d.ts +30 -18
  157. package/dist/shared/scaling.d.ts.map +1 -1
  158. package/dist/shared/scaling.js +153 -95
  159. package/dist/shared/scaling.js.map +1 -1
  160. package/dist/shared/signing.d.ts +17 -0
  161. package/dist/shared/signing.d.ts.map +1 -1
  162. package/dist/shared/signing.js +27 -0
  163. package/dist/shared/signing.js.map +1 -1
  164. package/dist/shared/types.d.ts +9 -7
  165. package/dist/shared/types.d.ts.map +1 -1
  166. package/dist/shared/types.js +12 -4
  167. package/dist/shared/types.js.map +1 -1
  168. package/dist/ws/client.browser.d.ts +4 -0
  169. package/dist/ws/client.browser.d.ts.map +1 -1
  170. package/dist/ws/client.browser.js +13 -1
  171. package/dist/ws/client.browser.js.map +1 -1
  172. package/dist/ws/client.node.d.ts +4 -0
  173. package/dist/ws/client.node.d.ts.map +1 -1
  174. package/dist/ws/client.node.js +13 -1
  175. package/dist/ws/client.node.js.map +1 -1
  176. package/dist/ws/index.d.ts +24 -0
  177. package/dist/ws/index.d.ts.map +1 -1
  178. package/dist/ws/index.js +176 -2
  179. package/dist/ws/index.js.map +1 -1
  180. package/dist/ws/subscriptions.d.ts +12 -1
  181. package/dist/ws/subscriptions.d.ts.map +1 -1
  182. package/dist/ws/subscriptions.js +11 -0
  183. package/dist/ws/subscriptions.js.map +1 -1
  184. package/dist/ws/types.d.ts +5 -0
  185. package/dist/ws/types.d.ts.map +1 -1
  186. package/dist/ws/types.js.map +1 -1
  187. package/package.json +6 -2
package/README.md CHANGED
@@ -17,6 +17,7 @@ TypeScript SDK for the Lightcone impact market protocol on Solana.
17
17
  - [Environment Configuration](#environment-configuration)
18
18
  - [Examples](#examples)
19
19
  - [Error Handling](#error-handling)
20
+ - [Transaction Fee Funding](#transaction-fee-funding)
20
21
  - [Retry Strategy](#retry-strategy)
21
22
 
22
23
  ## Installation
@@ -25,6 +26,24 @@ TypeScript SDK for the Lightcone impact market protocol on Solana.
25
26
  npm install @lightconexyz/lightcone-sdk
26
27
  ```
27
28
 
29
+ ## Transaction Fee Funding
30
+
31
+ Shared on-chain submission checks the exact prepared message fee and declared
32
+ fee-payer Native SOL Balance before signing when both RPC facts are available.
33
+ A proven shortfall is an `SdkError` with variant
34
+ `InsufficientSolForTransactionFees`, bigint `availableLamports` and
35
+ `requiredLamports` fields, and the canonical deposit-SOL message. Fee or balance
36
+ lookup failure continues through the existing submission path; planner-owned SOL
37
+ actions retain fail-closed live fee, rent, and reserve checks.
38
+
39
+ `LightconeClient.builder().transactionSponsorship(true)` and
40
+ `client.setTransactionSponsorshipEnabled(true)` are trusted application assertions
41
+ for wallet-adapter and Privy signing. The default is false, each transaction
42
+ captures its signer and capability before asynchronous RPC work, `clone()` copies
43
+ the current value, and local-keypair submission rejects an enabled capability. Raw
44
+ `Privy.signAndSendTx` forwarding and off-chain order-message signing are outside
45
+ this contract.
46
+
28
47
  ## Quick Start
29
48
 
30
49
  ```typescript
@@ -69,7 +88,7 @@ async function main() {
69
88
  .amount(1_000_000n)
70
89
  .buildIx();
71
90
 
72
- // 4. Build, sign, and submit a limit order
91
+ // 4. Fetch/cache immutable trading rules, validate exactly, sign, and submit
73
92
  const response = await client.orders().limitOrder()
74
93
  .maker(keypair.publicKey)
75
94
  .bid()
@@ -177,12 +196,137 @@ ws.subscribe({
177
196
  });
178
197
  ```
179
198
 
180
- Book streams are snapshot-only: every `book_update` frame carries the full top-20 levels per side (~50ms conflation) and replaces the previous book wholesale (`OrderbookState` handles this; never gate on `seq` — the initial snapshot after every (re)subscribe is `seq: 0`). Subscriptions accept an optional Hyperliquid-style aggregation (`nSigFigs` 2–5, `mantissa` 1/2/5 only with `nSigFigs: 5`; validate with `validateAggregation`). Each `(orderbook, aggregation)` pair is a distinct subscription, so one connection can hold the full-precision and a grouped view of the same book simultaneously — incoming frames are tagged with snake_case `n_sig_figs`/`mantissa` (absent = full precision); key state by `aggregationFromFrame(frame.n_sig_figs, frame.mantissa)`. See [`examples/ws_book_and_trades.ts`](examples/ws_book_and_trades.ts).
199
+ Book streams are snapshot-only: every accepted `book_update` replaces the full
200
+ top-20 view. `OrderbookState` discards equal/older `seq` values within a
201
+ subscription generation and accepts forward gaps. Call `beginGeneration()` on
202
+ reconnect/resubscribe; `resync: true` requires unsubscribe/resubscribe with the
203
+ same aggregation. Each `(orderbook, aggregation)` pair needs its own state.
204
+ Truncation flags are preserved and mean that side is not exhaustive. See
205
+ [`examples/ws_book_and_trades.ts`](examples/ws_book_and_trades.ts).
206
+ Each decoded bid and ask level includes exact decimal-string `quote_notional`.
207
+ For grouped books, `price` is a display bucket boundary, so quote liquidity
208
+ and totals must use `quote_notional` rather than `price * size`. The
209
+ price-to-base-size `OrderbookState` maps do not retain quote notional; read it
210
+ from the decoded `OrderBook` levels.
211
+ Ticker consumers should use the supplied `mid`; it is engine-authoritative and
212
+ may use one-sided-book or last-trade fallback.
213
+ REST depth is a coherent projection that may briefly lag a mutation. Use its
214
+ `revision` and `captured_at_ms` metadata, and expect revision gaps.
181
215
 
182
216
  ```typescript
183
217
  ws.subscribe({ type: "book_update", orderbook_ids: [orderbook.orderbookId], nSigFigs: 5, mantissa: 2 });
184
218
  ```
185
219
 
220
+ Order submission accepts decimal strings and uses exact `bigint` construction.
221
+ `submit()` fetches and caches `/api/orderbooks/{id}/decimals` before invoking a
222
+ signer. Direct `sign()`/`finalize()` calls require the returned `OrderbookRules`.
223
+ Raw amounts, explicit salts, and derived prices are preflighted against the same
224
+ signed-64-bit admission rules; no tick or size normalization is implicit.
225
+
226
+ ### Wallet Balances and SOL Action Planning
227
+
228
+ `depositTokenBalances()` returns a required exact nine-decimal
229
+ `native_sol_balance` alongside the separate mint-keyed SPL map. Initialize
230
+ `WalletDepositBalancesState` with `applyRestSnapshot(wallet, snapshot)` and feed
231
+ the outer `wallet_deposit_balances` channel's nested snapshot, absolute SPL,
232
+ absolute native-SOL, and status events to `applyEvent()`. Complete snapshots
233
+ replace state even after a higher component slot; status and wrong-wallet events
234
+ do not mutate it, pre-baseline component updates are ignored, and explicit-zero
235
+ SPL updates remove their mint. Matching SPL updates with invalid or negative
236
+ idle balances return `rejected` without mutation. `contextSlot` records the latest accepted
237
+ component observation rather than enforcing global monotonic ordering.
238
+ `combinedSolBalance()` sums native SOL and canonical WSOL with `bigint` precision
239
+ while retaining both stored values. REST response types are trusted rather than
240
+ runtime-decoded; WebSocket frames are validated strictly, while malformed REST
241
+ exact values fail when a state method scales them. The reducer owns its map
242
+ container but retains balance objects by reference, so treat applied payloads as
243
+ immutable.
244
+
245
+ `planSolSplit`, `planSolMerge`, `planSolRedeem`, and
246
+ `planNativeSolWithdrawal` return unsigned action plans with live fee/rent costs,
247
+ the action-specific reserve and spendable balance, and separate expected native
248
+ and canonical WSOL deltas. Each planner requires complete matching-wallet state,
249
+ checks the canonical account when needed, and fails closed when RPC estimates or
250
+ native reserve are unavailable. Unsponsored actions reserve the greater of live
251
+ costs and the applicable 0.001 SOL or 0.0035 SOL floor. Sponsored planning is
252
+ rejected until a concrete sponsor owns transaction fees and account rent.
253
+ An occupied canonical address is accepted only when it decodes as the wallet's
254
+ initialized, unfrozen Tokenkeg native-mint account.
255
+
256
+ Split plans consume canonical WSOL first and wrap only a shortfall in the same
257
+ transaction. Merge and redeem plans retain proceeds in the persistent canonical
258
+ account. Native withdrawal transfers directly when possible; otherwise it moves
259
+ only the shortfall through a bounded seeded temporary Tokenkeg account and closes
260
+ that temporary account before sending the exact native amount to the recipient.
261
+ The temporary account's create, initialize, WSOL transfer, close, and native
262
+ transfer instructions share one Solana transaction, so an instruction failure
263
+ rolls the entire conversion back atomically. No planner closes the canonical
264
+ account implicitly.
265
+
266
+ Native-keypair self-custody users can explicitly call `planWrapSol` with a
267
+ positive exact `bigint` lamport amount or call no-amount `planUnwrapWsolAll`.
268
+ These standalone planners require the authenticated Trading Wallet's local
269
+ native signing strategy; wallet-adapter and Privy strategies are rejected before
270
+ RPC planning. Wrap creates or reuses only that wallet's canonical Tokenkeg ATA,
271
+ transfers the exact amount, and runs `SyncNative`, the Token Program instruction
272
+ that recalculates the WSOL token amount from account lamports. Wrap retains the
273
+ ordinary 0.0035 SOL account-creation or 0.001 SOL existing-account reserve floor
274
+ above lower live costs. Live decoded canonical amounts must match the authoritative
275
+ wallet state. Wrap also requires an existing account's full lamports to equal
276
+ its decoded token amount plus native rent reserve; unsynchronized direct
277
+ donations reject before `SyncNative` can make the projected canonical delta
278
+ inexact. Unwrap-all still accepts such excess and returns it on close.
279
+
280
+ Unwrap-all accepts no partial amount and closes the entire positive canonical
281
+ account back to the same wallet. Its `SolActionCosts` fields contain the fresh fee, zero
282
+ upfront rent, no account creation, and no sponsorship. Availability reserves
283
+ only that fee rather than the ordinary persistent-account floor;
284
+ `unwrapAllSolBalanceAvailability(components, costs)` validates that complete
285
+ cost tuple before deriving fee-only fields. The
286
+ native delta credits every live account lamport, including refunded rent or
287
+ direct donations, minus the fee and removes the full canonical token amount.
288
+ Closing a pre-existing account returns all of its WSOL and means a future WSOL
289
+ action may need to fund account rent again. Ordinary split, merge, redeem,
290
+ claim, order, and native-withdraw paths never call these conversion planners or
291
+ close canonical WSOL.
292
+
293
+ For either explicit conversion, rebuild immediately before signing,
294
+ submit with `signAndSubmitPreparedTxConfirmedWithSlot` so the wallet cannot
295
+ replace the fee-estimated message, and refresh a complete snapshot covering its
296
+ slot before restoring action authority. Prepared submission is unavailable for
297
+ Privy because its final signed bytes cannot be verified by the SDK. Atomic
298
+ execution does not resolve uncertain submission or confirmation errors; inspect
299
+ authoritative balances before retrying. See the
300
+ [persistent canonical WSOL ADR](../docs/adr/0001-persistent-canonical-wsol.md).
301
+
302
+ WebSocket clients are owned independently from `Auth`; logout does not clean them
303
+ up. For each retained client, `clearAuthedSubscriptions()` purges User/wallet
304
+ reconnect tracking and queued authenticated messages. It does not stop an
305
+ already-open server stream; send the matching unsubscribe or disconnect the socket
306
+ for live teardown.
307
+
308
+ The [`deposit_token_balances`](examples/deposit_token_balances.ts) self-custody
309
+ example is manual-only and runs with `LIGHTCONE_ENV=local` or `staging` only
310
+ when `SDK_API_URL`, `SDK_WS_URL`, `SDK_RPC_URL`, and `SDK_PROGRAM_ID` are all
311
+ unset. It sends 0.001 SOL to the Python SDK wallet configured by
312
+ `LIGHTCONE_WALLET_PATH_PYTHON` from the distinct sender configured by
313
+ `LIGHTCONE_WALLET_PATH_TS`, confirms with a slot, and refreshes a complete
314
+ snapshot at that slot. Running it moves funds. If it fails after submission,
315
+ inspect authoritative balances before retrying because funds may already have moved.
316
+
317
+ The separate [`wsol_conversion`](examples/wsol_conversion.ts) example runs with
318
+ the TypeScript wallet in local aggregate runs and is included when the globally
319
+ gated stateful example workflow is enabled for staging CI; that workflow
320
+ currently disables all stateful CI jobs. Local runs may use a paid RPC while
321
+ retaining built-in API, WebSocket, and program identity; an enabled staging-CI
322
+ run may supply its managed endpoints. Direct staging runs remain override-free.
323
+ It permits a pre-existing canonical balance, wraps exactly 0.001 SOL, prints the
324
+ exact wallet, costs, full-account return, and future-rent warning, then unwraps
325
+ the complete canonical account without pausing. It retains each frozen
326
+ projection until a complete REST snapshot covers the confirmed slot. Any
327
+ planning, signing, submission, or uncertain-confirmation failure exits without
328
+ automatic retry.
329
+
186
330
  ### Step 5: Cancel an Order
187
331
 
188
332
  ```typescript
@@ -208,6 +352,8 @@ const txHash = await client.positions().merge()
208
352
  .signAndSubmit();
209
353
  ```
210
354
 
355
+ `market.numOutcomes` is the validated protocol outcome count. Market deposit, merge, and unified withdrawal use it instead of the length of display outcome metadata. The pubkey-only `withdrawFromPosition()` builder requires `.numOutcomes(market.numOutcomes)` before building.
356
+
211
357
  ### Step 7: Withdraw
212
358
 
213
359
  ```typescript
@@ -222,6 +368,17 @@ const withdrawIx = client.positions().withdraw()
222
368
 
223
369
  Authentication is only required for user-specific endpoints. Authentication is session-based using ED25519 signed messages. The flow is: request a nonce, sign it with your wallet, and exchange it for a session cookie.
224
370
 
371
+ Privy hosts can also authenticate with passwordless Email, Google, X, or Wallet. After every interactive success, call `client.auth().registerPrivy({ attempted_identity })`. The backend validates the exact selector against Privy's verified methods, creates or synchronizes the Account, and changes the Primary Login Identity only for a new Account. `session.user.identity` is that stable primary; `session.user.linked_identities` contains every connected method with primary first.
372
+
373
+ Use `walletDisplayName(session.user, session.auth_method)` to show a shortened
374
+ label for the wallet the session trades with, regardless of login identity.
375
+
376
+ `session.user.max_slippage_preference` is an exact decimal string strictly below
377
+ `10`, or `null` until one is stored. Persist a value greater than zero and less
378
+ than 10 using `client.auth().updateMaxSlippagePreference(value)`; the method
379
+ returns the canonical exact decimal string. Values at or above 10% remain valid
380
+ order protection but are not remembered through this API.
381
+
225
382
  ### Cookie handling
226
383
 
227
384
  After login succeeds, the SDK stores the session token internally and attaches it as `Cookie: lightcone-token=…` on every authenticated request. Behaviour depends on the runtime:
@@ -235,14 +392,17 @@ After login succeeds, the SDK stores the session token internally and attaches i
235
392
 
236
393
  When the SDK runs on a server (SSR, an Express / Next.js route handler, etc.) and the *user's* `auth_token` cookie arrives on an incoming HTTP request, the SDK's process-wide token store is the wrong place to route it through — the store is shared across all users of that server process.
237
394
 
395
+ > **Behavior change.** `getWithCookies` responses no longer capture `Set-Cookie` into the shared token slot (they previously did): a forwarded per-user request rotating its token must not leak that token to every later request from a shared server client. These requests also never consult the credential restorer.
396
+
238
397
  For these cases, authed methods that need per-call forwarding ship a `*WithAuth(authToken)` sibling that injects the cookie just for that one call:
239
398
 
240
399
  ```typescript
241
400
  // Inside a server route, after extracting the auth_token cookie
242
401
  // from the incoming request:
243
- const balances = await client
402
+ const snapshot = await client
244
403
  .positions()
245
- .depositTokenBalancesWithAuth(authToken);
404
+ .depositTokenBalancesWithCookies(undefined, authToken);
405
+ console.log(`snapshot slot ${snapshot.context_slot}: ${Object.keys(snapshot.balances).length} balances`);
246
406
 
247
407
  const positions = await client
248
408
  .positions()
@@ -279,6 +439,8 @@ Each environment configures the API URL, WebSocket URL, Solana RPC URL, and on-c
279
439
 
280
440
  The Solana RPC URL can also be overridden via the `SDK_RPC_URL` environment variable, which takes precedence over the environment default. This is useful for pointing all examples at a private RPC to avoid public devnet rate limits.
281
441
 
442
+ Favorite-market add and remove methods are idempotent set operations, so the SDK may safely replay them after supported credential restoration or transient transport failures. Per-call cookie variants used by SSR and route handlers retry transient failures with the supplied cookie but never invoke the process-wide credential restorer.
443
+
282
444
  ## Examples
283
445
 
284
446
  All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to the production environment and read the wallet keypair from `~/.config/solana/id.json`.
@@ -288,13 +450,13 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
288
450
  | Example | Description |
289
451
  |---------|-------------|
290
452
  | [`login`](examples/login.ts) | Full auth lifecycle: sign message, login, check session, logout |
291
- | [`with_auth`](examples/with_auth.ts) | Per-call auth-token forwarding for SSR / route-handler consumers — logs in, captures the token via `client.authToken()`, clears the SDK's internal store, and exercises every `*WithAuth` variant |
453
+ | [`with_cookies`](examples/with_cookies.ts) | Per-call cookie forwarding for SSR / route-handler consumers, including paginated favorite-market list/add/remove while restoring the original state |
292
454
 
293
455
  ### Market Discovery & Data
294
456
 
295
457
  | Example | Description |
296
458
  |---------|-------------|
297
- | [`markets`](examples/markets.ts) | Featured markets, paginated listing, fetch by pubkey, search, platform deposit assets via `globalDepositAssets()` |
459
+ | [`markets`](examples/markets.ts) | Featured markets, paginated listing, fetch by pubkey, search, and platform deposit assets via `globalDepositAssets()`; authenticated favorite-market APIs are demonstrated by `with_cookies` |
298
460
  | [`orderbook`](examples/orderbook.ts) | Fetch orderbook depth (bids/asks) and decimal precision metadata |
299
461
  | [`trades`](examples/trades.ts) | Recent trade history with cursor-based pagination (per-orderbook and market-wide) |
300
462
  | [`price_history`](examples/price_history.ts) | Historical candlestick data (OHLCV) at various resolutions |
@@ -305,7 +467,7 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
305
467
 
306
468
  | Example | Description |
307
469
  |---------|-------------|
308
- | [`submit_order`](examples/submit_order.ts) | Deposit the quote amount into the global pool, then place a limit order via `client.orders().limitOrder()` with human-readable price/size, auto-scaling, and fill tracking. Companion `cancel_order` cancels it and withdraws to stay net-neutral |
470
+ | [`submit_order`](examples/submit_order.ts) | Deposit collateral, then place an exactly validated limit order using cached trading rules. Companion `cancel_order` cancels it and withdraws to stay net-neutral |
309
471
 
310
472
  ### Cancelling Orders
311
473
 
@@ -322,6 +484,16 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
322
484
  | [`onchain_transactions`](examples/onchain_transactions.ts) | Build, sign, and submit mint/merge complete set and increment nonce on-chain |
323
485
  | [`global_deposit_withdrawal`](examples/global_deposit_withdrawal.ts) | Init position tokens, deposit to global pool, move capital into a market, extend an existing ALT, withdraw from global, and merge back to keep the run net-neutral |
324
486
 
487
+ ### Manual Fund-Moving Operations
488
+
489
+ These examples refuse production and endpoint overrides and are intentionally
490
+ excluded from routine example runs.
491
+
492
+ | Example | Description |
493
+ |---------|-------------|
494
+ | [`deposit_token_balances`](examples/deposit_token_balances.ts) | Confirm an exact native withdrawal without closing canonical WSOL, then refresh complete state past the confirmed slot |
495
+ | [`wsol_conversion`](examples/wsol_conversion.ts) | Wrap an exact native amount, warn, close and unwrap the complete canonical account, and hold frozen projections through covering refreshes |
496
+
325
497
  ### WebSocket Streaming
326
498
 
327
499
  | Example | Description |
@@ -359,6 +531,7 @@ When the backend rejects a request (insufficient balance, expired order, etc.),
359
531
  | `errorCode` | `string \| undefined` | API-level error code (for example `"NOT_FOUND"` or `"INVALID_ARGUMENT"`) |
360
532
  | `errorLogId` | `string \| undefined` | Backend support correlation ID (`LCERR_*`) |
361
533
  | `requestId` | `string \| undefined` | SDK-generated `x-request-id` for cross-service tracing |
534
+ | `existingMethod` | `string \| undefined` | Primary method of the conflicting Account when identity ownership has one deterministic owner |
362
535
 
363
536
  `ApiRejectedDetails.toString()` formats all present fields as a multi-line report for logs or support tickets.
364
537
 
@@ -430,14 +603,29 @@ The SDK generates a UUID v4 `x-request-id` header on every HTTP request. On reje
430
603
  | `NotFound` | 404 - resource not found |
431
604
  | `BadRequest` | 400 - invalid request |
432
605
  | `Timeout` | Request timed out |
433
- | `MaxRetriesExceeded` | All retry attempts exhausted |
606
+ | `MaxRetriesExceeded` | Never produced by the SDK itself: the HTTP retry loop propagates the final underlying error on exhaustion (structured details intact — see the retry-exhaustion tests). Kept public for consumer-built retry loops |
434
607
 
435
608
  ## Retry Strategy
436
609
 
437
- - **GET requests**: `RetryPolicy.Idempotent` - retries on transport failures and 502/503/504, backs off on 429 with exponential backoff + jitter.
438
- - **POST requests** (order submit, cancel, auth): `RetryPolicy.None` - no automatic retry. Non-idempotent actions are never retried to prevent duplicate side effects.
610
+ - **Replay-safe requests**: GETs and idempotent set operations such as favorite-market updates use `RetryPolicy.Idempotent`, which retries transport failures and 502/503/504 and backs off on 429 with exponential backoff + jitter.
611
+ - **Non-idempotent requests** (order submit, cancel, auth): `RetryPolicy.None` - no automatic retry, which prevents duplicate side effects.
439
612
  - Customizable per-call with `RetryPolicy.custom(config)`.
440
613
 
614
+ ### Credential restoration (401 recovery)
615
+
616
+ Sessions built on short-lived tokens expire mid-run: the backend starts answering 401 even though the app could mint a fresh token (e.g. a browser refreshing its Privy session). Rather than every caller hand-rolling "detect 401 → refresh → retry", the transport accepts a host-supplied hook:
617
+
618
+ ```typescript
619
+ client.setCredentialRestorer(async () => {
620
+ // e.g. ask the auth provider's SDK to refresh the session
621
+ return await refreshSession();
622
+ });
623
+ ```
624
+
625
+ When a request to the API origin fails with HTTP 401 and a restorer is registered, the transport consults it **at most once per logical request**, with concurrent 401s sharing one restoration (bounded by a 30-second timeout). A successful restoration replays the request once **only if it declared itself retry-safe** (an idempotent/custom retry policy); `RetryPolicy.None` requests — mutations like orders and cancels — are never auto-replayed: the restoration still heals the session for the caller's next attempt, but the original 401 propagates. Restoration is skipped for credential-management endpoints (login, logout) and for cookie-override/custom-session requests, redirects are never followed on the API transport, and without a registered restorer 401s propagate unchanged. A timed-out restoration has its `AbortSignal` fired — promises cannot be cancelled, so restorers whose work is non-idempotent (refresh-token rotation) must honor the signal or serialize internally.
626
+
627
+ The SDK stays credential-agnostic: what "restore" means belongs to the host. For classifying auth failures in your own code, use `isUnauthorized(error)` from the error module — it covers both bare 401s and 401s carrying a structured rejection envelope (`ApiRejectedDetails.httpStatus`).
628
+
441
629
  ## Trigger Orders
442
630
 
443
631
  Trigger orders (stop-limit, take-profit-limit) are under development and not yet available. Internal types exist in the source for internal use only.
@@ -1,5 +1,17 @@
1
1
  import { type LightconeHttp } from "../http";
2
- import type { AuthCredentials, SessionResponse } from "./index";
2
+ import type { AuthCredentials, LinkedIdentityType, RegisterPrivyRequest, SessionResponse } from "./index";
3
+ /** Bounded ownership conflicts safe for client recovery guidance. */
4
+ export type RegisterPrivyConflict = {
5
+ code: "IDENTITY_OWNED_BY_ANOTHER_ACCOUNT";
6
+ existingMethod?: LinkedIdentityType;
7
+ } | {
8
+ code: "IDENTITIES_OWNED_BY_MULTIPLE_ACCOUNTS";
9
+ } | {
10
+ code: "WALLET_OWNED_BY_ANOTHER_ACCOUNT";
11
+ existingMethod?: LinkedIdentityType;
12
+ };
13
+ /** Classify only stable register-or-sync ownership rejection codes. */
14
+ export declare function classifyRegisterPrivyConflict(error: unknown): RegisterPrivyConflict | undefined;
3
15
  interface AuthState {
4
16
  getCredentials(): AuthCredentials | undefined;
5
17
  setCredentials(credentials: AuthCredentials | undefined): void;
@@ -15,8 +27,22 @@ export declare class Auth {
15
27
  getNonce(): Promise<string>;
16
28
  loginWithMessage(message: string, signatureBs58: string, pubkeyBytes: Uint8Array, useEmbeddedWallet?: boolean): Promise<SessionResponse>;
17
29
  checkSession(): Promise<SessionResponse>;
30
+ /**
31
+ * Logout — clears the server-side cookie, internal token, and credentials.
32
+ *
33
+ * Local state is cleared even when the server call fails — the caller asked
34
+ * to be signed out locally regardless — but the failure is then rethrown:
35
+ * callers gating security decisions on teardown (e.g. whether an app may
36
+ * restart an authenticated transport) must be able to see that the
37
+ * server-side cookie may still be valid. A 401 counts as success: it means
38
+ * "already logged out".
39
+ */
18
40
  logout(): Promise<void>;
19
41
  disconnectX(): Promise<void>;
42
+ /** Create or synchronize a Privy Account and install the resulting session. */
43
+ registerPrivy(request: RegisterPrivyRequest): Promise<SessionResponse>;
44
+ /** Persist an account-wide max-slippage preference strictly below 10%. */
45
+ updateMaxSlippagePreference(maxSlippagePreference: string): Promise<string>;
20
46
  connectXUrl(): string;
21
47
  credentials(): AuthCredentials | undefined;
22
48
  isAuthenticated(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAA+B,eAAe,EAAE,MAAM,SAAS,CAAC;AAE7F,UAAU,SAAS;IACjB,cAAc,IAAI,eAAe,GAAG,SAAS,CAAC;IAC9C,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAAC;IAC/D,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,qBAAa,IAAI;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAE5C,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAM3B,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,UAAU,EACvB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,OAAO,CAAC,eAAe,CAAC;IAoBrB,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;IAgBxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAavB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC,WAAW,IAAI,MAAM;IAIrB,WAAW,IAAI,eAAe,GAAG,SAAS;IAI1C,eAAe,IAAI,OAAO;CAO3B"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,KAAK,EACV,eAAe,EAIf,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,qEAAqE;AACrE,MAAM,MAAM,qBAAqB,GAC7B;IACE,IAAI,EAAE,mCAAmC,CAAC;IAC1C,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC,GACD;IAAE,IAAI,EAAE,uCAAuC,CAAA;CAAE,GACjD;IACE,IAAI,EAAE,iCAAiC,CAAC;IACxC,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC,CAAC;AAEN,uEAAuE;AACvE,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,GACb,qBAAqB,GAAG,SAAS,CAoBnC;AAED,UAAU,SAAS;IACjB,cAAc,IAAI,eAAe,GAAG,SAAS,CAAC;IAC9C,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAAC;IAC/D,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,qBAAa,IAAI;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAE5C,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAS3B,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,UAAU,EACvB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,OAAO,CAAC,eAAe,CAAC;IAwBrB,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;IAoB9C;;;;;;;;;OASG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBvB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IASlC,+EAA+E;IACzE,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAe5E,0EAA0E;IACpE,2BAA2B,CAC/B,qBAAqB,EAAE,MAAM,GAC5B,OAAO,CAAC,MAAM,CAAC;IAuBlB,WAAW,IAAI,MAAM;IAIrB,WAAW,IAAI,eAAe,GAAG,SAAS;IAI1C,eAAe,IAAI,OAAO;CAO3B"}
@@ -1,10 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Auth = void 0;
4
+ exports.classifyRegisterPrivyConflict = classifyRegisterPrivyConflict;
4
5
  const error_1 = require("../error");
5
6
  const http_1 = require("../http");
6
7
  const shared_1 = require("../shared");
7
8
  const index_1 = require("./index");
9
+ /** Classify only stable register-or-sync ownership rejection codes. */
10
+ function classifyRegisterPrivyConflict(error) {
11
+ if (!(error instanceof error_1.SdkError) || error.variant !== "ApiRejected")
12
+ return undefined;
13
+ const details = error.apiRejectedDetails;
14
+ switch (details?.errorCode) {
15
+ case "IDENTITY_OWNED_BY_ANOTHER_ACCOUNT":
16
+ return {
17
+ code: details.errorCode,
18
+ existingMethod: details.existingMethod,
19
+ };
20
+ case "IDENTITIES_OWNED_BY_MULTIPLE_ACCOUNTS":
21
+ return { code: details.errorCode };
22
+ case "WALLET_OWNED_BY_ANOTHER_ACCOUNT":
23
+ return {
24
+ code: details.errorCode,
25
+ existingMethod: details.existingMethod,
26
+ };
27
+ default:
28
+ return undefined;
29
+ }
30
+ }
8
31
  class Auth {
9
32
  client;
10
33
  constructor(client) {
@@ -23,7 +46,12 @@ class Auth {
23
46
  pubkey_bytes: Array.from(pubkeyBytes),
24
47
  use_embedded_wallet: useEmbeddedWallet,
25
48
  };
26
- const session = await this.client.http.post(url, body, http_1.RetryPolicy.None);
49
+ // Credential-management endpoint: opts out of the transport's 401
50
+ // restore-and-replay. The backend consumes the login nonce before
51
+ // verifying the signature, so a replayed login deterministically fails —
52
+ // and restoring credentials in order to log in is circular.
53
+ const session = await this.client.http.postWithoutCredentialRestore(url, body, http_1.RetryPolicy.None);
54
+ normalizeSessionMaxSlippagePreference(session);
27
55
  this.client.authState.setCredentials(credentialsFromSession(session));
28
56
  return session;
29
57
  }
@@ -32,6 +60,7 @@ class Auth {
32
60
  let session;
33
61
  try {
34
62
  session = await this.client.http.get(url, http_1.RetryPolicy.Idempotent);
63
+ normalizeSessionMaxSlippagePreference(session);
35
64
  }
36
65
  catch (error) {
37
66
  this.client.authState.setCredentials(undefined);
@@ -40,22 +69,58 @@ class Auth {
40
69
  this.client.authState.setCredentials(credentialsFromSession(session));
41
70
  return session;
42
71
  }
72
+ /**
73
+ * Logout — clears the server-side cookie, internal token, and credentials.
74
+ *
75
+ * Local state is cleared even when the server call fails — the caller asked
76
+ * to be signed out locally regardless — but the failure is then rethrown:
77
+ * callers gating security decisions on teardown (e.g. whether an app may
78
+ * restart an authenticated transport) must be able to see that the
79
+ * server-side cookie may still be valid. A 401 counts as success: it means
80
+ * "already logged out".
81
+ */
43
82
  async logout() {
44
83
  const url = `${this.client.http.baseUrl()}/api/auth/logout`;
84
+ let logoutError = null;
45
85
  try {
46
- await this.client.http.post(url, {}, http_1.RetryPolicy.None);
86
+ // Credential-management endpoint: opts out of the transport's 401
87
+ // restore-and-replay — a 401 here means "already logged out".
88
+ await this.client.http.postWithoutCredentialRestore(url, {}, http_1.RetryPolicy.None);
47
89
  }
48
- catch {
49
- // Backend cookie clear can fail in local/dev setups; still clear local state.
90
+ catch (error) {
91
+ if (!(0, error_1.isUnauthorized)(error)) {
92
+ logoutError = error;
93
+ }
50
94
  }
51
95
  await this.client.http.clearAuthToken();
52
96
  this.client.authState.setCredentials(undefined);
53
97
  await this.client.authState.clearCaches();
98
+ if (logoutError !== null) {
99
+ throw logoutError;
100
+ }
54
101
  }
55
102
  async disconnectX() {
56
103
  const url = `${this.client.http.baseUrl()}/api/auth/disconnect_x`;
57
104
  await this.client.http.post(url, {}, http_1.RetryPolicy.None);
58
105
  }
106
+ /** Create or synchronize a Privy Account and install the resulting session. */
107
+ async registerPrivy(request) {
108
+ const url = `${this.client.http.baseUrl()}/api/auth/register-privy`;
109
+ const session = await this.client.http.post(url, request, http_1.RetryPolicy.Idempotent);
110
+ normalizeSessionMaxSlippagePreference(session);
111
+ this.client.authState.setCredentials(credentialsFromSession(session));
112
+ return session;
113
+ }
114
+ /** Persist an account-wide max-slippage preference strictly below 10%. */
115
+ async updateMaxSlippagePreference(maxSlippagePreference) {
116
+ const url = `${this.client.http.baseUrl()}/api/auth/max_slippage_preference`;
117
+ const response = await this.client.http.post(url, { max_slippage_preference: maxSlippagePreference }, http_1.RetryPolicy.Idempotent);
118
+ const preference = decodeMaxSlippagePreference(response, false, "update response");
119
+ if (preference === null) {
120
+ throw error_1.SdkError.serde("Max-slippage update response must contain a decimal string");
121
+ }
122
+ return preference;
123
+ }
59
124
  connectXUrl() {
60
125
  return `${this.client.http.baseUrl()}/api/auth/oauth/link/x`;
61
126
  }
@@ -71,6 +136,31 @@ class Auth {
71
136
  }
72
137
  }
73
138
  exports.Auth = Auth;
139
+ function normalizeSessionMaxSlippagePreference(session) {
140
+ const preference = decodeMaxSlippagePreference(session?.user, true, "session user", true);
141
+ session.user.max_slippage_preference = preference;
142
+ }
143
+ /** Enforces the exact nullable/string JSON contract erased by TypeScript types. */
144
+ function decodeMaxSlippagePreference(payload, allowNull, context, allowMissing = false) {
145
+ if (typeof payload !== "object" || payload === null) {
146
+ throw error_1.SdkError.serde(`Max-slippage ${context} is malformed`);
147
+ }
148
+ if (!Object.prototype.hasOwnProperty.call(payload, "max_slippage_preference")) {
149
+ if (allowMissing) {
150
+ return null;
151
+ }
152
+ throw error_1.SdkError.serde(`Max-slippage ${context} is missing max_slippage_preference`);
153
+ }
154
+ const preference = payload
155
+ .max_slippage_preference;
156
+ if (typeof preference === "string") {
157
+ return preference;
158
+ }
159
+ if (allowNull && preference === null) {
160
+ return null;
161
+ }
162
+ throw error_1.SdkError.serde(`Max-slippage ${context} must contain ${allowNull ? "a string or null" : "a string"}`);
163
+ }
74
164
  /**
75
165
  * Derive session credentials from the envelope. The trading wallet comes from
76
166
  * the identity + auth method.
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { SdkError } from \"../error\";\nimport { RetryPolicy, type LightconeHttp } from \"../http\";\nimport { asPubkeyStr } from \"../shared\";\nimport { tradingWallet } from \"./index\";\nimport type { AuthCredentials, LoginRequest, NonceResponse, SessionResponse } from \"./index\";\n\ninterface AuthState {\n getCredentials(): AuthCredentials | undefined;\n setCredentials(credentials: AuthCredentials | undefined): void;\n clearCaches(): Promise<void>;\n}\n\ninterface ClientContext {\n http: LightconeHttp;\n authState: AuthState;\n}\n\nexport class Auth {\n constructor(private readonly client: ClientContext) {}\n\n async getNonce(): Promise<string> {\n const url = `${this.client.http.baseUrl()}/api/auth/nonce`;\n const response = await this.client.http.get<NonceResponse>(url, RetryPolicy.None);\n return response.nonce;\n }\n\n async loginWithMessage(\n message: string,\n signatureBs58: string,\n pubkeyBytes: Uint8Array,\n useEmbeddedWallet?: boolean\n ): Promise<SessionResponse> {\n const url = `${this.client.http.baseUrl()}/api/auth/login_or_register_with_message`;\n const body: LoginRequest = {\n message,\n signature_bs58: signatureBs58,\n pubkey_bytes: Array.from(pubkeyBytes),\n use_embedded_wallet: useEmbeddedWallet,\n };\n\n const session = await this.client.http.post<SessionResponse, LoginRequest>(\n url,\n body,\n RetryPolicy.None\n );\n\n this.client.authState.setCredentials(credentialsFromSession(session));\n\n return session;\n }\n\n async checkSession(): Promise<SessionResponse> {\n const url = `${this.client.http.baseUrl()}/api/auth/me`;\n\n let session: SessionResponse;\n try {\n session = await this.client.http.get<SessionResponse>(url, RetryPolicy.Idempotent);\n } catch (error) {\n this.client.authState.setCredentials(undefined);\n throw SdkError.from(error);\n }\n\n this.client.authState.setCredentials(credentialsFromSession(session));\n\n return session;\n }\n\n async logout(): Promise<void> {\n const url = `${this.client.http.baseUrl()}/api/auth/logout`;\n try {\n await this.client.http.post<{ success: boolean }, Record<string, never>>(url, {}, RetryPolicy.None);\n } catch {\n // Backend cookie clear can fail in local/dev setups; still clear local state.\n }\n\n await this.client.http.clearAuthToken();\n this.client.authState.setCredentials(undefined);\n await this.client.authState.clearCaches();\n }\n\n async disconnectX(): Promise<void> {\n const url = `${this.client.http.baseUrl()}/api/auth/disconnect_x`;\n await this.client.http.post<{ success: boolean }, Record<string, never>>(url, {}, RetryPolicy.None);\n }\n\n connectXUrl(): string {\n return `${this.client.http.baseUrl()}/api/auth/oauth/link/x`;\n }\n\n credentials(): AuthCredentials | undefined {\n return this.client.authState.getCredentials();\n }\n\n isAuthenticated(): boolean {\n const credentials = this.credentials();\n if (!credentials) {\n return false;\n }\n return Date.now() < credentials.expires_at.getTime();\n }\n}\n\n/**\n * Derive session credentials from the envelope. The trading wallet comes from\n * the identity + auth method.\n */\nfunction credentialsFromSession(session: SessionResponse): AuthCredentials {\n return {\n user_id: session.user.user_id,\n wallet_address: asPubkeyStr(tradingWallet(session.user, session.auth_method)),\n expires_at: parseExpiry(session.expires_at),\n };\n}\n\nfunction parseExpiry(timestamp: number): Date {\n if (timestamp > 1_000_000_000_000) {\n return new Date(timestamp);\n }\n return new Date(timestamp * 1000);\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { SdkError, isUnauthorized } from \"../error\";\nimport { RetryPolicy, type LightconeHttp } from \"../http\";\nimport { asPubkeyStr } from \"../shared\";\nimport { tradingWallet } from \"./index\";\nimport type {\n AuthCredentials,\n LoginRequest,\n MaxSlippagePreferenceBody,\n NonceResponse,\n LinkedIdentityType,\n RegisterPrivyRequest,\n SessionResponse,\n} from \"./index\";\n\n/** Bounded ownership conflicts safe for client recovery guidance. */\nexport type RegisterPrivyConflict =\n | {\n code: \"IDENTITY_OWNED_BY_ANOTHER_ACCOUNT\";\n existingMethod?: LinkedIdentityType;\n }\n | { code: \"IDENTITIES_OWNED_BY_MULTIPLE_ACCOUNTS\" }\n | {\n code: \"WALLET_OWNED_BY_ANOTHER_ACCOUNT\";\n existingMethod?: LinkedIdentityType;\n };\n\n/** Classify only stable register-or-sync ownership rejection codes. */\nexport function classifyRegisterPrivyConflict(\n error: unknown,\n): RegisterPrivyConflict | undefined {\n if (!(error instanceof SdkError) || error.variant !== \"ApiRejected\")\n return undefined;\n const details = error.apiRejectedDetails;\n switch (details?.errorCode) {\n case \"IDENTITY_OWNED_BY_ANOTHER_ACCOUNT\":\n return {\n code: details.errorCode,\n existingMethod: details.existingMethod,\n };\n case \"IDENTITIES_OWNED_BY_MULTIPLE_ACCOUNTS\":\n return { code: details.errorCode };\n case \"WALLET_OWNED_BY_ANOTHER_ACCOUNT\":\n return {\n code: details.errorCode,\n existingMethod: details.existingMethod,\n };\n default:\n return undefined;\n }\n}\n\ninterface AuthState {\n getCredentials(): AuthCredentials | undefined;\n setCredentials(credentials: AuthCredentials | undefined): void;\n clearCaches(): Promise<void>;\n}\n\ninterface ClientContext {\n http: LightconeHttp;\n authState: AuthState;\n}\n\nexport class Auth {\n constructor(private readonly client: ClientContext) {}\n\n async getNonce(): Promise<string> {\n const url = `${this.client.http.baseUrl()}/api/auth/nonce`;\n const response = await this.client.http.get<NonceResponse>(\n url,\n RetryPolicy.None,\n );\n return response.nonce;\n }\n\n async loginWithMessage(\n message: string,\n signatureBs58: string,\n pubkeyBytes: Uint8Array,\n useEmbeddedWallet?: boolean,\n ): Promise<SessionResponse> {\n const url = `${this.client.http.baseUrl()}/api/auth/login_or_register_with_message`;\n const body: LoginRequest = {\n message,\n signature_bs58: signatureBs58,\n pubkey_bytes: Array.from(pubkeyBytes),\n use_embedded_wallet: useEmbeddedWallet,\n };\n\n // Credential-management endpoint: opts out of the transport's 401\n // restore-and-replay. The backend consumes the login nonce before\n // verifying the signature, so a replayed login deterministically fails —\n // and restoring credentials in order to log in is circular.\n const session = await this.client.http.postWithoutCredentialRestore<\n SessionResponse,\n LoginRequest\n >(url, body, RetryPolicy.None);\n normalizeSessionMaxSlippagePreference(session);\n\n this.client.authState.setCredentials(credentialsFromSession(session));\n\n return session;\n }\n\n async checkSession(): Promise<SessionResponse> {\n const url = `${this.client.http.baseUrl()}/api/auth/me`;\n\n let session: SessionResponse;\n try {\n session = await this.client.http.get<SessionResponse>(\n url,\n RetryPolicy.Idempotent,\n );\n normalizeSessionMaxSlippagePreference(session);\n } catch (error) {\n this.client.authState.setCredentials(undefined);\n throw SdkError.from(error);\n }\n\n this.client.authState.setCredentials(credentialsFromSession(session));\n\n return session;\n }\n\n /**\n * Logout — clears the server-side cookie, internal token, and credentials.\n *\n * Local state is cleared even when the server call fails — the caller asked\n * to be signed out locally regardless — but the failure is then rethrown:\n * callers gating security decisions on teardown (e.g. whether an app may\n * restart an authenticated transport) must be able to see that the\n * server-side cookie may still be valid. A 401 counts as success: it means\n * \"already logged out\".\n */\n async logout(): Promise<void> {\n const url = `${this.client.http.baseUrl()}/api/auth/logout`;\n let logoutError: unknown = null;\n try {\n // Credential-management endpoint: opts out of the transport's 401\n // restore-and-replay — a 401 here means \"already logged out\".\n await this.client.http.postWithoutCredentialRestore<\n { success: boolean },\n Record<string, never>\n >(url, {}, RetryPolicy.None);\n } catch (error) {\n if (!isUnauthorized(error)) {\n logoutError = error;\n }\n }\n\n await this.client.http.clearAuthToken();\n this.client.authState.setCredentials(undefined);\n await this.client.authState.clearCaches();\n\n if (logoutError !== null) {\n throw logoutError;\n }\n }\n\n async disconnectX(): Promise<void> {\n const url = `${this.client.http.baseUrl()}/api/auth/disconnect_x`;\n await this.client.http.post<{ success: boolean }, Record<string, never>>(\n url,\n {},\n RetryPolicy.None,\n );\n }\n\n /** Create or synchronize a Privy Account and install the resulting session. */\n async registerPrivy(request: RegisterPrivyRequest): Promise<SessionResponse> {\n const url = `${this.client.http.baseUrl()}/api/auth/register-privy`;\n const session = await this.client.http.post<\n SessionResponse,\n RegisterPrivyRequest\n >(\n url,\n request,\n RetryPolicy.Idempotent,\n );\n normalizeSessionMaxSlippagePreference(session);\n this.client.authState.setCredentials(credentialsFromSession(session));\n return session;\n }\n\n /** Persist an account-wide max-slippage preference strictly below 10%. */\n async updateMaxSlippagePreference(\n maxSlippagePreference: string,\n ): Promise<string> {\n const url = `${this.client.http.baseUrl()}/api/auth/max_slippage_preference`;\n const response = await this.client.http.post<\n MaxSlippagePreferenceBody,\n MaxSlippagePreferenceBody\n >(\n url,\n { max_slippage_preference: maxSlippagePreference },\n RetryPolicy.Idempotent,\n );\n const preference = decodeMaxSlippagePreference(\n response,\n false,\n \"update response\",\n );\n if (preference === null) {\n throw SdkError.serde(\n \"Max-slippage update response must contain a decimal string\",\n );\n }\n return preference;\n }\n\n connectXUrl(): string {\n return `${this.client.http.baseUrl()}/api/auth/oauth/link/x`;\n }\n\n credentials(): AuthCredentials | undefined {\n return this.client.authState.getCredentials();\n }\n\n isAuthenticated(): boolean {\n const credentials = this.credentials();\n if (!credentials) {\n return false;\n }\n return Date.now() < credentials.expires_at.getTime();\n }\n}\n\nfunction normalizeSessionMaxSlippagePreference(session: SessionResponse): void {\n const preference = decodeMaxSlippagePreference(\n session?.user,\n true,\n \"session user\",\n true,\n );\n session.user.max_slippage_preference = preference;\n}\n\n/** Enforces the exact nullable/string JSON contract erased by TypeScript types. */\nfunction decodeMaxSlippagePreference(\n payload: unknown,\n allowNull: boolean,\n context: string,\n allowMissing = false,\n): string | null {\n if (typeof payload !== \"object\" || payload === null) {\n throw SdkError.serde(`Max-slippage ${context} is malformed`);\n }\n if (\n !Object.prototype.hasOwnProperty.call(payload, \"max_slippage_preference\")\n ) {\n if (allowMissing) {\n return null;\n }\n throw SdkError.serde(\n `Max-slippage ${context} is missing max_slippage_preference`,\n );\n }\n\n const preference = (payload as Record<string, unknown>)\n .max_slippage_preference;\n if (typeof preference === \"string\") {\n return preference;\n }\n if (allowNull && preference === null) {\n return null;\n }\n throw SdkError.serde(\n `Max-slippage ${context} must contain ${allowNull ? \"a string or null\" : \"a string\"}`,\n );\n}\n\n/**\n * Derive session credentials from the envelope. The trading wallet comes from\n * the identity + auth method.\n */\nfunction credentialsFromSession(session: SessionResponse): AuthCredentials {\n return {\n user_id: session.user.user_id,\n wallet_address: asPubkeyStr(\n tradingWallet(session.user, session.auth_method),\n ),\n expires_at: parseExpiry(session.expires_at),\n };\n}\n\nfunction parseExpiry(timestamp: number): Date {\n if (timestamp > 1_000_000_000_000) {\n return new Date(timestamp);\n }\n return new Date(timestamp * 1000);\n}\n"]}