@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.
- package/README.md +129 -15
- package/dist/.invisible-sdk-build-target.json +7 -0
- package/dist/{chunk-QQSI3ZD7.js → chunk-3MDHAS4D.js} +109 -17
- package/dist/chunk-3MDHAS4D.js.map +1 -0
- package/dist/{chunk-NKRVQXRP.js → chunk-3TGQ4CZ3.js} +297 -11
- package/dist/chunk-3TGQ4CZ3.js.map +1 -0
- package/dist/chunk-DCTYBUY3.js +258 -0
- package/dist/chunk-DCTYBUY3.js.map +1 -0
- package/dist/{chunk-NHEHCUS5.js → chunk-EBWTAXNT.js} +3 -3
- package/dist/{chunk-NHEHCUS5.js.map → chunk-EBWTAXNT.js.map} +1 -1
- package/dist/{chunk-V5HXJRBW.js → chunk-HJFMDMPY.js} +3 -3
- package/dist/chunk-HJFMDMPY.js.map +1 -0
- package/dist/{chunk-GHBQF3A7.js → chunk-QRN46R3F.js} +5 -2
- package/dist/chunk-QRN46R3F.js.map +1 -0
- package/dist/chunk-Y7AK6RBV.js +4189 -0
- package/dist/chunk-Y7AK6RBV.js.map +1 -0
- package/dist/{coordinator-VVJ33WF2.js → coordinator-PEWHNZ7I.js} +3 -3
- package/dist/{coordinator-VVJ33WF2.js.map → coordinator-PEWHNZ7I.js.map} +1 -1
- package/dist/events.js +3 -3
- package/dist/index.d.ts +69 -353
- package/dist/index.js +161 -43
- package/dist/index.js.map +1 -1
- package/dist/lp.d.ts +76 -18
- package/dist/lp.js +511 -111
- package/dist/lp.js.map +1 -1
- package/dist/presets.d.ts +7 -40
- package/dist/presets.js +10 -68
- package/dist/presets.js.map +1 -1
- package/dist/stats.js +1 -1
- package/dist/storage.js +2 -243
- package/dist/storage.js.map +1 -1
- package/dist/{errors-N2touVm4.d.ts → types.generated-YhFu9ZBR.d.ts} +65 -4
- package/dist/user.d.ts +134 -8
- package/dist/user.js +991 -250
- package/dist/user.js.map +1 -1
- package/package.json +5 -31
- package/dist/chunk-BWCGNTN5.js +0 -1153
- package/dist/chunk-BWCGNTN5.js.map +0 -1
- package/dist/chunk-DIA6U2CV.js +0 -1927
- package/dist/chunk-DIA6U2CV.js.map +0 -1
- package/dist/chunk-GHBQF3A7.js.map +0 -1
- package/dist/chunk-NKRVQXRP.js.map +0 -1
- package/dist/chunk-QQSI3ZD7.js.map +0 -1
- package/dist/chunk-V5HXJRBW.js.map +0 -1
- package/dist/types.generated-BSEBz4jR.d.ts +0 -68
package/dist/user.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { scheduledPayoutWindowSeconds } from './chunk-
|
|
2
|
-
import { validatePayoutPolicyShape, formatAjvErrors, createSession, createExponentialBackoff } from './chunk-
|
|
1
|
+
import { scheduledPayoutWindowSeconds } from './chunk-3TGQ4CZ3.js';
|
|
2
|
+
import { validatePayoutPolicyShape, formatAjvErrors, persistTransferStatus, createSession, disabledClientPersistence, readPersistedTransferForResume, persistTransferReady, createExponentialBackoff, persistTransferTerminal, persistTransferRecoveryCode, persistTransferAllocation, persistTransferDkgMetadata } from './chunk-Y7AK6RBV.js';
|
|
3
|
+
export { getPersistedTransfer, listPersistedTransfers, purgePersistedTransfers, removePersistedTransfer } from './chunk-Y7AK6RBV.js';
|
|
3
4
|
import { PRODUCT_MAX_ENTRY_LAMPORTS, PRODUCT_MIN_ENTRY_LAMPORTS } from './chunk-4X5OCCQW.js';
|
|
4
|
-
import { assertAttested, publicKey, solanaAddressFromPublicKeyBytes } from './chunk-
|
|
5
|
-
import { requestEnvelope, CoordinatorMessageType, onEnvelope, closeProtocolRuntime, EnvelopeKind } from './chunk-
|
|
6
|
-
import { getSessionState } from './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import { PolicyValidationError, CommandError,
|
|
9
|
-
export { InvalidDepositAmountError } from './chunk-
|
|
5
|
+
import { assertAttested, publicKey, solanaAddressFromPublicKeyBytes } from './chunk-EBWTAXNT.js';
|
|
6
|
+
import { requestEnvelope, CoordinatorMessageType, onEnvelope, closeProtocolRuntime, EnvelopeKind } from './chunk-3MDHAS4D.js';
|
|
7
|
+
import { getSessionState } from './chunk-HJFMDMPY.js';
|
|
8
|
+
import './chunk-DCTYBUY3.js';
|
|
9
|
+
import { PolicyValidationError, CommandError, StorageError, TransportError, normalizeError, InvalidDepositAmountError, AttestationError, RoutingError } from './chunk-QRN46R3F.js';
|
|
10
|
+
export { InvalidDepositAmountError } from './chunk-QRN46R3F.js';
|
|
10
11
|
import './chunk-VR6T6OJS.js';
|
|
11
12
|
|
|
12
13
|
// src/validation/payoutPolicy.ts
|
|
@@ -171,7 +172,7 @@ function recoveryCodeFromUserInput(input) {
|
|
|
171
172
|
var protocolPromise = null;
|
|
172
173
|
var frostRuntimePromise = null;
|
|
173
174
|
function loadProtocol() {
|
|
174
|
-
protocolPromise ??= import('./coordinator-
|
|
175
|
+
protocolPromise ??= import('./coordinator-PEWHNZ7I.js');
|
|
175
176
|
return protocolPromise;
|
|
176
177
|
}
|
|
177
178
|
function loadFrostRuntime() {
|
|
@@ -185,20 +186,22 @@ var BENIGN_TRANSPORT_DROP = /* @__PURE__ */ Symbol("benignTransportDrop");
|
|
|
185
186
|
var ALICE_ID = 1;
|
|
186
187
|
var COORDINATOR_ID = 2;
|
|
187
188
|
var SYNC_SECRET_BYTE_LENGTH = 32;
|
|
189
|
+
var RESUME_SECRET_BYTE_LENGTH = SYNC_SECRET_BYTE_LENGTH;
|
|
188
190
|
var SYNC_CONTRACT_ID_MISMATCH_MESSAGE = "sync response contract id does not match requested contract";
|
|
189
|
-
var ACTIVE_CONTRACT_REQUEST_MESSAGE = "coordinator session already has an active contract";
|
|
190
191
|
var DEFAULT_SWAP_STATUS_RECONCILE_INTERVAL_MS = 5e3;
|
|
191
192
|
var MAX_SWAP_STATUS_RETRY_DELAY_MS = 3e4;
|
|
192
193
|
var SWAP_STATUS_RETRY_MULTIPLIER = 2;
|
|
194
|
+
var MAX_EVENT_DEDUP_ENTRIES_PER_KIND = 1024;
|
|
193
195
|
var SWAP_STATUS_RETRY_JITTER_RATIO = 0.2;
|
|
194
196
|
var DEFAULT_REFUND_OUTCOME_POLL_BUDGET_MS = 5 * 60 * 1e3;
|
|
195
197
|
var REFUND_OUTCOME_POLL_INTERVAL_MS = 3e3;
|
|
196
198
|
var REFUND_OUTCOME_PER_POLL_TIMEOUT_MS = 5e3;
|
|
199
|
+
var UNBOUND_RECONCILIATION_KEY = "__unbound__";
|
|
197
200
|
var REQUEST_TIMEOUT_MESSAGE = "coordinator request timed out";
|
|
201
|
+
var REFUND_PENDING_FINALIZATION_MESSAGE = "refund accepted and pending finalization; check status again shortly";
|
|
198
202
|
var CURRENT_SCHEDULED_PAYOUT_WINDOW_SECONDS = new Set(scheduledPayoutWindowSeconds());
|
|
199
203
|
var CURRENT_SCHEDULED_PAYOUT_WINDOW_MESSAGE = `scheduled payout_spec.window_seconds must be one of [${scheduledPayoutWindowSeconds().join(", ")}]`;
|
|
200
204
|
var TERMINAL_SWAP_STATES = /* @__PURE__ */ new Set(["completed", "refunded", "failed"]);
|
|
201
|
-
var activeFunctionalContractRequests = /* @__PURE__ */ new WeakSet();
|
|
202
205
|
function isBenignTransportDrop(err) {
|
|
203
206
|
return typeof err === "object" && err !== null && err[BENIGN_TRANSPORT_DROP] === true;
|
|
204
207
|
}
|
|
@@ -210,6 +213,16 @@ function readDkgComplete(protocol, envelope) {
|
|
|
210
213
|
coordinatorTimestampMs: envelope.timestamp_ms
|
|
211
214
|
};
|
|
212
215
|
}
|
|
216
|
+
function assertDepositConfirmedAmount(protocol, envelope, expectedAmountLamports) {
|
|
217
|
+
const payload = protocol.asDepositConfirmedPayload(envelope);
|
|
218
|
+
if (payload.amount_lamports !== expectedAmountLamports) {
|
|
219
|
+
throw new InvalidDepositAmountError(
|
|
220
|
+
BigInt(payload.amount_lamports),
|
|
221
|
+
BigInt(expectedAmountLamports)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return payload;
|
|
225
|
+
}
|
|
213
226
|
function tagBenignTransportDrop(err) {
|
|
214
227
|
err[BENIGN_TRANSPORT_DROP] = true;
|
|
215
228
|
return err;
|
|
@@ -227,22 +240,27 @@ var UserCoordinatorSession = class {
|
|
|
227
240
|
this.sessionStatus = "idle";
|
|
228
241
|
this.swapId = null;
|
|
229
242
|
this.syncSecret = null;
|
|
243
|
+
this.resumeSecret = null;
|
|
230
244
|
this.attested = false;
|
|
231
245
|
this.pushListeners = /* @__PURE__ */ new Map();
|
|
246
|
+
this.swapContexts = /* @__PURE__ */ new Map();
|
|
247
|
+
this.openingSession = null;
|
|
248
|
+
this.activeOperations = 0;
|
|
232
249
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
233
250
|
this.logListeners = /* @__PURE__ */ new Set();
|
|
234
251
|
this.payoutExecutedListeners = /* @__PURE__ */ new Set();
|
|
235
252
|
this.refundExecutedListeners = /* @__PURE__ */ new Set();
|
|
236
253
|
this.normalUserActorSyncListeners = /* @__PURE__ */ new Set();
|
|
237
254
|
this.coordinatorErrorListeners = /* @__PURE__ */ new Set();
|
|
255
|
+
this.storageErrorListeners = /* @__PURE__ */ new Set();
|
|
238
256
|
this.emittedPayoutKeys = /* @__PURE__ */ new Set();
|
|
239
257
|
this.emittedRefundKeys = /* @__PURE__ */ new Set();
|
|
240
|
-
this.
|
|
241
|
-
this.
|
|
242
|
-
this.
|
|
258
|
+
this.statusReconciliationStops = /* @__PURE__ */ new Map();
|
|
259
|
+
this.pushProcessing = /* @__PURE__ */ new Map();
|
|
260
|
+
this.terminalOutcomes = /* @__PURE__ */ new Map();
|
|
243
261
|
this.coordinator = options.coordinator;
|
|
244
262
|
this.createSessionImpl = options.createSession ?? createSession;
|
|
245
|
-
this.
|
|
263
|
+
this.closeSessionAfterOperationFailure = closeSessionAfterRefund;
|
|
246
264
|
}
|
|
247
265
|
onStateChange(listener) {
|
|
248
266
|
this.stateListeners.add(listener);
|
|
@@ -268,6 +286,10 @@ var UserCoordinatorSession = class {
|
|
|
268
286
|
this.coordinatorErrorListeners.add(listener);
|
|
269
287
|
return () => this.coordinatorErrorListeners.delete(listener);
|
|
270
288
|
}
|
|
289
|
+
onStorageError(listener) {
|
|
290
|
+
this.storageErrorListeners.add(listener);
|
|
291
|
+
return () => this.storageErrorListeners.delete(listener);
|
|
292
|
+
}
|
|
271
293
|
get currentSwapState() {
|
|
272
294
|
return this.swapState;
|
|
273
295
|
}
|
|
@@ -278,51 +300,301 @@ var UserCoordinatorSession = class {
|
|
|
278
300
|
get currentSwapId() {
|
|
279
301
|
return this.swapId;
|
|
280
302
|
}
|
|
303
|
+
clientPersistenceState() {
|
|
304
|
+
return this.session ? getSessionState(this.session).clientPersistence : disabledClientPersistence();
|
|
305
|
+
}
|
|
306
|
+
beginOperation() {
|
|
307
|
+
this.activeOperations += 1;
|
|
308
|
+
if (this.activeOperations === 1) this.setSessionStatus("connecting");
|
|
309
|
+
}
|
|
310
|
+
endOperation() {
|
|
311
|
+
this.activeOperations = Math.max(0, this.activeOperations - 1);
|
|
312
|
+
if (this.activeOperations === 0 && this.sessionStatus === "connecting") {
|
|
313
|
+
this.setSessionStatus("idle");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
ensureSwapContext(swapId, syncSecret, resumeSecret) {
|
|
317
|
+
let context = this.swapContexts.get(swapId);
|
|
318
|
+
if (!context) {
|
|
319
|
+
context = {
|
|
320
|
+
swapId,
|
|
321
|
+
requestedAmountLamports: null,
|
|
322
|
+
swapState: this.swapId === swapId ? this.swapState : "new",
|
|
323
|
+
syncSecret: this.swapId === swapId && this.syncSecret ? new Uint8Array(this.syncSecret) : null,
|
|
324
|
+
resumeSecret: this.swapId === swapId && this.resumeSecret ? new Uint8Array(this.resumeSecret) : null,
|
|
325
|
+
bufferDepositConfirmedUntilDelegateAck: false,
|
|
326
|
+
bufferedDepositConfirmed: null
|
|
327
|
+
};
|
|
328
|
+
this.swapContexts.set(swapId, context);
|
|
329
|
+
}
|
|
330
|
+
if (syncSecret !== void 0) context.syncSecret = new Uint8Array(syncSecret);
|
|
331
|
+
if (resumeSecret !== void 0) context.resumeSecret = new Uint8Array(resumeSecret);
|
|
332
|
+
this.setCurrentContext(context);
|
|
333
|
+
return context;
|
|
334
|
+
}
|
|
335
|
+
setCurrentContext(context) {
|
|
336
|
+
this.swapId = context.swapId;
|
|
337
|
+
this.swapState = context.swapState;
|
|
338
|
+
this.syncSecret = context.syncSecret ? new Uint8Array(context.syncSecret) : null;
|
|
339
|
+
this.resumeSecret = context.resumeSecret ? new Uint8Array(context.resumeSecret) : null;
|
|
340
|
+
}
|
|
341
|
+
updateContextState(context, state) {
|
|
342
|
+
context.swapState = state;
|
|
343
|
+
if (this.swapId === context.swapId) {
|
|
344
|
+
this.swapState = state;
|
|
345
|
+
}
|
|
346
|
+
if (TERMINAL_SWAP_STATES.has(state)) {
|
|
347
|
+
this.evictSwapContext(context.swapId);
|
|
348
|
+
}
|
|
349
|
+
this.emitStateChange(context.swapId, state);
|
|
350
|
+
}
|
|
351
|
+
evictSwapContext(swapId) {
|
|
352
|
+
this.statusReconciliationStops.get(swapId)?.();
|
|
353
|
+
this.statusReconciliationStops.delete(swapId);
|
|
354
|
+
const context = this.swapContexts.get(swapId);
|
|
355
|
+
if (context) {
|
|
356
|
+
if (isTerminalSwapState(context.swapState)) {
|
|
357
|
+
this.rememberTerminalOutcome(swapId, context.swapState);
|
|
358
|
+
}
|
|
359
|
+
context.syncSecret?.fill(0);
|
|
360
|
+
context.resumeSecret?.fill(0);
|
|
361
|
+
context.bufferedDepositConfirmed = null;
|
|
362
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
363
|
+
this.swapContexts.delete(swapId);
|
|
364
|
+
}
|
|
365
|
+
if (this.swapId === swapId) {
|
|
366
|
+
this.syncSecret?.fill(0);
|
|
367
|
+
this.resumeSecret?.fill(0);
|
|
368
|
+
this.syncSecret = null;
|
|
369
|
+
this.resumeSecret = null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
clearSwapContexts() {
|
|
373
|
+
for (const swapId of this.swapContexts.keys()) this.evictSwapContext(swapId);
|
|
374
|
+
this.swapContexts.clear();
|
|
375
|
+
this.pushProcessing.clear();
|
|
376
|
+
this.terminalOutcomes.clear();
|
|
377
|
+
this.emittedPayoutKeys.clear();
|
|
378
|
+
this.emittedRefundKeys.clear();
|
|
379
|
+
}
|
|
380
|
+
isActiveSwapContext(context) {
|
|
381
|
+
return this.swapContexts.get(context.swapId) === context;
|
|
382
|
+
}
|
|
383
|
+
rememberTerminalOutcome(swapId, state, refund, storageError) {
|
|
384
|
+
const previous = this.terminalOutcomes.get(swapId);
|
|
385
|
+
this.terminalOutcomes.set(swapId, {
|
|
386
|
+
state,
|
|
387
|
+
...refund !== void 0 && refund !== null ? { refund } : previous?.refund !== void 0 ? { refund: previous.refund } : {},
|
|
388
|
+
...storageError !== void 0 ? { storageError } : previous?.storageError !== void 0 ? { storageError: previous.storageError } : {}
|
|
389
|
+
});
|
|
390
|
+
if (this.terminalOutcomes.size > MAX_EVENT_DEDUP_ENTRIES_PER_KIND) {
|
|
391
|
+
const oldest = this.terminalOutcomes.keys().next().value;
|
|
392
|
+
if (oldest !== void 0) this.terminalOutcomes.delete(oldest);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
281
395
|
async runSwap(params) {
|
|
282
396
|
assertCurrentUserPayoutSpecRuntimeLimits(params.payoutSpec);
|
|
283
|
-
this.
|
|
284
|
-
this.setSessionStatus("connecting");
|
|
397
|
+
this.beginOperation();
|
|
285
398
|
let completed = false;
|
|
399
|
+
let terminalStatus = null;
|
|
286
400
|
try {
|
|
287
401
|
await ensureFrostWasm();
|
|
288
402
|
await this.openSession();
|
|
289
403
|
this.setSessionStatus("running");
|
|
290
404
|
const result = await this.runProtocol(params);
|
|
291
405
|
completed = true;
|
|
292
|
-
|
|
406
|
+
terminalStatus = "done";
|
|
293
407
|
return result;
|
|
294
408
|
} catch (err) {
|
|
295
409
|
const error = this.normalizeSessionError(err);
|
|
296
410
|
if (!isBenignTransportDrop(error)) {
|
|
297
|
-
|
|
411
|
+
terminalStatus = "failed";
|
|
298
412
|
}
|
|
299
413
|
throw error;
|
|
300
414
|
} finally {
|
|
301
|
-
|
|
415
|
+
this.endOperation();
|
|
416
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
417
|
+
this.setSessionStatus(terminalStatus);
|
|
418
|
+
}
|
|
419
|
+
if (!completed && this.activeOperations === 0 && this.closeSessionAfterOperationFailure) {
|
|
302
420
|
this.close();
|
|
303
421
|
}
|
|
304
422
|
}
|
|
305
423
|
}
|
|
306
424
|
async contractRequest(params) {
|
|
307
425
|
assertCurrentUserPayoutSpecRuntimeLimits(params.payoutSpec);
|
|
308
|
-
this.
|
|
309
|
-
|
|
426
|
+
this.beginOperation();
|
|
427
|
+
let completed = false;
|
|
428
|
+
let terminalStatus = null;
|
|
310
429
|
try {
|
|
311
430
|
await ensureFrostWasm();
|
|
312
431
|
await this.openSession();
|
|
313
432
|
this.setSessionStatus("running");
|
|
314
433
|
const prepared = await this.prepareContract(params);
|
|
434
|
+
completed = true;
|
|
435
|
+
terminalStatus = "done";
|
|
315
436
|
return toContractRequestResult(prepared);
|
|
316
437
|
} catch (err) {
|
|
317
438
|
const error = this.normalizeSessionError(err);
|
|
318
|
-
|
|
439
|
+
terminalStatus = "failed";
|
|
319
440
|
throw error;
|
|
441
|
+
} finally {
|
|
442
|
+
this.endOperation();
|
|
443
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
444
|
+
this.setSessionStatus(terminalStatus);
|
|
445
|
+
}
|
|
446
|
+
if (!completed && this.activeOperations === 0 && this.closeSessionAfterOperationFailure) {
|
|
447
|
+
this.close();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async resumePreDelegation(params) {
|
|
452
|
+
if (!params.policySnapshot) {
|
|
453
|
+
throw new CommandError(
|
|
454
|
+
"REJECTED_GUARD",
|
|
455
|
+
"policySnapshot is required to resume pre-delegation"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (params.recoveryCommitment.length !== SYNC_SECRET_BYTE_LENGTH) {
|
|
459
|
+
throw new CommandError(
|
|
460
|
+
"REJECTED_GUARD",
|
|
461
|
+
`recoveryCommitment must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
this.beginOperation();
|
|
465
|
+
let completed = false;
|
|
466
|
+
let terminalStatus = null;
|
|
467
|
+
try {
|
|
468
|
+
await ensureFrostWasm();
|
|
469
|
+
this.restoreSwapContext({
|
|
470
|
+
swapId: params.swapId,
|
|
471
|
+
syncSecret: params.syncSecret,
|
|
472
|
+
resumeSecret: params.resumeSecret
|
|
473
|
+
});
|
|
474
|
+
await this.openSession();
|
|
475
|
+
this.setSessionStatus("running");
|
|
476
|
+
const context = this.ensureSwapContext(params.swapId);
|
|
477
|
+
context.swapState = params.delegateShare ? "awaiting_deposit" : "awaiting_dkg";
|
|
478
|
+
context.bufferDepositConfirmedUntilDelegateAck = true;
|
|
479
|
+
this.setCurrentContext(context);
|
|
480
|
+
let jointPublicKey;
|
|
481
|
+
let depositAddress;
|
|
482
|
+
let depositExpiresAtMs;
|
|
483
|
+
let coordinatorTimestampMs;
|
|
484
|
+
let delegateShare = params.delegateShare ? new Uint8Array(params.delegateShare) : void 0;
|
|
485
|
+
const persistence = this.clientPersistenceState();
|
|
486
|
+
const persistedResumeRecord = await readPersistedTransferForResume(
|
|
487
|
+
persistence,
|
|
488
|
+
context.swapId
|
|
489
|
+
);
|
|
490
|
+
if (params.delegateShare !== void 0 && persistedResumeRecord !== null) {
|
|
491
|
+
if (persistedResumeRecord.depositAddress === void 0 || persistedResumeRecord.depositExpiresAtMs === void 0 || persistedResumeRecord.coordinatorTimestampMs === void 0) {
|
|
492
|
+
throw new StorageError(
|
|
493
|
+
"STORAGE_KEY_MISSING",
|
|
494
|
+
"persisted DKG metadata is missing for saved delegate share",
|
|
495
|
+
{ phase: "ready", swapId: context.swapId, remoteAccepted: false }
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
depositAddress = persistedResumeRecord.depositAddress;
|
|
499
|
+
depositExpiresAtMs = persistedResumeRecord.depositExpiresAtMs;
|
|
500
|
+
coordinatorTimestampMs = persistedResumeRecord.coordinatorTimestampMs;
|
|
501
|
+
}
|
|
502
|
+
if (!delegateShare) {
|
|
503
|
+
const dkg = await this.runDkg(
|
|
504
|
+
context,
|
|
505
|
+
params.onDelegateShareReady,
|
|
506
|
+
persistedResumeRecord !== null
|
|
507
|
+
);
|
|
508
|
+
jointPublicKey = dkg.jointPublicKey;
|
|
509
|
+
depositAddress = solanaAddressFromPublicKeyBytes(jointPublicKey);
|
|
510
|
+
depositExpiresAtMs = dkg.depositExpiresAtMs;
|
|
511
|
+
coordinatorTimestampMs = dkg.coordinatorTimestampMs;
|
|
512
|
+
delegateShare = dkg.keyHandle.secretShareBytes();
|
|
513
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
514
|
+
}
|
|
515
|
+
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck, context.swapId);
|
|
516
|
+
let delegateResp;
|
|
517
|
+
try {
|
|
518
|
+
delegateResp = await this.request(
|
|
519
|
+
CoordinatorMessageType.DelegateShare,
|
|
520
|
+
{
|
|
521
|
+
share: delegateShare,
|
|
522
|
+
recovery_commitment: new Uint8Array(params.recoveryCommitment),
|
|
523
|
+
resume_secret: context.resumeSecret
|
|
524
|
+
},
|
|
525
|
+
{ swapId: context.swapId }
|
|
526
|
+
);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
void delegateAckPromise.catch(() => void 0);
|
|
529
|
+
throw err;
|
|
530
|
+
}
|
|
531
|
+
const delegatedAtMs = this.requireProtocol().asDelegateShareResponsePayload(delegateResp).delegated_at_ms;
|
|
532
|
+
const delegateAck = this.requireProtocol().asDelegateAckPayload(await delegateAckPromise);
|
|
533
|
+
if (delegateAck.delegated_at_ms !== delegatedAtMs) {
|
|
534
|
+
throw new CommandError(
|
|
535
|
+
"REJECTED_GUARD",
|
|
536
|
+
"DelegateAck delegated_at_ms does not match DelegateShare response"
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (!spendingPoliciesEqual(delegateAck.spending_policy, params.policySnapshot)) {
|
|
540
|
+
throw new CommandError(
|
|
541
|
+
"REJECTED_GUARD",
|
|
542
|
+
"DelegateAck spending_policy does not match persisted policy_snapshot"
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
context.requestedAmountLamports = delegateAck.spending_policy.entry_amount_lamports;
|
|
546
|
+
if (context.bufferedDepositConfirmed) {
|
|
547
|
+
assertDepositConfirmedAmount(
|
|
548
|
+
this.requireProtocol(),
|
|
549
|
+
context.bufferedDepositConfirmed,
|
|
550
|
+
delegateAck.spending_policy.entry_amount_lamports
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (persistedResumeRecord !== null) {
|
|
554
|
+
if (depositAddress === void 0 || depositExpiresAtMs === void 0 || coordinatorTimestampMs === void 0) {
|
|
555
|
+
throw new StorageError(
|
|
556
|
+
"STORAGE_KEY_MISSING",
|
|
557
|
+
"persisted DKG metadata is missing for saved delegate share",
|
|
558
|
+
{ phase: "ready", swapId: context.swapId, remoteAccepted: true }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
await persistTransferReady(persistence, {
|
|
562
|
+
swapId: context.swapId,
|
|
563
|
+
depositAddress,
|
|
564
|
+
depositExpiresAtMs,
|
|
565
|
+
delegatedAtMs,
|
|
566
|
+
coordinatorTimestampMs
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
const hadBufferedDeposit = context.bufferedDepositConfirmed !== null;
|
|
570
|
+
await this.releaseDepositConfirmedGate(context);
|
|
571
|
+
if (!hadBufferedDeposit && this.isActiveSwapContext(context)) {
|
|
572
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
573
|
+
}
|
|
574
|
+
completed = true;
|
|
575
|
+
terminalStatus = "done";
|
|
576
|
+
return {
|
|
577
|
+
swapId: context.swapId,
|
|
578
|
+
jointPublicKey,
|
|
579
|
+
depositExpiresAtMs,
|
|
580
|
+
coordinatorTimestampMs,
|
|
581
|
+
delegatedAtMs
|
|
582
|
+
};
|
|
583
|
+
} catch (err) {
|
|
584
|
+
terminalStatus = "failed";
|
|
585
|
+
throw this.normalizeSessionError(err);
|
|
586
|
+
} finally {
|
|
587
|
+
if (!completed) this.cancelPushWaitersForSwap(params.swapId);
|
|
588
|
+
this.endOperation();
|
|
589
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
590
|
+
this.setSessionStatus(terminalStatus);
|
|
591
|
+
}
|
|
320
592
|
}
|
|
321
593
|
}
|
|
322
594
|
/** @internal Used by the functional Session API without closing its caller-owned session. */
|
|
323
595
|
releaseWithoutClosing() {
|
|
324
|
-
this.
|
|
325
|
-
this.
|
|
596
|
+
this.stopAllStatusReconciliations();
|
|
597
|
+
this.clearSwapContexts();
|
|
326
598
|
this.detachSession(
|
|
327
599
|
new TransportError("CONNECTION_LOST", "coordinator session operation released"),
|
|
328
600
|
false,
|
|
@@ -330,12 +602,16 @@ var UserCoordinatorSession = class {
|
|
|
330
602
|
);
|
|
331
603
|
}
|
|
332
604
|
close() {
|
|
333
|
-
this.
|
|
334
|
-
this.
|
|
605
|
+
this.stopAllStatusReconciliations();
|
|
606
|
+
this.clearSwapContexts();
|
|
335
607
|
this.detachSession(new TransportError("CONNECTION_LOST", "coordinator session closed"), true);
|
|
336
608
|
}
|
|
609
|
+
stopAllStatusReconciliations() {
|
|
610
|
+
for (const stop of [...this.statusReconciliationStops.values()]) stop();
|
|
611
|
+
this.statusReconciliationStops.clear();
|
|
612
|
+
}
|
|
337
613
|
restoreSwapContext(params) {
|
|
338
|
-
this.
|
|
614
|
+
if (this.terminalOutcomes.has(params.swapId)) return;
|
|
339
615
|
if (params.syncSecret !== void 0) {
|
|
340
616
|
if (params.syncSecret.length !== SYNC_SECRET_BYTE_LENGTH) {
|
|
341
617
|
throw new CommandError(
|
|
@@ -343,28 +619,52 @@ var UserCoordinatorSession = class {
|
|
|
343
619
|
`syncSecret must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
344
620
|
);
|
|
345
621
|
}
|
|
346
|
-
this.syncSecret = new Uint8Array(params.syncSecret);
|
|
347
622
|
}
|
|
623
|
+
if (params.resumeSecret !== void 0) {
|
|
624
|
+
if (params.resumeSecret.length !== RESUME_SECRET_BYTE_LENGTH) {
|
|
625
|
+
throw new CommandError(
|
|
626
|
+
"REJECTED_GUARD",
|
|
627
|
+
`resumeSecret must be ${RESUME_SECRET_BYTE_LENGTH} bytes`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
this.ensureSwapContext(params.swapId, params.syncSecret, params.resumeSecret);
|
|
348
632
|
}
|
|
349
633
|
async requestSwapStatus(timeoutMs) {
|
|
350
634
|
if (!this.swapId) {
|
|
351
635
|
throw new CommandError("REJECTED_STATE", "Sync requires swapId");
|
|
352
636
|
}
|
|
637
|
+
const context = this.ensureSwapContext(this.swapId, this.syncSecret ?? void 0);
|
|
638
|
+
return this.requestSwapStatusForContext(context, timeoutMs);
|
|
639
|
+
}
|
|
640
|
+
async requestSwapStatusForContext(context, timeoutMs) {
|
|
353
641
|
await this.openSession();
|
|
354
642
|
const env = await this.request(
|
|
355
643
|
CoordinatorMessageType.Sync,
|
|
356
644
|
{
|
|
357
645
|
actor: "user",
|
|
358
|
-
...
|
|
646
|
+
...context.syncSecret ? { sync_secret: context.syncSecret } : {}
|
|
359
647
|
},
|
|
360
|
-
|
|
648
|
+
{
|
|
649
|
+
swapId: context.swapId,
|
|
650
|
+
timeoutMs,
|
|
651
|
+
rejectMatchingOutOfBandErrors: true
|
|
652
|
+
}
|
|
361
653
|
);
|
|
362
654
|
const status = this.requireProtocol().asSyncUserResponsePayload(env);
|
|
363
|
-
this.
|
|
655
|
+
const persistence = this.clientPersistenceState();
|
|
656
|
+
await this.enqueueSwapProcessing(context.swapId, async () => {
|
|
657
|
+
if (!this.isActiveSwapContext(context)) return;
|
|
658
|
+
await persistTransferStatus(persistence, context.swapId, status, env.timestamp_ms);
|
|
659
|
+
if (!this.isActiveSwapContext(context)) return;
|
|
660
|
+
this.applySwapStatus(context, status, env.timestamp_ms);
|
|
661
|
+
});
|
|
364
662
|
return status;
|
|
365
663
|
}
|
|
366
664
|
startSwapStatusReconciliation(intervalMs = DEFAULT_SWAP_STATUS_RECONCILE_INTERVAL_MS) {
|
|
367
|
-
this.
|
|
665
|
+
const reconciliationContext = this.swapId ? this.ensureSwapContext(this.swapId, this.syncSecret ?? void 0) : null;
|
|
666
|
+
const reconciliationKey = reconciliationContext?.swapId ?? UNBOUND_RECONCILIATION_KEY;
|
|
667
|
+
this.statusReconciliationStops.get(reconciliationKey)?.();
|
|
368
668
|
let stopped = false;
|
|
369
669
|
let reconciling = false;
|
|
370
670
|
let timer = null;
|
|
@@ -389,20 +689,18 @@ var UserCoordinatorSession = class {
|
|
|
389
689
|
if (stopped || reconciling || !isVisible()) return;
|
|
390
690
|
reconciling = true;
|
|
391
691
|
try {
|
|
392
|
-
const status = await this.requestSwapStatus();
|
|
692
|
+
const status = reconciliationContext ? await this.requestSwapStatusForContext(reconciliationContext) : await this.requestSwapStatus();
|
|
393
693
|
retryBackoff.reset();
|
|
394
694
|
if (status.state === "completed" || status.state === "refunded" || status.state === "failed") {
|
|
395
|
-
|
|
396
|
-
if (timer) clearTimeout(timer);
|
|
695
|
+
stopReconciliation();
|
|
397
696
|
} else {
|
|
398
697
|
schedule(intervalMs);
|
|
399
698
|
}
|
|
400
699
|
} catch (err) {
|
|
401
700
|
this.log(`SwapStatus reconcile failed: ${normalizeError(err)}`, "info");
|
|
402
701
|
if (!isRetryableSwapStatusReconciliationError(err)) {
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
this.close();
|
|
702
|
+
stopReconciliation();
|
|
703
|
+
if (err instanceof AttestationError) this.close();
|
|
406
704
|
return;
|
|
407
705
|
}
|
|
408
706
|
schedule(retryBackoff.nextDelayMs());
|
|
@@ -438,7 +736,8 @@ var UserCoordinatorSession = class {
|
|
|
438
736
|
browserDocument?.addEventListener("freeze", handleFreeze);
|
|
439
737
|
browserDocument?.addEventListener("resume", reconcileWhenVisible);
|
|
440
738
|
reconcileWhenVisible();
|
|
441
|
-
|
|
739
|
+
const stopReconciliation = () => {
|
|
740
|
+
if (stopped) return;
|
|
442
741
|
stopped = true;
|
|
443
742
|
if (timer) clearTimeout(timer);
|
|
444
743
|
browserWindow?.removeEventListener("focus", reconcileWhenVisible);
|
|
@@ -447,8 +746,12 @@ var UserCoordinatorSession = class {
|
|
|
447
746
|
browserDocument?.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
448
747
|
browserDocument?.removeEventListener("freeze", handleFreeze);
|
|
449
748
|
browserDocument?.removeEventListener("resume", reconcileWhenVisible);
|
|
749
|
+
if (this.statusReconciliationStops.get(reconciliationKey) === stopReconciliation) {
|
|
750
|
+
this.statusReconciliationStops.delete(reconciliationKey);
|
|
751
|
+
}
|
|
450
752
|
};
|
|
451
|
-
|
|
753
|
+
this.statusReconciliationStops.set(reconciliationKey, stopReconciliation);
|
|
754
|
+
return stopReconciliation;
|
|
452
755
|
}
|
|
453
756
|
async requestRefund(params) {
|
|
454
757
|
const result = await this.requestRefundIntentInternal(params, { waitForFinality: true });
|
|
@@ -460,9 +763,10 @@ var UserCoordinatorSession = class {
|
|
|
460
763
|
}
|
|
461
764
|
async requestRefundIntentInternal(params, options) {
|
|
462
765
|
assertRecoveryCode(params.recoveryCode);
|
|
766
|
+
this.beginOperation();
|
|
463
767
|
const recoveryCode = new Uint8Array(params.recoveryCode);
|
|
464
768
|
const ackTimeout = params.timeoutMs ?? 3e4;
|
|
465
|
-
let
|
|
769
|
+
let requestSwapId = params.swapId ?? null;
|
|
466
770
|
let openedForRefund = false;
|
|
467
771
|
try {
|
|
468
772
|
if (params.syncSecret !== void 0) {
|
|
@@ -471,34 +775,51 @@ var UserCoordinatorSession = class {
|
|
|
471
775
|
throw new CommandError("REJECTED_STATE", "syncSecret requires swapId");
|
|
472
776
|
}
|
|
473
777
|
this.restoreSwapContext({ swapId, syncSecret: params.syncSecret });
|
|
778
|
+
this.ensureRefundContext(swapId);
|
|
779
|
+
requestSwapId = swapId;
|
|
474
780
|
} else if (params.swapId) {
|
|
475
781
|
const swapId = params.swapId;
|
|
476
782
|
this.restoreSwapContext({ swapId });
|
|
783
|
+
this.ensureRefundContext(swapId);
|
|
784
|
+
requestSwapId = swapId;
|
|
477
785
|
}
|
|
478
786
|
openedForRefund = !this.hasUsableSession();
|
|
479
787
|
await this.openSession();
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
788
|
+
let env;
|
|
789
|
+
try {
|
|
790
|
+
env = await this.request(
|
|
791
|
+
CoordinatorMessageType.WithdrawRequest,
|
|
792
|
+
{
|
|
793
|
+
recovery_code: recoveryCode
|
|
794
|
+
},
|
|
795
|
+
{ swapId: requestSwapId, timeoutMs: ackTimeout }
|
|
796
|
+
);
|
|
797
|
+
} catch (err) {
|
|
798
|
+
if (isRequestTimeoutError(err)) {
|
|
799
|
+
throw new CommandError(
|
|
800
|
+
"REJECTED_STALE_JOB",
|
|
801
|
+
`WithdrawRequest acknowledgement timed out after ${ackTimeout}ms`
|
|
493
802
|
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
803
|
+
}
|
|
804
|
+
throw err;
|
|
805
|
+
}
|
|
497
806
|
if (env.type === CoordinatorMessageType.WithdrawExecuted) {
|
|
498
|
-
|
|
499
|
-
|
|
807
|
+
const resolvedSwapId2 = env.swap_id ?? requestSwapId;
|
|
808
|
+
if (resolvedSwapId2) requestSwapId = resolvedSwapId2;
|
|
809
|
+
if (resolvedSwapId2) await this.waitForPushProcessing(resolvedSwapId2);
|
|
810
|
+
const terminalOutcome2 = resolvedSwapId2 ? this.terminalOutcomes.get(resolvedSwapId2) : void 0;
|
|
811
|
+
if (terminalOutcome2?.storageError) throw terminalOutcome2.storageError;
|
|
812
|
+
if (!terminalOutcome2) this.ensureRefundContext(resolvedSwapId2);
|
|
813
|
+
const refund = this.requireProtocol().asWithdrawExecutedPayload(env);
|
|
814
|
+
if (resolvedSwapId2 && terminalOutcome2?.refund === void 0) {
|
|
815
|
+
await persistTransferTerminal(
|
|
816
|
+
this.clientPersistenceState(),
|
|
817
|
+
resolvedSwapId2,
|
|
818
|
+
"refunded",
|
|
819
|
+
env.timestamp_ms
|
|
820
|
+
);
|
|
500
821
|
}
|
|
501
|
-
return { kind: "executed", refund
|
|
822
|
+
return { kind: "executed", refund };
|
|
502
823
|
}
|
|
503
824
|
if (env.type !== CoordinatorMessageType.WithdrawAccepted) {
|
|
504
825
|
throw new CommandError(
|
|
@@ -506,29 +827,61 @@ var UserCoordinatorSession = class {
|
|
|
506
827
|
`Expected WithdrawAccepted or WithdrawExecuted response, got ${env.type}`
|
|
507
828
|
);
|
|
508
829
|
}
|
|
509
|
-
if (env.swap_id && !this.swapId) {
|
|
510
|
-
this.swapId = env.swap_id;
|
|
511
|
-
}
|
|
512
830
|
const accepted = this.requireProtocol().asWithdrawAcceptedPayload(env);
|
|
831
|
+
const resolvedSwapId = env.swap_id ?? requestSwapId;
|
|
832
|
+
if (resolvedSwapId) requestSwapId = resolvedSwapId;
|
|
833
|
+
if (resolvedSwapId) await this.waitForPushProcessing(resolvedSwapId);
|
|
834
|
+
let terminalOutcome = resolvedSwapId ? this.terminalOutcomes.get(resolvedSwapId) : void 0;
|
|
835
|
+
if (terminalOutcome?.storageError) throw terminalOutcome.storageError;
|
|
836
|
+
let resolvedContext = terminalOutcome ? null : this.ensureRefundContext(resolvedSwapId);
|
|
837
|
+
if (resolvedSwapId && !terminalOutcome) {
|
|
838
|
+
await persistTransferRecoveryCode(
|
|
839
|
+
this.clientPersistenceState(),
|
|
840
|
+
resolvedSwapId,
|
|
841
|
+
recoveryCode
|
|
842
|
+
);
|
|
843
|
+
await this.waitForPushProcessing(resolvedSwapId);
|
|
844
|
+
terminalOutcome = this.terminalOutcomes.get(resolvedSwapId);
|
|
845
|
+
if (terminalOutcome?.storageError) throw terminalOutcome.storageError;
|
|
846
|
+
if (terminalOutcome) resolvedContext = null;
|
|
847
|
+
}
|
|
513
848
|
if (!options.waitForFinality) {
|
|
514
849
|
return { kind: "accepted", accepted };
|
|
515
850
|
}
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
"REJECTED_STALE_JOB",
|
|
519
|
-
"refund accepted and pending finalization; check status again shortly"
|
|
520
|
-
);
|
|
851
|
+
if (terminalOutcome?.refund !== void 0) {
|
|
852
|
+
return { kind: "executed", refund: terminalOutcome.refund };
|
|
521
853
|
}
|
|
522
|
-
|
|
854
|
+
if (!resolvedContext) {
|
|
855
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
856
|
+
}
|
|
857
|
+
if (!resolvedContext.syncSecret) {
|
|
858
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
859
|
+
}
|
|
860
|
+
this.setCurrentContext(resolvedContext);
|
|
861
|
+
return {
|
|
862
|
+
kind: "executed",
|
|
863
|
+
refund: await this.awaitRefundOutcomeFromSwapStatus(resolvedContext)
|
|
864
|
+
};
|
|
523
865
|
} finally {
|
|
524
|
-
if (ackTimer) clearTimeout(ackTimer);
|
|
525
866
|
recoveryCode.fill(0);
|
|
526
|
-
|
|
867
|
+
this.endOperation();
|
|
868
|
+
if (openedForRefund && this.closeSessionAfterOperationFailure && this.activeOperations === 0) {
|
|
869
|
+
await this.waitForPushProcessing(requestSwapId);
|
|
527
870
|
this.close();
|
|
528
871
|
}
|
|
529
872
|
}
|
|
530
873
|
}
|
|
531
|
-
|
|
874
|
+
ensureRefundContext(swapId) {
|
|
875
|
+
if (swapId === null) return null;
|
|
876
|
+
if (this.terminalOutcomes.has(swapId)) return null;
|
|
877
|
+
const context = this.ensureSwapContext(swapId);
|
|
878
|
+
if (context.swapState === "new") {
|
|
879
|
+
context.swapState = "awaiting_deposit";
|
|
880
|
+
this.setCurrentContext(context);
|
|
881
|
+
}
|
|
882
|
+
return context;
|
|
883
|
+
}
|
|
884
|
+
async awaitRefundOutcomeFromSwapStatus(context) {
|
|
532
885
|
const deadline = Date.now() + DEFAULT_REFUND_OUTCOME_POLL_BUDGET_MS;
|
|
533
886
|
for (; ; ) {
|
|
534
887
|
const perPollTimeout = Math.min(
|
|
@@ -536,7 +889,7 @@ var UserCoordinatorSession = class {
|
|
|
536
889
|
Math.max(0, deadline - Date.now())
|
|
537
890
|
);
|
|
538
891
|
try {
|
|
539
|
-
const status = await this.
|
|
892
|
+
const status = await this.requestSwapStatusForContext(context, perPollTimeout);
|
|
540
893
|
if (status.withdraw_execution) {
|
|
541
894
|
return status.withdraw_execution;
|
|
542
895
|
}
|
|
@@ -546,10 +899,7 @@ var UserCoordinatorSession = class {
|
|
|
546
899
|
}
|
|
547
900
|
}
|
|
548
901
|
if (Date.now() >= deadline) {
|
|
549
|
-
throw new CommandError(
|
|
550
|
-
"REJECTED_STALE_JOB",
|
|
551
|
-
"refund accepted and pending finalization; check status again shortly"
|
|
552
|
-
);
|
|
902
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
553
903
|
}
|
|
554
904
|
await delay(REFUND_OUTCOME_POLL_INTERVAL_MS);
|
|
555
905
|
}
|
|
@@ -558,25 +908,29 @@ var UserCoordinatorSession = class {
|
|
|
558
908
|
const protocol = this.requireProtocol();
|
|
559
909
|
const prepared = await this.prepareContract(params);
|
|
560
910
|
const depositEnv = await prepared.depositConfirmedPromise;
|
|
561
|
-
const depositPayload =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
);
|
|
567
|
-
}
|
|
911
|
+
const depositPayload = assertDepositConfirmedAmount(
|
|
912
|
+
protocol,
|
|
913
|
+
depositEnv,
|
|
914
|
+
params.amountLamports
|
|
915
|
+
);
|
|
568
916
|
this.log(
|
|
569
917
|
`deposit confirmed tx=${depositPayload.tx_signature} amount=${depositPayload.amount_lamports}`,
|
|
570
918
|
"in"
|
|
571
919
|
);
|
|
572
920
|
await params.onDepositConfirmed?.({ originAddress: depositPayload.origin_address });
|
|
573
|
-
|
|
921
|
+
const context = this.swapContexts.get(prepared.swapId);
|
|
922
|
+
if (!context) {
|
|
923
|
+
throw new CommandError("REJECTED_STATE", `missing client context for ${prepared.swapId}`);
|
|
924
|
+
}
|
|
925
|
+
await this.requestSwapStatusForContext(context);
|
|
574
926
|
return {
|
|
575
927
|
swapId: prepared.swapId,
|
|
576
928
|
jointPublicKey: prepared.jointPublicKey,
|
|
577
929
|
depositTxSignature: depositPayload.tx_signature,
|
|
578
930
|
depositObservedAtMs: depositPayload.observed_at_ms,
|
|
579
931
|
delegatedAtMs: prepared.delegatedAtMs,
|
|
932
|
+
resumeSecret: new Uint8Array(prepared.resumeSecret),
|
|
933
|
+
resumeSecretHex: prepared.resumeSecretHex,
|
|
580
934
|
recoveryCodeHex: prepared.recoveryCodeHex,
|
|
581
935
|
policySnapshot: prepared.policySnapshot,
|
|
582
936
|
delegatePolicySnapshot: prepared.delegatePolicySnapshot
|
|
@@ -587,6 +941,7 @@ var UserCoordinatorSession = class {
|
|
|
587
941
|
const requestedPayoutWindowMs = params.payoutWindowMs ?? payoutWindowMsFromSpec(params.payoutSpec);
|
|
588
942
|
const payoutDeadlineMs = requestedPayoutWindowMs !== void 0 ? Date.now() + requestedPayoutWindowMs : void 0;
|
|
589
943
|
let generatedSyncSecret = null;
|
|
944
|
+
let generatedResumeSecret = null;
|
|
590
945
|
const syncSecret = params.syncSecret ? new Uint8Array(params.syncSecret) : (() => {
|
|
591
946
|
generatedSyncSecret = randomSecret32();
|
|
592
947
|
return generatedSyncSecret;
|
|
@@ -597,105 +952,193 @@ var UserCoordinatorSession = class {
|
|
|
597
952
|
`syncSecret must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
598
953
|
);
|
|
599
954
|
}
|
|
600
|
-
this.syncSecret = new Uint8Array(syncSecret);
|
|
601
955
|
const syncSecretHex = bytesToHex(syncSecret);
|
|
602
956
|
const syncCommitment = await sha256Commitment(syncSecret);
|
|
603
957
|
if (generatedSyncSecret) {
|
|
604
958
|
await params.onSyncSecretReady?.({ secretHex: syncSecretHex });
|
|
605
959
|
}
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
...(params.payoutDeadlineMs !== void 0 || payoutDeadlineMs !== void 0) && {
|
|
612
|
-
payout_deadline_ms: params.payoutDeadlineMs ?? payoutDeadlineMs
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
const swapPayload = protocol.asSwapRequestResponsePayload(swapResp);
|
|
616
|
-
const swapId = swapPayload.swap_id;
|
|
617
|
-
this.swapId = swapId;
|
|
618
|
-
params.onPolicySnapshot?.(swapPayload.policy_snapshot);
|
|
619
|
-
this.log(`swap accepted - id=${swapId}`, "in");
|
|
620
|
-
this.bufferDepositConfirmedUntilDelegateAck = true;
|
|
621
|
-
this.bufferedDepositConfirmed = null;
|
|
622
|
-
const depositConfirmedPromise = this.awaitPush(CoordinatorMessageType.DepositConfirmed, null);
|
|
623
|
-
void depositConfirmedPromise.catch(() => void 0);
|
|
624
|
-
const { jointPublicKey, keyHandle, depositExpiresAtMs, coordinatorTimestampMs } = await this.runDkg();
|
|
625
|
-
let recoveryCommitment = params.recoveryCommitment;
|
|
626
|
-
let recoveryCodeHex;
|
|
627
|
-
if (!recoveryCommitment) {
|
|
628
|
-
const generated = await generateRecoveryCode();
|
|
629
|
-
recoveryCommitment = generated.commitment;
|
|
630
|
-
try {
|
|
631
|
-
recoveryCodeHex = recoveryCodeToHex(generated.code);
|
|
632
|
-
await params.onRecoveryCodeReady?.({ codeHex: recoveryCodeHex });
|
|
633
|
-
} finally {
|
|
634
|
-
generated.code.fill(0);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck);
|
|
638
|
-
const share = params.delegateShare ?? keyHandle.secretShareBytes();
|
|
639
|
-
let delegateResp;
|
|
640
|
-
try {
|
|
641
|
-
delegateResp = await this.request(CoordinatorMessageType.DelegateShare, {
|
|
642
|
-
share,
|
|
643
|
-
recovery_commitment: recoveryCommitment
|
|
644
|
-
});
|
|
645
|
-
} catch (err) {
|
|
646
|
-
void delegateAckPromise.catch(() => void 0);
|
|
647
|
-
throw err;
|
|
648
|
-
}
|
|
649
|
-
const delegatePayload = protocol.asDelegateShareResponsePayload(delegateResp);
|
|
650
|
-
const delegateAck = protocol.asDelegateAckPayload(await delegateAckPromise);
|
|
651
|
-
if (delegateAck.delegated_at_ms !== delegatePayload.delegated_at_ms) {
|
|
960
|
+
const resumeSecret = params.resumeSecret ? new Uint8Array(params.resumeSecret) : (() => {
|
|
961
|
+
generatedResumeSecret = randomSecret32();
|
|
962
|
+
return generatedResumeSecret;
|
|
963
|
+
})();
|
|
964
|
+
if (resumeSecret.length !== RESUME_SECRET_BYTE_LENGTH) {
|
|
652
965
|
throw new CommandError(
|
|
653
966
|
"REJECTED_GUARD",
|
|
654
|
-
|
|
967
|
+
`resumeSecret must be ${RESUME_SECRET_BYTE_LENGTH} bytes`
|
|
655
968
|
);
|
|
656
969
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
);
|
|
970
|
+
const resumeSecretHex = bytesToHex(resumeSecret);
|
|
971
|
+
const resumeCommitment = await sha256Commitment(resumeSecret);
|
|
972
|
+
if (generatedResumeSecret) {
|
|
973
|
+
await params.onResumeSecretReady?.({ secretHex: resumeSecretHex });
|
|
662
974
|
}
|
|
663
|
-
this.
|
|
664
|
-
|
|
665
|
-
|
|
975
|
+
const swapResp = await this.request(
|
|
976
|
+
CoordinatorMessageType.ContractRequest,
|
|
977
|
+
{
|
|
978
|
+
actor: "user",
|
|
979
|
+
amount_lamports: params.amountLamports,
|
|
980
|
+
payout_spec: params.payoutSpec,
|
|
981
|
+
sync_commitment: syncCommitment,
|
|
982
|
+
resume_commitment: resumeCommitment,
|
|
983
|
+
...(params.payoutDeadlineMs !== void 0 || payoutDeadlineMs !== void 0) && {
|
|
984
|
+
payout_deadline_ms: params.payoutDeadlineMs ?? payoutDeadlineMs
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
{ swapId: null, requireNonNullResponseSwapId: true }
|
|
988
|
+
);
|
|
989
|
+
const swapPayload = protocol.asSwapRequestResponsePayload(swapResp);
|
|
990
|
+
const swapId = swapPayload.swap_id;
|
|
991
|
+
const context = {
|
|
666
992
|
swapId,
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
993
|
+
requestedAmountLamports: params.amountLamports,
|
|
994
|
+
swapState: "awaiting_dkg",
|
|
995
|
+
syncSecret: new Uint8Array(syncSecret),
|
|
996
|
+
resumeSecret: new Uint8Array(resumeSecret),
|
|
997
|
+
bufferDepositConfirmedUntilDelegateAck: true,
|
|
998
|
+
bufferedDepositConfirmed: null
|
|
999
|
+
};
|
|
1000
|
+
this.swapContexts.set(swapId, context);
|
|
1001
|
+
this.setCurrentContext(context);
|
|
1002
|
+
await persistTransferAllocation(this.clientPersistenceState(), {
|
|
674
1003
|
swapId,
|
|
675
1004
|
amountLamports: params.amountLamports,
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
coordinatorTimestampMs,
|
|
679
|
-
delegatedAtMs: delegateAck.delegated_at_ms,
|
|
1005
|
+
payoutSpec: params.payoutSpec,
|
|
1006
|
+
policySnapshot: swapPayload.policy_snapshot,
|
|
680
1007
|
syncSecret,
|
|
1008
|
+
coordinatorTimestampMs: swapResp.timestamp_ms
|
|
1009
|
+
});
|
|
1010
|
+
await params.onSwapAllocated?.({
|
|
1011
|
+
swapId,
|
|
681
1012
|
syncSecretHex,
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1013
|
+
resumeSecretHex
|
|
1014
|
+
});
|
|
1015
|
+
params.onPolicySnapshot?.(swapPayload.policy_snapshot);
|
|
1016
|
+
this.log(`swap accepted - id=${swapId}`, "in");
|
|
1017
|
+
let preparedSuccessfully = false;
|
|
1018
|
+
try {
|
|
1019
|
+
const depositConfirmedPromise = this.awaitPush(
|
|
1020
|
+
CoordinatorMessageType.DepositConfirmed,
|
|
1021
|
+
context.swapId,
|
|
1022
|
+
null
|
|
1023
|
+
);
|
|
1024
|
+
void depositConfirmedPromise.catch(() => void 0);
|
|
1025
|
+
const { jointPublicKey, keyHandle, depositExpiresAtMs, coordinatorTimestampMs } = await this.runDkg(context, params.delegateShare ? void 0 : params.onDelegateShareReady);
|
|
1026
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
1027
|
+
let recoveryCommitment = params.recoveryCommitment;
|
|
1028
|
+
let recoveryCodeHex;
|
|
1029
|
+
if (!recoveryCommitment) {
|
|
1030
|
+
const generated = await generateRecoveryCode();
|
|
1031
|
+
recoveryCommitment = generated.commitment;
|
|
1032
|
+
try {
|
|
1033
|
+
recoveryCodeHex = recoveryCodeToHex(generated.code);
|
|
1034
|
+
await persistTransferRecoveryCode(
|
|
1035
|
+
this.clientPersistenceState(),
|
|
1036
|
+
context.swapId,
|
|
1037
|
+
generated.code
|
|
1038
|
+
);
|
|
1039
|
+
await params.onRecoveryCodeReady?.({ codeHex: recoveryCodeHex });
|
|
1040
|
+
} finally {
|
|
1041
|
+
generated.code.fill(0);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck, context.swapId);
|
|
1045
|
+
const share = params.delegateShare ?? keyHandle.secretShareBytes();
|
|
1046
|
+
let delegateResp;
|
|
1047
|
+
try {
|
|
1048
|
+
delegateResp = await this.request(
|
|
1049
|
+
CoordinatorMessageType.DelegateShare,
|
|
1050
|
+
{
|
|
1051
|
+
share,
|
|
1052
|
+
recovery_commitment: recoveryCommitment,
|
|
1053
|
+
resume_secret: context.resumeSecret
|
|
1054
|
+
},
|
|
1055
|
+
{ swapId: context.swapId }
|
|
1056
|
+
);
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
void delegateAckPromise.catch(() => void 0);
|
|
1059
|
+
throw err;
|
|
1060
|
+
}
|
|
1061
|
+
const delegatePayload = protocol.asDelegateShareResponsePayload(delegateResp);
|
|
1062
|
+
const delegateAck = protocol.asDelegateAckPayload(await delegateAckPromise);
|
|
1063
|
+
if (delegateAck.delegated_at_ms !== delegatePayload.delegated_at_ms) {
|
|
1064
|
+
throw new CommandError(
|
|
1065
|
+
"REJECTED_GUARD",
|
|
1066
|
+
"DelegateAck delegated_at_ms does not match DelegateShare response"
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
if (!spendingPoliciesEqual(delegateAck.spending_policy, swapPayload.policy_snapshot)) {
|
|
1070
|
+
throw new CommandError(
|
|
1071
|
+
"REJECTED_GUARD",
|
|
1072
|
+
"DelegateAck spending_policy does not match ContractRequest policy_snapshot"
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (context.bufferedDepositConfirmed) {
|
|
1076
|
+
assertDepositConfirmedAmount(
|
|
1077
|
+
this.requireProtocol(),
|
|
1078
|
+
context.bufferedDepositConfirmed,
|
|
1079
|
+
params.amountLamports
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
await persistTransferReady(this.clientPersistenceState(), {
|
|
1083
|
+
swapId,
|
|
1084
|
+
depositAddress: solanaAddressFromPublicKeyBytes(jointPublicKey),
|
|
1085
|
+
depositExpiresAtMs,
|
|
1086
|
+
delegatedAtMs: delegateAck.delegated_at_ms,
|
|
1087
|
+
coordinatorTimestampMs
|
|
1088
|
+
});
|
|
1089
|
+
this.log(`delegated at ${delegateAck.delegated_at_ms}`, "in");
|
|
1090
|
+
await this.releaseDepositConfirmedGate(context);
|
|
1091
|
+
await params.onDepositAddressReady?.({
|
|
1092
|
+
swapId,
|
|
1093
|
+
jointPublicKey,
|
|
1094
|
+
amountLamports: params.amountLamports,
|
|
1095
|
+
depositExpiresAtMs,
|
|
1096
|
+
coordinatorTimestampMs
|
|
1097
|
+
});
|
|
1098
|
+
preparedSuccessfully = true;
|
|
1099
|
+
return {
|
|
1100
|
+
requestId: swapId,
|
|
1101
|
+
swapId,
|
|
1102
|
+
amountLamports: params.amountLamports,
|
|
1103
|
+
jointPublicKey,
|
|
1104
|
+
depositExpiresAtMs,
|
|
1105
|
+
coordinatorTimestampMs,
|
|
1106
|
+
delegatedAtMs: delegateAck.delegated_at_ms,
|
|
1107
|
+
syncSecret,
|
|
1108
|
+
syncSecretHex,
|
|
1109
|
+
resumeSecret,
|
|
1110
|
+
resumeSecretHex,
|
|
1111
|
+
recoveryCodeHex,
|
|
1112
|
+
policySnapshot: swapPayload.policy_snapshot,
|
|
1113
|
+
delegatePolicySnapshot: delegateAck.spending_policy,
|
|
1114
|
+
depositConfirmedPromise
|
|
1115
|
+
};
|
|
1116
|
+
} finally {
|
|
1117
|
+
if (!preparedSuccessfully) {
|
|
1118
|
+
this.cancelPushWaitersForSwap(context.swapId);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
687
1121
|
}
|
|
688
|
-
async runDkg() {
|
|
1122
|
+
async runDkg(context, onDelegateShareReady, persistDkgMetadata = true) {
|
|
689
1123
|
await ensureFrostWasm();
|
|
690
1124
|
const protocol = this.requireProtocol();
|
|
691
1125
|
const { dkgRound1 } = await loadFrost();
|
|
692
|
-
await this.request(
|
|
1126
|
+
await this.request(
|
|
1127
|
+
CoordinatorMessageType.DkgInit,
|
|
1128
|
+
{ resume_secret: context.resumeSecret },
|
|
1129
|
+
{ swapId: context.swapId }
|
|
1130
|
+
);
|
|
693
1131
|
this.log("DKG initialised", "info");
|
|
694
1132
|
const aliceRound1State = dkgRound1(ALICE_ID);
|
|
695
1133
|
const aliceRound1Bytes = aliceRound1State.round1PackageBytes();
|
|
696
|
-
const round1Resp = await this.request(
|
|
697
|
-
|
|
698
|
-
|
|
1134
|
+
const round1Resp = await this.request(
|
|
1135
|
+
CoordinatorMessageType.DkgRound1,
|
|
1136
|
+
{
|
|
1137
|
+
package: aliceRound1Bytes,
|
|
1138
|
+
resume_secret: context.resumeSecret
|
|
1139
|
+
},
|
|
1140
|
+
{ swapId: context.swapId }
|
|
1141
|
+
);
|
|
699
1142
|
const round1Payload = protocol.asDkgRound1ResponsePayload(round1Resp);
|
|
700
1143
|
this.log(`DKG round1 complete - coord pkg ${round1Payload.package.byteLength}B`, "in");
|
|
701
1144
|
const aliceRound2State = aliceRound1State.intoRound2(COORDINATOR_ID, round1Payload.package);
|
|
@@ -710,10 +1153,20 @@ var UserCoordinatorSession = class {
|
|
|
710
1153
|
} catch (err) {
|
|
711
1154
|
throw new CommandError("REJECTED_GUARD", `Alice DKG finalize failed: ${normalizeError(err)}`);
|
|
712
1155
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1156
|
+
await onDelegateShareReady?.({
|
|
1157
|
+
swapId: context.swapId,
|
|
1158
|
+
delegateShare: new Uint8Array(aliceKeyHandle.secretShareBytes())
|
|
716
1159
|
});
|
|
1160
|
+
const dkgCompletePromise = this.awaitPush(CoordinatorMessageType.DkgComplete, context.swapId);
|
|
1161
|
+
void dkgCompletePromise.catch(() => void 0);
|
|
1162
|
+
await this.request(
|
|
1163
|
+
CoordinatorMessageType.DkgRound2,
|
|
1164
|
+
{
|
|
1165
|
+
package: aliceRound2Bytes,
|
|
1166
|
+
resume_secret: context.resumeSecret
|
|
1167
|
+
},
|
|
1168
|
+
{ swapId: context.swapId }
|
|
1169
|
+
);
|
|
717
1170
|
const completeEnvelope = await dkgCompletePromise;
|
|
718
1171
|
const complete = readDkgComplete(protocol, completeEnvelope);
|
|
719
1172
|
const alicePubkey = aliceKeyHandle.groupPublicKeyBytes();
|
|
@@ -723,6 +1176,14 @@ var UserCoordinatorSession = class {
|
|
|
723
1176
|
"DKG joint public key mismatch between Alice and coordinator"
|
|
724
1177
|
);
|
|
725
1178
|
}
|
|
1179
|
+
if (persistDkgMetadata) {
|
|
1180
|
+
await persistTransferDkgMetadata(this.clientPersistenceState(), {
|
|
1181
|
+
swapId: context.swapId,
|
|
1182
|
+
depositAddress: solanaAddressFromPublicKeyBytes(alicePubkey),
|
|
1183
|
+
depositExpiresAtMs: complete.depositExpiresAtMs,
|
|
1184
|
+
coordinatorTimestampMs: complete.coordinatorTimestampMs
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
726
1187
|
const pubkeyHexPrefix = Array.from(alicePubkey.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
727
1188
|
this.log(`DKG complete - joint pubkey ${pubkeyHexPrefix}`, "info");
|
|
728
1189
|
return {
|
|
@@ -733,6 +1194,20 @@ var UserCoordinatorSession = class {
|
|
|
733
1194
|
};
|
|
734
1195
|
}
|
|
735
1196
|
async openSession() {
|
|
1197
|
+
if (this.hasUsableSession()) return;
|
|
1198
|
+
if (this.openingSession !== null) {
|
|
1199
|
+
await this.openingSession;
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
const opening = this.openSessionOnce();
|
|
1203
|
+
this.openingSession = opening;
|
|
1204
|
+
try {
|
|
1205
|
+
await opening;
|
|
1206
|
+
} finally {
|
|
1207
|
+
if (this.openingSession === opening) this.openingSession = null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
async openSessionOnce() {
|
|
736
1211
|
if (this.hasUsableSession()) return;
|
|
737
1212
|
if (this.session !== null) {
|
|
738
1213
|
this.detachSession(
|
|
@@ -758,11 +1233,6 @@ var UserCoordinatorSession = class {
|
|
|
758
1233
|
this.log(`connected to ${this.coordinator.endpoints[0]?.wsUrl ?? "coordinator"}`, "info");
|
|
759
1234
|
this.log("attestation verified", "info");
|
|
760
1235
|
}
|
|
761
|
-
assertNoActiveContract() {
|
|
762
|
-
if (this.swapId !== null || this.sessionStatus === "connecting" || this.sessionStatus === "running") {
|
|
763
|
-
throw new CommandError("REJECTED_STATE", ACTIVE_CONTRACT_REQUEST_MESSAGE);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
1236
|
hasUsableSession() {
|
|
767
1237
|
if (this.session === null) return false;
|
|
768
1238
|
const state = getSessionState(this.session);
|
|
@@ -788,61 +1258,213 @@ var UserCoordinatorSession = class {
|
|
|
788
1258
|
handleEnvelope(env) {
|
|
789
1259
|
const protocol = this.requireProtocol();
|
|
790
1260
|
this.log(`<- ${env.type} kind=${env.kind}`, "in");
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1261
|
+
if (env.kind === EnvelopeKind.PUSH) {
|
|
1262
|
+
if (env.swap_id === null) {
|
|
1263
|
+
this.log(`ignored ${env.type} without an explicit swap_id`, "info");
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (!this.swapContexts.has(env.swap_id)) {
|
|
1267
|
+
this.log(`ignored ${env.type} for unknown swap ${env.swap_id}`, "info");
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
this.enqueuePush(env);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) ?? null : null;
|
|
1274
|
+
if (env.swap_id === null && env.kind !== EnvelopeKind.ERROR) {
|
|
1275
|
+
this.log(`ignored ${env.type} without an explicit swap_id`, "info");
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (env.swap_id !== null && context === null) {
|
|
1279
|
+
this.log(`ignored ${env.type} for unknown swap ${env.swap_id}`, "info");
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const stateForEnvelope = context?.swapState ?? this.swapState;
|
|
1283
|
+
const next = protocol.nextState(stateForEnvelope, env.kind, env.type);
|
|
1284
|
+
if (next !== null) {
|
|
1285
|
+
if (context) {
|
|
1286
|
+
this.updateContextState(context, next);
|
|
1287
|
+
} else {
|
|
1288
|
+
this.swapState = next;
|
|
1289
|
+
this.emitStateChange(this.swapId, next);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (env.kind === EnvelopeKind.ERROR) {
|
|
1293
|
+
const coordinatorError = coordinatorErrorFromEnvelope(protocol, env);
|
|
1294
|
+
if (env.req_id === 0) {
|
|
1295
|
+
for (const listener of this.coordinatorErrorListeners) {
|
|
1296
|
+
listener(coordinatorError);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
enqueuePush(env) {
|
|
1303
|
+
const swapId = env.swap_id;
|
|
1304
|
+
if (swapId === null) return;
|
|
1305
|
+
const context = this.swapContexts.get(swapId);
|
|
1306
|
+
if (context === void 0) return;
|
|
1307
|
+
const persistence = this.clientPersistenceState();
|
|
1308
|
+
void this.enqueueSwapProcessing(
|
|
1309
|
+
swapId,
|
|
1310
|
+
() => this.processPushEnvelope(env, context, persistence)
|
|
1311
|
+
).catch(() => void 0);
|
|
1312
|
+
}
|
|
1313
|
+
enqueueSwapProcessing(swapId, process) {
|
|
1314
|
+
const previous = this.pushProcessing.get(swapId) ?? Promise.resolve();
|
|
1315
|
+
const processing = previous.catch(() => void 0).then(process);
|
|
1316
|
+
this.pushProcessing.set(swapId, processing);
|
|
1317
|
+
void processing.finally(() => {
|
|
1318
|
+
if (this.pushProcessing.get(swapId) === processing) {
|
|
1319
|
+
this.pushProcessing.delete(swapId);
|
|
1320
|
+
}
|
|
1321
|
+
}).catch(() => void 0);
|
|
1322
|
+
return processing;
|
|
1323
|
+
}
|
|
1324
|
+
async waitForPushProcessing(swapId) {
|
|
1325
|
+
if (swapId === null) return;
|
|
1326
|
+
await (this.pushProcessing.get(swapId)?.catch(() => void 0) ?? Promise.resolve());
|
|
1327
|
+
}
|
|
1328
|
+
async processPushEnvelope(env, context, persistence) {
|
|
1329
|
+
const protocol = this.requireProtocol();
|
|
1330
|
+
const payoutExecuted = env.type === CoordinatorMessageType.PayoutExecuted ? protocol.asPayoutExecutedPayload(env) : null;
|
|
1331
|
+
const refundExecuted = env.type === CoordinatorMessageType.WithdrawExecuted ? protocol.asWithdrawExecutedPayload(env) : null;
|
|
794
1332
|
if (this.shouldBufferDepositConfirmed(env)) {
|
|
795
1333
|
this.bufferDepositConfirmed(env);
|
|
796
1334
|
return;
|
|
797
1335
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1336
|
+
if (env.type === CoordinatorMessageType.DkgComplete) {
|
|
1337
|
+
this.settlePushListener(env);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
if (env.type === CoordinatorMessageType.DepositConfirmed) {
|
|
1341
|
+
if (context.requestedAmountLamports === null) {
|
|
1342
|
+
this.log(
|
|
1343
|
+
`ignored DepositConfirmed for swap ${context.swapId} without requested amount`,
|
|
1344
|
+
"info"
|
|
1345
|
+
);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
try {
|
|
1349
|
+
assertDepositConfirmedAmount(protocol, env, context.requestedAmountLamports);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
this.settlePushListener(env, error instanceof Error ? error : new Error(String(error)));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const next = protocol.nextState(
|
|
1356
|
+
context.swapState,
|
|
801
1357
|
env.kind,
|
|
802
1358
|
env.type,
|
|
803
1359
|
payoutExecuted ? { payoutFinal: payoutExecuted.final } : void 0
|
|
804
1360
|
);
|
|
805
|
-
if (
|
|
1361
|
+
if (next === null) {
|
|
806
1362
|
this.log(
|
|
807
|
-
|
|
1363
|
+
`ignored ${env.type} for swap ${env.swap_id ?? "null"} while client context is ${context.swapId} in state ${context.swapState}`,
|
|
808
1364
|
"info"
|
|
809
1365
|
);
|
|
810
1366
|
return;
|
|
811
1367
|
}
|
|
812
|
-
if (
|
|
813
|
-
this.
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if (env.kind === EnvelopeKind.ERROR) {
|
|
817
|
-
const coordinatorError = coordinatorErrorFromEnvelope(protocol, env);
|
|
818
|
-
if (env.req_id === 0) {
|
|
819
|
-
for (const listener of this.coordinatorErrorListeners) {
|
|
820
|
-
listener(coordinatorError);
|
|
821
|
-
}
|
|
1368
|
+
if (env.type === CoordinatorMessageType.DelegateAck) {
|
|
1369
|
+
if (!this.isActiveSwapContext(context)) {
|
|
1370
|
+
context.swapState = next;
|
|
1371
|
+
return;
|
|
822
1372
|
}
|
|
1373
|
+
this.updateContextState(context, next);
|
|
1374
|
+
this.settlePushListener(env);
|
|
823
1375
|
return;
|
|
824
1376
|
}
|
|
825
|
-
if (
|
|
826
|
-
|
|
827
|
-
|
|
1377
|
+
if (isTerminalSwapState(next)) {
|
|
1378
|
+
await this.handleTerminalPush(
|
|
1379
|
+
env,
|
|
1380
|
+
context,
|
|
1381
|
+
next,
|
|
1382
|
+
payoutExecuted,
|
|
1383
|
+
refundExecuted,
|
|
1384
|
+
persistence
|
|
1385
|
+
);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
await this.handleNonTerminalPush(env, context, next, payoutExecuted, persistence);
|
|
1389
|
+
}
|
|
1390
|
+
async handleNonTerminalPush(env, context, next, payoutExecuted, persistence) {
|
|
1391
|
+
if (!this.isActiveSwapContext(context)) {
|
|
1392
|
+
context.swapState = next;
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
let storageError = null;
|
|
1396
|
+
try {
|
|
1397
|
+
await persistTransferStatus(persistence, context.swapId, { state: next }, env.timestamp_ms);
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
storageError = storageErrorFromPush(error, context.swapId, null);
|
|
1400
|
+
}
|
|
1401
|
+
if (!this.isActiveSwapContext(context)) {
|
|
1402
|
+
context.swapState = next;
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
this.updateContextState(context, next);
|
|
1407
|
+
if (payoutExecuted) this.emitPayoutExecuted(payoutExecuted, context.swapId);
|
|
1408
|
+
} finally {
|
|
1409
|
+
try {
|
|
1410
|
+
if (storageError) this.emitStorageError(storageError);
|
|
1411
|
+
} finally {
|
|
1412
|
+
this.settlePushListener(env);
|
|
828
1413
|
}
|
|
829
|
-
|
|
830
|
-
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
async handleTerminalPush(env, context, next, payoutExecuted, refundExecuted, persistence) {
|
|
1417
|
+
this.rememberTerminalOutcome(context.swapId, next, refundExecuted);
|
|
1418
|
+
let storageError = null;
|
|
1419
|
+
if (persistence.enabled) {
|
|
1420
|
+
try {
|
|
1421
|
+
await persistTransferTerminal(
|
|
1422
|
+
persistence,
|
|
1423
|
+
context.swapId,
|
|
1424
|
+
next,
|
|
1425
|
+
env.timestamp_ms,
|
|
1426
|
+
refundExecuted ? "refund" : "sync"
|
|
1427
|
+
);
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
storageError = storageErrorFromPush(error, context.swapId, refundExecuted);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (storageError) {
|
|
1433
|
+
this.rememberTerminalOutcome(context.swapId, next, refundExecuted, storageError);
|
|
1434
|
+
}
|
|
1435
|
+
if (!this.isActiveSwapContext(context)) {
|
|
1436
|
+
context.swapState = next;
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
try {
|
|
1440
|
+
this.updateContextState(context, next);
|
|
1441
|
+
if (payoutExecuted) this.emitPayoutExecuted(payoutExecuted, context.swapId);
|
|
1442
|
+
if (refundExecuted) this.emitRefundExecuted(refundExecuted, context.swapId);
|
|
1443
|
+
} finally {
|
|
1444
|
+
try {
|
|
1445
|
+
if (storageError) this.emitStorageError(storageError);
|
|
1446
|
+
} finally {
|
|
1447
|
+
this.settlePushListener(env);
|
|
831
1448
|
}
|
|
832
|
-
this.resolvePushListener(env);
|
|
833
1449
|
}
|
|
834
1450
|
}
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1451
|
+
emitStorageError(error) {
|
|
1452
|
+
for (const listener of this.storageErrorListeners) listener(error);
|
|
1453
|
+
this.log(`client persistence failed: ${normalizeError(error)}`, "info");
|
|
1454
|
+
}
|
|
1455
|
+
applySwapStatus(context, status, coordinatorTimestampMs) {
|
|
1456
|
+
const keepTerminalState = TERMINAL_SWAP_STATES.has(context.swapState) && status.state !== context.swapState;
|
|
1457
|
+
if (!keepTerminalState && isTerminalSwapState(status.state)) {
|
|
1458
|
+
this.rememberTerminalOutcome(context.swapId, status.state, status.withdraw_execution);
|
|
1459
|
+
}
|
|
1460
|
+
if (!keepTerminalState && context.swapState !== status.state) {
|
|
1461
|
+
this.updateContextState(context, status.state);
|
|
840
1462
|
}
|
|
841
1463
|
for (const fragment of status.fragments_executed) {
|
|
842
|
-
this.emitPayoutExecuted(fragment,
|
|
1464
|
+
this.emitPayoutExecuted(fragment, context.swapId);
|
|
843
1465
|
}
|
|
844
1466
|
if (status.withdraw_execution) {
|
|
845
|
-
this.emitRefundExecuted(status.withdraw_execution,
|
|
1467
|
+
this.emitRefundExecuted(status.withdraw_execution, context.swapId);
|
|
846
1468
|
}
|
|
847
1469
|
if (status.actor_sync !== void 0) {
|
|
848
1470
|
for (const listener of this.normalUserActorSyncListeners) {
|
|
@@ -853,8 +1475,7 @@ var UserCoordinatorSession = class {
|
|
|
853
1475
|
emitPayoutExecuted(payload, swapId) {
|
|
854
1476
|
if (!swapId) return;
|
|
855
1477
|
const key = `${swapId}:${payload.index}:${payload.tx_signature}`;
|
|
856
|
-
if (this.emittedPayoutKeys
|
|
857
|
-
this.emittedPayoutKeys.add(key);
|
|
1478
|
+
if (!this.rememberEventKey(this.emittedPayoutKeys, key)) return;
|
|
858
1479
|
this.log(
|
|
859
1480
|
`payout executed tx=${payload.tx_signature} amount=${payload.amount_lamports} final=${payload.final}`,
|
|
860
1481
|
"in"
|
|
@@ -866,88 +1487,150 @@ var UserCoordinatorSession = class {
|
|
|
866
1487
|
emitRefundExecuted(payload, swapId) {
|
|
867
1488
|
if (!swapId) return;
|
|
868
1489
|
const key = `${swapId}:${payload.tx_signature}`;
|
|
869
|
-
if (this.emittedRefundKeys
|
|
870
|
-
this.emittedRefundKeys.add(key);
|
|
1490
|
+
if (!this.rememberEventKey(this.emittedRefundKeys, key)) return;
|
|
871
1491
|
this.log(`refund executed tx=${payload.tx_signature} amount=${payload.amount_lamports}`, "in");
|
|
872
1492
|
for (const listener of this.refundExecutedListeners) {
|
|
873
1493
|
listener(payload, swapId);
|
|
874
1494
|
}
|
|
875
1495
|
}
|
|
876
|
-
|
|
1496
|
+
rememberEventKey(keys, key) {
|
|
1497
|
+
if (keys.has(key)) return false;
|
|
1498
|
+
keys.add(key);
|
|
1499
|
+
if (keys.size > MAX_EVENT_DEDUP_ENTRIES_PER_KIND) {
|
|
1500
|
+
const oldest = keys.values().next().value;
|
|
1501
|
+
if (oldest !== void 0) keys.delete(oldest);
|
|
1502
|
+
}
|
|
1503
|
+
return true;
|
|
1504
|
+
}
|
|
1505
|
+
request(type, payload, options = {}) {
|
|
877
1506
|
if (this.session === null) {
|
|
878
1507
|
throw new TransportError("CONNECTION_LOST", "coordinator session is not connected");
|
|
879
1508
|
}
|
|
880
1509
|
return requestEnvelope(this.session, type, payload, {
|
|
881
|
-
swapId: this.swapId,
|
|
882
|
-
timeoutMs,
|
|
1510
|
+
swapId: options.swapId === void 0 ? this.swapId : options.swapId,
|
|
1511
|
+
timeoutMs: options.timeoutMs,
|
|
1512
|
+
rejectMatchingOutOfBandErrors: options.rejectMatchingOutOfBandErrors,
|
|
1513
|
+
requireNonNullResponseSwapId: options.requireNonNullResponseSwapId,
|
|
883
1514
|
onLog: (message, direction) => this.log(message, direction)
|
|
884
1515
|
});
|
|
885
1516
|
}
|
|
886
|
-
awaitPush(type, timeoutMs = 3e4) {
|
|
1517
|
+
awaitPush(type, swapId, timeoutMs = 3e4) {
|
|
887
1518
|
return new Promise((resolve, reject) => {
|
|
888
1519
|
const timer = timeoutMs === null ? null : setTimeout(() => {
|
|
889
|
-
this.
|
|
1520
|
+
this.removePushListener(type, listener);
|
|
890
1521
|
reject(new CommandError("REJECTED_STALE_JOB", `timed out waiting for ${type} push`));
|
|
891
1522
|
}, timeoutMs);
|
|
892
|
-
|
|
1523
|
+
const listener = { swapId, resolve, reject, timer };
|
|
1524
|
+
const listeners = this.pushListeners.get(type) ?? [];
|
|
1525
|
+
listeners.push(listener);
|
|
1526
|
+
this.pushListeners.set(type, listeners);
|
|
893
1527
|
});
|
|
894
1528
|
}
|
|
895
1529
|
rejectAllPushListeners(err) {
|
|
896
|
-
for (const
|
|
897
|
-
|
|
898
|
-
|
|
1530
|
+
for (const listeners of this.pushListeners.values()) {
|
|
1531
|
+
for (const listener of listeners) {
|
|
1532
|
+
if (listener.timer) clearTimeout(listener.timer);
|
|
1533
|
+
listener.reject(err);
|
|
1534
|
+
}
|
|
899
1535
|
}
|
|
900
1536
|
this.pushListeners.clear();
|
|
901
|
-
this.
|
|
902
|
-
|
|
1537
|
+
for (const context of this.swapContexts.values()) {
|
|
1538
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
1539
|
+
context.bufferedDepositConfirmed = null;
|
|
1540
|
+
}
|
|
903
1541
|
}
|
|
904
1542
|
shouldBufferDepositConfirmed(env) {
|
|
905
|
-
|
|
1543
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) : null;
|
|
1544
|
+
return env.kind === EnvelopeKind.PUSH && env.type === CoordinatorMessageType.DepositConfirmed && context?.bufferDepositConfirmedUntilDelegateAck === true;
|
|
906
1545
|
}
|
|
907
1546
|
bufferDepositConfirmed(env) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1547
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) : null;
|
|
1548
|
+
if (!context) return;
|
|
1549
|
+
if (!context.bufferedDepositConfirmed) {
|
|
1550
|
+
context.bufferedDepositConfirmed = env;
|
|
1551
|
+
this.clearPushListenerTimer(CoordinatorMessageType.DepositConfirmed, context.swapId);
|
|
911
1552
|
this.log("buffered DepositConfirmed until DelegateAck is verified", "info");
|
|
912
1553
|
return;
|
|
913
1554
|
}
|
|
914
1555
|
this.log("ignored duplicate DepositConfirmed while waiting for DelegateAck", "info");
|
|
915
1556
|
}
|
|
916
|
-
releaseDepositConfirmedGate() {
|
|
1557
|
+
async releaseDepositConfirmedGate(context) {
|
|
917
1558
|
const protocol = this.requireProtocol();
|
|
918
|
-
|
|
919
|
-
const buffered =
|
|
1559
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
1560
|
+
const buffered = context.bufferedDepositConfirmed;
|
|
920
1561
|
if (!buffered) return;
|
|
921
|
-
|
|
922
|
-
const next = protocol.nextState(
|
|
1562
|
+
context.bufferedDepositConfirmed = null;
|
|
1563
|
+
const next = protocol.nextState(context.swapState, buffered.kind, buffered.type);
|
|
923
1564
|
if (next === null) {
|
|
924
|
-
this.log(`ignored buffered DepositConfirmed in state ${
|
|
1565
|
+
this.log(`ignored buffered DepositConfirmed in state ${context.swapState}`, "info");
|
|
925
1566
|
return;
|
|
926
1567
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1568
|
+
let storageError = null;
|
|
1569
|
+
try {
|
|
1570
|
+
await persistTransferStatus(
|
|
1571
|
+
this.clientPersistenceState(),
|
|
1572
|
+
context.swapId,
|
|
1573
|
+
{ state: next },
|
|
1574
|
+
buffered.timestamp_ms
|
|
1575
|
+
);
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
storageError = storageErrorFromPush(error, context.swapId, null);
|
|
1578
|
+
}
|
|
1579
|
+
if (storageError) {
|
|
1580
|
+
this.emitStorageError(storageError);
|
|
1581
|
+
throw storageError;
|
|
1582
|
+
}
|
|
1583
|
+
if (!this.isActiveSwapContext(context)) return;
|
|
1584
|
+
this.updateContextState(context, next);
|
|
1585
|
+
this.settlePushListener(buffered);
|
|
1586
|
+
}
|
|
1587
|
+
settlePushListener(env, error) {
|
|
1588
|
+
const listeners = this.pushListeners.get(env.type);
|
|
1589
|
+
if (!listeners) return;
|
|
1590
|
+
const index = listeners.findIndex(
|
|
1591
|
+
(candidate) => candidate.swapId === void 0 || candidate.swapId === env.swap_id
|
|
1592
|
+
);
|
|
1593
|
+
if (index < 0) return;
|
|
1594
|
+
const listener = listeners.splice(index, 1)[0];
|
|
1595
|
+
if (listeners.length === 0) this.pushListeners.delete(env.type);
|
|
935
1596
|
if (listener.timer) clearTimeout(listener.timer);
|
|
936
|
-
listener.
|
|
1597
|
+
if (error) listener.reject(error);
|
|
1598
|
+
else listener.resolve(env);
|
|
937
1599
|
}
|
|
938
|
-
clearPushListenerTimer(type) {
|
|
939
|
-
const listener = this.pushListeners.get(type);
|
|
1600
|
+
clearPushListenerTimer(type, swapId) {
|
|
1601
|
+
const listener = this.pushListeners.get(type)?.find((candidate) => candidate.swapId === void 0 || candidate.swapId === swapId);
|
|
940
1602
|
if (!listener?.timer) return;
|
|
941
1603
|
clearTimeout(listener.timer);
|
|
942
1604
|
listener.timer = null;
|
|
943
1605
|
}
|
|
1606
|
+
removePushListener(type, listener) {
|
|
1607
|
+
const listeners = this.pushListeners.get(type);
|
|
1608
|
+
if (!listeners) return;
|
|
1609
|
+
const index = listeners.indexOf(listener);
|
|
1610
|
+
if (index >= 0) listeners.splice(index, 1);
|
|
1611
|
+
if (listeners.length === 0) this.pushListeners.delete(type);
|
|
1612
|
+
}
|
|
1613
|
+
cancelPushWaitersForSwap(swapId) {
|
|
1614
|
+
for (const [type, listeners] of this.pushListeners.entries()) {
|
|
1615
|
+
const remaining = listeners.filter((listener) => {
|
|
1616
|
+
if (listener.swapId !== swapId) return true;
|
|
1617
|
+
if (listener.timer) clearTimeout(listener.timer);
|
|
1618
|
+
return false;
|
|
1619
|
+
});
|
|
1620
|
+
if (remaining.length === 0) {
|
|
1621
|
+
this.pushListeners.delete(type);
|
|
1622
|
+
} else if (remaining.length !== listeners.length) {
|
|
1623
|
+
this.pushListeners.set(type, remaining);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
944
1627
|
setSessionStatus(status) {
|
|
945
1628
|
this.sessionStatus = status;
|
|
946
1629
|
this.emitStateChange();
|
|
947
1630
|
}
|
|
948
|
-
emitStateChange() {
|
|
1631
|
+
emitStateChange(swapId = this.swapId, swapState = this.swapState) {
|
|
949
1632
|
for (const listener of this.stateListeners) {
|
|
950
|
-
listener(
|
|
1633
|
+
listener(swapState, this.sessionStatus, swapId);
|
|
951
1634
|
}
|
|
952
1635
|
}
|
|
953
1636
|
log(msg, direction) {
|
|
@@ -983,28 +1666,42 @@ function toContractRequestResult(prepared) {
|
|
|
983
1666
|
delegatedAtMs: prepared.delegatedAtMs,
|
|
984
1667
|
syncSecret: new Uint8Array(prepared.syncSecret),
|
|
985
1668
|
syncSecretHex: prepared.syncSecretHex,
|
|
1669
|
+
resumeSecret: new Uint8Array(prepared.resumeSecret),
|
|
1670
|
+
resumeSecretHex: prepared.resumeSecretHex,
|
|
986
1671
|
recoveryCodeHex: prepared.recoveryCodeHex,
|
|
987
1672
|
policySnapshot: prepared.policySnapshot,
|
|
988
1673
|
delegatePolicySnapshot: prepared.delegatePolicySnapshot
|
|
989
1674
|
};
|
|
990
1675
|
}
|
|
991
1676
|
async function contractRequestOnSession(session, params) {
|
|
992
|
-
if (activeFunctionalContractRequests.has(session)) {
|
|
993
|
-
throw new CommandError("REJECTED_STATE", ACTIVE_CONTRACT_REQUEST_MESSAGE);
|
|
994
|
-
}
|
|
995
|
-
activeFunctionalContractRequests.add(session);
|
|
996
1677
|
let client = null;
|
|
997
1678
|
try {
|
|
998
|
-
client = new UserCoordinatorSession(
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1679
|
+
client = new UserCoordinatorSession(
|
|
1680
|
+
{
|
|
1681
|
+
coordinator: getSessionState(session).pool,
|
|
1682
|
+
createSession: async () => session
|
|
1683
|
+
},
|
|
1684
|
+
false
|
|
1685
|
+
);
|
|
1002
1686
|
return await client.contractRequest(params);
|
|
1003
1687
|
} finally {
|
|
1004
|
-
activeFunctionalContractRequests.delete(session);
|
|
1005
1688
|
client?.releaseWithoutClosing();
|
|
1006
1689
|
}
|
|
1007
1690
|
}
|
|
1691
|
+
async function resumePreDelegationOnSession(session, params) {
|
|
1692
|
+
const client = new UserCoordinatorSession(
|
|
1693
|
+
{
|
|
1694
|
+
coordinator: getSessionState(session).pool,
|
|
1695
|
+
createSession: async () => session
|
|
1696
|
+
},
|
|
1697
|
+
false
|
|
1698
|
+
);
|
|
1699
|
+
try {
|
|
1700
|
+
return await client.resumePreDelegation(params);
|
|
1701
|
+
} finally {
|
|
1702
|
+
client.releaseWithoutClosing();
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1008
1705
|
async function requestRefundOnSession(session, params) {
|
|
1009
1706
|
const client = new UserCoordinatorSession(
|
|
1010
1707
|
{
|
|
@@ -1057,6 +1754,12 @@ async function requestUserSync(session, swapId, syncSecret, timeoutMs) {
|
|
|
1057
1754
|
);
|
|
1058
1755
|
const payload = (await loadProtocol()).asSyncUserResponsePayload(env);
|
|
1059
1756
|
assertSyncResponseContractId(env, payload, swapId);
|
|
1757
|
+
await persistTransferStatus(
|
|
1758
|
+
getSessionState(session).clientPersistence,
|
|
1759
|
+
swapId,
|
|
1760
|
+
payload,
|
|
1761
|
+
env.timestamp_ms
|
|
1762
|
+
);
|
|
1060
1763
|
return {
|
|
1061
1764
|
...payload,
|
|
1062
1765
|
coordinatorTimestampMs: env.timestamp_ms
|
|
@@ -1140,6 +1843,18 @@ function isRetryableSwapStatusReconciliationError(err) {
|
|
|
1140
1843
|
if (err instanceof AttestationError) return false;
|
|
1141
1844
|
return err instanceof CommandError && err.code === "REJECTED_STALE_JOB";
|
|
1142
1845
|
}
|
|
1846
|
+
function isTerminalSwapState(state) {
|
|
1847
|
+
return state === "completed" || state === "refunded" || state === "failed";
|
|
1848
|
+
}
|
|
1849
|
+
function storageErrorFromPush(error, swapId, refundExecuted) {
|
|
1850
|
+
if (error instanceof StorageError) return error;
|
|
1851
|
+
return new StorageError("STORAGE_NOT_AVAILABLE", "client persistence update failed", {
|
|
1852
|
+
cause: error,
|
|
1853
|
+
swapId,
|
|
1854
|
+
phase: refundExecuted ? "refund" : "sync",
|
|
1855
|
+
remoteAccepted: true
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1143
1858
|
function coordinatorErrorFromEnvelope(protocol, env) {
|
|
1144
1859
|
try {
|
|
1145
1860
|
const payload = protocol.asErrorPayload(env);
|
|
@@ -1182,11 +1897,35 @@ async function contractRequest(session, args) {
|
|
|
1182
1897
|
...args.onSyncSecretReady !== void 0 && {
|
|
1183
1898
|
onSyncSecretReady: args.onSyncSecretReady
|
|
1184
1899
|
},
|
|
1900
|
+
...args.resumeSecret !== void 0 && { resumeSecret: args.resumeSecret },
|
|
1901
|
+
...args.onResumeSecretReady !== void 0 && {
|
|
1902
|
+
onResumeSecretReady: args.onResumeSecretReady
|
|
1903
|
+
},
|
|
1904
|
+
...args.onSwapAllocated !== void 0 && {
|
|
1905
|
+
onSwapAllocated: args.onSwapAllocated
|
|
1906
|
+
},
|
|
1185
1907
|
...args.onRecoveryCodeReady !== void 0 && {
|
|
1186
1908
|
onRecoveryCodeReady: args.onRecoveryCodeReady
|
|
1909
|
+
},
|
|
1910
|
+
...args.onDelegateShareReady !== void 0 && {
|
|
1911
|
+
onDelegateShareReady: args.onDelegateShareReady
|
|
1187
1912
|
}
|
|
1188
1913
|
});
|
|
1189
1914
|
}
|
|
1915
|
+
async function resumePreDelegation(session, args) {
|
|
1916
|
+
assertAttested(session);
|
|
1917
|
+
return resumePreDelegationOnSession(session, {
|
|
1918
|
+
swapId: args.swapId,
|
|
1919
|
+
resumeSecret: new Uint8Array(args.resumeSecret),
|
|
1920
|
+
...args.syncSecret !== void 0 && { syncSecret: new Uint8Array(args.syncSecret) },
|
|
1921
|
+
recoveryCommitment: new Uint8Array(args.recoveryCommitment),
|
|
1922
|
+
policySnapshot: args.policySnapshot,
|
|
1923
|
+
...args.onDelegateShareReady !== void 0 && {
|
|
1924
|
+
onDelegateShareReady: args.onDelegateShareReady
|
|
1925
|
+
},
|
|
1926
|
+
...args.delegateShare !== void 0 && { delegateShare: new Uint8Array(args.delegateShare) }
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1190
1929
|
async function sync(session, contractId, syncSecret, timeoutMs) {
|
|
1191
1930
|
assertAttested(session);
|
|
1192
1931
|
return requestUserSync(session, contractId, syncSecret, timeoutMs);
|
|
@@ -1228,10 +1967,12 @@ async function requestRefund(session, args) {
|
|
|
1228
1967
|
assertAttested(session);
|
|
1229
1968
|
assertRecoveryCode(args.recoveryCode);
|
|
1230
1969
|
return requestRefundOnSession(session, {
|
|
1970
|
+
...args.swapId !== void 0 && { swapId: args.swapId },
|
|
1971
|
+
...args.syncSecret !== void 0 && { syncSecret: new Uint8Array(args.syncSecret) },
|
|
1231
1972
|
recoveryCode: new Uint8Array(args.recoveryCode)
|
|
1232
1973
|
});
|
|
1233
1974
|
}
|
|
1234
1975
|
|
|
1235
|
-
export { INSTANT_PAYOUT_WINDOW_MS, MAX_ENTRY_AMOUNT_LAMPORTS, MIN_ENTRY_AMOUNT_LAMPORTS, PAYOUT_WINDOW_MILLISECONDS_PER_SECOND, RECOVERY_CODE_BYTE_LENGTH, SUPPORTED_TOTAL_DEADLINE_MS, commitmentFromCode, contractRequest, generateRecoveryCode, recoveryCodeFromHex, recoveryCodeFromUserInput, recoveryCodeToHex, requestRefund, sync };
|
|
1976
|
+
export { INSTANT_PAYOUT_WINDOW_MS, MAX_ENTRY_AMOUNT_LAMPORTS, MIN_ENTRY_AMOUNT_LAMPORTS, PAYOUT_WINDOW_MILLISECONDS_PER_SECOND, RECOVERY_CODE_BYTE_LENGTH, SUPPORTED_TOTAL_DEADLINE_MS, commitmentFromCode, contractRequest, generateRecoveryCode, recoveryCodeFromHex, recoveryCodeFromUserInput, recoveryCodeToHex, requestRefund, resumePreDelegation, sync };
|
|
1236
1977
|
//# sourceMappingURL=user.js.map
|
|
1237
1978
|
//# sourceMappingURL=user.js.map
|