@lightconexyz/lightcone-sdk 0.8.1-rc.4 → 0.8.1-rc.40

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 (194) hide show
  1. package/README.md +179 -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 +36 -3
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +47 -2
  13. package/dist/client.js.map +1 -1
  14. package/dist/context.d.ts +50 -2
  15. package/dist/context.d.ts.map +1 -1
  16. package/dist/context.js +178 -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 -11
  24. package/dist/domain/market/convert.js.map +1 -1
  25. package/dist/domain/market/index.d.ts +18 -5
  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/tokens.d.ts +1 -0
  29. package/dist/domain/market/tokens.d.ts.map +1 -1
  30. package/dist/domain/market/tokens.js +11 -4
  31. package/dist/domain/market/tokens.js.map +1 -1
  32. package/dist/domain/market/wire.d.ts +17 -0
  33. package/dist/domain/market/wire.d.ts.map +1 -1
  34. package/dist/domain/market/wire.js.map +1 -1
  35. package/dist/domain/order/client.d.ts +8 -7
  36. package/dist/domain/order/client.d.ts.map +1 -1
  37. package/dist/domain/order/client.js +23 -8
  38. package/dist/domain/order/client.js.map +1 -1
  39. package/dist/domain/order/wire.d.ts +2 -0
  40. package/dist/domain/order/wire.d.ts.map +1 -1
  41. package/dist/domain/order/wire.js +9 -0
  42. package/dist/domain/order/wire.js.map +1 -1
  43. package/dist/domain/orderbook/client.d.ts +7 -1
  44. package/dist/domain/orderbook/client.d.ts.map +1 -1
  45. package/dist/domain/orderbook/client.js +54 -1
  46. package/dist/domain/orderbook/client.js.map +1 -1
  47. package/dist/domain/orderbook/index.d.ts +7 -9
  48. package/dist/domain/orderbook/index.d.ts.map +1 -1
  49. package/dist/domain/orderbook/index.js +28 -24
  50. package/dist/domain/orderbook/index.js.map +1 -1
  51. package/dist/domain/orderbook/state.d.ts +21 -16
  52. package/dist/domain/orderbook/state.d.ts.map +1 -1
  53. package/dist/domain/orderbook/state.js +36 -15
  54. package/dist/domain/orderbook/state.js.map +1 -1
  55. package/dist/domain/orderbook/ticker.d.ts +1 -0
  56. package/dist/domain/orderbook/ticker.d.ts.map +1 -1
  57. package/dist/domain/orderbook/ticker.js.map +1 -1
  58. package/dist/domain/orderbook/wire.d.ts +34 -10
  59. package/dist/domain/orderbook/wire.d.ts.map +1 -1
  60. package/dist/domain/orderbook/wire.js +47 -0
  61. package/dist/domain/orderbook/wire.js.map +1 -1
  62. package/dist/domain/position/builders.d.ts +6 -2
  63. package/dist/domain/position/builders.d.ts.map +1 -1
  64. package/dist/domain/position/builders.js +24 -8
  65. package/dist/domain/position/builders.js.map +1 -1
  66. package/dist/domain/position/client.d.ts +166 -12
  67. package/dist/domain/position/client.d.ts.map +1 -1
  68. package/dist/domain/position/client.js +614 -15
  69. package/dist/domain/position/client.js.map +1 -1
  70. package/dist/domain/position/index.d.ts +94 -3
  71. package/dist/domain/position/index.d.ts.map +1 -1
  72. package/dist/domain/position/index.js +110 -0
  73. package/dist/domain/position/index.js.map +1 -1
  74. package/dist/domain/position/state.d.ts +117 -0
  75. package/dist/domain/position/state.d.ts.map +1 -0
  76. package/dist/domain/position/state.js +261 -0
  77. package/dist/domain/position/state.js.map +1 -0
  78. package/dist/env.d.ts.map +1 -1
  79. package/dist/env.js +2 -2
  80. package/dist/env.js.map +1 -1
  81. package/dist/error.d.ts +26 -2
  82. package/dist/error.d.ts.map +1 -1
  83. package/dist/error.js +46 -1
  84. package/dist/error.js.map +1 -1
  85. package/dist/http/client.d.ts +76 -1
  86. package/dist/http/client.d.ts.map +1 -1
  87. package/dist/http/client.js +259 -39
  88. package/dist/http/client.js.map +1 -1
  89. package/dist/http/credentialRestorer.d.ts +49 -0
  90. package/dist/http/credentialRestorer.d.ts.map +1 -0
  91. package/dist/http/credentialRestorer.js +3 -0
  92. package/dist/http/credentialRestorer.js.map +1 -0
  93. package/dist/http/index.d.ts +1 -0
  94. package/dist/http/index.d.ts.map +1 -1
  95. package/dist/http/index.js.map +1 -1
  96. package/dist/index.d.ts +6 -5
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +6 -1
  99. package/dist/index.js.map +1 -1
  100. package/dist/prelude.d.ts +10 -7
  101. package/dist/prelude.d.ts.map +1 -1
  102. package/dist/prelude.js +12 -2
  103. package/dist/prelude.js.map +1 -1
  104. package/dist/privy/index.d.ts +4 -4
  105. package/dist/privy/index.d.ts.map +1 -1
  106. package/dist/privy/index.js +11 -16
  107. package/dist/privy/index.js.map +1 -1
  108. package/dist/program/accounts.d.ts +10 -6
  109. package/dist/program/accounts.d.ts.map +1 -1
  110. package/dist/program/accounts.js +33 -9
  111. package/dist/program/accounts.js.map +1 -1
  112. package/dist/program/builder.d.ts +13 -11
  113. package/dist/program/builder.d.ts.map +1 -1
  114. package/dist/program/builder.js +31 -16
  115. package/dist/program/builder.js.map +1 -1
  116. package/dist/program/constants.d.ts +14 -3
  117. package/dist/program/constants.d.ts.map +1 -1
  118. package/dist/program/constants.js +15 -4
  119. package/dist/program/constants.js.map +1 -1
  120. package/dist/program/envelope.d.ts +14 -16
  121. package/dist/program/envelope.d.ts.map +1 -1
  122. package/dist/program/envelope.js +48 -39
  123. package/dist/program/envelope.js.map +1 -1
  124. package/dist/program/error.d.ts +7 -2
  125. package/dist/program/error.d.ts.map +1 -1
  126. package/dist/program/error.js +17 -2
  127. package/dist/program/error.js.map +1 -1
  128. package/dist/program/index.d.ts +4 -4
  129. package/dist/program/index.d.ts.map +1 -1
  130. package/dist/program/index.js +23 -4
  131. package/dist/program/index.js.map +1 -1
  132. package/dist/program/instructions.d.ts +77 -9
  133. package/dist/program/instructions.d.ts.map +1 -1
  134. package/dist/program/instructions.js +241 -19
  135. package/dist/program/instructions.js.map +1 -1
  136. package/dist/program/orders.d.ts +7 -8
  137. package/dist/program/orders.d.ts.map +1 -1
  138. package/dist/program/orders.js +20 -22
  139. package/dist/program/orders.js.map +1 -1
  140. package/dist/program/types.d.ts +69 -7
  141. package/dist/program/types.d.ts.map +1 -1
  142. package/dist/program/types.js +11 -1
  143. package/dist/program/types.js.map +1 -1
  144. package/dist/rpc.d.ts +87 -1
  145. package/dist/rpc.d.ts.map +1 -1
  146. package/dist/rpc.js +280 -4
  147. package/dist/rpc.js.map +1 -1
  148. package/dist/shared/api_response.d.ts +16 -1
  149. package/dist/shared/api_response.d.ts.map +1 -1
  150. package/dist/shared/api_response.js +26 -1
  151. package/dist/shared/api_response.js.map +1 -1
  152. package/dist/shared/index.d.ts +3 -3
  153. package/dist/shared/index.d.ts.map +1 -1
  154. package/dist/shared/index.js +13 -6
  155. package/dist/shared/index.js.map +1 -1
  156. package/dist/shared/json.d.ts +5 -0
  157. package/dist/shared/json.d.ts.map +1 -0
  158. package/dist/shared/json.js +38 -0
  159. package/dist/shared/json.js.map +1 -0
  160. package/dist/shared/rejection.d.ts.map +1 -1
  161. package/dist/shared/rejection.js +8 -0
  162. package/dist/shared/rejection.js.map +1 -1
  163. package/dist/shared/scaling.d.ts +30 -18
  164. package/dist/shared/scaling.d.ts.map +1 -1
  165. package/dist/shared/scaling.js +153 -95
  166. package/dist/shared/scaling.js.map +1 -1
  167. package/dist/shared/signing.d.ts +17 -0
  168. package/dist/shared/signing.d.ts.map +1 -1
  169. package/dist/shared/signing.js +27 -0
  170. package/dist/shared/signing.js.map +1 -1
  171. package/dist/shared/types.d.ts +9 -7
  172. package/dist/shared/types.d.ts.map +1 -1
  173. package/dist/shared/types.js +12 -4
  174. package/dist/shared/types.js.map +1 -1
  175. package/dist/ws/client.browser.d.ts +4 -0
  176. package/dist/ws/client.browser.d.ts.map +1 -1
  177. package/dist/ws/client.browser.js +13 -1
  178. package/dist/ws/client.browser.js.map +1 -1
  179. package/dist/ws/client.node.d.ts +4 -0
  180. package/dist/ws/client.node.d.ts.map +1 -1
  181. package/dist/ws/client.node.js +13 -1
  182. package/dist/ws/client.node.js.map +1 -1
  183. package/dist/ws/index.d.ts +24 -0
  184. package/dist/ws/index.d.ts.map +1 -1
  185. package/dist/ws/index.js +176 -2
  186. package/dist/ws/index.js.map +1 -1
  187. package/dist/ws/subscriptions.d.ts +12 -1
  188. package/dist/ws/subscriptions.d.ts.map +1 -1
  189. package/dist/ws/subscriptions.js +11 -0
  190. package/dist/ws/subscriptions.js.map +1 -1
  191. package/dist/ws/types.d.ts +5 -0
  192. package/dist/ws/types.d.ts.map +1 -1
  193. package/dist/ws/types.js.map +1 -1
  194. package/package.json +6 -2
