@lightconexyz/lightcone-sdk 0.8.1 → 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 (95) hide show
  1. package/README.md +150 -3
  2. package/dist/auth/client.d.ts +17 -1
  3. package/dist/auth/client.d.ts.map +1 -1
  4. package/dist/auth/client.js +68 -0
  5. package/dist/auth/client.js.map +1 -1
  6. package/dist/auth/index.d.ts +57 -5
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +26 -5
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/client.d.ts +23 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +38 -2
  13. package/dist/client.js.map +1 -1
  14. package/dist/context.d.ts +23 -2
  15. package/dist/context.d.ts.map +1 -1
  16. package/dist/context.js +238 -17
  17. package/dist/context.js.map +1 -1
  18. package/dist/domain/market/outcome.d.ts +9 -3
  19. package/dist/domain/market/outcome.d.ts.map +1 -1
  20. package/dist/domain/market/outcome.js +10 -7
  21. package/dist/domain/market/outcome.js.map +1 -1
  22. package/dist/domain/market/wire.d.ts +3 -3
  23. package/dist/domain/market/wire.d.ts.map +1 -1
  24. package/dist/domain/market/wire.js.map +1 -1
  25. package/dist/domain/orderbook/wire.d.ts +5 -0
  26. package/dist/domain/orderbook/wire.d.ts.map +1 -1
  27. package/dist/domain/orderbook/wire.js.map +1 -1
  28. package/dist/domain/position/client.d.ts +160 -4
  29. package/dist/domain/position/client.d.ts.map +1 -1
  30. package/dist/domain/position/client.js +592 -4
  31. package/dist/domain/position/client.js.map +1 -1
  32. package/dist/domain/position/index.d.ts +54 -3
  33. package/dist/domain/position/index.d.ts.map +1 -1
  34. package/dist/domain/position/index.js +1 -0
  35. package/dist/domain/position/index.js.map +1 -1
  36. package/dist/domain/position/state.d.ts +118 -0
  37. package/dist/domain/position/state.d.ts.map +1 -0
  38. package/dist/domain/position/state.js +262 -0
  39. package/dist/domain/position/state.js.map +1 -0
  40. package/dist/error.d.ts +8 -2
  41. package/dist/error.d.ts.map +1 -1
  42. package/dist/error.js +14 -1
  43. package/dist/error.js.map +1 -1
  44. package/dist/index.d.ts +4 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +3 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/prelude.d.ts +6 -5
  49. package/dist/prelude.d.ts.map +1 -1
  50. package/dist/prelude.js +10 -1
  51. package/dist/prelude.js.map +1 -1
  52. package/dist/privy/client.d.ts +4 -0
  53. package/dist/privy/client.d.ts.map +1 -1
  54. package/dist/privy/client.js +4 -0
  55. package/dist/privy/client.js.map +1 -1
  56. package/dist/rpc.d.ts +46 -1
  57. package/dist/rpc.d.ts.map +1 -1
  58. package/dist/rpc.js +125 -0
  59. package/dist/rpc.js.map +1 -1
  60. package/dist/shared/api_response.d.ts +7 -0
  61. package/dist/shared/api_response.d.ts.map +1 -1
  62. package/dist/shared/api_response.js +16 -0
  63. package/dist/shared/api_response.js.map +1 -1
  64. package/dist/shared/index.d.ts +1 -1
  65. package/dist/shared/index.d.ts.map +1 -1
  66. package/dist/shared/index.js +2 -1
  67. package/dist/shared/index.js.map +1 -1
  68. package/dist/shared/signing.d.ts +17 -0
  69. package/dist/shared/signing.d.ts.map +1 -1
  70. package/dist/shared/signing.js +27 -0
  71. package/dist/shared/signing.js.map +1 -1
  72. package/dist/shared/types.d.ts +3 -1
  73. package/dist/shared/types.d.ts.map +1 -1
  74. package/dist/shared/types.js +12 -4
  75. package/dist/shared/types.js.map +1 -1
  76. package/dist/ws/client.browser.d.ts +4 -0
  77. package/dist/ws/client.browser.d.ts.map +1 -1
  78. package/dist/ws/client.browser.js +13 -1
  79. package/dist/ws/client.browser.js.map +1 -1
  80. package/dist/ws/client.node.d.ts +4 -0
  81. package/dist/ws/client.node.d.ts.map +1 -1
  82. package/dist/ws/client.node.js +13 -1
  83. package/dist/ws/client.node.js.map +1 -1
  84. package/dist/ws/index.d.ts +24 -0
  85. package/dist/ws/index.d.ts.map +1 -1
  86. package/dist/ws/index.js +162 -1
  87. package/dist/ws/index.js.map +1 -1
  88. package/dist/ws/subscriptions.d.ts +12 -1
  89. package/dist/ws/subscriptions.d.ts.map +1 -1
  90. package/dist/ws/subscriptions.js +11 -0
  91. package/dist/ws/subscriptions.js.map +1 -1
  92. package/dist/ws/types.d.ts +5 -0
  93. package/dist/ws/types.d.ts.map +1 -1
  94. package/dist/ws/types.js.map +1 -1
  95. package/package.json +2 -1
