@lightconexyz/lightcone-sdk 0.8.3 → 0.8.4-rc.54

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 (2) hide show
  1. package/README.md +72 -54
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -123,23 +123,17 @@ submission is unavailable until signed v1 bytes can be verified.
123
123
 
124
124
  ## Quick Start
125
125
 
126
+ This example authenticates and subscribes to public market data. For funded order submission, use the [trading example](examples/submit_order.ts).
127
+
126
128
  ```typescript
127
- import { Keypair, PublicKey } from "@solana/web3.js";
129
+ import { Keypair } from "@solana/web3.js";
128
130
  import {
129
131
  LightconeClient,
130
- DepositSource,
131
132
  auth,
132
133
  } from "@lightconexyz/lightcone-sdk";
133
134
 
134
135
  async function main() {
135
- const client = LightconeClient.builder()
136
- .depositSource(DepositSource.Market)
137
- .transactionResources({
138
- computeUnitLimit: 200_000,
139
- loadedAccountsDataSizeLimit: 65_536,
140
- priorityFeeLamports: 0n,
141
- })
142
- .build();
136
+ const client = LightconeClient.builder().build();
143
137
  const keypair = Keypair.generate();
144
138
 
145
139
  // 1. Authenticate
@@ -162,31 +156,7 @@ async function main() {
162
156
  throw new Error("Selected market has no orderbooks");
163
157
  }
164
158
 
165
- // 3. Deposit collateral to the global pool
166
- const depositMint = new PublicKey(market.depositAssets[0].pubkey);
167
- const depositIx = client.positions().deposit()
168
- .user(keypair.publicKey)
169
- .mint(depositMint)
170
- .amount(1_000_000n)
171
- .buildIx();
172
-
173
- // 4. Fetch/cache immutable trading rules, validate exactly, sign, and submit
174
- const response = await client.orders().limitOrder()
175
- .maker(keypair.publicKey)
176
- .bid()
177
- .price("0.55")
178
- .size("100")
179
- .submit(client, orderbook);
180
- console.log("Order submitted:", response);
181
-
182
- // 5. Withdraw from the global pool
183
- const withdrawIx = client.positions().withdraw()
184
- .user(keypair.publicKey)
185
- .mint(depositMint)
186
- .amount(1_000_000n)
187
- .buildIx();
188
-
189
- // 6. Stream real-time updates
159
+ // 3. Subscribe to real-time updates.
190
160
  const ws = client.ws();
191
161
  await ws.connect();
192
162
  ws.subscribe({ type: "book_update", orderbook_ids: [orderbook.orderbookId] });
@@ -197,6 +167,8 @@ main().catch(console.error);
197
167
 
198
168
  ## Start Trading
199
169
 
170
+ These excerpts use top-level `await` in an ES module. Use a funded keypair for the selected environment. The [submit example](examples/submit_order.ts) confirms the deposit and waits for the API balance before ordering.
171
+
200
172
  ```typescript
201
173
  import * as fs from "fs";
202
174
  import * as os from "os";
@@ -205,6 +177,7 @@ import { Keypair, PublicKey } from "@solana/web3.js";
205
177
  import {
206
178
  LightconeClient,
207
179
  DepositSource,
180
+ auth,
208
181
  } from "@lightconexyz/lightcone-sdk";
209
182
 