package/README.md CHANGED
@@ -69,7 +69,7 @@ async function main() {
69
69
  .amount(1_000_000n)
70
70
  .buildIx();
71
71
 
72
- // 4. Build, sign, and submit a limit order
72
+ // 4. Fetch/cache immutable trading rules, validate exactly, sign, and submit
73
73
  const response = await client.orders().limitOrder()
74
74
  .maker(keypair.publicKey)
75
75
  .bid()
@@ -177,12 +177,137 @@ ws.subscribe({
177
177
  });
178
178
  ```
179
179
 
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).
180
+ Book streams are snapshot-only: every accepted `book_update` replaces the full
181
+ top-20 view. `OrderbookState` discards equal/older `seq` values within a
182
+ subscription generation and accepts forward gaps. Call `beginGeneration()` on
183
+ reconnect/resubscribe; `resync: true` requires unsubscribe/resubscribe with the
184
+ same aggregation. Each `(orderbook, aggregation)` pair needs its own state.
185
+ Truncation flags are preserved and mean that side is not exhaustive. See
186
+ [`examples/ws_book_and_trades.ts`](examples/ws_book_and_trades.ts).
187
+ Each decoded bid and ask level includes exact decimal-string `quote_notional`.
188
+ For grouped books, `price` is a display bucket boundary, so quote liquidity
189
+ and totals must use `quote_notional` rather than `price * size`. The
190
+ price-to-base-size `OrderbookState` maps do not retain quote notional; read it
191
+ from the decoded `OrderBook` levels.
192
+ Ticker consumers should use the supplied `mid`; it is engine-authoritative and
193
+ may use one-sided-book or last-trade fallback.
194
+ REST depth is a coherent projection that may briefly lag a mutation. Use its
195
+ `revision` and `captured_at_ms` metadata, and expect revision gaps.
181
196
 
182
197
  ```typescript
183
198
  ws.subscribe({ type: "book_update", orderbook_ids: [orderbook.orderbookId], nSigFigs: 5, mantissa: 2 });
184
199
  ```
185
200
 
201
+ Order submission accepts decimal strings and uses exact `bigint` construction.
202
+ `submit()` fetches and caches `/api/orderbooks/{id}/decimals` before invoking a
203
+ signer. Direct `sign()`/`finalize()` calls require the returned `OrderbookRules`.
204
+ Raw amounts, explicit salts, and derived prices are preflighted against the same
205
+ signed-64-bit admission rules; no tick or size normalization is implicit.
206
+
207
+ ### Wallet Balances and SOL Action Planning
208
+
209
+ `depositTokenBalances()` returns a required exact nine-decimal
210
+ `native_sol_balance` alongside the separate mint-keyed SPL map. Initialize
211
+ `WalletDepositBalancesState` with `applyRestSnapshot(wallet, snapshot)` and feed
212
+ the outer `wallet_deposit_balances` channel's nested snapshot, absolute SPL,
213
+ absolute native-SOL, and status events to `applyEvent()`. Complete snapshots
214
+ replace state even after a higher component slot; status and wrong-wallet events
215
+ do not mutate it, pre-baseline component updates are ignored, and explicit-zero
216
+ SPL updates remove their mint. Matching SPL updates with invalid or negative
217
+ idle balances return `rejected` without mutation. `contextSlot` records the latest accepted
218
+ component observation rather than enforcing global monotonic ordering.
219
+ `combinedSolBalance()` sums native SOL and canonical WSOL with `bigint` precision
220
+ while retaining both stored values. REST response types are trusted rather than
221
+ runtime-decoded; WebSocket frames are validated strictly, while malformed REST
222
+ exact values fail when a state method scales them. The reducer owns its map
223
+ container but retains balance objects by reference, so treat applied payloads as
224
+ immutable.
225
+
226
+ `planSolSplit`, `planSolMerge`, `planSolRedeem`, and
227
+ `planNativeSolWithdrawal` return unsigned action plans with live fee/rent costs,
228
+ the action-specific reserve and spendable balance, and separate expected native
229
+ and canonical WSOL deltas. Each planner requires complete matching-wallet state,
230
+ checks the canonical account when needed, and fails closed when RPC estimates or
231
+ native reserve are unavailable. Unsponsored actions reserve the greater of live
232
+ costs and the applicable 0.001 SOL or 0.0035 SOL floor. Sponsored planning is
233
+ rejected until a concrete sponsor owns transaction fees and account rent.
234
+ An occupied canonical address is accepted only when it decodes as the wallet's
235
+ initialized, unfrozen Tokenkeg native-mint account.
236
+
237
+ Split plans consume canonical WSOL first and wrap only a shortfall in the same
238
+ transaction. Merge and redeem plans retain proceeds in the persistent canonical
239
+ account. Native withdrawal transfers directly when possible; otherwise it moves
240
+ only the shortfall through a bounded seeded temporary Tokenkeg account and closes
241
+ that temporary account before sending the exact native amount to the recipient.
242
+ The temporary account's create, initialize, WSOL transfer, close, and native
243
+ transfer instructions share one Solana transaction, so an instruction failure
244
+ rolls the entire conversion back atomically. No planner closes the canonical
245
+ account implicitly.
246
+
247
+ Native-keypair self-custody users can explicitly call `planWrapSol` with a
248
+ positive exact `bigint` lamport amount or call no-amount `planUnwrapWsolAll`.
249
+ These standalone planners require the authenticated Trading Wallet's local
250
+ native signing strategy; wallet-adapter and Privy strategies are rejected before
251
+ RPC planning. Wrap creates or reuses only that wallet's canonical Tokenkeg ATA,
252
+ transfers the exact amount, and runs `SyncNative`, the Token Program instruction
253
+ that recalculates the WSOL token amount from account lamports. Wrap retains the
254
+ ordinary 0.0035 SOL account-creation or 0.001 SOL existing-account reserve floor
255
+ above lower live costs. Live decoded canonical amounts must match the authoritative
256
+ wallet state. Wrap also requires an existing account's full lamports to equal
257
+ its decoded token amount plus native rent reserve; unsynchronized direct
258
+ donations reject before `SyncNative` can make the projected canonical delta
259
+ inexact. Unwrap-all still accepts such excess and returns it on close.
260
+
261
+ Unwrap-all accepts no partial amount and closes the entire positive canonical
262
+ account back to the same wallet. Its `SolActionCosts` fields contain the fresh fee, zero
263
+ upfront rent, no account creation, and no sponsorship. Availability reserves
264
+ only that fee rather than the ordinary persistent-account floor;
265
+ `unwrapAllSolBalanceAvailability(components, costs)` validates that complete
266
+ cost tuple before deriving fee-only fields. The
267
+ native delta credits every live account lamport, including refunded rent or
268
+ direct donations, minus the fee and removes the full canonical token amount.
269
+ Closing a pre-existing account returns all of its WSOL and means a future WSOL
270
+ action may need to fund account rent again. Ordinary split, merge, redeem,
271
+ claim, order, and native-withdraw paths never call these conversion planners or
272
+ close canonical WSOL.
273
+
274
+ For either explicit conversion, rebuild immediately before signing,
275
+ submit with `signAndSubmitPreparedTxConfirmedWithSlot` so the wallet cannot
276
+ replace the fee-estimated message, and refresh a complete snapshot covering its
277
+ slot before restoring action authority. Prepared submission is unavailable for
278
+ Privy because its final signed bytes cannot be verified by the SDK. Atomic
279
+ execution does not resolve uncertain submission or confirmation errors; inspect
280
+ authoritative balances before retrying. See the
281
+ [persistent canonical WSOL ADR](../docs/adr/0001-persistent-canonical-wsol.md).
282
+
283
+ WebSocket clients are owned independently from `Auth`; logout does not clean them
284
+ up. For each retained client, `clearAuthedSubscriptions()` purges User/wallet
285
+ reconnect tracking and queued authenticated messages. It does not stop an
286
+ already-open server stream; send the matching unsubscribe or disconnect the socket
287
+ for live teardown.
288
+
289
+ The [`deposit_token_balances`](examples/deposit_token_balances.ts) self-custody
290
+ example is manual-only and runs with `LIGHTCONE_ENV=local` or `staging` only
291
+ when `SDK_API_URL`, `SDK_WS_URL`, `SDK_RPC_URL`, and `SDK_PROGRAM_ID` are all
292
+ unset. It sends 0.001 SOL to the Python SDK wallet configured by
293
+ `LIGHTCONE_WALLET_PATH_PYTHON` from the distinct sender configured by
294
+ `LIGHTCONE_WALLET_PATH_TS`, confirms with a slot, and refreshes a complete
295
+ snapshot at that slot. Running it moves funds. If it fails after submission,
296
+ inspect authoritative balances before retrying because funds may already have moved.
297
+
298
+ The separate [`wsol_conversion`](examples/wsol_conversion.ts) example runs with
299
+ the TypeScript wallet in local aggregate runs and is included when the globally
300
+ gated stateful example workflow is enabled for staging CI; that workflow
301
+ currently disables all stateful CI jobs. Local runs may use a paid RPC while
302
+ retaining built-in API, WebSocket, and program identity; an enabled staging-CI
303
+ run may supply its managed endpoints. Direct staging runs remain override-free.
304
+ It permits a pre-existing canonical balance, wraps exactly 0.001 SOL, prints the
305
+ exact wallet, costs, full-account return, and future-rent warning, then unwraps
306
+ the complete canonical account without pausing. It retains each frozen
307
+ projection until a complete REST snapshot covers the confirmed slot. Any
308
+ planning, signing, submission, or uncertain-confirmation failure exits without
309
+ automatic retry.
310
+
186
311
  ### Step 5: Cancel an Order
187
312
 
188
313
  ```typescript
@@ -208,6 +333,8 @@ const txHash = await client.positions().merge()
208
333
  .signAndSubmit();
209
334
  ```
210
335
 
336
+ `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.
337
+
211
338
  ### Step 7: Withdraw
212
339
 
213
340
  ```typescript
@@ -222,6 +349,17 @@ const withdrawIx = client.positions().withdraw()
222
349
 
223
350
  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
351
 
352
+ 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.
353
+
354
+ Use `walletDisplayName(session.user, session.auth_method)` to show a shortened
355
+ label for the wallet the session trades with, regardless of login identity.
356
+
357
+ `session.user.max_slippage_preference` is an exact decimal string strictly below
358
+ `10`, or `null` until one is stored. Persist a value greater than zero and less
359
+ than 10 using `client.auth().updateMaxSlippagePreference(value)`; the method
360
+ returns the canonical exact decimal string. Values at or above 10% remain valid
361
+ order protection but are not remembered through this API.
362
+
225
363
  ### Cookie handling
226
364
 
227
365
  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 +373,17 @@ After login succeeds, the SDK stores the session token internally and attaches i
235
373
 
236
374
  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
375
 
376
+ > **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.
377
+
238
378
  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
379
 
240
380
  ```typescript
241
381
  // Inside a server route, after extracting the auth_token cookie
242
382
  // from the incoming request:
243
- const balances = await client
383
+ const snapshot = await client
244
384
  .positions()
245
- .depositTokenBalancesWithAuth(authToken);
385
+ .depositTokenBalancesWithCookies(undefined, authToken);
386
+ console.log(`snapshot slot ${snapshot.context_slot}: ${Object.keys(snapshot.balances).length} balances`);
246
387
 
247
388
  const positions = await client
248
389
  .positions()
@@ -279,6 +420,8 @@ Each environment configures the API URL, WebSocket URL, Solana RPC URL, and on-c
279
420
 
280
421
  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
422
 
423
+ 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.
424
+
282
425
  ## Examples
283
426
 
284
427
  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 +431,13 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
288
431
  | Example | Description |
289
432
  |---------|-------------|
290
433
  | [`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 |
434
+ | [`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
435
 
293
436
  ### Market Discovery & Data
294
437
 
295
438
  | Example | Description |
296
439
  |---------|-------------|
297
- | [`markets`](examples/markets.ts) | Featured markets, paginated listing, fetch by pubkey, search, platform deposit assets via `globalDepositAssets()` |
440
+ | [`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
441
  | [`orderbook`](examples/orderbook.ts) | Fetch orderbook depth (bids/asks) and decimal precision metadata |
299
442
  | [`trades`](examples/trades.ts) | Recent trade history with cursor-based pagination (per-orderbook and market-wide) |
300
443
  | [`price_history`](examples/price_history.ts) | Historical candlestick data (OHLCV) at various resolutions |
@@ -305,7 +448,7 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
305
448
 
306
449
  | Example | Description |
307
450
  |---------|-------------|
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 |
451
+ | [`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
452
 
310
453
  ### Cancelling Orders
311
454
 
@@ -322,6 +465,16 @@ All examples are runnable with `npx tsx examples/<name>.ts`. Examples default to
322
465
  | [`onchain_transactions`](examples/onchain_transactions.ts) | Build, sign, and submit mint/merge complete set and increment nonce on-chain |
323
466
  | [`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
467
 
468
+ ### Manual Fund-Moving Operations
469
+
470
+ These examples refuse production and endpoint overrides and are intentionally
471
+ excluded from routine example runs.
472
+
473
+ | Example | Description |
474
+ |---------|-------------|
475
+ | [`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 |
476
+ | [`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 |
477
+
325
478
  ### WebSocket Streaming
326
479
 
327
480
  | Example | Description |
@@ -359,6 +512,7 @@ When the backend rejects a request (insufficient balance, expired order, etc.),
359
512
  | `errorCode` | `string \| undefined` | API-level error code (for example `"NOT_FOUND"` or `"INVALID_ARGUMENT"`) |
360
513
  | `errorLogId` | `string \| undefined` | Backend support correlation ID (`LCERR_*`) |
361
514
  | `requestId` | `string \| undefined` | SDK-generated `x-request-id` for cross-service tracing |
515
+ | `existingMethod` | `string \| undefined` | Primary method of the conflicting Account when identity ownership has one deterministic owner |
362
516
 
363
517
  `ApiRejectedDetails.toString()` formats all present fields as a multi-line report for logs or support tickets.
364
518
 
@@ -430,14 +584,29 @@ The SDK generates a UUID v4 `x-request-id` header on every HTTP request. On reje
430
584
  | `NotFound` | 404 - resource not found |
431
585
  | `BadRequest` | 400 - invalid request |
432
586
  | `Timeout` | Request timed out |
433
- | `MaxRetriesExceeded` | All retry attempts exhausted |
587
+ | `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
588
 
435
589
  ## Retry Strategy
436
590
 
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.
591
+ - **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.
592
+ - **Non-idempotent requests** (order submit, cancel, auth): `RetryPolicy.None` - no automatic retry, which prevents duplicate side effects.
439
593
  - Customizable per-call with `RetryPolicy.custom(config)`.
440
594
 
595
+ ### Credential restoration (401 recovery)
596
+
597
+ 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:
598
+
599
+ ```typescript
600
+ client.setCredentialRestorer(async () => {
601
+ // e.g. ask the auth provider's SDK to refresh the session
602
+ return await refreshSession();
603
+ });
604
+ ```
605
+
606
+ 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.
607
+
608
+ 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`).
609
+
441
610
  ## Trigger Orders
442
611
 
443
612
  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"]}
@@ -48,6 +48,46 @@ export interface GoogleAccountData {
48
48
  family_name?: string;
49
49
  avatar_url?: string;
50
50
  }
51
+ /** Canonical address for a passwordless Email login identity. */
52
+ export interface EmailAccountData {
53
+ email: string;
54
+ }
55
+ /** Public login-method names exposed in profiles and bounded conflicts. */
56
+ export type LinkedIdentityType = "email" | "google" | "x" | "wallet";
57
+ /** Connected Login Identity without repeated Account-level Privy wallet data. */
58
+ export type LinkedIdentity = {
59
+ type: "email";
60
+ account: EmailAccountData;
61
+ } | {
62
+ type: "google";
63
+ account: GoogleAccountData;
64
+ } | {
65
+ type: "x";
66
+ account: XAccountData;
67
+ } | {
68
+ type: "wallet";
69
+ address: string;
70
+ chain: ChainType;
71
+ };
72
+ /** Verified method and canonical identifier that initiated Privy authentication. */
73
+ export type LinkedIdentitySelector = {
74
+ type: "email";
75
+ email: string;
76
+ } | {
77
+ type: "google";
78
+ email: string;
79
+ } | {
80
+ type: "x";
81
+ username: string;
82
+ } | {
83
+ type: "wallet";
84
+ address: string;
85
+ chain: ChainType;
86
+ };
87
+ /** Register-or-sync request naming the verified attempted identity. */
88
+ export interface RegisterPrivyRequest {
89
+ attempted_identity: LinkedIdentitySelector;
90
+ }
51
91
  /**
52
92
  * The login identity — how the user authenticates. `type` narrows the variant.
53
93
  *
@@ -57,6 +97,10 @@ export interface GoogleAccountData {
57
97
  * self-custody.
58
98
  */
59
99
  export type UserIdentity = {
100
+ type: "email";
101
+ account: EmailAccountData;
102
+ privy: UserPrivyData;
103
+ } | {
60
104
  type: "google";
61
105
  account: GoogleAccountData;
62
106
  privy: UserPrivyData;
@@ -74,6 +118,10 @@ export type UserIdentity = {
74
118
  export interface User {
75
119
  user_id: string;
76
120
  identity: UserIdentity;
121
+ /** Every verified login identity connected to the Account, primary first. */
122
+ linked_identities?: LinkedIdentity[];
123
+ /** Remembered account-wide percentage below 10%; null until one is stored. */
124
+ max_slippage_preference: string | null;
77
125
  /** X account connected by a non-X-identity user; absent when identity is X. */
78
126
  connected_x?: XAccountData;
79
127
  }
@@ -88,8 +136,12 @@ export interface SessionResponse {
88
136
  auth_method: AuthMethod;
89
137
  is_beta: boolean;
90
138
  }
91
- /** Human-readable login-method label ("Google" / "X" / "Solana"). */
92
- export declare function identityText(identity: UserIdentity): "Google" | "X" | "Solana";
139
+ /** Exact decimal-string body used to update and return max slippage. */
140
+ export interface MaxSlippagePreferenceBody {
141
+ max_slippage_preference: string;
142
+ }
143
+ /** Human-readable login-method label ("Email" / "Google" / "X" / "Solana"). */
144
+ export declare function identityText(identity: UserIdentity): "Email" | "Google" | "X" | "Solana";
93
145
  /** Privy account data, regardless of identity type. */
94
146
  export declare function userPrivy(user: User): UserPrivyData | undefined;
95
147
  /** The X account, whether it is the login identity or a connected account. */
@@ -104,10 +156,12 @@ export declare function userXAccount(user: User): XAccountData | undefined;
104
156
  * wallet that signed in.
105
157
  */
106
158
  export declare function tradingWallet(user: User, authMethod: AuthMethod): string;
159
+ /** Short display label for the wallet a session operates as. */
160
+ export declare function walletDisplayName(user: User, authMethod: AuthMethod): string;
107
161
  /**
108
- * Best display name for the user. Google: `name`, falling back to the email;
109
- * X: `display_name`, falling back to the username; wallet identities show the
110
- * shortened address (`FRGk...WcPR`).
162
+ * Best display name for the user. Email addresses are limited to 20 characters;
163
+ * Google uses `name` with an email fallback; X uses `display_name` with a
164
+ * username fallback; wallet identities show the shortened address (`FRGk...WcPR`).
111
165
  */
112
166
  export declare function displayName(user: User): string;
113
167
  /** Avatar URL from the login identity's OAuth provider, if any. */