@invisible-labs/sdk 0.6.0-devnet.3 → 0.6.0-devnet.5

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 (45) hide show
  1. package/README.md +129 -15
  2. package/dist/.invisible-sdk-build-target.json +7 -0
  3. package/dist/{chunk-QQSI3ZD7.js → chunk-3MDHAS4D.js} +109 -17
  4. package/dist/chunk-3MDHAS4D.js.map +1 -0
  5. package/dist/{chunk-NKRVQXRP.js → chunk-3TGQ4CZ3.js} +297 -11
  6. package/dist/chunk-3TGQ4CZ3.js.map +1 -0
  7. package/dist/chunk-DCTYBUY3.js +258 -0
  8. package/dist/chunk-DCTYBUY3.js.map +1 -0
  9. package/dist/{chunk-NHEHCUS5.js → chunk-EBWTAXNT.js} +3 -3
  10. package/dist/{chunk-NHEHCUS5.js.map → chunk-EBWTAXNT.js.map} +1 -1
  11. package/dist/{chunk-V5HXJRBW.js → chunk-HJFMDMPY.js} +3 -3
  12. package/dist/chunk-HJFMDMPY.js.map +1 -0
  13. package/dist/{chunk-GHBQF3A7.js → chunk-QRN46R3F.js} +5 -2
  14. package/dist/chunk-QRN46R3F.js.map +1 -0
  15. package/dist/chunk-Y7AK6RBV.js +4189 -0
  16. package/dist/chunk-Y7AK6RBV.js.map +1 -0
  17. package/dist/{coordinator-VVJ33WF2.js → coordinator-PEWHNZ7I.js} +3 -3
  18. package/dist/{coordinator-VVJ33WF2.js.map → coordinator-PEWHNZ7I.js.map} +1 -1
  19. package/dist/events.js +3 -3
  20. package/dist/index.d.ts +69 -353
  21. package/dist/index.js +161 -43
  22. package/dist/index.js.map +1 -1
  23. package/dist/lp.d.ts +76 -18
  24. package/dist/lp.js +511 -111
  25. package/dist/lp.js.map +1 -1
  26. package/dist/presets.d.ts +7 -40
  27. package/dist/presets.js +10 -68
  28. package/dist/presets.js.map +1 -1
  29. package/dist/stats.js +1 -1
  30. package/dist/storage.js +2 -243
  31. package/dist/storage.js.map +1 -1
  32. package/dist/{errors-N2touVm4.d.ts → types.generated-YhFu9ZBR.d.ts} +65 -4
  33. package/dist/user.d.ts +134 -8
  34. package/dist/user.js +991 -250
  35. package/dist/user.js.map +1 -1
  36. package/package.json +5 -31
  37. package/dist/chunk-BWCGNTN5.js +0 -1153
  38. package/dist/chunk-BWCGNTN5.js.map +0 -1
  39. package/dist/chunk-DIA6U2CV.js +0 -1927
  40. package/dist/chunk-DIA6U2CV.js.map +0 -1
  41. package/dist/chunk-GHBQF3A7.js.map +0 -1
  42. package/dist/chunk-NKRVQXRP.js.map +0 -1
  43. package/dist/chunk-QQSI3ZD7.js.map +0 -1
  44. package/dist/chunk-V5HXJRBW.js.map +0 -1
  45. package/dist/types.generated-BSEBz4jR.d.ts +0 -68
package/dist/lp.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { isValidLpPositionId, LP_DEFAULT_REFILL_BATCH_SIZE, LP_DEFAULT_TARGET_SHARDS, LP_MAX_TARGET_SHARDS, LP_MAX_REFILL_BATCH_SIZE } from './chunk-4X5OCCQW.js';
2
- import { publicKey, assertAttested, lamports } from './chunk-NHEHCUS5.js';
3
- import { requestEnvelope } from './chunk-QQSI3ZD7.js';
4
- import { getSessionState } from './chunk-V5HXJRBW.js';
5
- import { CommandError, PolicyValidationError, RoutingError, LpLifecycleError } from './chunk-GHBQF3A7.js';
2
+ import { publicKey, assertAttested, lamports } from './chunk-EBWTAXNT.js';
3
+ import { requestEnvelope, CoordinatorMessageType } from './chunk-3MDHAS4D.js';
4
+ import { getSessionState } from './chunk-HJFMDMPY.js';
5
+ import { CommandError, WalletError, TransportError, PolicyValidationError, RoutingError, LpLifecycleError } from './chunk-QRN46R3F.js';
6
6
  import './chunk-VR6T6OJS.js';
7
7
 
8
8
  // src/lp/positionCode.ts