210
183
  function readKeypairFile(filePath: string): Keypair {
@@ -220,8 +193,19 @@ const keypair = readKeypairFile("~/.config/solana/id.json");
220
193
  // Defaults to Prod. Use .env(LightconeEnv.Staging) for staging.
221
194
  const client = LightconeClient.builder()
222
195
  .nativeSigner(keypair)
223
- .depositSource(DepositSource.Market)
196
+ .depositSource(DepositSource.Global)
197
+ .transactionResources({
198
+ computeUnitLimit: 200_000,
199
+ loadedAccountsDataSizeLimit: 1_048_576,
200
+ priorityFeeLamports: 0n,
201
+ })
224
202
  .build();
203
+ const nonce = await client.auth().getNonce();
204
+ const signed = auth.signLoginMessage(keypair, nonce);
205
+ await client.auth().loginWithMessage(
206
+ signed.message, signed.signature_bs58, signed.pubkey_bytes
207
+ );
208
+ client.setOrderNonce(await client.orders().currentNonce(keypair.publicKey));
225
209
  ```
226
210
 
227
211
  ### Step 1: Find a Market
@@ -241,13 +225,42 @@ if (!orderbook) {
241
225
 
242
226
  ### Step 2: Deposit Collateral
243
227
 
228
+ The amount is in the deposit mint's smallest units. Instruction construction alone does not transfer collateral. Submit and confirm the transaction, then wait for the API balance to cover the order. Select the deposit mint backing the orderbook's quote token, as shown in the [submit example](examples/submit_order.ts).
229
+
230
+ Calculate the deposit from the same price and size used by the order. Poll at most 15 times, with two seconds between unsuccessful reads. A timeout or read failure stops before order submission. Check the confirmed deposit before repeating this step.
231
+
244
232
  ```typescript
245
- const depositMint = new PublicKey(market.depositAssets[0].pubkey);
246
- const depositIx = client.positions().deposit()
233
+ import { shared } from "@lightconexyz/lightcone-sdk";
234
+ import { program } from "@lightconexyz/lightcone-sdk";
235
+
236
+ const rules = await client.orderbooks().decimals(orderbook.orderbookId);
237
+ const orderPrice = "0.55"; // Quote tokens per base token.
238
+ const orderSize = "1"; // Base tokens.
239
+ const quoteAtoms = shared.scalePriceSize(
240
+ orderPrice, orderSize, program.OrderSide.BID, rules
241
+ ).quoteAtoms;
242
+ const depositMint = new PublicKey(orderbook.quote.depositAsset);
243
+ const depositContext = await client.transactionContext();
244
+ const depositTx = client.positions().deposit()
247
245
  .user(keypair.publicKey)
248
246
  .mint(depositMint)
249
- .amount(1_000_000n)
250
- .buildIx();
247
+ .amount(quoteAtoms)
248
+ .buildTx(depositContext);
249
+ await client.signAndSubmitTxConfirmedWithSlot(depositTx);
250
+
251
+ // Confirmation can precede indexing. Check the available global collateral.
252
+ for (let attempt = 0; attempt < 15; attempt++) {
253
+ const snapshot = await client.positions().depositTokenBalances();
254
+ const entry = Object.values(snapshot.balances).find(
255
+ (balance) => balance.mint === depositMint.toBase58()
256
+ );
257
+ const idleAtoms = shared.exactScaledInteger(entry?.idle ?? "0", rules.quoteDecimals);
258
+ if (idleAtoms >= quoteAtoms) break;
259
+ if (attempt === 14) {
260
+ throw new Error("Global collateral is not indexed yet; order was not submitted");
261
+ }
262
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
263
+ }
251
264
  ```
252
265
 
253
266
  ### Step 3: Place an Order
@@ -256,8 +269,8 @@ const depositIx = client.positions().deposit()
256
269
  const order = await client.orders().limitOrder()
257
270
  .maker(keypair.publicKey)
258
271
  .bid()
259
- .price("0.55")
260
- .size("1")
272
+ .price(orderPrice)
273
+ .size(orderSize)
261
274
  .submit(client, orderbook);
262
275
  ```
263
276
 
@@ -268,7 +281,7 @@ import { asPubkeyStr } from "@lightconexyz/lightcone-sdk";
268
281
 
269
282
  const open = await client
270
283
  .orders()
271
- .getUserOrders(keypair.publicKey.toBase58(), 50);
284
+ .getUserOrders(50);
272
285
  const ws = client.ws();
273
286
  await ws.connect();
274
287
  ws.subscribe({ type: "book_update", orderbook_ids: [orderbook.orderbookId] });
@@ -305,6 +318,8 @@ signer. Direct `sign()`/`finalize()` calls require the returned `OrderbookRules`
305
318
  Raw amounts, explicit salts, and derived prices are preflighted against the same
306
319
  signed-64-bit admission rules; no tick or size normalization is implicit.
307
320
 
321
+ The envelope generates a salt when omitted. The price is quote tokens per base token, and the size is base tokens. Example values must satisfy the selected orderbook's trading rules and available collateral.
322
+
308
323
  ### Wallet Balances and SOL Action Planning
309
324
 
310
325
  `depositTokenBalances()` returns a required exact nine-decimal
@@ -415,7 +430,7 @@ automatic retry.
415
430
  ### Step 5: Cancel an Order
416
431
 
417
432
  ```typescript
418
- import { program, asPubkeyStr } from "@lightconexyz/lightcone-sdk";
433
+ // program and asPubkeyStr were imported in the preceding steps.
419
434
 
420
435
  const signature = program.signCancelOrder(order.order_hash, keypair);
421
436
  await client.orders().cancel({
@@ -427,6 +442,8 @@ await client.orders().cancel({
427
442
 
428
443
  ### Step 6: Exit a Position
429
444
 
445
+ Merge only when the wallet holds a complete set of outcome tokens for this deposit mint. Cancelling an unfilled order does not create that set. To return unused global collateral, proceed to withdrawal.
446
+
430
447
  ```typescript
431
448
  // Uses the explicit transaction resources configured on the client.
432
449
  // The signature means RPC acceptance; confirm before dependent account operations.
@@ -452,7 +469,9 @@ const withdrawIx = client.positions().withdraw()
452
469
 
453
470
  ## Authentication
454
471
 
455
- 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.
472
+ Authentication is required for user-specific endpoints. Fetch `/api/auth/nonce`, then sign the exact message `Sign in to Lightcone\nNonce: {nonce}` with ED25519. Use `auth.signLoginMessage` to construct this challenge, then exchange the signed message for a session. Do not sign a timestamp or the nonce alone.
473
+
474
+ Authenticate before connecting a private WebSocket. Node clients send the session cookie during the upgrade, and browsers supply the cookie automatically. Private user subscriptions require `wallet_address`. Derive the Trading Wallet with `tradingWallet(session.user, session.auth_method)`. Refer to the [authenticated streaming example](examples/ws_user_and_market.ts).
456
475
 
457
476
  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.
458
477
 
@@ -472,30 +491,29 @@ After login succeeds, the SDK stores the session token internally and attaches i
472
491
  - **Node / non-browser**: token is stored on the `LightconeHttp` instance and added as a `Cookie` header per request.
473
492
  - **Browser**: requests use `credentials: "include"` and the runtime supplies the cookie automatically — the SDK's internal store is unused.
474
493
 
475
- ### Server-side cookie forwarding (`*WithAuth` variants)
494
+ ### Server-side cookie forwarding (`*WithCookies` variants)
476
495
 
477
- > **Naming note.** The `WithAuth` suffix does **not** mean other methods are unauthed — most SDK methods that talk to authed endpoints (e.g. `positions().positions()`, `metrics().user()`) read auth from the SDK's process-wide token store / browser cookie automatically; that's the typical client-side path. The `*WithAuth(authToken: string)` siblings exist for **server-side rendering (SSR) and route-handler callers** where the per-request browser cookie can't propagate to the shared client. Those callers extract the token from the incoming request and pass it explicitly. Same wire contract, different credentials path.
496
+ Ordinary authenticated methods use the client's session or the browser cookie. Server-side rendering and route handlers must pass the incoming raw `Cookie` header to the corresponding `*WithCookies` method.
478
497
 
479
- 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.
498
+ The cookie name is `lightcone-token`. Do not install a user's token on a client shared between requests from different users.
480
499
 
481
500
  > **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.
482
501
 
483
- For these cases, authed methods that need per-call forwarding ship a `*WithAuth(authToken)` sibling that injects the cookie just for that one call:
502
+ These methods forward the supplied header for one call:
484
503
 
485
504
  ```typescript
486
- // Inside a server route, after extracting the auth_token cookie
487
- // from the incoming request:
505
+ // cookieHeader is the incoming raw Cookie header, including lightcone-token=...
488
506
  const snapshot = await client
489
507
  .positions()
490
- .depositTokenBalancesWithCookies(undefined, authToken);
508
+ .depositTokenBalancesWithCookies(undefined, cookieHeader);
491
509
  console.log(`snapshot slot ${snapshot.context_slot}: ${Object.keys(snapshot.balances).length} balances`);
492
510
 
493
511
  const positions = await client
494
512
  .positions()
495
- .positionsWithAuth(authToken);
513
+ .positionsWithCookies(cookieHeader);
496
514
  ```
497
515
 
498
- In a browser context these methods are equivalent to their non-`WithAuth` counterparts because the runtime is already attaching the cookie via `credentials: "include"`.
516
+ In browsers, use the ordinary methods. The browser supplies the cookie through `credentials: "include"`.
499
517
 
500
518
  ## Environment Configuration
501
519
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightconexyz/lightcone-sdk",
3
- "version": "0.8.3",
3
+ "version": "0.8.4-rc.54",
4
4
  "description": "TypeScript SDK for Lightcone",
5
5
  "author": "Lightcone",
6
6
  "license": "MIT",