@@ -1,13 +1,92 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.Positions = void 0;
7
+ exports.nativeWithdrawSeed = nativeWithdrawSeed;
4
8
  const web3_js_1 = require("@solana/web3.js");
9
+ const spl_token_1 = require("@solana/spl-token");
10
+ const sha256_1 = require("@noble/hashes/sha256");
11
+ const bs58_1 = __importDefault(require("bs58"));
12
+ const auth_1 = require("../../auth");
5
13
  const context_1 = require("../../context");
14
+ const error_1 = require("../../error");
6
15
  const http_1 = require("../../http");
7
16
  const instructions_1 = require("../../program/instructions");
17
+ const rpc_1 = require("../../rpc");
8
18
  const pda_1 = require("../../program/pda");
9
19
  const accounts_1 = require("../../program/accounts");
20
+ const utils_1 = require("../../program/utils");
21
+ const signing_1 = require("../../shared/signing");
22
+ const state_1 = require("./state");
10
23
  const builders_1 = require("./builders");
24
+ /** Byte allocation for a legacy SPL Token Program (Tokenkeg) account. */
25
+ const TOKEN_ACCOUNT_SPACE = 165;
26
+ /** Largest exact lamport amount accepted by Solana transaction instructions. */
27
+ const MAX_U64 = 0xffffffffffffffffn;
28
+ /** Reject non-bigint, non-positive, or non-u64 lamports before any RPC side effect. */
29
+ function assertSolActionAmount(amountLamports, action) {
30
+ if (typeof amountLamports !== "bigint") {
31
+ throw error_1.SdkError.validation(`${action} amount must be exact bigint lamports`);
32
+ }
33
+ if (amountLamports <= 0n) {
34
+ throw error_1.SdkError.validation(`${action} amount must be greater than zero`);
35
+ }
36
+ if (amountLamports > MAX_U64) {
37
+ throw error_1.SdkError.validation(`${action} amount must fit u64`);
38
+ }
39
+ }
40
+ function assertUnsponsoredPlan(sponsored) {
41
+ if (sponsored) {
42
+ throw error_1.SdkError.validation("sponsored SOL action planning is not supported");
43
+ }
44
+ }
45
+ /**
46
+ * Derive the cross-SDK temporary-account seed for native withdrawal.
47
+ *
48
+ * SHA-256 receives the ASCII domain `lightcone:wsol-withdraw:v1`, one zero byte,
49
+ * raw 32-byte blockhash, wallet, and recipient keys, the amount as unsigned
50
+ * eight-byte big-endian lamports, then the one-byte attempt. The first 16 digest
51
+ * bytes become 32 lowercase hexadecimal ASCII characters for Solana's seed limit.
52
+ */
53
+ function nativeWithdrawSeed(recentBlockhash, wallet, recipient, amountLamports, attempt) {
54
+ if (!Number.isInteger(attempt) || attempt < 0 || attempt > 255) {
55
+ throw error_1.SdkError.validation("temporary WSOL seed attempt must fit u8");
56
+ }
57
+ if (amountLamports < 0n || amountLamports > MAX_U64) {
58
+ throw error_1.SdkError.validation("withdraw amount must fit u64");
59
+ }
60
+ const domain = new TextEncoder().encode("lightcone:wsol-withdraw:v1");
61
+ const blockhash = bs58_1.default.decode(recentBlockhash);
62
+ if (blockhash.length !== 32) {
63
+ throw error_1.SdkError.validation("recent blockhash must decode to 32 bytes");
64
+ }
65
+ const preimage = new Uint8Array(domain.length + 1 + 32 + 32 + 32 + 8 + 1);
66
+ let offset = 0;
67
+ preimage.set(domain, offset);
68
+ offset += domain.length;
69
+ preimage[offset++] = 0;
70
+ preimage.set(blockhash, offset);
71
+ offset += 32;
72
+ preimage.set(wallet.toBytes(), offset);
73
+ offset += 32;
74
+ preimage.set(recipient.toBytes(), offset);
75
+ offset += 32;
76
+ new DataView(preimage.buffer).setBigUint64(offset, amountLamports, false);
77
+ offset += 8;
78
+ preimage[offset] = attempt;
79
+ return Array.from((0, sha256_1.sha256)(preimage).slice(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join("");
80
+ }
81
+ /**
82
+ * Plans position operations and explicit canonical WSOL conversions.
83
+ *
84
+ * Explicit conversion state flows as follows: complete matching wallet state and
85
+ * native keypair -> live account and cost reads -> signer, account, reserve, and
86
+ * amount guards -> fee-prepared plan -> unchanged prepared submission -> complete
87
+ * snapshot covering the confirmed slot. An uncertain submission returns control
88
+ * to the caller, which refreshes authoritative state before planning again.
89
+ */
11
90
  class Positions {
12
91
  client;
13
92
  constructor(client) {
@@ -71,9 +150,12 @@ class Positions {
71
150
  return this.client.http.getWithCookies(url, http_1.RetryPolicy.Idempotent, cookieHeader);
72
151
  }
73
152
  /**
74
- * Get a confirmed-slot snapshot of the authenticated user's deposit-token
75
- * balances. When supplied, `minContextSlot` prevents an older cached
76
- * snapshot from satisfying the request.
153
+ * Fetch a complete authenticated SPL and native-SOL balance snapshot.
154
+ *
155
+ * `minContextSlot` lower-bounds the cross-component snapshot. Native SOL is
156
+ * required canonical nine-decimal text and remains outside the SPL map. The
157
+ * generic HTTP layer trusts that shape at runtime; WebSocket frames are decoded
158
+ * strictly, while malformed REST exact values fail later when state scales them.
77
159
  */
78
160
  async depositTokenBalances(minContextSlot) {
79
161
  const query = minContextSlot === undefined
@@ -88,7 +170,8 @@ class Positions {
88
170
  *
89
171
  * Intended for server-side cookie forwarding (SSR / server functions)
90
172
  * where the per-request browser cookie can't propagate to the shared
91
- * client. In a browser context this is equivalent to
173
+ * client. The complete response has the same separate, exact native-SOL
174
+ * contract as {@link depositTokenBalances}. In a browser this is equivalent to
92
175
  * {@link depositTokenBalances} because the runtime is already attaching
93
176
  * the cookie via `credentials: "include"`.
94
177
  */
@@ -99,6 +182,511 @@ class Positions {
99
182
  const url = `${this.client.http.baseUrl()}/api/users/deposit-token-balances${query}`;
100
183
  return this.client.http.getWithCookies(url, http_1.RetryPolicy.Idempotent, cookieHeader);
101
184
  }
185
+ /**
186
+ * Return a fee-prepared plan for an exact canonical WSOL wrap.
187
+ *
188
+ * The authenticated Trading Wallet must have a local native keypair and complete
189
+ * balance state. Live canonical account data must match that state. An existing
190
+ * account must have account lamports equal to its token amount plus native
191
+ * reserve. Otherwise a later `SyncNative` instruction would recalculate the WSOL
192
+ * token amount from account lamports and wrap donated excess beyond
193
+ * `amountLamports`. The returned transaction contains strict Tokenkeg ATA
194
+ * creation only when the account is absent. It then contains the exact transfer
195
+ * and `SyncNative`. Availability uses the ordinary reserve floor.
196
+ *
197
+ * Callers rebuild immediately before prepared submission. They retain the
198
+ * returned component projection until a complete snapshot covers the confirmed
199
+ * slot. An uncertain outcome requires authoritative refresh before another plan.
200
+ */
201
+ async planWrapSol(amountLamports, state) {
202
+ assertSolActionAmount(amountLamports, "wrap");
203
+ const wallet = this.conversionPlanningWallet(state);
204
+ const components = state.solComponents();
205
+ const rpc = new rpc_1.Rpc(this.client);
206
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
207
+ const account = await rpc.canonicalWsolAccountInfo(canonical, wallet);
208
+ if (!account && components.canonicalWsolLamports > 0n) {
209
+ throw error_1.SdkError.validation("canonical WSOL balance is positive but its account is unavailable");
210
+ }
211
+ if (account &&
212
+ account.tokenAmountLamports !== components.canonicalWsolLamports) {
213
+ throw error_1.SdkError.validation("live canonical WSOL amount does not match wallet balance state");
214
+ }
215
+ if (account &&
216
+ account.accountLamports !==
217
+ account.tokenAmountLamports + account.nativeReserveLamports) {
218
+ throw error_1.SdkError.validation("canonical WSOL account has unsynchronized native lamports");
219
+ }
220
+ if (account &&
221
+ (account.tokenAmountLamports + amountLamports > MAX_U64 ||
222
+ account.accountLamports + amountLamports > MAX_U64)) {
223
+ throw error_1.SdkError.validation("wrap would exceed canonical WSOL token or account u64 range");
224
+ }
225
+ const createsCanonicalWsolAccount = account === null;
226
+ const upfrontRentLamports = createsCanonicalWsolAccount
227
+ ? await rpc.minimumBalanceForRentExemption(TOKEN_ACCOUNT_SPACE)
228
+ : 0n;
229
+ const transaction = this.buildWrapSolTransaction(wallet, amountLamports, createsCanonicalWsolAccount);
230
+ const feeLamports = await rpc.prepareAndEstimateTransactionFee(transaction);
231
+ const costs = {
232
+ feeLamports,
233
+ upfrontRentLamports,
234
+ createsCanonicalWsolAccount,
235
+ sponsored: false,
236
+ };
237
+ const availability = (0, state_1.solBalanceAvailability)(components, costs);
238
+ const requiredNativeLamports = amountLamports + availability.reserveLamports;
239
+ if (requiredNativeLamports > MAX_U64) {
240
+ throw error_1.SdkError.validation("wrap amount and transaction reserve exceed u64 lamports");
241
+ }
242
+ if (components.nativeLamports < requiredNativeLamports) {
243
+ throw error_1.SdkError.validation("native SOL cannot fund the wrap amount and transaction reserve");
244
+ }
245
+ return {
246
+ kind: "wrap",
247
+ transaction,
248
+ costs,
249
+ availability,
250
+ expectedDelta: {
251
+ nativeLamports: -amountLamports - feeLamports - upfrontRentLamports,
252
+ canonicalWsolLamports: amountLamports,
253
+ },
254
+ };
255
+ }
256
+ /**
257
+ * Return a fee-prepared plan for closing the complete canonical WSOL account.
258
+ *
259
+ * The Trading Wallet must have a local native keypair. Canonical WSOL in the
260
+ * complete balance state must be positive and equal the live token amount. The
261
+ * returned transaction contains one `CloseAccount` instruction whose authority,
262
+ * destination, and fee payer are that wallet. If submitted successfully, the
263
+ * instruction transfers the complete account balance, including rent and donated
264
+ * lamports. The returned costs contain only the fresh fee. Availability requires
265
+ * native SOL to fund that fee without relying on the later account transfer.
266
+ *
267
+ * Callers rebuild immediately before prepared submission. They retain the
268
+ * returned component projection until a complete snapshot covers the confirmed
269
+ * slot. Signing, submission, or confirmation uncertainty requires authoritative
270
+ * refresh and does not authorize automatic resubmission.
271
+ */
272
+ async planUnwrapWsolAll(state) {
273
+ const wallet = this.conversionPlanningWallet(state);
274
+ const components = state.solComponents();
275
+ if (components.canonicalWsolLamports === 0n) {
276
+ throw error_1.SdkError.validation("unwrap-all requires a positive canonical WSOL balance");
277
+ }
278
+ const rpc = new rpc_1.Rpc(this.client);
279
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
280
+ const account = await rpc.canonicalWsolAccountInfo(canonical, wallet);
281
+ if (!account) {
282
+ throw error_1.SdkError.validation("canonical WSOL account is required for unwrap-all");
283
+ }
284
+ if (account.tokenAmountLamports !== components.canonicalWsolLamports) {
285
+ throw error_1.SdkError.validation("live canonical WSOL amount does not match wallet balance state");
286
+ }
287
+ const transaction = this.buildUnwrapWsolAllTransaction(wallet);
288
+ const feeLamports = await rpc.prepareAndEstimateTransactionFee(transaction);
289
+ const costs = {
290
+ feeLamports,
291
+ upfrontRentLamports: 0n,
292
+ createsCanonicalWsolAccount: false,
293
+ sponsored: false,
294
+ };
295
+ // Unwrap-all removes the persistent account, so its availability validates
296
+ // SolActionCosts and reserves the fee without the ordinary account floor.
297
+ const availability = (0, state_1.unwrapAllSolBalanceAvailability)(components, costs);
298
+ const projectedNativeLamports = components.nativeLamports + account.accountLamports - feeLamports;
299
+ if (projectedNativeLamports > MAX_U64) {
300
+ throw error_1.SdkError.validation("unwrap-all projected native SOL exceeds the transaction u64 range");
301
+ }
302
+ return {
303
+ kind: "unwrapAll",
304
+ transaction,
305
+ costs,
306
+ availability,
307
+ expectedDelta: {
308
+ nativeLamports: account.accountLamports - feeLamports,
309
+ canonicalWsolLamports: -components.canonicalWsolLamports,
310
+ },
311
+ };
312
+ }
313
+ /**
314
+ * Plan one atomic split that consumes canonical WSOL before wrapping a shortfall.
315
+ * Amounts and live costs are lamports; unavailable account, fee, or rent reads
316
+ * fail closed, and sponsored planning is rejected until a sponsor owns costs.
317
+ */
318
+ async planSolSplit(market, amountLamports, state, sponsored) {
319
+ assertUnsponsoredPlan(sponsored);
320
+ assertSolActionAmount(amountLamports, "split");
321
+ const wallet = this.planningWallet(state);
322
+ const components = state.solComponents();
323
+ const rpc = new rpc_1.Rpc(this.client);
324
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
325
+ const canonicalExists = await rpc.canonicalWsolAccountExists(canonical, wallet);
326
+ if (components.canonicalWsolLamports > 0n && !canonicalExists) {
327
+ throw error_1.SdkError.validation("canonical WSOL balance is positive but its account is unavailable");
328
+ }
329
+ const shortfall = amountLamports > components.canonicalWsolLamports
330
+ ? amountLamports - components.canonicalWsolLamports
331
+ : 0n;
332
+ const upfrontRentLamports = canonicalExists
333
+ ? 0n
334
+ : await rpc.minimumBalanceForRentExemption(TOKEN_ACCOUNT_SPACE);
335
+ const transaction = new web3_js_1.Transaction({ feePayer: wallet });
336
+ if (!canonicalExists) {
337
+ transaction.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet, canonical, wallet, spl_token_1.NATIVE_MINT, spl_token_1.TOKEN_PROGRAM_ID));
338
+ }
339
+ if (shortfall > 0n) {
340
+ transaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: wallet, toPubkey: canonical, lamports: shortfall }), (0, spl_token_1.createSyncNativeInstruction)(canonical, spl_token_1.TOKEN_PROGRAM_ID));
341
+ }
342
+ transaction.add((0, instructions_1.buildDepositIx)({
343
+ user: wallet,
344
+ market: new web3_js_1.PublicKey(market.pubkey),
345
+ depositMint: spl_token_1.NATIVE_MINT,
346
+ amount: amountLamports,
347
+ }, market.numOutcomes, this.client.programId));
348
+ const feeLamports = await rpc.prepareAndEstimateTransactionFee(transaction);
349
+ const costs = {
350
+ feeLamports,
351
+ upfrontRentLamports,
352
+ createsCanonicalWsolAccount: !canonicalExists,
353
+ sponsored,
354
+ };
355
+ const availability = (0, state_1.solBalanceAvailability)(components, costs);
356
+ if (amountLamports > availability.spendableLamports) {
357
+ throw error_1.SdkError.validation("split amount exceeds spendable SOL after transaction reserve");
358
+ }
359
+ if (shortfall + availability.reserveLamports > components.nativeLamports) {
360
+ throw error_1.SdkError.validation("native SOL cannot fund the wrap shortfall and transaction reserve");
361
+ }
362
+ const walletCosts = sponsored ? 0n : feeLamports + upfrontRentLamports;
363
+ return {
364
+ kind: "split",
365
+ transaction,
366
+ costs,
367
+ availability,
368
+ expectedDelta: {
369
+ nativeLamports: -shortfall - walletCosts,
370
+ canonicalWsolLamports: shortfall - amountLamports,
371
+ },
372
+ };
373
+ }
374
+ /**
375
+ * Plan a merge that leaves returned WSOL in the persistent canonical ATA.
376
+ * The prepared transaction does not mutate cached state; refresh authority
377
+ * after confirmed submission.
378
+ */
379
+ async planSolMerge(market, amountLamports, state, sponsored) {
380
+ assertUnsponsoredPlan(sponsored);
381
+ assertSolActionAmount(amountLamports, "merge");
382
+ const wallet = this.planningWallet(state);
383
+ const transaction = new web3_js_1.Transaction({ feePayer: wallet });
384
+ const { rpc, components, canonicalExists, upfrontRentLamports } = await this.receivePlanContext(wallet, state);
385
+ if (!canonicalExists) {
386
+ transaction.add(this.createCanonicalWsolAccount(wallet));
387
+ }
388
+ transaction.add((0, instructions_1.buildMergeIx)({
389
+ user: wallet,
390
+ market: new web3_js_1.PublicKey(market.pubkey),
391
+ depositMint: spl_token_1.NATIVE_MINT,
392
+ amount: amountLamports,
393
+ }, market.numOutcomes, this.client.programId));
394
+ return this.finishReceivePlan("merge", amountLamports, transaction, rpc, components, upfrontRentLamports, !canonicalExists, sponsored);
395
+ }
396
+ /**
397
+ * Plan a redemption that leaves returned WSOL in the persistent canonical ATA.
398
+ * `amountLamports` is exact collateral scale; `outcomeIndex` is validated
399
+ * against the supplied authoritative `numOutcomes`.
400
+ */
401
+ async planSolRedeem(market, amountLamports, outcomeIndex, numOutcomes, state, sponsored) {
402
+ assertUnsponsoredPlan(sponsored);
403
+ assertSolActionAmount(amountLamports, "redeem");
404
+ (0, utils_1.validateOutcomes)(numOutcomes);
405
+ (0, utils_1.validateOutcomeIndex)(outcomeIndex, numOutcomes);
406
+ const wallet = this.planningWallet(state);
407
+ const transaction = new web3_js_1.Transaction({ feePayer: wallet });
408
+ const { rpc, components, canonicalExists, upfrontRentLamports } = await this.receivePlanContext(wallet, state);
409
+ if (!canonicalExists) {
410
+ transaction.add(this.createCanonicalWsolAccount(wallet));
411
+ }
412
+ transaction.add((0, instructions_1.buildRedeemWinningsIx)({
413
+ user: wallet,
414
+ market,
415
+ depositMint: spl_token_1.NATIVE_MINT,
416
+ amount: amountLamports,
417
+ }, outcomeIndex, this.client.programId));
418
+ return this.finishReceivePlan("redeem", amountLamports, transaction, rpc, components, upfrontRentLamports, !canonicalExists, sponsored);
419
+ }
420
+ /**
421
+ * Plan exact native SOL delivery without closing the canonical WSOL ATA.
422
+ *
423
+ * Native funds are preferred. A shortfall uses a bounded seeded Tokenkeg
424
+ * account whose rent returns on close; all account, rent, and fee reads fail
425
+ * closed. At most eight blockhash-scoped candidates bound RPC latency while
426
+ * making accidental exhaustion negligible. The returned transaction already
427
+ * carries its prepared message.
428
+ */
429
+ async planNativeSolWithdrawal(recipient, amountLamports, state, sponsored) {
430
+ assertUnsponsoredPlan(sponsored);
431
+ assertSolActionAmount(amountLamports, "withdraw");
432
+ const wallet = this.planningWallet(state);
433
+ const components = state.solComponents();
434
+ const rpc = new rpc_1.Rpc(this.client);
435
+ const direct = new web3_js_1.Transaction({ feePayer: wallet }).add(web3_js_1.SystemProgram.transfer({ fromPubkey: wallet, toPubkey: recipient, lamports: amountLamports }));
436
+ const directFee = await rpc.prepareAndEstimateTransactionFee(direct);
437
+ const directCosts = {
438
+ feeLamports: directFee,
439
+ upfrontRentLamports: 0n,
440
+ createsCanonicalWsolAccount: false,
441
+ sponsored,
442
+ };
443
+ const directAvailability = (0, state_1.solBalanceAvailability)(components, directCosts);
444
+ if (amountLamports > directAvailability.spendableLamports) {
445
+ throw error_1.SdkError.validation("withdraw amount exceeds spendable SOL after transaction reserve");
446
+ }
447
+ if (components.nativeLamports >=
448
+ amountLamports + directAvailability.reserveLamports) {
449
+ return {
450
+ kind: "nativeWithdraw",
451
+ transaction: direct,
452
+ costs: directCosts,
453
+ availability: directAvailability,
454
+ expectedDelta: {
455
+ nativeLamports: -amountLamports - (sponsored ? 0n : directFee),
456
+ canonicalWsolLamports: 0n,
457
+ },
458
+ };
459
+ }
460
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
461
+ if (!(await rpc.canonicalWsolAccountExists(canonical, wallet))) {
462
+ throw error_1.SdkError.validation("canonical WSOL is required for this native withdrawal");
463
+ }
464
+ const temporaryRent = await rpc.minimumBalanceForRentExemption(TOKEN_ACCOUNT_SPACE);
465
+ const { blockhash, lastValidBlockHeight } = await rpc.getLatestBlockhash();
466
+ let seed;
467
+ let temporary;
468
+ // Bound account-existence RPCs; the blockhash and attempt byte make eight collisions remote.
469
+ for (let attempt = 0; attempt <= 7; attempt++) {
470
+ const candidateSeed = nativeWithdrawSeed(blockhash, wallet, recipient, amountLamports, attempt);
471
+ const candidate = await web3_js_1.PublicKey.createWithSeed(wallet, candidateSeed, spl_token_1.TOKEN_PROGRAM_ID);
472
+ if (!(await rpc.accountExists(candidate))) {
473
+ seed = candidateSeed;
474
+ temporary = candidate;
475
+ break;
476
+ }
477
+ }
478
+ if (!seed || !temporary) {
479
+ throw error_1.SdkError.validation("temporary WSOL seed attempts are exhausted");
480
+ }
481
+ let transaction = this.buildTemporaryNativeWithdrawal(wallet, recipient, amountLamports, 1n, temporaryRent, seed, temporary);
482
+ transaction.recentBlockhash = blockhash;
483
+ transaction.lastValidBlockHeight = lastValidBlockHeight;
484
+ const initialFee = await rpc.estimatePreparedTransactionFee(transaction);
485
+ const initialCosts = {
486
+ feeLamports: initialFee,
487
+ upfrontRentLamports: temporaryRent,
488
+ createsCanonicalWsolAccount: false,
489
+ sponsored,
490
+ };
491
+ const initialAvailability = (0, state_1.solBalanceAvailability)(components, initialCosts);
492
+ const initialRequired = amountLamports + initialAvailability.reserveLamports;
493
+ if (initialRequired < components.nativeLamports) {
494
+ throw error_1.SdkError.validation("invalid temporary withdrawal requirement");
495
+ }
496
+ const initialTransfer = initialRequired - components.nativeLamports;
497
+ transaction = this.buildTemporaryNativeWithdrawal(wallet, recipient, amountLamports, initialTransfer, temporaryRent, seed, temporary);
498
+ transaction.recentBlockhash = blockhash;
499
+ transaction.lastValidBlockHeight = lastValidBlockHeight;
500
+ const finalFee = await rpc.estimatePreparedTransactionFee(transaction);
501
+ const costs = {
502
+ feeLamports: finalFee,
503
+ upfrontRentLamports: temporaryRent,
504
+ createsCanonicalWsolAccount: false,
505
+ sponsored,
506
+ };
507
+ const availability = (0, state_1.solBalanceAvailability)(components, costs);
508
+ const finalRequired = amountLamports + availability.reserveLamports;
509
+ if (finalRequired < components.nativeLamports) {
510
+ throw error_1.SdkError.validation("invalid temporary withdrawal requirement");
511
+ }
512
+ const canonicalTransfer = finalRequired - components.nativeLamports;
513
+ if (canonicalTransfer > components.canonicalWsolLamports) {
514
+ throw error_1.SdkError.validation("canonical WSOL cannot fund the native withdrawal shortfall");
515
+ }
516
+ if (canonicalTransfer !== initialTransfer) {
517
+ transaction = this.buildTemporaryNativeWithdrawal(wallet, recipient, amountLamports, canonicalTransfer, temporaryRent, seed, temporary);
518
+ transaction.recentBlockhash = blockhash;
519
+ transaction.lastValidBlockHeight = lastValidBlockHeight;
520
+ const stableFee = await rpc.estimatePreparedTransactionFee(transaction);
521
+ if (stableFee !== finalFee) {
522
+ throw error_1.SdkError.validation("transaction fee changed while rebuilding native withdrawal");
523
+ }
524
+ }
525
+ return {
526
+ kind: "nativeWithdraw",
527
+ transaction,
528
+ costs,
529
+ availability,
530
+ expectedDelta: {
531
+ nativeLamports: canonicalTransfer - amountLamports - (sponsored ? 0n : finalFee),
532
+ canonicalWsolLamports: -canonicalTransfer,
533
+ },
534
+ };
535
+ }
536
+ /** Resolve the authenticated wallet only from fresh matching cached authority. */
537
+ planningWallet(state) {
538
+ // Cached identity is a signing trust boundary: validate expiry, complete
539
+ // state initialization, and wallet equality before constructing a transaction.
540
+ const credentials = this.client.authCredentials;
541
+ if (!credentials) {
542
+ throw error_1.SdkError.validation("authenticated credentials are required");
543
+ }
544
+ if (!(0, auth_1.isAuthenticated)(credentials)) {
545
+ throw error_1.SdkError.validation("authenticated credentials have expired");
546
+ }
547
+ if (state.walletAddress === undefined ||
548
+ state.contextSlot === undefined ||
549
+ state.nativeSolBalance === undefined) {
550
+ throw error_1.SdkError.validation("wallet balance state is not initialized");
551
+ }
552
+ if (state.walletAddress !== credentials.wallet_address) {
553
+ throw error_1.SdkError.validation("authenticated wallet does not match wallet balance state");
554
+ }
555
+ let wallet;
556
+ try {
557
+ wallet = new web3_js_1.PublicKey(credentials.wallet_address);
558
+ }
559
+ catch (error) {
560
+ throw error_1.SdkError.validation(`authenticated wallet is invalid: ${error instanceof Error ? error.message : String(error)}`);
561
+ }
562
+ const strategy = (0, context_1.requireSigningStrategy)(this.client);
563
+ const signingAddress = (0, signing_1.signingStrategyWalletAddress)(strategy);
564
+ if (!signingAddress) {
565
+ throw error_1.SdkError.validation("signing strategy wallet identity is required");
566
+ }
567
+ let signingWallet;
568
+ try {
569
+ signingWallet = new web3_js_1.PublicKey(signingAddress);
570
+ }
571
+ catch (error) {
572
+ throw error_1.SdkError.validation(`signing strategy wallet is invalid: ${error instanceof Error ? error.message : String(error)}`);
573
+ }
574
+ if (!signingWallet.equals(wallet)) {
575
+ throw error_1.SdkError.validation("signing strategy does not control authenticated wallet");
576
+ }
577
+ return wallet;
578
+ }
579
+ /**
580
+ * Return the authenticated Trading Wallet after native-keypair validation.
581
+ *
582
+ * Complete wallet state and identity are validated by `planningWallet`. This
583
+ * additional guard rejects wallet-adapter and Privy strategies before conversion
584
+ * RPC reads. Ordinary planners do not call this method.
585
+ */
586
+ conversionPlanningWallet(state) {
587
+ (0, signing_1.requireNativeSigningStrategy)((0, context_1.requireSigningStrategy)(this.client));
588
+ return this.planningWallet(state);
589
+ }
590
+ /**
591
+ * Return an unsigned wrap transaction with exact instruction ordering.
592
+ *
593
+ * When planning observed no canonical ATA, the first instruction is strict ATA
594
+ * creation. A concurrently created ATA therefore makes execution fail instead of
595
+ * using account state that was absent from the plan. The transfer and `SyncNative`
596
+ * instructions follow. Fee preparation later attaches the live blockhash.
597
+ */
598
+ buildWrapSolTransaction(wallet, amountLamports, createsCanonicalWsolAccount) {
599
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
600
+ const transaction = new web3_js_1.Transaction({ feePayer: wallet });
601
+ if (createsCanonicalWsolAccount) {
602
+ transaction.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(wallet, canonical, wallet, spl_token_1.NATIVE_MINT, spl_token_1.TOKEN_PROGRAM_ID));
603
+ }
604
+ return transaction.add(web3_js_1.SystemProgram.transfer({
605
+ fromPubkey: wallet,
606
+ toPubkey: canonical,
607
+ lamports: amountLamports,
608
+ }), (0, spl_token_1.createSyncNativeInstruction)(canonical, spl_token_1.TOKEN_PROGRAM_ID));
609
+ }
610
+ /**
611
+ * Return an unsigned transaction containing one canonical `CloseAccount`.
612
+ *
613
+ * The Trading Wallet is the fee payer, close authority, and destination. A later
614
+ * successful submission transfers the complete account balance to that wallet.
615
+ */
616
+ buildUnwrapWsolAllTransaction(wallet) {
617
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
618
+ return new web3_js_1.Transaction({ feePayer: wallet }).add((0, spl_token_1.createCloseAccountInstruction)(canonical, wallet, wallet, [], spl_token_1.TOKEN_PROGRAM_ID));
619
+ }
620
+ /**
621
+ * Build idempotent creation of the persistent Tokenkeg WSOL ATA.
622
+ * Tokenkeg is Solana's legacy SPL Token Program; canonical native-mint ATA
623
+ * derivation is pinned to it rather than Token-2022 across the protocol.
624
+ */
625
+ createCanonicalWsolAccount(wallet) {
626
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
627
+ return (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet, canonical, wallet, spl_token_1.NATIVE_MINT, spl_token_1.TOKEN_PROGRAM_ID);
628
+ }
629
+ /** Read canonical-account existence and upfront rent for merge/redeem plans. */
630
+ async receivePlanContext(wallet, state) {
631
+ const rpc = new rpc_1.Rpc(this.client);
632
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
633
+ const canonicalExists = await rpc.canonicalWsolAccountExists(canonical, wallet);
634
+ const components = state.solComponents();
635
+ if (components.canonicalWsolLamports > 0n && !canonicalExists) {
636
+ throw error_1.SdkError.validation("canonical WSOL balance is positive but its account is unavailable");
637
+ }
638
+ return {
639
+ rpc,
640
+ components,
641
+ canonicalExists,
642
+ upfrontRentLamports: canonicalExists
643
+ ? 0n
644
+ : await rpc.minimumBalanceForRentExemption(TOKEN_ACCOUNT_SPACE),
645
+ };
646
+ }
647
+ /** Finish merge/redeem planning with live fee authority and component deltas. */
648
+ async finishReceivePlan(kind, amountLamports, transaction, rpc, components, upfrontRentLamports, createsCanonicalWsolAccount, sponsored) {
649
+ const feeLamports = await rpc.prepareAndEstimateTransactionFee(transaction);
650
+ const costs = {
651
+ feeLamports,
652
+ upfrontRentLamports,
653
+ createsCanonicalWsolAccount,
654
+ sponsored,
655
+ };
656
+ const availability = (0, state_1.solBalanceAvailability)(components, costs);
657
+ const walletCosts = sponsored ? 0n : feeLamports + upfrontRentLamports;
658
+ return {
659
+ kind,
660
+ transaction,
661
+ costs,
662
+ availability,
663
+ expectedDelta: {
664
+ nativeLamports: -walletCosts,
665
+ canonicalWsolLamports: amountLamports,
666
+ },
667
+ };
668
+ }
669
+ /**
670
+ * Build the sole WSOL-to-native path without closing canonical authority.
671
+ * The temporary Tokenkeg account is initialized, funded, and closed back to
672
+ * the wallet before the exact recipient transfer in the same transaction.
673
+ */
674
+ buildTemporaryNativeWithdrawal(wallet, recipient, amountLamports, canonicalTransfer, temporaryRent, seed, temporary) {
675
+ const canonical = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, wallet);
676
+ return new web3_js_1.Transaction({ feePayer: wallet }).add(web3_js_1.SystemProgram.createAccountWithSeed({
677
+ fromPubkey: wallet,
678
+ newAccountPubkey: temporary,
679
+ basePubkey: wallet,
680
+ seed,
681
+ lamports: Number(temporaryRent),
682
+ space: TOKEN_ACCOUNT_SPACE,
683
+ programId: spl_token_1.TOKEN_PROGRAM_ID,
684
+ }), (0, spl_token_1.createInitializeAccount3Instruction)(temporary, spl_token_1.NATIVE_MINT, wallet, spl_token_1.TOKEN_PROGRAM_ID), (0, spl_token_1.createTransferInstruction)(canonical, temporary, wallet, canonicalTransfer, [], spl_token_1.TOKEN_PROGRAM_ID), (0, spl_token_1.createCloseAccountInstruction)(temporary, wallet, wallet, [], spl_token_1.TOKEN_PROGRAM_ID), web3_js_1.SystemProgram.transfer({
685
+ fromPubkey: wallet,
686
+ toPubkey: recipient,
687
+ lamports: amountLamports,
688
+ }));
689
+ }
102
690
  // ── On-chain transaction builders ────────────────────────────────────
103
691
  redeemWinningsIx(params, outcomeIndex) {
104
692
  return (0, instructions_1.buildRedeemWinningsIx)(params, outcomeIndex, this.client.programId);