@@ -172,14 +172,8 @@ function getCrypto() {
172
172
  var SINGLE_DESTINATION_WITHDRAWAL_MESSAGE = "single-destination withdrawal is privacy-degrading; pass allowManyToOne: true to acknowledge";
173
173
  var DESTINATION_SOURCE_COUNT_MESSAGE = "LP withdrawal requires one destination per withdrawable source unless allowManyToOne is true";
174
174
  var SOURCE_COUNT_MESSAGE = "LP withdrawal source count must be a positive integer";
175
- function validateLpWithdrawalPlan(input) {
176
- const {
177
- depositPrincipalLamports,
178
- earnedFeeLamports,
179
- destinationAddresses,
180
- allowManyToOne,
181
- sourceCount
182
- } = input;
175
+ function validateLpWithdrawalClaim(input) {
176
+ const { depositPrincipalLamports, earnedFeeLamports } = input;
183
177
  lamports(depositPrincipalLamports);
184
178
  lamports(earnedFeeLamports);
185
179
  if (depositPrincipalLamports === 0 && earnedFeeLamports === 0) {
@@ -188,6 +182,16 @@ function validateLpWithdrawalPlan(input) {
188
182
  "LP withdrawal requires a non-zero principal or earned-fee claim"
189
183
  );
190
184
  }
185
+ }
186
+ function validateLpWithdrawalPlan(input) {
187
+ const {
188
+ depositPrincipalLamports,
189
+ earnedFeeLamports,
190
+ destinationAddresses,
191
+ allowManyToOne,
192
+ sourceCount
193
+ } = input;
194
+ validateLpWithdrawalClaim({ depositPrincipalLamports, earnedFeeLamports });
191
195
  validateDestinationAddresses(destinationAddresses);
192
196
  if (sourceCount !== void 0 && (!Number.isInteger(sourceCount) || sourceCount < 1)) {
193
197
  throw new PolicyValidationError("INVALID_WITHDRAWAL_PLAN", SOURCE_COUNT_MESSAGE);
@@ -224,59 +228,206 @@ function validateDestinationAddresses(destinationAddresses) {
224
228
 
225
229
  // src/lp/coordinatorClient.ts
226
230
  var COMMAND_TTL_MS = 3e4;
227
- var LP_COMMAND_REQUEST_TIMEOUT_MS = 12e4;
231
+ var LP_POSITION_OPEN_COMMAND_TTL_MS = 55e3;
232
+ var LP_POSITION_OPEN_REQUEST_TIMEOUT_MS = 2e4;
233
+ var LP_COMMAND_REQUEST_TIMEOUT_MS = COMMAND_TTL_MS;
234
+ var LP_CREATION_CHALLENGE_REQUEST_TIMEOUT_MS = 3e4;
235
+ var LP_CREATION_WALLET_SIGNATURE_TIMEOUT_MS = 45e3;
236
+ var LP_CREATION_PROOF_REQUEST_TIMEOUT_MS = 15e3;
237
+ var LP_CREATION_PROOF_RETRY_TIMEOUT_MS = 15e3;
238
+ var LP_CREATION_AUTHORIZATION_MARGIN_MS = 15e3;
239
+ var LP_CREATION_MIN_STEP_TIMEOUT_MS = 1;
228
240
  var LP_DKG_ABORT_REQUEST_TIMEOUT_MS = 1e4;
229
241
  var encoder2 = new TextEncoder();
230
242
  var protocolPromise = null;
231
- function createLpCoordinatorClient(session) {
243
+ function createLpCoordinatorClient(session, options = {}) {
244
+ const sessionRef = { current: session };
245
+ const reconnectSession = options.reconnectSession === void 0 ? void 0 : async () => {
246
+ const nextSession = await options.reconnectSession();
247
+ sessionRef.current = nextSession;
248
+ return nextSession;
249
+ };
232
250
  return {
251
+ authorizePositionCreation(intent, signer) {
252
+ return authorizePositionCreation(sessionRef.current, intent, signer);
253
+ },
233
254
  initDkg(input, auth) {
234
- return initDkg(session, input, auth);
255
+ return initDkg(sessionRef.current, input, auth);
235
256
  },
236
257
  abortDkgSessions(lpPositionId, auth) {
237
- return abortDkgSessions(session, lpPositionId, auth);
258
+ return abortDkgSessions(sessionRef.current, lpPositionId, auth);
238
259
  },
239
260
  dkgRound1(input, auth) {
240
- return dkgRound1(session, input, auth);
261
+ return dkgRound1(sessionRef.current, input, auth);
241
262
  },
242
263
  dkgRound2(input, auth) {
243
- return dkgRound2(session, input, auth);
264
+ return dkgRound2(sessionRef.current, input, auth);
244
265
  },
245
266
  openPosition(input, auth) {
246
- return openPosition(session, input, auth);
267
+ return openPosition(sessionRef.current, input, auth, reconnectSession);
247
268
  },
248
- getPosition(lpPositionId, auth) {
249
- return getPosition(session, lpPositionId, auth);
269
+ getPosition(lpPositionId, auth, options2) {
270
+ return getPosition(sessionRef.current, lpPositionId, auth, options2);
250
271
  },
251
272
  finalizeDkgBatch(input, auth) {
252
- return finalizeDkgBatch(session, input, auth);
273
+ return finalizeDkgBatch(sessionRef.current, input, auth);
253
274
  },
254
275
  refillPosition(input, auth) {
255
- return refillPosition(session, input, auth);
276
+ return refillPosition(sessionRef.current, input, auth);
256
277
  },
257
278
  reconcileFunding(lpPositionId, auth) {
258
- return reconcileFunding(session, lpPositionId, auth);
279
+ return reconcileFunding(sessionRef.current, lpPositionId, auth);
259
280
  },
260
281
  planFunding(input, auth) {
261
- return planFunding(session, input, auth);
282
+ return planFunding(sessionRef.current, input, auth);
262
283
  },
263
284
  confirmFunding(input, auth) {
264
- return confirmFunding(session, input, auth);
285
+ return confirmFunding(sessionRef.current, input, auth);
265
286
  },
266
287
  prepareTopUp(lpPositionId, auth) {
267
- return prepareTopUp(session, lpPositionId, auth);
288
+ return prepareTopUp(sessionRef.current, lpPositionId, auth);
268
289
  },
269
290
  getTopUpStatus(input, auth) {
270
- return getTopUpStatus(session, input, auth);
291
+ return getTopUpStatus(sessionRef.current, input, auth);
271
292
  },
272
293
  makeAvailable(input, auth) {
273
- return makeAvailable(session, input, auth);
294
+ return makeAvailable(sessionRef.current, input, auth);
274
295
  },
275
296
  executeWithdrawal(input, auth) {
276
- return executeWithdrawal(session, input, auth);
297
+ return executeWithdrawal(sessionRef.current, input, auth);
277
298
  }
278
299
  };
279
300
  }
301
+ async function authorizePositionCreation(session, intent, signer) {
302
+ const signerPublicKey = publicKey(signer.publicKey);
303
+ const challengeRequestStartedAtMs = Date.now();
304
+ const challengeEnvelope = await requestEnvelope(
305
+ session,
306
+ CoordinatorMessageType.LpCreateAuthorization,
307
+ {
308
+ case: "challenge",
309
+ value: {
310
+ public_key: signerPublicKey,
311
+ intent: {
312
+ committed_lamports: intent.committedLamports,
313
+ shard_count: intent.shardCount,
314
+ shard_amount_lamports: intent.shardAmountLamports,
315
+ refill_threshold: intent.refillThreshold
316
+ }
317
+ }
318
+ },
319
+ { timeoutMs: LP_CREATION_CHALLENGE_REQUEST_TIMEOUT_MS }
320
+ );
321
+ const challenge = challengeEnvelope.payload;
322
+ if (challenge.case !== "challenge") {
323
+ throw new CommandError(
324
+ "REJECTED_STATE",
325
+ "Coordinator returned an unexpected LP creation authorization response"
326
+ );
327
+ }
328
+ const localAuthorizationDeadlineMs = challengeRequestStartedAtMs + challenge.value.valid_for_ms;
329
+ const walletTimeoutMs = lpCreationAuthorizationStepTimeout(
330
+ localAuthorizationDeadlineMs,
331
+ LP_CREATION_PROOF_REQUEST_TIMEOUT_MS + LP_CREATION_PROOF_RETRY_TIMEOUT_MS + LP_CREATION_AUTHORIZATION_MARGIN_MS,
332
+ LP_CREATION_WALLET_SIGNATURE_TIMEOUT_MS
333
+ );
334
+ const signature = await signLpCreationMessageWithTimeout(
335
+ signer,
336
+ encoder2.encode(challenge.value.message),
337
+ walletTimeoutMs
338
+ );
339
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) {
340
+ throw new WalletError(
341
+ "WALLET_INVALID_SIGNATURE",
342
+ "Solana wallet returned an invalid LP creation signature"
343
+ );
344
+ }
345
+ const proofPayload = {
346
+ case: "proof",
347
+ value: {
348
+ authorization_id: challenge.value.authorization_id,
349
+ public_key: signerPublicKey,
350
+ message: challenge.value.message,
351
+ signature
352
+ }
353
+ };
354
+ let proofEnvelope;
355
+ try {
356
+ const proofTimeoutMs = lpCreationAuthorizationStepTimeout(
357
+ localAuthorizationDeadlineMs,
358
+ LP_CREATION_PROOF_RETRY_TIMEOUT_MS + LP_CREATION_AUTHORIZATION_MARGIN_MS,
359
+ LP_CREATION_PROOF_REQUEST_TIMEOUT_MS
360
+ );
361
+ proofEnvelope = await requestEnvelope(
362
+ session,
363
+ CoordinatorMessageType.LpCreateAuthorization,
364
+ proofPayload,
365
+ { timeoutMs: proofTimeoutMs }
366
+ );
367
+ } catch (error) {
368
+ if (!isAmbiguousCoordinatorRequestFailure(error)) throw error;
369
+ const proofRetryTimeoutMs = lpCreationAuthorizationStepTimeout(
370
+ localAuthorizationDeadlineMs,
371
+ LP_CREATION_AUTHORIZATION_MARGIN_MS,
372
+ LP_CREATION_PROOF_RETRY_TIMEOUT_MS
373
+ );
374
+ proofEnvelope = await requestEnvelope(
375
+ session,
376
+ CoordinatorMessageType.LpCreateAuthorization,
377
+ proofPayload,
378
+ { timeoutMs: proofRetryTimeoutMs }
379
+ );
380
+ }
381
+ const authorization = proofEnvelope.payload;
382
+ if (authorization.case !== "authorized") {
383
+ throw new CommandError(
384
+ "REJECTED_STATE",
385
+ "Coordinator returned an unexpected LP creation authorization proof response"
386
+ );
387
+ }
388
+ if (authorization.value.authorization_id !== challenge.value.authorization_id) {
389
+ throw new CommandError(
390
+ "REJECTED_STATE",
391
+ "Coordinator returned an LP creation authorization for a different challenge"
392
+ );
393
+ }
394
+ lpCreationAuthorizationStepTimeout(
395
+ localAuthorizationDeadlineMs,
396
+ LP_CREATION_AUTHORIZATION_MARGIN_MS,
397
+ LP_CREATION_MIN_STEP_TIMEOUT_MS
398
+ );
399
+ return { authorizationId: authorization.value.authorization_id };
400
+ }
401
+ function lpCreationAuthorizationStepTimeout(localDeadlineMs, reservedMs, maximumTimeoutMs) {
402
+ const availableMs = localDeadlineMs - Date.now() - reservedMs;
403
+ if (availableMs < LP_CREATION_MIN_STEP_TIMEOUT_MS) {
404
+ throw new CommandError(
405
+ "REJECTED_EXPIRED",
406
+ "LP creation authorization expired; request a fresh coordinator challenge"
407
+ );
408
+ }
409
+ return Math.min(availableMs, maximumTimeoutMs);
410
+ }
411
+ async function signLpCreationMessageWithTimeout(signer, message, timeoutMs) {
412
+ let timeoutId;
413
+ try {
414
+ return await Promise.race([
415
+ signer.signMessage(message),
416
+ new Promise((_resolve, reject) => {
417
+ timeoutId = setTimeout(() => {
418
+ reject(
419
+ new WalletError(
420
+ "WALLET_USER_REJECTED",
421
+ "Solana wallet did not sign the LP creation challenge before it expired"
422
+ )
423
+ );
424
+ }, timeoutMs);
425
+ })
426
+ ]);
427
+ } finally {
428
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
429
+ }
430
+ }
280
431
  async function initDkg(session, input, auth) {
281
432
  const response = await requestForPosition(session, input.lpPositionId, auth, "dkg_init", {
282
433
  case: "dkgInit",
@@ -295,7 +446,7 @@ async function abortDkgSessions(session, lpPositionId, auth) {
295
446
  case: "dkgAbort",
296
447
  value: {}
297
448
  },
298
- LP_DKG_ABORT_REQUEST_TIMEOUT_MS
449
+ { timeoutMs: LP_DKG_ABORT_REQUEST_TIMEOUT_MS }
299
450
  );
300
451
  if (response.case !== "dkgAbort") throw new Error("Unexpected LP DKG abort response");
301
452
  return { abortedSessionCount: response.value.aborted_session_count };
@@ -331,10 +482,11 @@ async function dkgRound2(session, input, auth) {
331
482
  index: response.value.index
332
483
  };
333
484
  }
334
- async function openPosition(session, input, auth) {
335
- const response = await requestForPosition(session, input.lpPositionId, auth, "position_open", {
485
+ async function openPosition(session, input, auth, reconnectSession) {
486
+ const payload = {
336
487
  case: "positionOpen",
337
488
  value: {
489
+ creation_authorization_id: input.creationAuthorizationId,
338
490
  auth_public_key: Uint8Array.from(input.authPublicKey),
339
491
  refill_public_key: Uint8Array.from(input.refillPublicKey),
340
492
  redemption_commitment: Uint8Array.from(input.withdrawalCommitment),
@@ -345,18 +497,86 @@ async function openPosition(session, input, auth) {
345
497
  addresses: input.addresses,
346
498
  created_at_ms: input.createdAtMs
347
499
  }
348
- });
500
+ };
501
+ const commandDeadlineMs = Date.now() + LP_POSITION_OPEN_COMMAND_TTL_MS;
502
+ const commandOptions = {
503
+ commandNonce: randomBytes(32),
504
+ commandExpiresAtMs: commandDeadlineMs,
505
+ deadlineMs: commandDeadlineMs,
506
+ timeoutMs: LP_POSITION_OPEN_REQUEST_TIMEOUT_MS
507
+ };
508
+ let response;
509
+ try {
510
+ response = await requestForPosition(
511
+ session,
512
+ input.lpPositionId,
513
+ auth,
514
+ "position_open",
515
+ payload,
516
+ commandOptions
517
+ );
518
+ } catch (error) {
519
+ if (!isAmbiguousLpPositionOpenFailure(error)) throw error;
520
+ const replaySession = reconnectSession === void 0 ? session : await reconnectSessionWithinDeadline(reconnectSession, commandDeadlineMs);
521
+ response = await requestForPosition(
522
+ replaySession,
523
+ input.lpPositionId,
524
+ auth,
525
+ "position_open",
526
+ payload,
527
+ commandOptions
528
+ );
529
+ }
349
530
  return expectPosition(response);
350
531
  }
351
- async function getPosition(session, lpPositionId, auth) {
532
+ async function reconnectSessionWithinDeadline(reconnectSession, deadlineMs) {
533
+ const remainingMs = deadlineMs - Date.now();
534
+ if (remainingMs <= 0) {
535
+ throw new CommandError(
536
+ "REJECTED_EXPIRED",
537
+ "LP position-open command expired before coordinator reconnect"
538
+ );
539
+ }
540
+ let timer;
541
+ try {
542
+ return await new Promise((resolve, reject) => {
543
+ timer = setTimeout(() => {
544
+ reject(
545
+ new CommandError(
546
+ "REJECTED_EXPIRED",
547
+ "LP position-open command expired during coordinator reconnect"
548
+ )
549
+ );
550
+ }, remainingMs);
551
+ Promise.resolve().then(reconnectSession).then(resolve, reject);
552
+ });
553
+ } finally {
554
+ if (timer !== void 0) clearTimeout(timer);
555
+ }
556
+ }
557
+ function isAmbiguousLpPositionOpenFailure(error) {
558
+ return isAmbiguousCoordinatorRequestFailure(error);
559
+ }
560
+ function isAmbiguousCoordinatorRequestFailure(error) {
561
+ return error instanceof TransportError || error instanceof CommandError && error.code === "REJECTED_STALE_JOB";
562
+ }
563
+ async function getPosition(session, lpPositionId, auth, options = {}) {
352
564
  let cursor;
353
565
  let position;
354
566
  const seenCursors = /* @__PURE__ */ new Set();
355
567
  for (; ; ) {
356
- const response = await requestForPosition(session, lpPositionId, auth, "position_get", {
357
- case: "positionGet",
358
- value: cursor === void 0 ? {} : { redemption_history_cursor: cursor }
359
- });
568
+ const pageOptions = lpPositionPageRequestOptions(options);
569
+ const response = await requestForPosition(
570
+ session,
571
+ lpPositionId,
572
+ auth,
573
+ "position_get",
574
+ {
575
+ case: "positionGet",
576
+ value: cursor === void 0 ? {} : { redemption_history_cursor: cursor }
577
+ },
578
+ pageOptions
579
+ );
360
580
  const page = expectPosition(response);
361
581
  const mergedPosition = position === void 0 ? page : mergePositionHistoryPages(position, page);
362
582
  position = mergedPosition;
@@ -373,6 +593,18 @@ async function getPosition(session, lpPositionId, auth) {
373
593
  cursor = nextCursor;
374
594
  }
375
595
  }
596
+ function lpPositionPageRequestOptions(options) {
597
+ if (options.deadlineMs === void 0) return options;
598
+ const remainingMs = options.deadlineMs - Date.now();
599
+ if (remainingMs <= 0) {
600
+ throw new CommandError("REJECTED_STALE_JOB", "LP position observation deadline elapsed");
601
+ }
602
+ return {
603
+ deadlineMs: options.deadlineMs,
604
+ signal: options.signal,
605
+ timeoutMs: Math.min(options.timeoutMs ?? LP_COMMAND_REQUEST_TIMEOUT_MS, remainingMs)
606
+ };
607
+ }
376
608
  async function finalizeDkgBatch(session, input, auth) {
377
609
  const response = await requestForPosition(session, input.lpPositionId, auth, "dkg_finalize", {
378
610
  case: "dkgFinalize",
@@ -461,41 +693,64 @@ async function makeAvailable(session, input, auth) {
461
693
  async function executeWithdrawal(session, input, auth) {
462
694
  validateLpWithdrawalPlan({
463
695
  destinationAddresses: input.destinationAddresses,
464
- depositPrincipalLamports: input.depositPrincipalLamports,
465
- earnedFeeLamports: input.earnedFeeLamports,
466
- allowManyToOne: input.allowManyToOne
696
+ depositPrincipalLamports: input.withdrawalMode === "max_available" ? 1 : input.depositPrincipalLamports,
697
+ earnedFeeLamports: input.withdrawalMode === "max_available" ? 0 : input.earnedFeeLamports,
698
+ // Max mode deliberately does not preview source count. The coordinator
699
+ // owns the accepted split, so local destination validation must not reject
700
+ // a one-destination request based on an unknown current source count.
701
+ allowManyToOne: input.withdrawalMode === "max_available" || input.allowManyToOne
467
702
  });
468
703
  const response = await requestForPosition(session, input.lpPositionId, auth, "withdraw_request", {
469
704
  case: "withdrawRequest",
470
705
  value: {
471
706
  destination_addresses: input.destinationAddresses,
472
- deposit_principal_lamports: input.depositPrincipalLamports,
473
- earned_fee_lamports: input.earnedFeeLamports,
707
+ deposit_principal_lamports: input.withdrawalMode === "max_available" ? 0 : input.depositPrincipalLamports,
708
+ earned_fee_lamports: input.withdrawalMode === "max_available" ? 0 : input.earnedFeeLamports,
709
+ withdrawal_mode: input.withdrawalMode,
474
710
  requested_at_ms: input.requestedAtMs
475
711
  }
476
712
  });
477
713
  if (response.case !== "withdrawalExecution") {
478
714
  throw new Error("Unexpected LP withdrawal execution response");
479
715
  }
716
+ const execution = response.value;
717
+ if (BigInt(execution.accepted_lamports) !== BigInt(execution.accepted_deposit_principal_lamports) + BigInt(execution.accepted_earned_fee_lamports)) {
718
+ throw new PolicyValidationError(
719
+ "INVALID_WITHDRAWAL_PLAN",
720
+ "Coordinator accepted withdrawal total does not match its principal and fee split"
721
+ );
722
+ }
723
+ if (input.withdrawalMode === "exact" && (execution.accepted_deposit_principal_lamports !== input.depositPrincipalLamports || execution.accepted_earned_fee_lamports !== input.earnedFeeLamports)) {
724
+ throw new PolicyValidationError(
725
+ "INVALID_WITHDRAWAL_PLAN",
726
+ "Coordinator accepted withdrawal split does not match the signed exact claim"
727
+ );
728
+ }
480
729
  return {
481
- withdrawalId: response.value.withdrawal_id,
482
- txSignatures: [...response.value.tx_signatures]
730
+ withdrawalId: execution.withdrawal_id,
731
+ txSignatures: [...execution.tx_signatures],
732
+ acceptedLamports: execution.accepted_lamports,
733
+ acceptedDepositPrincipalLamports: execution.accepted_deposit_principal_lamports,
734
+ acceptedEarnedFeeLamports: execution.accepted_earned_fee_lamports
483
735
  };
484
736
  }
485
737
  function request(session, auth, action, payload) {
486
738
  return requestForPosition(session, auth.lpPositionId, auth, action, payload);
487
739
  }
488
- async function requestForPosition(session, lpPositionId, auth, action, payload, timeoutMs = LP_COMMAND_REQUEST_TIMEOUT_MS) {
740
+ async function requestForPosition(session, lpPositionId, auth, action, payload, options = {}) {
741
+ const timeoutMs = options.timeoutMs ?? LP_COMMAND_REQUEST_TIMEOUT_MS;
489
742
  assertAuthMatchesPosition(lpPositionId, auth);
490
743
  const protocol = await loadProtocol();
491
744
  const state = getSessionState(session);
492
745
  const attestation = state.channel?.attestation;
493
- if (!attestation) throw new Error("LP coordinator attestation is not available");
746
+ if (!attestation) {
747
+ throw new TransportError("CONNECTION_LOST", "LP coordinator attestation is not available");
748
+ }
494
749
  const command = {
495
750
  lp_position_id: lpPositionId,
496
751
  action,
497
- nonce: randomBytes(32),
498
- expires_at_ms: Date.now() + Math.min(COMMAND_TTL_MS, timeoutMs),
752
+ nonce: options.commandNonce === void 0 ? randomBytes(32) : new Uint8Array(options.commandNonce),
753
+ expires_at_ms: options.commandExpiresAtMs ?? Date.now() + Math.min(COMMAND_TTL_MS, timeoutMs),
499
754
  coordinator_attestation_hash: await coordinatorAttestationHash(
500
755
  attestation.binaryHash,
501
756
  attestation.pubkeyHex
@@ -504,8 +759,10 @@ async function requestForPosition(session, lpPositionId, auth, action, payload,
504
759
  };
505
760
  const signature = await signLpCommandBytes(auth, protocol.encodeLpCommandForSigning(command));
506
761
  const { type, payload: envelopePayload } = protocol.lpSignedCommandEnvelope(command, signature);
762
+ const requestOptions = lpPositionPageRequestOptions(options);
507
763
  const envelope = await requestEnvelope(session, type, envelopePayload, {
508
- timeoutMs
764
+ timeoutMs: requestOptions.timeoutMs ?? LP_COMMAND_REQUEST_TIMEOUT_MS,
765
+ signal: requestOptions.signal
509
766
  });
510
767
  return protocol.asLpCommandResponsePayload(envelope);
511
768
  }
@@ -533,9 +790,20 @@ function mapPosition(position) {
533
790
  refillThreshold: position.refill_threshold,
534
791
  shards: position.shards.map(mapShard),
535
792
  createdAt: position.created_at_ms,
793
+ redemptionPermission: mapLpRedemptionPermission(position.redemption_permission),
536
794
  ...position.actor_sync === void 0 ? {} : { actorSync: mapLpActorSync(position.actor_sync) }
537
795
  };
538
796
  }
797
+ function mapLpRedemptionPermission(permission) {
798
+ if (permission === void 0) {
799
+ return { state: "unknown", grantRevision: 0, observedAtMs: 0 };
800
+ }
801
+ return {
802
+ state: permission.state,
803
+ grantRevision: permission.grant_revision,
804
+ observedAtMs: permission.observed_at_ms
805
+ };
806
+ }
539
807
  function mapLpActorSync(actorSync) {
540
808
  const redeemableBucketValues = [
541
809
  actorSync.redeemable_principal_lamports,
@@ -564,6 +832,7 @@ function mapLpActorSync(actorSync) {
564
832
  lifetimeEarnedLamports: actorSync.lifetime_earned_lamports ?? 0,
565
833
  redeemablePrincipalSourceCount: actorSync.redeemable_principal_source_count ?? 0,
566
834
  redeemableFeeSourceCount: actorSync.redeemable_fee_source_count ?? 0,
835
+ redeemableSourceCount: actorSync.redeemable_source_count,
567
836
  pendingReservationCount: actorSync.pending_reservation_count,
568
837
  reservedPayoutLamports: actorSync.reserved_payout_lamports,
569
838
  estimatedNetworkFeeLamports: actorSync.estimated_network_fee_lamports,
@@ -607,6 +876,7 @@ function mapLpRedemptionHistory(history) {
607
876
  return {
608
877
  redemptionId: history.redemption_id,
609
878
  state: history.state,
879
+ ...history.withdrawal_mode === void 0 ? {} : { withdrawalMode: history.withdrawal_mode },
610
880
  acceptedLamports: history.accepted_lamports,
611
881
  acceptedDepositPrincipalLamports: history.accepted_deposit_principal_lamports,
612
882
  acceptedEarnedFeeLamports: history.accepted_earned_fee_lamports,
@@ -685,7 +955,7 @@ function randomBytes(length) {
685
955
  return bytes;
686
956
  }
687
957
  function loadProtocol() {
688
- protocolPromise ??= import('./coordinator-VVJ33WF2.js');
958
+ protocolPromise ??= import('./coordinator-PEWHNZ7I.js');
689
959
  return protocolPromise;
690
960
  }
691
961
 
@@ -960,9 +1230,10 @@ var WITHDRAWAL_MISSING_RECORD_MESSAGE = "LP withdrawal record is missing";
960
1230
  var WITHDRAWAL_RECORD_MISMATCH_MESSAGE = "LP withdrawal record does not match this confirmation request";
961
1231
  var WITHDRAWAL_NO_LAMPORTS_MESSAGE = "LP redemption did not land any lamports on-chain";
962
1232
  var WITHDRAWAL_HISTORY_MISMATCH_MESSAGE = "LP redemption history does not match this request";
1233
+ var LP_REDEMPTION_PERMISSION_UNAVAILABLE_MESSAGE = "LP redemption is unavailable until the coordinator confirms permission";
963
1234
  var LP_WITHDRAWAL_UNRESOLVED_FUNDING_MESSAGE = "LP position has unresolved funding shards before redemption";
964
1235
  var UNSUPPORTED_FEE_POLICY_MESSAGE = "LP fee policy overrides are not advertised by the coordinator";
965
- var FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE = "LP earned-fee source count is not available; pass allowManyToOne: true to acknowledge many-to-one withdrawal risk";
1236
+ var LP_FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE = "LP earned-fee source count is not available; pass allowManyToOne: true to acknowledge many-to-one withdrawal risk";
966
1237
  var LpRedemptionReconcilingError = class extends LpLifecycleError {
967
1238
  constructor() {
968
1239
  super("LP_REDEMPTION_RECONCILING", LP_REDEMPTION_RECONCILING_MESSAGE);
@@ -981,6 +1252,16 @@ function isLpRedemptionReconcilingError(error) {
981
1252
  function isLpPositionAuthMissingError(error) {
982
1253
  return error instanceof LpPositionAuthMissingError || error instanceof Error && error.message === LP_MISSING_POSITION_CODE_MESSAGE;
983
1254
  }
1255
+ function previewLpWithdrawalPlan(position, args = {}) {
1256
+ const claim = resolveWithdrawalClaim(position, args);
1257
+ return {
1258
+ ...claim,
1259
+ sourceCount: lpWithdrawalSourceCountForClaim(position, {
1260
+ ...claim,
1261
+ allowManyToOne: args.allowManyToOne
1262
+ })
1263
+ };
1264
+ }
984
1265
  var lifecycleClients = /* @__PURE__ */ new WeakMap();
985
1266
  function createLpLifecycleClient(session, runtime = {}) {
986
1267
  const state = {
@@ -989,7 +1270,13 @@ function createLpLifecycleClient(session, runtime = {}) {
989
1270
  redemptions: /* @__PURE__ */ new Map(),
990
1271
  topUps: /* @__PURE__ */ new Map()
991
1272
  };
992
- const client = runtime.coordinatorClient ?? createLpCoordinatorClient(session);
1273
+ let currentSession = session;
1274
+ const reconnectSession = runtime.reconnectSession === void 0 ? void 0 : async () => {
1275
+ const nextSession = await runtime.reconnectSession();
1276
+ currentSession = nextSession;
1277
+ return nextSession;
1278
+ };
1279
+ const client = runtime.coordinatorClient ?? createLpCoordinatorClient(session, { reconnectSession });
993
1280
  const nowMs = runtime.nowMs ?? Date.now;
994
1281
  const sleep2 = runtime.sleep ?? defaultSleep;
995
1282
  const requireAuth = (positionId) => {
@@ -999,9 +1286,17 @@ function createLpLifecycleClient(session, runtime = {}) {
999
1286
  };
1000
1287
  const rememberPosition = (position) => {
1001
1288
  const snapshot = clonePosition(position);
1002
- state.positions.set(snapshot.id, snapshot);
1003
- hydrateLpRedemptionsFromPosition(state, snapshot);
1004
- return position;
1289
+ const previous = state.positions.get(snapshot.id);
1290
+ const merged = previous === void 0 ? snapshot : {
1291
+ ...snapshot,
1292
+ redemptionPermission: mergeLpRedemptionPermission(
1293
+ previous.redemptionPermission,
1294
+ snapshot.redemptionPermission
1295
+ )
1296
+ };
1297
+ state.positions.set(merged.id, merged);
1298
+ hydrateLpRedemptionsFromPosition(state, merged);
1299
+ return merged;
1005
1300
  };
1006
1301
  const rememberAuth = (auth) => {
1007
1302
  state.authByPosition.set(auth.lpPositionId, auth);
@@ -1025,7 +1320,7 @@ function createLpLifecycleClient(session, runtime = {}) {
1025
1320
  }
1026
1321
  };
1027
1322
  const completeDkgBatch2 = async (positionId, options = {}) => {
1028
- assertAttested(session);
1323
+ assertAttested(currentSession);
1029
1324
  const auth = requireAuth(positionId);
1030
1325
  let activeSessionRetryStartedAtMs = null;
1031
1326
  for (; ; ) {
@@ -1089,39 +1384,55 @@ function createLpLifecycleClient(session, runtime = {}) {
1089
1384
  }
1090
1385
  };
1091
1386
  return {
1092
- async createPosition(args = {}) {
1387
+ async createPosition(args) {
1093
1388
  assertNoUnsupportedFeePolicy(args.feePolicy);
1094
1389
  const shardCount = args.shardCount ?? LP_DEFAULT_TARGET_SHARDS;
1095
1390
  assertShardCount(shardCount, LP_MAX_TARGET_SHARDS, "shardCount");
1096
- assertAttested(session);
1391
+ assertAttested(currentSession);
1392
+ const committedLamports = args.committedLamports ?? DEFAULT_SHARD_FUNDING_LAMPORTS;
1393
+ const refillThreshold = args.refillThreshold ?? Math.ceil(shardCount * DEFAULT_REFILL_THRESHOLD_RATIO);
1394
+ const creationAuthorization = await client.authorizePositionCreation(
1395
+ {
1396
+ committedLamports,
1397
+ shardCount,
1398
+ shardAmountLamports: DEFAULT_SHARD_FUNDING_LAMPORTS,
1399
+ refillThreshold
1400
+ },
1401
+ args.wallet
1402
+ );
1097
1403
  const positionId = runtime.idFactory?.() ?? generateId(LP_ID_PREFIX);
1098
1404
  const auth = await generateLpPositionCode(positionId);
1099
1405
  rememberAuth(auth);
1100
- args.onPositionCode?.({ positionId, lpPositionCode: auth.code });
1101
- const committedLamports = args.committedLamports ?? DEFAULT_SHARD_FUNDING_LAMPORTS;
1102
1406
  const position = rememberPosition(
1103
1407
  await client.openPosition(
1104
1408
  {
1105
1409
  lpPositionId: positionId,
1410
+ creationAuthorizationId: creationAuthorization.authorizationId,
1106
1411
  authPublicKey: Array.from(auth.authPublicKey),
1107
1412
  refillPublicKey: Array.from(auth.refillPublicKey),
1108
1413
  withdrawalCommitment: Array.from(auth.withdrawalCommitment),
1109
1414
  committedLamports,
1110
1415
  shardCount,
1111
1416
  shardAmountLamports: DEFAULT_SHARD_FUNDING_LAMPORTS,
1112
- refillThreshold: args.refillThreshold ?? Math.ceil(shardCount * DEFAULT_REFILL_THRESHOLD_RATIO),
1417
+ refillThreshold,
1113
1418
  addresses: [],
1114
1419
  createdAtMs: nowMs()
1115
1420
  },
1116
1421
  auth
1117
1422
  )
1118
1423
  );
1424
+ args.onPositionCode?.({ positionId, lpPositionCode: auth.code });
1119
1425
  return { positionId, lpPositionCode: auth.code, position };
1120
1426
  },
1121
1427
  async recoverPosition(args) {
1122
1428
  const auth = await deriveLpPositionCodeAuth(args.code);
1123
1429
  rememberAuth(auth);
1124
- const position = rememberPosition(await client.getPosition(auth.lpPositionId, auth));
1430
+ const position = rememberPosition(
1431
+ await client.getPosition(auth.lpPositionId, auth, {
1432
+ deadlineMs: args.deadlineMs,
1433
+ signal: args.signal
1434
+ })
1435
+ );
1125
1436
  assertPositionMatchesAuth(position, auth);
1126
1437
  return position;
1127
1438
  },
@@ -1133,7 +1444,7 @@ function createLpLifecycleClient(session, runtime = {}) {
1133
1444
  async refill(positionId, args = {}) {
1134
1445
  const requestedShardCount = args.shardCount ?? LP_DEFAULT_REFILL_BATCH_SIZE;
1135
1446
  assertShardCount(requestedShardCount, LP_MAX_REFILL_BATCH_SIZE, "shardCount");
1136
- assertAttested(session);
1447
+ assertAttested(currentSession);
1137
1448
  const auth = requireAuth(positionId);
1138
1449
  const current = await refreshPosition2(positionId);
1139
1450
  args.onPosition?.(current);
@@ -1165,7 +1476,7 @@ function createLpLifecycleClient(session, runtime = {}) {
1165
1476
  });
1166
1477
  },
1167
1478
  async prepareInitialFunding(positionId) {
1168
- assertAttested(session);
1479
+ assertAttested(currentSession);
1169
1480
  const auth = requireAuth(positionId);
1170
1481
  const currentPosition = rememberPosition(await client.getPosition(positionId, auth));
1171
1482
  if (!isLpPositionReadyForInitialFunding(currentPosition)) return null;
@@ -1194,7 +1505,7 @@ function createLpLifecycleClient(session, runtime = {}) {
1194
1505
  return fundingPlanFromShard(position, plannedShard);
1195
1506
  },
1196
1507
  async prepareTopUp(positionId) {
1197
- assertAttested(session);
1508
+ assertAttested(currentSession);
1198
1509
  const auth = requireAuth(positionId);
1199
1510
  const request2 = await client.prepareTopUp(positionId, auth);
1200
1511
  state.topUps.set(request2.topUpId, request2);
@@ -1242,21 +1553,30 @@ function createLpLifecycleClient(session, runtime = {}) {
1242
1553
  }
1243
1554
  },
1244
1555
  async reconcileFunding(positionId) {
1245
- assertAttested(session);
1556
+ assertAttested(currentSession);
1246
1557
  const auth = requireAuth(positionId);
1247
1558
  return rememberPosition(await client.reconcileFunding(positionId, auth));
1248
1559
  },
1249
1560
  async withdrawPosition(positionId, args) {
1250
1561
  validateExplicitWithdrawalInput(args);
1251
1562
  validateWithdrawalDestinations(args);
1252
- assertAttested(session);
1563
+ assertAttested(currentSession);
1253
1564
  const auth = requireAuth(positionId);
1254
1565
  const before = rememberPosition(await client.reconcileFunding(positionId, auth));
1566
+ if (before.redemptionPermission?.state !== "enabled") {
1567
+ throw new PolicyValidationError(
1568
+ "INVALID_REDEMPTION_PLAN",
1569
+ LP_REDEMPTION_PERMISSION_UNAVAILABLE_MESSAGE
1570
+ );
1571
+ }
1255
1572
  const existingRedemption = findOpenRedemptionHistory(before, args);
1256
1573
  if (existingRedemption !== void 0) {
1257
1574
  const execution2 = {
1258
1575
  withdrawalId: existingRedemption.redemptionId,
1259
- txSignatures: [...existingRedemption.txSignatures]
1576
+ txSignatures: [...existingRedemption.txSignatures],
1577
+ acceptedLamports: existingRedemption.acceptedLamports,
1578
+ acceptedDepositPrincipalLamports: existingRedemption.acceptedDepositPrincipalLamports,
1579
+ acceptedEarnedFeeLamports: existingRedemption.acceptedEarnedFeeLamports
1260
1580
  };
1261
1581
  const redemption2 = {
1262
1582
  ...buildLpRedemptionRecord({
@@ -1276,39 +1596,44 @@ function createLpLifecycleClient(session, runtime = {}) {
1276
1596
  state.redemptions.set(execution2.withdrawalId, redemption2);
1277
1597
  return { execution: execution2, position: before, redemption: redemption2 };
1278
1598
  }
1279
- const claim = resolveWithdrawalClaim(before, args);
1280
- const sourceCount = lpWithdrawalSourceCountForClaim(before, {
1281
- ...claim,
1282
- allowManyToOne: args.allowManyToOne
1283
- });
1284
- const validationInput = {
1285
- lpPositionId: positionId,
1286
- destinationAddresses: args.destinationAddresses,
1287
- depositPrincipalLamports: claim.depositPrincipalLamports,
1288
- earnedFeeLamports: claim.earnedFeeLamports,
1289
- requestedAtMs: nowMs(),
1290
- allowManyToOne: args.allowManyToOne ?? false,
1291
- sourceCount
1292
- };
1293
- validateLpWithdrawalPlan(validationInput);
1599
+ const preview = args.withdrawalMode === "exact" ? previewLpWithdrawalPlan(before, args) : void 0;
1600
+ const requestedAtMs = nowMs();
1601
+ if (args.withdrawalMode === "exact") {
1602
+ validateLpWithdrawalPlan({
1603
+ destinationAddresses: args.destinationAddresses,
1604
+ depositPrincipalLamports: preview.depositPrincipalLamports,
1605
+ earnedFeeLamports: preview.earnedFeeLamports,
1606
+ allowManyToOne: args.allowManyToOne ?? false,
1607
+ sourceCount: preview.sourceCount
1608
+ });
1609
+ }
1294
1610
  const execution = await executeWithdrawalAfterFundingReadiness(
1295
1611
  positionId,
1296
1612
  {
1297
- lpPositionId: validationInput.lpPositionId,
1298
- destinationAddresses: validationInput.destinationAddresses,
1299
- depositPrincipalLamports: validationInput.depositPrincipalLamports,
1300
- earnedFeeLamports: validationInput.earnedFeeLamports,
1301
- requestedAtMs: validationInput.requestedAtMs,
1302
- allowManyToOne: args.allowManyToOne === true || sourceCount === 1
1613
+ lpPositionId: positionId,
1614
+ destinationAddresses: args.destinationAddresses,
1615
+ depositPrincipalLamports: args.withdrawalMode === "max_available" ? 0 : preview.depositPrincipalLamports,
1616
+ earnedFeeLamports: args.withdrawalMode === "max_available" ? 0 : preview.earnedFeeLamports,
1617
+ withdrawalMode: args.withdrawalMode,
1618
+ requestedAtMs,
1619
+ allowManyToOne: args.allowManyToOne === true || preview?.sourceCount === 1
1303
1620
  },
1304
1621
  auth
1305
1622
  );
1623
+ const acceptedLamports = execution.acceptedLamports;
1624
+ if (args.withdrawalMode === "max_available" && acceptedLamports === void 0) {
1625
+ throw new PolicyValidationError(
1626
+ "INVALID_WITHDRAWAL_PLAN",
1627
+ "LP redemption response did not include the sealed accepted amount"
1628
+ );
1629
+ }
1630
+ const acceptedAmount = acceptedLamports ?? preview.depositPrincipalLamports + preview.earnedFeeLamports;
1306
1631
  const pendingRedemption = buildLpRedemptionRecord({
1307
1632
  execution,
1308
1633
  position: before,
1309
1634
  destinationAddresses: args.destinationAddresses,
1310
- acceptedLamports: claim.depositPrincipalLamports + claim.earnedFeeLamports,
1311
- requestedTargetLamports: (before.actorSync?.withdrawalRequestedLamports ?? 0) + claim.depositPrincipalLamports + claim.earnedFeeLamports,
1635
+ acceptedLamports: acceptedAmount,
1636
+ requestedTargetLamports: (before.actorSync?.withdrawalRequestedLamports ?? 0) + acceptedAmount,
1312
1637
  landedBaselineLamports: before.actorSync?.withdrawalLandedLamports ?? 0,
1313
1638
  landedFeeBaselineLamports: before.actorSync?.withdrawalLandedFeeLamports ?? 0,
1314
1639
  txSignatureBaseline: before.actorSync?.withdrawalTxHashes ?? [],
@@ -1329,10 +1654,32 @@ function createLpLifecycleClient(session, runtime = {}) {
1329
1654
  options
1330
1655
  });
1331
1656
  state.redemptions.set(record.redemptionId, record);
1332
- const deadlineMs = nowMs() + (runtime.withdrawalConfirmationTimeoutMs ?? LP_WITHDRAWAL_CONFIRMATION_TIMEOUT_MS);
1657
+ const deadlineMs = options.deadlineMs ?? nowMs() + (runtime.withdrawalConfirmationTimeoutMs ?? LP_WITHDRAWAL_CONFIRMATION_TIMEOUT_MS);
1333
1658
  const pollIntervalMs = runtime.withdrawalConfirmationPollIntervalMs ?? LP_WITHDRAWAL_CONFIRMATION_POLL_INTERVAL_MS;
1334
1659
  for (; ; ) {
1335
- const position = rememberPosition(await client.getPosition(positionId, auth));
1660
+ throwIfWithdrawalConfirmationAborted(options.signal);
1661
+ if (nowMs() >= deadlineMs) {
1662
+ markLpRedemptionReconciling(state, record, options);
1663
+ }
1664
+ const remainingMs = deadlineMs - nowMs();
1665
+ if (remainingMs <= 0) {
1666
+ markLpRedemptionReconciling(state, record, options);
1667
+ }
1668
+ let position;
1669
+ try {
1670
+ position = rememberPosition(
1671
+ await client.getPosition(positionId, auth, {
1672
+ deadlineMs,
1673
+ signal: options.signal
1674
+ })
1675
+ );
1676
+ } catch (error) {
1677
+ if (isLpPositionObservationDeadlineError(error, deadlineMs, nowMs)) {
1678
+ markLpRedemptionReconciling(state, record, options);
1679
+ }
1680
+ throw error;
1681
+ }
1682
+ throwIfWithdrawalConfirmationAborted(options.signal);
1336
1683
  options.onPosition?.(position);
1337
1684
  const reconciled = reconcileLpRedemptionFromActorSync(record, position);
1338
1685
  record = reconciled;
@@ -1348,17 +1695,15 @@ function createLpLifecycleClient(session, runtime = {}) {
1348
1695
  if (reconciled.status === "failed") {
1349
1696
  throw new Error(reconciled.error ?? WITHDRAWAL_NO_LAMPORTS_MESSAGE);
1350
1697
  }
1351
- if (nowMs() > deadlineMs) {
1352
- state.redemptions.set(record.redemptionId, { ...reconciled, status: "reconciling" });
1353
- options.onRedemptionUpdate?.({ ...reconciled, status: "reconciling" });
1354
- throw new LpRedemptionReconcilingError();
1698
+ if (nowMs() >= deadlineMs) {
1699
+ markLpRedemptionReconciling(state, reconciled, options);
1355
1700
  }
1356
- await sleep2(pollIntervalMs);
1701
+ await sleepWithAbort(sleep2, pollIntervalMs, options.signal);
1357
1702
  }
1358
1703
  },
1359
1704
  async withdrawFees(_positionId, args) {
1360
1705
  publicKey(args.destination);
1361
- assertAttested(session);
1706
+ assertAttested(currentSession);
1362
1707
  throw new RoutingError(
1363
1708
  "VERSION_NOT_SUPPORTED",
1364
1709
  "LP fee withdrawal is not advertised by the coordinator"
@@ -1462,10 +1807,12 @@ function isLpPositionReadyForInitialFunding(position) {
1462
1807
  }
1463
1808
  function resolveWithdrawalClaim(position, args) {
1464
1809
  if (args.depositPrincipalLamports !== void 0 || args.earnedFeeLamports !== void 0) {
1465
- return {
1810
+ const claim = {
1466
1811
  depositPrincipalLamports: args.depositPrincipalLamports ?? 0,
1467
1812
  earnedFeeLamports: args.earnedFeeLamports ?? 0
1468
1813
  };
1814
+ validateLpWithdrawalClaim(claim);
1815
+ return claim;
1469
1816
  }
1470
1817
  const legacyDepositPrincipalLamports = position.shards.filter((shard) => shard.status === "AVAILABLE" && shard.amountLamports > 0).reduce((sum, shard) => sum + shard.amountLamports, 0);
1471
1818
  const actorRedeemablePrincipalLamports = Math.max(
@@ -1491,11 +1838,11 @@ function resolveWithdrawalClaim(position, args) {
1491
1838
  return { depositPrincipalLamports, earnedFeeLamports };
1492
1839
  }
1493
1840
  function findOpenRedemptionHistory(position, args) {
1494
- const hasExplicitClaim = args.depositPrincipalLamports !== void 0 || args.earnedFeeLamports !== void 0;
1841
+ const hasExplicitClaim = args.withdrawalMode === "exact";
1495
1842
  const requestedPrincipalLamports = args.depositPrincipalLamports ?? 0;
1496
1843
  const requestedFeeLamports = args.earnedFeeLamports ?? 0;
1497
1844
  return position.actorSync?.redemptionHistory?.find(
1498
- (history) => LP_REDEMPTION_HISTORY_OPEN_STATES.has(history.state) && history.detailsTruncated !== true && sameStringList(history.destinationAddresses, args.destinationAddresses) && (!hasExplicitClaim || history.acceptedDepositPrincipalLamports === requestedPrincipalLamports && history.acceptedEarnedFeeLamports === requestedFeeLamports)
1845
+ (history) => LP_REDEMPTION_HISTORY_OPEN_STATES.has(history.state) && history.detailsTruncated !== true && (history.withdrawalMode ?? "exact") === args.withdrawalMode && sameStringList(history.destinationAddresses, args.destinationAddresses) && (!hasExplicitClaim || history.acceptedDepositPrincipalLamports === requestedPrincipalLamports && history.acceptedEarnedFeeLamports === requestedFeeLamports)
1499
1846
  );
1500
1847
  }
1501
1848
  function hasActorSyncLpActivity(sync) {
@@ -1517,11 +1864,25 @@ function hasActorRedeemableProjection(sync) {
1517
1864
  return sync?.redeemableProjectionAvailable === true;
1518
1865
  }
1519
1866
  function validateExplicitWithdrawalInput(args) {
1520
- if (args.depositPrincipalLamports === void 0 && args.earnedFeeLamports === void 0) return;
1867
+ if (args.withdrawalMode === "max_available") {
1868
+ if (args.depositPrincipalLamports !== void 0 || args.earnedFeeLamports !== void 0) {
1869
+ throw new PolicyValidationError(
1870
+ "INVALID_WITHDRAWAL_PLAN",
1871
+ "max_available withdrawal must not carry exact bucket claims"
1872
+ );
1873
+ }
1874
+ return;
1875
+ }
1876
+ if (args.depositPrincipalLamports === void 0 || args.earnedFeeLamports === void 0) {
1877
+ throw new PolicyValidationError(
1878
+ "INVALID_WITHDRAWAL_PLAN",
1879
+ "exact withdrawal requires both bucket claims"
1880
+ );
1881
+ }
1521
1882
  validateLpWithdrawalPlan({
1522
1883
  destinationAddresses: args.destinationAddresses,
1523
- depositPrincipalLamports: args.depositPrincipalLamports ?? 0,
1524
- earnedFeeLamports: args.earnedFeeLamports ?? 0,
1884
+ depositPrincipalLamports: args.depositPrincipalLamports,
1885
+ earnedFeeLamports: args.earnedFeeLamports,
1525
1886
  allowManyToOne: args.allowManyToOne,
1526
1887
  sourceCount: args.destinationAddresses.length
1527
1888
  });
@@ -1554,6 +1915,13 @@ function resolveWithdrawalRecord({
1554
1915
  };
1555
1916
  }
1556
1917
  function lpWithdrawalSourceCountForClaim(position, claim) {
1918
+ const actorSync = position.actorSync;
1919
+ const actorRedeemablePrincipalLamports = actorSync?.redeemablePrincipalLamports ?? 0;
1920
+ const actorRedeemableFeeLamports = actorSync?.redeemableFeeLamports ?? 0;
1921
+ const actorRedeemableSourceCount = actorSync?.redeemableSourceCount;
1922
+ if (actorSync?.redeemableProjectionAvailable === true && claim.depositPrincipalLamports === actorRedeemablePrincipalLamports && claim.earnedFeeLamports === actorRedeemableFeeLamports && actorRedeemableSourceCount !== void 0) {
1923
+ return actorRedeemableSourceCount;
1924
+ }
1557
1925
  let remainingPrincipalLamports = claim.depositPrincipalLamports;
1558
1926
  let principalSourceCount = 0;
1559
1927
  const principalCandidates = [...position.shards].filter((shard) => shard.status === "AVAILABLE" && shard.amountLamports > 0).sort((left, right) => left.shardId.localeCompare(right.shardId));
@@ -1562,7 +1930,6 @@ function lpWithdrawalSourceCountForClaim(position, claim) {
1562
1930
  principalSourceCount += 1;
1563
1931
  remainingPrincipalLamports -= shard.amountLamports;
1564
1932
  }
1565
- const actorRedeemablePrincipalLamports = position.actorSync?.redeemablePrincipalLamports ?? 0;
1566
1933
  const actorRedeemablePrincipalSourceCount = position.actorSync?.redeemablePrincipalSourceCount ?? 0;
1567
1934
  if (claim.depositPrincipalLamports > 0 && position.actorSync?.redeemableProjectionAvailable === true && actorRedeemablePrincipalLamports >= claim.depositPrincipalLamports && actorRedeemablePrincipalSourceCount > 0) {
1568
1935
  principalSourceCount = actorRedeemablePrincipalSourceCount;
@@ -1571,7 +1938,7 @@ function lpWithdrawalSourceCountForClaim(position, claim) {
1571
1938
  if (claim.earnedFeeLamports > 0 && observedEarnedFeeSourceCount < 1 && claim.allowManyToOne !== true) {
1572
1939
  throw new PolicyValidationError(
1573
1940
  "INVALID_WITHDRAWAL_PLAN",
1574
- FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE
1941
+ LP_FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE
1575
1942
  );
1576
1943
  }
1577
1944
  const earnedFeeSourceCount = claim.earnedFeeLamports > 0 ? Math.max(1, observedEarnedFeeSourceCount) : 0;
@@ -1725,6 +2092,15 @@ function clonePosition(position) {
1725
2092
  }
1726
2093
  };
1727
2094
  }
2095
+ function mergeLpRedemptionPermission(existing, next) {
2096
+ const current = existing ?? { state: "unknown", grantRevision: 0, observedAtMs: 0 };
2097
+ const incoming = next ?? { state: "unknown", grantRevision: 0, observedAtMs: 0 };
2098
+ if (incoming.state === "unknown") return incoming;
2099
+ if (current.state === "enabled" && incoming.state !== "enabled") return current;
2100
+ if (incoming.grantRevision > current.grantRevision) return incoming;
2101
+ if (incoming.grantRevision < current.grantRevision) return current;
2102
+ return incoming.observedAtMs > current.observedAtMs ? incoming : current;
2103
+ }
1728
2104
  function generateId(prefix) {
1729
2105
  return `${prefix}_${Array.from(randomBytes2(8)).map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
1730
2106
  }
@@ -1737,6 +2113,30 @@ function throwIfTopUpWaitAborted(signal) {
1737
2113
  if (signal?.aborted !== true) return;
1738
2114
  throw new DOMException("LP top-up wait aborted", "AbortError");
1739
2115
  }
2116
+ function throwIfWithdrawalConfirmationAborted(signal) {
2117
+ if (signal?.aborted !== true) return;
2118
+ throw new DOMException("LP withdrawal confirmation wait aborted", "AbortError");
2119
+ }
2120
+ function markLpRedemptionReconciling(state, record, options) {
2121
+ const reconciling = { ...record, status: "reconciling" };
2122
+ state.redemptions.set(record.redemptionId, reconciling);
2123
+ options.onRedemptionUpdate?.(reconciling);
2124
+ throw new LpRedemptionReconcilingError();
2125
+ }
2126
+ function isLpPositionObservationDeadlineError(error, deadlineMs, nowMs) {
2127
+ return error instanceof CommandError && error.code === "REJECTED_STALE_JOB" && nowMs() >= deadlineMs;
2128
+ }
2129
+ function sleepWithAbort(sleep2, delayMs, signal) {
2130
+ if (signal === void 0) return sleep2(delayMs);
2131
+ throwIfWithdrawalConfirmationAborted(signal);
2132
+ return new Promise((resolve, reject) => {
2133
+ const onAbort = () => reject(new DOMException("LP withdrawal confirmation wait aborted", "AbortError"));
2134
+ signal.addEventListener("abort", onAbort, { once: true });
2135
+ void sleep2(delayMs).then(resolve, reject).finally(() => {
2136
+ signal.removeEventListener("abort", onAbort);
2137
+ });
2138
+ });
2139
+ }
1740
2140
  function defaultSleep(delayMs) {
1741
2141
  return new Promise((resolve) => {
1742
2142
  globalThis.setTimeout(resolve, delayMs);
@@ -1744,7 +2144,7 @@ function defaultSleep(delayMs) {
1744
2144
  }
1745
2145
 
1746
2146
  // src/lp/index.ts
1747
- function createPosition(session, args = {}) {
2147
+ function createPosition(session, args) {
1748
2148
  return defaultLpLifecycleClient(session).createPosition(args);
1749
2149
  }
1750
2150
  function recoverPosition(session, args) {
@@ -1793,6 +2193,6 @@ function listPositions(session) {
1793
2193
  return defaultLpLifecycleClient(session).listPositions();
1794
2194
  }
1795
2195
 
1796
- export { LP_COMMAND_REQUEST_TIMEOUT_MS, LP_DKG_ABORT_CLEANUP_TIMEOUT_MS, LP_DKG_ABORT_REQUEST_TIMEOUT_MS, LP_DKG_WORKER_REQUEST_TIMEOUT_MESSAGE, LP_DKG_WORKER_REQUEST_TIMEOUT_MS, LP_MISSING_POSITION_CODE_MESSAGE, LP_REDEMPTION_RECONCILING_MESSAGE, bytesToHex, completeDkgBatch, confirmPositionCodeSaved, createLpCoordinatorClient, createLpDkgBatch, createLpLifecycleClient, createPosition, defaultLpLifecycleClient, deriveLpPositionCodeAuth, generateLpPositionCode, isLpPositionAuthMissingError, isLpRedemptionReconcilingError, listPositions, normalizeLpPositionCode, prepareInitialFunding, prepareTopUp2 as prepareTopUp, reconcileFunding2 as reconcileFunding, recoverPosition, refill, refreshPosition, signLpCommandBytes, waitForTopUp, waitForWithdrawalConfirmation, withdrawFees, withdrawPosition };
2196
+ export { LP_COMMAND_REQUEST_TIMEOUT_MS, LP_DKG_ABORT_CLEANUP_TIMEOUT_MS, LP_DKG_ABORT_REQUEST_TIMEOUT_MS, LP_DKG_WORKER_REQUEST_TIMEOUT_MESSAGE, LP_DKG_WORKER_REQUEST_TIMEOUT_MS, LP_FEE_SOURCE_COUNT_UNAVAILABLE_MESSAGE, LP_MISSING_POSITION_CODE_MESSAGE, LP_REDEMPTION_RECONCILING_MESSAGE, bytesToHex, completeDkgBatch, confirmPositionCodeSaved, createLpCoordinatorClient, createLpDkgBatch, createLpLifecycleClient, createPosition, defaultLpLifecycleClient, deriveLpPositionCodeAuth, generateLpPositionCode, isLpPositionAuthMissingError, isLpRedemptionReconcilingError, listPositions, normalizeLpPositionCode, prepareInitialFunding, prepareTopUp2 as prepareTopUp, previewLpWithdrawalPlan, reconcileFunding2 as reconcileFunding, recoverPosition, refill, refreshPosition, signLpCommandBytes, waitForTopUp, waitForWithdrawalConfirmation, withdrawFees, withdrawPosition };
1797
2197
  //# sourceMappingURL=lp.js.map
1798
2198
  //# sourceMappingURL=lp.js.map