@invisible-labs/sdk 0.6.0-devnet.2 → 0.6.0-devnet.4
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 +186 -70
- package/dist/.invisible-sdk-build-target.json +7 -0
- package/dist/{chunk-TQNNTV5F.js → chunk-4X5OCCQW.js} +3 -3
- package/dist/{chunk-TQNNTV5F.js.map → chunk-4X5OCCQW.js.map} +1 -1
- package/dist/{chunk-7DSVN3MV.js → chunk-5NNEO7IK.js} +41 -36
- package/dist/chunk-5NNEO7IK.js.map +1 -0
- package/dist/chunk-BRDLLGCZ.js +59 -0
- package/dist/chunk-BRDLLGCZ.js.map +1 -0
- package/dist/{chunk-NCY4FCYU.js → chunk-DIA6U2CV.js} +19 -3
- package/dist/chunk-DIA6U2CV.js.map +1 -0
- package/dist/{chunk-LBC7ALYO.js → chunk-E4DCEDAG.js} +177 -12
- package/dist/chunk-E4DCEDAG.js.map +1 -0
- package/dist/{chunk-OHIM2YWU.js → chunk-GHBQF3A7.js} +2 -2
- package/dist/chunk-GHBQF3A7.js.map +1 -0
- package/dist/{chunk-SZAYO2L5.js → chunk-NHEHCUS5.js} +3 -3
- package/dist/{chunk-SZAYO2L5.js.map → chunk-NHEHCUS5.js.map} +1 -1
- package/dist/{chunk-XUWBGET5.js → chunk-UADJCUPK.js} +125 -18
- package/dist/chunk-UADJCUPK.js.map +1 -0
- package/dist/{chunk-JRMKCE3S.js → chunk-V5HXJRBW.js} +3 -3
- package/dist/{chunk-JRMKCE3S.js.map → chunk-V5HXJRBW.js.map} +1 -1
- package/dist/{coordinator-7Y45MCCZ.js → coordinator-ZNLGAYXD.js} +3 -3
- package/dist/{coordinator-7Y45MCCZ.js.map → coordinator-ZNLGAYXD.js.map} +1 -1
- package/dist/events.js +3 -3
- package/dist/index.d.ts +62 -300
- package/dist/index.js +98 -43
- package/dist/index.js.map +1 -1
- package/dist/lp.d.ts +51 -10
- package/dist/lp.js +313 -86
- package/dist/lp.js.map +1 -1
- package/dist/presets.d.ts +5 -42
- package/dist/presets.js +11 -62
- package/dist/presets.js.map +1 -1
- package/dist/stats.js +1 -1
- package/dist/storage.js +1 -1
- package/dist/{createSession-D3ym-Ira.d.ts → types.generated-DJBTjLpP.d.ts} +37 -53
- package/dist/user.d.ts +94 -112
- package/dist/user.js +649 -248
- package/dist/user.js.map +1 -1
- package/package.json +2 -28
- package/dist/chunk-7DSVN3MV.js.map +0 -1
- package/dist/chunk-LBC7ALYO.js.map +0 -1
- package/dist/chunk-NCY4FCYU.js.map +0 -1
- package/dist/chunk-OHIM2YWU.js.map +0 -1
- package/dist/chunk-XUWBGET5.js.map +0 -1
- package/dist/types.generated-CHGSbmLp.d.ts +0 -67
package/dist/user.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { scheduledPayoutWindowSeconds } from './chunk-
|
|
2
|
-
import {
|
|
3
|
-
import { PRODUCT_MAX_ENTRY_LAMPORTS, PRODUCT_MIN_ENTRY_LAMPORTS } from './chunk-
|
|
4
|
-
import {
|
|
5
|
-
import { CoordinatorMessageType, onEnvelope, closeProtocolRuntime, EnvelopeKind
|
|
6
|
-
import { getSessionState } from './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import {
|
|
9
|
-
export { InvalidDepositAmountError } from './chunk-
|
|
1
|
+
import { scheduledPayoutWindowSeconds } from './chunk-E4DCEDAG.js';
|
|
2
|
+
import { validatePayoutPolicyShape, formatAjvErrors, createSession, createExponentialBackoff } from './chunk-5NNEO7IK.js';
|
|
3
|
+
import { PRODUCT_MAX_ENTRY_LAMPORTS, PRODUCT_MIN_ENTRY_LAMPORTS } from './chunk-4X5OCCQW.js';
|
|
4
|
+
import { assertAttested, publicKey, solanaAddressFromPublicKeyBytes } from './chunk-NHEHCUS5.js';
|
|
5
|
+
import { requestEnvelope, CoordinatorMessageType, onEnvelope, closeProtocolRuntime, EnvelopeKind } from './chunk-UADJCUPK.js';
|
|
6
|
+
import { getSessionState } from './chunk-V5HXJRBW.js';
|
|
7
|
+
import './chunk-DIA6U2CV.js';
|
|
8
|
+
import { PolicyValidationError, CommandError, TransportError, InvalidDepositAmountError, normalizeError, AttestationError, RoutingError } from './chunk-GHBQF3A7.js';
|
|
9
|
+
export { InvalidDepositAmountError } from './chunk-GHBQF3A7.js';
|
|
10
10
|
import './chunk-VR6T6OJS.js';
|
|
11
11
|
|
|
12
12
|
// src/validation/payoutPolicy.ts
|
|
@@ -171,7 +171,7 @@ function recoveryCodeFromUserInput(input) {
|
|
|
171
171
|
var protocolPromise = null;
|
|
172
172
|
var frostRuntimePromise = null;
|
|
173
173
|
function loadProtocol() {
|
|
174
|
-
protocolPromise ??= import('./coordinator-
|
|
174
|
+
protocolPromise ??= import('./coordinator-ZNLGAYXD.js');
|
|
175
175
|
return protocolPromise;
|
|
176
176
|
}
|
|
177
177
|
function loadFrostRuntime() {
|
|
@@ -185,22 +185,22 @@ var BENIGN_TRANSPORT_DROP = /* @__PURE__ */ Symbol("benignTransportDrop");
|
|
|
185
185
|
var ALICE_ID = 1;
|
|
186
186
|
var COORDINATOR_ID = 2;
|
|
187
187
|
var SYNC_SECRET_BYTE_LENGTH = 32;
|
|
188
|
-
var
|
|
188
|
+
var RESUME_SECRET_BYTE_LENGTH = SYNC_SECRET_BYTE_LENGTH;
|
|
189
|
+
var SYNC_CONTRACT_ID_MISMATCH_MESSAGE = "sync response contract id does not match requested contract";
|
|
189
190
|
var DEFAULT_SWAP_STATUS_RECONCILE_INTERVAL_MS = 5e3;
|
|
190
191
|
var MAX_SWAP_STATUS_RETRY_DELAY_MS = 3e4;
|
|
191
192
|
var SWAP_STATUS_RETRY_MULTIPLIER = 2;
|
|
193
|
+
var MAX_EVENT_DEDUP_ENTRIES_PER_KIND = 1024;
|
|
192
194
|
var SWAP_STATUS_RETRY_JITTER_RATIO = 0.2;
|
|
193
195
|
var DEFAULT_REFUND_OUTCOME_POLL_BUDGET_MS = 5 * 60 * 1e3;
|
|
194
196
|
var REFUND_OUTCOME_POLL_INTERVAL_MS = 3e3;
|
|
195
197
|
var REFUND_OUTCOME_PER_POLL_TIMEOUT_MS = 5e3;
|
|
198
|
+
var UNBOUND_RECONCILIATION_KEY = "__unbound__";
|
|
196
199
|
var REQUEST_TIMEOUT_MESSAGE = "coordinator request timed out";
|
|
200
|
+
var REFUND_PENDING_FINALIZATION_MESSAGE = "refund accepted and pending finalization; check status again shortly";
|
|
197
201
|
var CURRENT_SCHEDULED_PAYOUT_WINDOW_SECONDS = new Set(scheduledPayoutWindowSeconds());
|
|
198
202
|
var CURRENT_SCHEDULED_PAYOUT_WINDOW_MESSAGE = `scheduled payout_spec.window_seconds must be one of [${scheduledPayoutWindowSeconds().join(", ")}]`;
|
|
199
203
|
var TERMINAL_SWAP_STATES = /* @__PURE__ */ new Set(["completed", "refunded", "failed"]);
|
|
200
|
-
var activeFunctionalContractRequests = /* @__PURE__ */ new WeakSet();
|
|
201
|
-
function createCoordinatorSession(options) {
|
|
202
|
-
return new UserCoordinatorSession(options);
|
|
203
|
-
}
|
|
204
204
|
function isBenignTransportDrop(err) {
|
|
205
205
|
return typeof err === "object" && err !== null && err[BENIGN_TRANSPORT_DROP] === true;
|
|
206
206
|
}
|
|
@@ -229,8 +229,12 @@ var UserCoordinatorSession = class {
|
|
|
229
229
|
this.sessionStatus = "idle";
|
|
230
230
|
this.swapId = null;
|
|
231
231
|
this.syncSecret = null;
|
|
232
|
+
this.resumeSecret = null;
|
|
232
233
|
this.attested = false;
|
|
233
234
|
this.pushListeners = /* @__PURE__ */ new Map();
|
|
235
|
+
this.swapContexts = /* @__PURE__ */ new Map();
|
|
236
|
+
this.openingSession = null;
|
|
237
|
+
this.activeOperations = 0;
|
|
234
238
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
235
239
|
this.logListeners = /* @__PURE__ */ new Set();
|
|
236
240
|
this.payoutExecutedListeners = /* @__PURE__ */ new Set();
|
|
@@ -239,12 +243,10 @@ var UserCoordinatorSession = class {
|
|
|
239
243
|
this.coordinatorErrorListeners = /* @__PURE__ */ new Set();
|
|
240
244
|
this.emittedPayoutKeys = /* @__PURE__ */ new Set();
|
|
241
245
|
this.emittedRefundKeys = /* @__PURE__ */ new Set();
|
|
242
|
-
this.
|
|
243
|
-
this.bufferDepositConfirmedUntilDelegateAck = false;
|
|
244
|
-
this.bufferedDepositConfirmed = null;
|
|
246
|
+
this.statusReconciliationStops = /* @__PURE__ */ new Map();
|
|
245
247
|
this.coordinator = options.coordinator;
|
|
246
248
|
this.createSessionImpl = options.createSession ?? createSession;
|
|
247
|
-
this.
|
|
249
|
+
this.closeSessionAfterOperationFailure = closeSessionAfterRefund;
|
|
248
250
|
}
|
|
249
251
|
onStateChange(listener) {
|
|
250
252
|
this.stateListeners.add(listener);
|
|
@@ -280,51 +282,226 @@ var UserCoordinatorSession = class {
|
|
|
280
282
|
get currentSwapId() {
|
|
281
283
|
return this.swapId;
|
|
282
284
|
}
|
|
285
|
+
beginOperation() {
|
|
286
|
+
this.activeOperations += 1;
|
|
287
|
+
if (this.activeOperations === 1) this.setSessionStatus("connecting");
|
|
288
|
+
}
|
|
289
|
+
endOperation() {
|
|
290
|
+
this.activeOperations = Math.max(0, this.activeOperations - 1);
|
|
291
|
+
if (this.activeOperations === 0 && this.sessionStatus === "connecting") {
|
|
292
|
+
this.setSessionStatus("idle");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
ensureSwapContext(swapId, syncSecret, resumeSecret) {
|
|
296
|
+
let context = this.swapContexts.get(swapId);
|
|
297
|
+
if (!context) {
|
|
298
|
+
context = {
|
|
299
|
+
swapId,
|
|
300
|
+
swapState: this.swapId === swapId ? this.swapState : "new",
|
|
301
|
+
syncSecret: this.swapId === swapId && this.syncSecret ? new Uint8Array(this.syncSecret) : null,
|
|
302
|
+
resumeSecret: this.swapId === swapId && this.resumeSecret ? new Uint8Array(this.resumeSecret) : null,
|
|
303
|
+
bufferDepositConfirmedUntilDelegateAck: false,
|
|
304
|
+
bufferedDepositConfirmed: null
|
|
305
|
+
};
|
|
306
|
+
this.swapContexts.set(swapId, context);
|
|
307
|
+
}
|
|
308
|
+
if (syncSecret !== void 0) context.syncSecret = new Uint8Array(syncSecret);
|
|
309
|
+
if (resumeSecret !== void 0) context.resumeSecret = new Uint8Array(resumeSecret);
|
|
310
|
+
this.setCurrentContext(context);
|
|
311
|
+
return context;
|
|
312
|
+
}
|
|
313
|
+
setCurrentContext(context) {
|
|
314
|
+
this.swapId = context.swapId;
|
|
315
|
+
this.swapState = context.swapState;
|
|
316
|
+
this.syncSecret = context.syncSecret ? new Uint8Array(context.syncSecret) : null;
|
|
317
|
+
this.resumeSecret = context.resumeSecret ? new Uint8Array(context.resumeSecret) : null;
|
|
318
|
+
}
|
|
319
|
+
updateContextState(context, state) {
|
|
320
|
+
context.swapState = state;
|
|
321
|
+
if (this.swapId === context.swapId) {
|
|
322
|
+
this.swapState = state;
|
|
323
|
+
}
|
|
324
|
+
this.emitStateChange(context.swapId, state);
|
|
325
|
+
if (TERMINAL_SWAP_STATES.has(state)) {
|
|
326
|
+
this.evictSwapContext(context.swapId);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
evictSwapContext(swapId) {
|
|
330
|
+
this.statusReconciliationStops.get(swapId)?.();
|
|
331
|
+
this.statusReconciliationStops.delete(swapId);
|
|
332
|
+
const context = this.swapContexts.get(swapId);
|
|
333
|
+
if (context) {
|
|
334
|
+
context.syncSecret?.fill(0);
|
|
335
|
+
context.resumeSecret?.fill(0);
|
|
336
|
+
context.bufferedDepositConfirmed = null;
|
|
337
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
338
|
+
this.swapContexts.delete(swapId);
|
|
339
|
+
}
|
|
340
|
+
if (this.swapId === swapId) {
|
|
341
|
+
this.syncSecret?.fill(0);
|
|
342
|
+
this.resumeSecret?.fill(0);
|
|
343
|
+
this.syncSecret = null;
|
|
344
|
+
this.resumeSecret = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
clearSwapContexts() {
|
|
348
|
+
for (const swapId of this.swapContexts.keys()) this.evictSwapContext(swapId);
|
|
349
|
+
this.swapContexts.clear();
|
|
350
|
+
this.emittedPayoutKeys.clear();
|
|
351
|
+
this.emittedRefundKeys.clear();
|
|
352
|
+
}
|
|
283
353
|
async runSwap(params) {
|
|
284
354
|
assertCurrentUserPayoutSpecRuntimeLimits(params.payoutSpec);
|
|
285
|
-
this.
|
|
286
|
-
this.setSessionStatus("connecting");
|
|
355
|
+
this.beginOperation();
|
|
287
356
|
let completed = false;
|
|
357
|
+
let terminalStatus = null;
|
|
288
358
|
try {
|
|
289
359
|
await ensureFrostWasm();
|
|
290
360
|
await this.openSession();
|
|
291
361
|
this.setSessionStatus("running");
|
|
292
362
|
const result = await this.runProtocol(params);
|
|
293
363
|
completed = true;
|
|
294
|
-
|
|
364
|
+
terminalStatus = "done";
|
|
295
365
|
return result;
|
|
296
366
|
} catch (err) {
|
|
297
367
|
const error = this.normalizeSessionError(err);
|
|
298
368
|
if (!isBenignTransportDrop(error)) {
|
|
299
|
-
|
|
369
|
+
terminalStatus = "failed";
|
|
300
370
|
}
|
|
301
371
|
throw error;
|
|
302
372
|
} finally {
|
|
303
|
-
|
|
373
|
+
this.endOperation();
|
|
374
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
375
|
+
this.setSessionStatus(terminalStatus);
|
|
376
|
+
}
|
|
377
|
+
if (!completed && this.activeOperations === 0 && this.closeSessionAfterOperationFailure) {
|
|
304
378
|
this.close();
|
|
305
379
|
}
|
|
306
380
|
}
|
|
307
381
|
}
|
|
308
382
|
async contractRequest(params) {
|
|
309
383
|
assertCurrentUserPayoutSpecRuntimeLimits(params.payoutSpec);
|
|
310
|
-
this.
|
|
311
|
-
|
|
384
|
+
this.beginOperation();
|
|
385
|
+
let completed = false;
|
|
386
|
+
let terminalStatus = null;
|
|
312
387
|
try {
|
|
313
388
|
await ensureFrostWasm();
|
|
314
389
|
await this.openSession();
|
|
315
390
|
this.setSessionStatus("running");
|
|
316
391
|
const prepared = await this.prepareContract(params);
|
|
392
|
+
completed = true;
|
|
393
|
+
terminalStatus = "done";
|
|
317
394
|
return toContractRequestResult(prepared);
|
|
318
395
|
} catch (err) {
|
|
319
396
|
const error = this.normalizeSessionError(err);
|
|
320
|
-
|
|
397
|
+
terminalStatus = "failed";
|
|
321
398
|
throw error;
|
|
399
|
+
} finally {
|
|
400
|
+
this.endOperation();
|
|
401
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
402
|
+
this.setSessionStatus(terminalStatus);
|
|
403
|
+
}
|
|
404
|
+
if (!completed && this.activeOperations === 0 && this.closeSessionAfterOperationFailure) {
|
|
405
|
+
this.close();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async resumePreDelegation(params) {
|
|
410
|
+
if (!params.policySnapshot) {
|
|
411
|
+
throw new CommandError(
|
|
412
|
+
"REJECTED_GUARD",
|
|
413
|
+
"policySnapshot is required to resume pre-delegation"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (params.recoveryCommitment.length !== SYNC_SECRET_BYTE_LENGTH) {
|
|
417
|
+
throw new CommandError(
|
|
418
|
+
"REJECTED_GUARD",
|
|
419
|
+
`recoveryCommitment must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
this.beginOperation();
|
|
423
|
+
let completed = false;
|
|
424
|
+
let terminalStatus = null;
|
|
425
|
+
try {
|
|
426
|
+
await ensureFrostWasm();
|
|
427
|
+
this.restoreSwapContext({
|
|
428
|
+
swapId: params.swapId,
|
|
429
|
+
syncSecret: params.syncSecret,
|
|
430
|
+
resumeSecret: params.resumeSecret
|
|
431
|
+
});
|
|
432
|
+
await this.openSession();
|
|
433
|
+
this.setSessionStatus("running");
|
|
434
|
+
const context = this.ensureSwapContext(params.swapId);
|
|
435
|
+
context.swapState = params.delegateShare ? "awaiting_deposit" : "awaiting_dkg";
|
|
436
|
+
this.setCurrentContext(context);
|
|
437
|
+
let jointPublicKey;
|
|
438
|
+
let depositExpiresAtMs;
|
|
439
|
+
let coordinatorTimestampMs;
|
|
440
|
+
let delegateShare = params.delegateShare ? new Uint8Array(params.delegateShare) : void 0;
|
|
441
|
+
if (!delegateShare) {
|
|
442
|
+
const dkg = await this.runDkg(context, params.onDelegateShareReady);
|
|
443
|
+
jointPublicKey = dkg.jointPublicKey;
|
|
444
|
+
depositExpiresAtMs = dkg.depositExpiresAtMs;
|
|
445
|
+
coordinatorTimestampMs = dkg.coordinatorTimestampMs;
|
|
446
|
+
delegateShare = dkg.keyHandle.secretShareBytes();
|
|
447
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
448
|
+
}
|
|
449
|
+
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck, context.swapId);
|
|
450
|
+
let delegateResp;
|
|
451
|
+
try {
|
|
452
|
+
delegateResp = await this.request(
|
|
453
|
+
CoordinatorMessageType.DelegateShare,
|
|
454
|
+
{
|
|
455
|
+
share: delegateShare,
|
|
456
|
+
recovery_commitment: new Uint8Array(params.recoveryCommitment),
|
|
457
|
+
resume_secret: context.resumeSecret
|
|
458
|
+
},
|
|
459
|
+
{ swapId: context.swapId }
|
|
460
|
+
);
|
|
461
|
+
} catch (err) {
|
|
462
|
+
void delegateAckPromise.catch(() => void 0);
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
const delegatedAtMs = this.requireProtocol().asDelegateShareResponsePayload(delegateResp).delegated_at_ms;
|
|
466
|
+
const delegateAck = this.requireProtocol().asDelegateAckPayload(await delegateAckPromise);
|
|
467
|
+
if (delegateAck.delegated_at_ms !== delegatedAtMs) {
|
|
468
|
+
throw new CommandError(
|
|
469
|
+
"REJECTED_GUARD",
|
|
470
|
+
"DelegateAck delegated_at_ms does not match DelegateShare response"
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
if (!spendingPoliciesEqual(delegateAck.spending_policy, params.policySnapshot)) {
|
|
474
|
+
throw new CommandError(
|
|
475
|
+
"REJECTED_GUARD",
|
|
476
|
+
"DelegateAck spending_policy does not match persisted policy_snapshot"
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
480
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
481
|
+
completed = true;
|
|
482
|
+
terminalStatus = "done";
|
|
483
|
+
return {
|
|
484
|
+
swapId: context.swapId,
|
|
485
|
+
jointPublicKey,
|
|
486
|
+
depositExpiresAtMs,
|
|
487
|
+
coordinatorTimestampMs,
|
|
488
|
+
delegatedAtMs
|
|
489
|
+
};
|
|
490
|
+
} catch (err) {
|
|
491
|
+
terminalStatus = "failed";
|
|
492
|
+
throw this.normalizeSessionError(err);
|
|
493
|
+
} finally {
|
|
494
|
+
if (!completed) this.cancelPushWaitersForSwap(params.swapId);
|
|
495
|
+
this.endOperation();
|
|
496
|
+
if (this.activeOperations === 0 && terminalStatus !== null) {
|
|
497
|
+
this.setSessionStatus(terminalStatus);
|
|
498
|
+
}
|
|
322
499
|
}
|
|
323
500
|
}
|
|
324
501
|
/** @internal Used by the functional Session API without closing its caller-owned session. */
|
|
325
502
|
releaseWithoutClosing() {
|
|
326
|
-
this.
|
|
327
|
-
this.
|
|
503
|
+
this.stopAllStatusReconciliations();
|
|
504
|
+
this.clearSwapContexts();
|
|
328
505
|
this.detachSession(
|
|
329
506
|
new TransportError("CONNECTION_LOST", "coordinator session operation released"),
|
|
330
507
|
false,
|
|
@@ -332,12 +509,15 @@ var UserCoordinatorSession = class {
|
|
|
332
509
|
);
|
|
333
510
|
}
|
|
334
511
|
close() {
|
|
335
|
-
this.
|
|
336
|
-
this.
|
|
512
|
+
this.stopAllStatusReconciliations();
|
|
513
|
+
this.clearSwapContexts();
|
|
337
514
|
this.detachSession(new TransportError("CONNECTION_LOST", "coordinator session closed"), true);
|
|
338
515
|
}
|
|
516
|
+
stopAllStatusReconciliations() {
|
|
517
|
+
for (const stop of [...this.statusReconciliationStops.values()]) stop();
|
|
518
|
+
this.statusReconciliationStops.clear();
|
|
519
|
+
}
|
|
339
520
|
restoreSwapContext(params) {
|
|
340
|
-
this.swapId = params.swapId;
|
|
341
521
|
if (params.syncSecret !== void 0) {
|
|
342
522
|
if (params.syncSecret.length !== SYNC_SECRET_BYTE_LENGTH) {
|
|
343
523
|
throw new CommandError(
|
|
@@ -345,28 +525,46 @@ var UserCoordinatorSession = class {
|
|
|
345
525
|
`syncSecret must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
346
526
|
);
|
|
347
527
|
}
|
|
348
|
-
this.syncSecret = new Uint8Array(params.syncSecret);
|
|
349
528
|
}
|
|
529
|
+
if (params.resumeSecret !== void 0) {
|
|
530
|
+
if (params.resumeSecret.length !== RESUME_SECRET_BYTE_LENGTH) {
|
|
531
|
+
throw new CommandError(
|
|
532
|
+
"REJECTED_GUARD",
|
|
533
|
+
`resumeSecret must be ${RESUME_SECRET_BYTE_LENGTH} bytes`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
this.ensureSwapContext(params.swapId, params.syncSecret, params.resumeSecret);
|
|
350
538
|
}
|
|
351
539
|
async requestSwapStatus(timeoutMs) {
|
|
352
540
|
if (!this.swapId) {
|
|
353
541
|
throw new CommandError("REJECTED_STATE", "Sync requires swapId");
|
|
354
542
|
}
|
|
543
|
+
const context = this.ensureSwapContext(this.swapId, this.syncSecret ?? void 0);
|
|
544
|
+
return this.requestSwapStatusForContext(context, timeoutMs);
|
|
545
|
+
}
|
|
546
|
+
async requestSwapStatusForContext(context, timeoutMs) {
|
|
355
547
|
await this.openSession();
|
|
356
548
|
const env = await this.request(
|
|
357
549
|
CoordinatorMessageType.Sync,
|
|
358
550
|
{
|
|
359
551
|
actor: "user",
|
|
360
|
-
...
|
|
552
|
+
...context.syncSecret ? { sync_secret: context.syncSecret } : {}
|
|
361
553
|
},
|
|
362
|
-
|
|
554
|
+
{
|
|
555
|
+
swapId: context.swapId,
|
|
556
|
+
timeoutMs,
|
|
557
|
+
rejectMatchingOutOfBandErrors: true
|
|
558
|
+
}
|
|
363
559
|
);
|
|
364
560
|
const status = this.requireProtocol().asSyncUserResponsePayload(env);
|
|
365
|
-
this.applySwapStatus(status, env.timestamp_ms);
|
|
561
|
+
this.applySwapStatus(context, status, env.timestamp_ms);
|
|
366
562
|
return status;
|
|
367
563
|
}
|
|
368
564
|
startSwapStatusReconciliation(intervalMs = DEFAULT_SWAP_STATUS_RECONCILE_INTERVAL_MS) {
|
|
369
|
-
this.
|
|
565
|
+
const reconciliationContext = this.swapId ? this.ensureSwapContext(this.swapId, this.syncSecret ?? void 0) : null;
|
|
566
|
+
const reconciliationKey = reconciliationContext?.swapId ?? UNBOUND_RECONCILIATION_KEY;
|
|
567
|
+
this.statusReconciliationStops.get(reconciliationKey)?.();
|
|
370
568
|
let stopped = false;
|
|
371
569
|
let reconciling = false;
|
|
372
570
|
let timer = null;
|
|
@@ -391,20 +589,18 @@ var UserCoordinatorSession = class {
|
|
|
391
589
|
if (stopped || reconciling || !isVisible()) return;
|
|
392
590
|
reconciling = true;
|
|
393
591
|
try {
|
|
394
|
-
const status = await this.requestSwapStatus();
|
|
592
|
+
const status = reconciliationContext ? await this.requestSwapStatusForContext(reconciliationContext) : await this.requestSwapStatus();
|
|
395
593
|
retryBackoff.reset();
|
|
396
594
|
if (status.state === "completed" || status.state === "refunded" || status.state === "failed") {
|
|
397
|
-
|
|
398
|
-
if (timer) clearTimeout(timer);
|
|
595
|
+
stopReconciliation();
|
|
399
596
|
} else {
|
|
400
597
|
schedule(intervalMs);
|
|
401
598
|
}
|
|
402
599
|
} catch (err) {
|
|
403
600
|
this.log(`SwapStatus reconcile failed: ${normalizeError(err)}`, "info");
|
|
404
601
|
if (!isRetryableSwapStatusReconciliationError(err)) {
|
|
405
|
-
|
|
406
|
-
if (
|
|
407
|
-
this.close();
|
|
602
|
+
stopReconciliation();
|
|
603
|
+
if (err instanceof AttestationError) this.close();
|
|
408
604
|
return;
|
|
409
605
|
}
|
|
410
606
|
schedule(retryBackoff.nextDelayMs());
|
|
@@ -440,7 +636,8 @@ var UserCoordinatorSession = class {
|
|
|
440
636
|
browserDocument?.addEventListener("freeze", handleFreeze);
|
|
441
637
|
browserDocument?.addEventListener("resume", reconcileWhenVisible);
|
|
442
638
|
reconcileWhenVisible();
|
|
443
|
-
|
|
639
|
+
const stopReconciliation = () => {
|
|
640
|
+
if (stopped) return;
|
|
444
641
|
stopped = true;
|
|
445
642
|
if (timer) clearTimeout(timer);
|
|
446
643
|
browserWindow?.removeEventListener("focus", reconcileWhenVisible);
|
|
@@ -449,8 +646,12 @@ var UserCoordinatorSession = class {
|
|
|
449
646
|
browserDocument?.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
450
647
|
browserDocument?.removeEventListener("freeze", handleFreeze);
|
|
451
648
|
browserDocument?.removeEventListener("resume", reconcileWhenVisible);
|
|
649
|
+
if (this.statusReconciliationStops.get(reconciliationKey) === stopReconciliation) {
|
|
650
|
+
this.statusReconciliationStops.delete(reconciliationKey);
|
|
651
|
+
}
|
|
452
652
|
};
|
|
453
|
-
|
|
653
|
+
this.statusReconciliationStops.set(reconciliationKey, stopReconciliation);
|
|
654
|
+
return stopReconciliation;
|
|
454
655
|
}
|
|
455
656
|
async requestRefund(params) {
|
|
456
657
|
const result = await this.requestRefundIntentInternal(params, { waitForFinality: true });
|
|
@@ -462,9 +663,10 @@ var UserCoordinatorSession = class {
|
|
|
462
663
|
}
|
|
463
664
|
async requestRefundIntentInternal(params, options) {
|
|
464
665
|
assertRecoveryCode(params.recoveryCode);
|
|
666
|
+
this.beginOperation();
|
|
465
667
|
const recoveryCode = new Uint8Array(params.recoveryCode);
|
|
466
668
|
const ackTimeout = params.timeoutMs ?? 3e4;
|
|
467
|
-
let
|
|
669
|
+
let requestSwapId = params.swapId ?? null;
|
|
468
670
|
let openedForRefund = false;
|
|
469
671
|
try {
|
|
470
672
|
if (params.syncSecret !== void 0) {
|
|
@@ -473,33 +675,36 @@ var UserCoordinatorSession = class {
|
|
|
473
675
|
throw new CommandError("REJECTED_STATE", "syncSecret requires swapId");
|
|
474
676
|
}
|
|
475
677
|
this.restoreSwapContext({ swapId, syncSecret: params.syncSecret });
|
|
678
|
+
requestSwapId = swapId;
|
|
476
679
|
} else if (params.swapId) {
|
|
477
680
|
const swapId = params.swapId;
|
|
478
681
|
this.restoreSwapContext({ swapId });
|
|
682
|
+
requestSwapId = swapId;
|
|
479
683
|
}
|
|
480
684
|
openedForRefund = !this.hasUsableSession();
|
|
481
685
|
await this.openSession();
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
686
|
+
let env;
|
|
687
|
+
try {
|
|
688
|
+
env = await this.request(
|
|
689
|
+
CoordinatorMessageType.WithdrawRequest,
|
|
690
|
+
{
|
|
691
|
+
recovery_code: recoveryCode
|
|
692
|
+
},
|
|
693
|
+
{ swapId: requestSwapId, timeoutMs: ackTimeout }
|
|
694
|
+
);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
if (isRequestTimeoutError(err)) {
|
|
697
|
+
throw new CommandError(
|
|
698
|
+
"REJECTED_STALE_JOB",
|
|
699
|
+
`WithdrawRequest acknowledgement timed out after ${ackTimeout}ms`
|
|
495
700
|
);
|
|
496
|
-
})
|
|
497
|
-
]);
|
|
498
|
-
if (ackTimer) clearTimeout(ackTimer);
|
|
499
|
-
if (env.type === CoordinatorMessageType.WithdrawExecuted) {
|
|
500
|
-
if (env.swap_id && !this.swapId) {
|
|
501
|
-
this.swapId = env.swap_id;
|
|
502
701
|
}
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
if (env.type === CoordinatorMessageType.WithdrawExecuted) {
|
|
705
|
+
const resolvedSwapId2 = env.swap_id ?? requestSwapId;
|
|
706
|
+
const resolvedContext2 = resolvedSwapId2 ? this.ensureSwapContext(resolvedSwapId2) : null;
|
|
707
|
+
if (resolvedContext2) this.setCurrentContext(resolvedContext2);
|
|
503
708
|
return { kind: "executed", refund: this.requireProtocol().asWithdrawExecutedPayload(env) };
|
|
504
709
|
}
|
|
505
710
|
if (env.type !== CoordinatorMessageType.WithdrawAccepted) {
|
|
@@ -508,29 +713,32 @@ var UserCoordinatorSession = class {
|
|
|
508
713
|
`Expected WithdrawAccepted or WithdrawExecuted response, got ${env.type}`
|
|
509
714
|
);
|
|
510
715
|
}
|
|
511
|
-
if (env.swap_id && !this.swapId) {
|
|
512
|
-
this.swapId = env.swap_id;
|
|
513
|
-
}
|
|
514
716
|
const accepted = this.requireProtocol().asWithdrawAcceptedPayload(env);
|
|
717
|
+
const resolvedSwapId = env.swap_id ?? requestSwapId;
|
|
718
|
+
const resolvedContext = resolvedSwapId ? this.ensureSwapContext(resolvedSwapId) : null;
|
|
515
719
|
if (!options.waitForFinality) {
|
|
516
720
|
return { kind: "accepted", accepted };
|
|
517
721
|
}
|
|
518
|
-
if (!
|
|
519
|
-
throw new CommandError(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
);
|
|
722
|
+
if (!resolvedContext) {
|
|
723
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
724
|
+
}
|
|
725
|
+
if (!resolvedContext.syncSecret) {
|
|
726
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
523
727
|
}
|
|
524
|
-
|
|
728
|
+
this.setCurrentContext(resolvedContext);
|
|
729
|
+
return {
|
|
730
|
+
kind: "executed",
|
|
731
|
+
refund: await this.awaitRefundOutcomeFromSwapStatus(resolvedContext)
|
|
732
|
+
};
|
|
525
733
|
} finally {
|
|
526
|
-
if (ackTimer) clearTimeout(ackTimer);
|
|
527
734
|
recoveryCode.fill(0);
|
|
528
|
-
|
|
735
|
+
this.endOperation();
|
|
736
|
+
if (openedForRefund && this.closeSessionAfterOperationFailure && this.activeOperations === 0) {
|
|
529
737
|
this.close();
|
|
530
738
|
}
|
|
531
739
|
}
|
|
532
740
|
}
|
|
533
|
-
async awaitRefundOutcomeFromSwapStatus() {
|
|
741
|
+
async awaitRefundOutcomeFromSwapStatus(context) {
|
|
534
742
|
const deadline = Date.now() + DEFAULT_REFUND_OUTCOME_POLL_BUDGET_MS;
|
|
535
743
|
for (; ; ) {
|
|
536
744
|
const perPollTimeout = Math.min(
|
|
@@ -538,7 +746,7 @@ var UserCoordinatorSession = class {
|
|
|
538
746
|
Math.max(0, deadline - Date.now())
|
|
539
747
|
);
|
|
540
748
|
try {
|
|
541
|
-
const status = await this.
|
|
749
|
+
const status = await this.requestSwapStatusForContext(context, perPollTimeout);
|
|
542
750
|
if (status.withdraw_execution) {
|
|
543
751
|
return status.withdraw_execution;
|
|
544
752
|
}
|
|
@@ -548,10 +756,7 @@ var UserCoordinatorSession = class {
|
|
|
548
756
|
}
|
|
549
757
|
}
|
|
550
758
|
if (Date.now() >= deadline) {
|
|
551
|
-
throw new CommandError(
|
|
552
|
-
"REJECTED_STALE_JOB",
|
|
553
|
-
"refund accepted and pending finalization; check status again shortly"
|
|
554
|
-
);
|
|
759
|
+
throw new CommandError("REJECTED_STALE_JOB", REFUND_PENDING_FINALIZATION_MESSAGE);
|
|
555
760
|
}
|
|
556
761
|
await delay(REFUND_OUTCOME_POLL_INTERVAL_MS);
|
|
557
762
|
}
|
|
@@ -572,13 +777,19 @@ var UserCoordinatorSession = class {
|
|
|
572
777
|
"in"
|
|
573
778
|
);
|
|
574
779
|
await params.onDepositConfirmed?.({ originAddress: depositPayload.origin_address });
|
|
575
|
-
|
|
780
|
+
const context = this.swapContexts.get(prepared.swapId);
|
|
781
|
+
if (!context) {
|
|
782
|
+
throw new CommandError("REJECTED_STATE", `missing client context for ${prepared.swapId}`);
|
|
783
|
+
}
|
|
784
|
+
await this.requestSwapStatusForContext(context);
|
|
576
785
|
return {
|
|
577
786
|
swapId: prepared.swapId,
|
|
578
787
|
jointPublicKey: prepared.jointPublicKey,
|
|
579
788
|
depositTxSignature: depositPayload.tx_signature,
|
|
580
789
|
depositObservedAtMs: depositPayload.observed_at_ms,
|
|
581
790
|
delegatedAtMs: prepared.delegatedAtMs,
|
|
791
|
+
resumeSecret: new Uint8Array(prepared.resumeSecret),
|
|
792
|
+
resumeSecretHex: prepared.resumeSecretHex,
|
|
582
793
|
recoveryCodeHex: prepared.recoveryCodeHex,
|
|
583
794
|
policySnapshot: prepared.policySnapshot,
|
|
584
795
|
delegatePolicySnapshot: prepared.delegatePolicySnapshot
|
|
@@ -589,6 +800,7 @@ var UserCoordinatorSession = class {
|
|
|
589
800
|
const requestedPayoutWindowMs = params.payoutWindowMs ?? payoutWindowMsFromSpec(params.payoutSpec);
|
|
590
801
|
const payoutDeadlineMs = requestedPayoutWindowMs !== void 0 ? Date.now() + requestedPayoutWindowMs : void 0;
|
|
591
802
|
let generatedSyncSecret = null;
|
|
803
|
+
let generatedResumeSecret = null;
|
|
592
804
|
const syncSecret = params.syncSecret ? new Uint8Array(params.syncSecret) : (() => {
|
|
593
805
|
generatedSyncSecret = randomSecret32();
|
|
594
806
|
return generatedSyncSecret;
|
|
@@ -599,105 +811,165 @@ var UserCoordinatorSession = class {
|
|
|
599
811
|
`syncSecret must be ${SYNC_SECRET_BYTE_LENGTH} bytes`
|
|
600
812
|
);
|
|
601
813
|
}
|
|
602
|
-
this.syncSecret = new Uint8Array(syncSecret);
|
|
603
814
|
const syncSecretHex = bytesToHex(syncSecret);
|
|
604
815
|
const syncCommitment = await sha256Commitment(syncSecret);
|
|
605
816
|
if (generatedSyncSecret) {
|
|
606
817
|
await params.onSyncSecretReady?.({ secretHex: syncSecretHex });
|
|
607
818
|
}
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
...(params.payoutDeadlineMs !== void 0 || payoutDeadlineMs !== void 0) && {
|
|
614
|
-
payout_deadline_ms: params.payoutDeadlineMs ?? payoutDeadlineMs
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
const swapPayload = protocol.asSwapRequestResponsePayload(swapResp);
|
|
618
|
-
const swapId = swapPayload.swap_id;
|
|
619
|
-
this.swapId = swapId;
|
|
620
|
-
params.onPolicySnapshot?.(swapPayload.policy_snapshot);
|
|
621
|
-
this.log(`swap accepted - id=${swapId}`, "in");
|
|
622
|
-
this.bufferDepositConfirmedUntilDelegateAck = true;
|
|
623
|
-
this.bufferedDepositConfirmed = null;
|
|
624
|
-
const depositConfirmedPromise = this.awaitPush(CoordinatorMessageType.DepositConfirmed, null);
|
|
625
|
-
void depositConfirmedPromise.catch(() => void 0);
|
|
626
|
-
const { jointPublicKey, keyHandle, depositExpiresAtMs, coordinatorTimestampMs } = await this.runDkg();
|
|
627
|
-
let recoveryCommitment = params.recoveryCommitment;
|
|
628
|
-
let recoveryCodeHex;
|
|
629
|
-
if (!recoveryCommitment) {
|
|
630
|
-
const generated = await generateRecoveryCode();
|
|
631
|
-
recoveryCommitment = generated.commitment;
|
|
632
|
-
try {
|
|
633
|
-
recoveryCodeHex = recoveryCodeToHex(generated.code);
|
|
634
|
-
await params.onRecoveryCodeReady?.({ codeHex: recoveryCodeHex });
|
|
635
|
-
} finally {
|
|
636
|
-
generated.code.fill(0);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck);
|
|
640
|
-
const share = params.delegateShare ?? keyHandle.secretShareBytes();
|
|
641
|
-
let delegateResp;
|
|
642
|
-
try {
|
|
643
|
-
delegateResp = await this.request(CoordinatorMessageType.DelegateShare, {
|
|
644
|
-
share,
|
|
645
|
-
recovery_commitment: recoveryCommitment
|
|
646
|
-
});
|
|
647
|
-
} catch (err) {
|
|
648
|
-
void delegateAckPromise.catch(() => void 0);
|
|
649
|
-
throw err;
|
|
650
|
-
}
|
|
651
|
-
const delegatePayload = protocol.asDelegateShareResponsePayload(delegateResp);
|
|
652
|
-
const delegateAck = protocol.asDelegateAckPayload(await delegateAckPromise);
|
|
653
|
-
if (delegateAck.delegated_at_ms !== delegatePayload.delegated_at_ms) {
|
|
819
|
+
const resumeSecret = params.resumeSecret ? new Uint8Array(params.resumeSecret) : (() => {
|
|
820
|
+
generatedResumeSecret = randomSecret32();
|
|
821
|
+
return generatedResumeSecret;
|
|
822
|
+
})();
|
|
823
|
+
if (resumeSecret.length !== RESUME_SECRET_BYTE_LENGTH) {
|
|
654
824
|
throw new CommandError(
|
|
655
825
|
"REJECTED_GUARD",
|
|
656
|
-
|
|
826
|
+
`resumeSecret must be ${RESUME_SECRET_BYTE_LENGTH} bytes`
|
|
657
827
|
);
|
|
658
828
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
);
|
|
829
|
+
const resumeSecretHex = bytesToHex(resumeSecret);
|
|
830
|
+
const resumeCommitment = await sha256Commitment(resumeSecret);
|
|
831
|
+
if (generatedResumeSecret) {
|
|
832
|
+
await params.onResumeSecretReady?.({ secretHex: resumeSecretHex });
|
|
664
833
|
}
|
|
665
|
-
this.
|
|
666
|
-
|
|
667
|
-
|
|
834
|
+
const swapResp = await this.request(
|
|
835
|
+
CoordinatorMessageType.ContractRequest,
|
|
836
|
+
{
|
|
837
|
+
actor: "user",
|
|
838
|
+
amount_lamports: params.amountLamports,
|
|
839
|
+
payout_spec: params.payoutSpec,
|
|
840
|
+
sync_commitment: syncCommitment,
|
|
841
|
+
resume_commitment: resumeCommitment,
|
|
842
|
+
...(params.payoutDeadlineMs !== void 0 || payoutDeadlineMs !== void 0) && {
|
|
843
|
+
payout_deadline_ms: params.payoutDeadlineMs ?? payoutDeadlineMs
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
{ swapId: null, requireNonNullResponseSwapId: true }
|
|
847
|
+
);
|
|
848
|
+
const swapPayload = protocol.asSwapRequestResponsePayload(swapResp);
|
|
849
|
+
const swapId = swapPayload.swap_id;
|
|
850
|
+
const context = {
|
|
668
851
|
swapId,
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
852
|
+
swapState: "awaiting_dkg",
|
|
853
|
+
syncSecret: new Uint8Array(syncSecret),
|
|
854
|
+
resumeSecret: new Uint8Array(resumeSecret),
|
|
855
|
+
bufferDepositConfirmedUntilDelegateAck: true,
|
|
856
|
+
bufferedDepositConfirmed: null
|
|
857
|
+
};
|
|
858
|
+
this.swapContexts.set(swapId, context);
|
|
859
|
+
this.setCurrentContext(context);
|
|
860
|
+
await params.onSwapAllocated?.({
|
|
676
861
|
swapId,
|
|
677
|
-
amountLamports: params.amountLamports,
|
|
678
|
-
jointPublicKey,
|
|
679
|
-
depositExpiresAtMs,
|
|
680
|
-
coordinatorTimestampMs,
|
|
681
|
-
delegatedAtMs: delegateAck.delegated_at_ms,
|
|
682
|
-
syncSecret,
|
|
683
862
|
syncSecretHex,
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
863
|
+
resumeSecretHex
|
|
864
|
+
});
|
|
865
|
+
params.onPolicySnapshot?.(swapPayload.policy_snapshot);
|
|
866
|
+
this.log(`swap accepted - id=${swapId}`, "in");
|
|
867
|
+
let preparedSuccessfully = false;
|
|
868
|
+
try {
|
|
869
|
+
const depositConfirmedPromise = this.awaitPush(
|
|
870
|
+
CoordinatorMessageType.DepositConfirmed,
|
|
871
|
+
context.swapId,
|
|
872
|
+
null
|
|
873
|
+
);
|
|
874
|
+
void depositConfirmedPromise.catch(() => void 0);
|
|
875
|
+
const { jointPublicKey, keyHandle, depositExpiresAtMs, coordinatorTimestampMs } = await this.runDkg(context, params.delegateShare ? void 0 : params.onDelegateShareReady);
|
|
876
|
+
this.updateContextState(context, "awaiting_deposit");
|
|
877
|
+
let recoveryCommitment = params.recoveryCommitment;
|
|
878
|
+
let recoveryCodeHex;
|
|
879
|
+
if (!recoveryCommitment) {
|
|
880
|
+
const generated = await generateRecoveryCode();
|
|
881
|
+
recoveryCommitment = generated.commitment;
|
|
882
|
+
try {
|
|
883
|
+
recoveryCodeHex = recoveryCodeToHex(generated.code);
|
|
884
|
+
await params.onRecoveryCodeReady?.({ codeHex: recoveryCodeHex });
|
|
885
|
+
} finally {
|
|
886
|
+
generated.code.fill(0);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const delegateAckPromise = this.awaitPush(CoordinatorMessageType.DelegateAck, context.swapId);
|
|
890
|
+
const share = params.delegateShare ?? keyHandle.secretShareBytes();
|
|
891
|
+
let delegateResp;
|
|
892
|
+
try {
|
|
893
|
+
delegateResp = await this.request(
|
|
894
|
+
CoordinatorMessageType.DelegateShare,
|
|
895
|
+
{
|
|
896
|
+
share,
|
|
897
|
+
recovery_commitment: recoveryCommitment,
|
|
898
|
+
resume_secret: context.resumeSecret
|
|
899
|
+
},
|
|
900
|
+
{ swapId: context.swapId }
|
|
901
|
+
);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
void delegateAckPromise.catch(() => void 0);
|
|
904
|
+
throw err;
|
|
905
|
+
}
|
|
906
|
+
const delegatePayload = protocol.asDelegateShareResponsePayload(delegateResp);
|
|
907
|
+
const delegateAck = protocol.asDelegateAckPayload(await delegateAckPromise);
|
|
908
|
+
if (delegateAck.delegated_at_ms !== delegatePayload.delegated_at_ms) {
|
|
909
|
+
throw new CommandError(
|
|
910
|
+
"REJECTED_GUARD",
|
|
911
|
+
"DelegateAck delegated_at_ms does not match DelegateShare response"
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
if (!spendingPoliciesEqual(delegateAck.spending_policy, swapPayload.policy_snapshot)) {
|
|
915
|
+
throw new CommandError(
|
|
916
|
+
"REJECTED_GUARD",
|
|
917
|
+
"DelegateAck spending_policy does not match ContractRequest policy_snapshot"
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
this.log(`delegated at ${delegateAck.delegated_at_ms}`, "in");
|
|
921
|
+
this.releaseDepositConfirmedGate(context);
|
|
922
|
+
await params.onDepositAddressReady?.({
|
|
923
|
+
swapId,
|
|
924
|
+
jointPublicKey,
|
|
925
|
+
amountLamports: params.amountLamports,
|
|
926
|
+
depositExpiresAtMs,
|
|
927
|
+
coordinatorTimestampMs
|
|
928
|
+
});
|
|
929
|
+
preparedSuccessfully = true;
|
|
930
|
+
return {
|
|
931
|
+
requestId: swapId,
|
|
932
|
+
swapId,
|
|
933
|
+
amountLamports: params.amountLamports,
|
|
934
|
+
jointPublicKey,
|
|
935
|
+
depositExpiresAtMs,
|
|
936
|
+
coordinatorTimestampMs,
|
|
937
|
+
delegatedAtMs: delegateAck.delegated_at_ms,
|
|
938
|
+
syncSecret,
|
|
939
|
+
syncSecretHex,
|
|
940
|
+
resumeSecret,
|
|
941
|
+
resumeSecretHex,
|
|
942
|
+
recoveryCodeHex,
|
|
943
|
+
policySnapshot: swapPayload.policy_snapshot,
|
|
944
|
+
delegatePolicySnapshot: delegateAck.spending_policy,
|
|
945
|
+
depositConfirmedPromise
|
|
946
|
+
};
|
|
947
|
+
} finally {
|
|
948
|
+
if (!preparedSuccessfully) {
|
|
949
|
+
this.cancelPushWaitersForSwap(context.swapId);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
689
952
|
}
|
|
690
|
-
async runDkg() {
|
|
953
|
+
async runDkg(context, onDelegateShareReady) {
|
|
691
954
|
await ensureFrostWasm();
|
|
692
955
|
const protocol = this.requireProtocol();
|
|
693
956
|
const { dkgRound1 } = await loadFrost();
|
|
694
|
-
await this.request(
|
|
957
|
+
await this.request(
|
|
958
|
+
CoordinatorMessageType.DkgInit,
|
|
959
|
+
{ resume_secret: context.resumeSecret },
|
|
960
|
+
{ swapId: context.swapId }
|
|
961
|
+
);
|
|
695
962
|
this.log("DKG initialised", "info");
|
|
696
963
|
const aliceRound1State = dkgRound1(ALICE_ID);
|
|
697
964
|
const aliceRound1Bytes = aliceRound1State.round1PackageBytes();
|
|
698
|
-
const round1Resp = await this.request(
|
|
699
|
-
|
|
700
|
-
|
|
965
|
+
const round1Resp = await this.request(
|
|
966
|
+
CoordinatorMessageType.DkgRound1,
|
|
967
|
+
{
|
|
968
|
+
package: aliceRound1Bytes,
|
|
969
|
+
resume_secret: context.resumeSecret
|
|
970
|
+
},
|
|
971
|
+
{ swapId: context.swapId }
|
|
972
|
+
);
|
|
701
973
|
const round1Payload = protocol.asDkgRound1ResponsePayload(round1Resp);
|
|
702
974
|
this.log(`DKG round1 complete - coord pkg ${round1Payload.package.byteLength}B`, "in");
|
|
703
975
|
const aliceRound2State = aliceRound1State.intoRound2(COORDINATOR_ID, round1Payload.package);
|
|
@@ -712,10 +984,20 @@ var UserCoordinatorSession = class {
|
|
|
712
984
|
} catch (err) {
|
|
713
985
|
throw new CommandError("REJECTED_GUARD", `Alice DKG finalize failed: ${normalizeError(err)}`);
|
|
714
986
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
987
|
+
await onDelegateShareReady?.({
|
|
988
|
+
swapId: context.swapId,
|
|
989
|
+
delegateShare: new Uint8Array(aliceKeyHandle.secretShareBytes())
|
|
718
990
|
});
|
|
991
|
+
const dkgCompletePromise = this.awaitPush(CoordinatorMessageType.DkgComplete, context.swapId);
|
|
992
|
+
void dkgCompletePromise.catch(() => void 0);
|
|
993
|
+
await this.request(
|
|
994
|
+
CoordinatorMessageType.DkgRound2,
|
|
995
|
+
{
|
|
996
|
+
package: aliceRound2Bytes,
|
|
997
|
+
resume_secret: context.resumeSecret
|
|
998
|
+
},
|
|
999
|
+
{ swapId: context.swapId }
|
|
1000
|
+
);
|
|
719
1001
|
const completeEnvelope = await dkgCompletePromise;
|
|
720
1002
|
const complete = readDkgComplete(protocol, completeEnvelope);
|
|
721
1003
|
const alicePubkey = aliceKeyHandle.groupPublicKeyBytes();
|
|
@@ -735,6 +1017,20 @@ var UserCoordinatorSession = class {
|
|
|
735
1017
|
};
|
|
736
1018
|
}
|
|
737
1019
|
async openSession() {
|
|
1020
|
+
if (this.hasUsableSession()) return;
|
|
1021
|
+
if (this.openingSession !== null) {
|
|
1022
|
+
await this.openingSession;
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
const opening = this.openSessionOnce();
|
|
1026
|
+
this.openingSession = opening;
|
|
1027
|
+
try {
|
|
1028
|
+
await opening;
|
|
1029
|
+
} finally {
|
|
1030
|
+
if (this.openingSession === opening) this.openingSession = null;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
async openSessionOnce() {
|
|
738
1034
|
if (this.hasUsableSession()) return;
|
|
739
1035
|
if (this.session !== null) {
|
|
740
1036
|
this.detachSession(
|
|
@@ -760,11 +1056,6 @@ var UserCoordinatorSession = class {
|
|
|
760
1056
|
this.log(`connected to ${this.coordinator.endpoints[0]?.wsUrl ?? "coordinator"}`, "info");
|
|
761
1057
|
this.log("attestation verified", "info");
|
|
762
1058
|
}
|
|
763
|
-
assertNoActiveContract() {
|
|
764
|
-
if (this.swapId !== null || this.sessionStatus === "connecting" || this.sessionStatus === "running") {
|
|
765
|
-
throw new CommandError("REJECTED_STATE", ACTIVE_CONTRACT_REQUEST_MESSAGE);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
1059
|
hasUsableSession() {
|
|
769
1060
|
if (this.session === null) return false;
|
|
770
1061
|
const state = getSessionState(this.session);
|
|
@@ -790,30 +1081,43 @@ var UserCoordinatorSession = class {
|
|
|
790
1081
|
handleEnvelope(env) {
|
|
791
1082
|
const protocol = this.requireProtocol();
|
|
792
1083
|
this.log(`<- ${env.type} kind=${env.kind}`, "in");
|
|
1084
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) ?? null : null;
|
|
1085
|
+
if (env.swap_id === null && env.kind !== EnvelopeKind.ERROR) {
|
|
1086
|
+
this.log(`ignored ${env.type} without an explicit swap_id`, "info");
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
if (env.swap_id !== null && context === null) {
|
|
1090
|
+
this.log(`ignored ${env.type} for unknown swap ${env.swap_id}`, "info");
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
793
1093
|
const payoutExecuted = env.kind === EnvelopeKind.PUSH && env.type === CoordinatorMessageType.PayoutExecuted ? protocol.asPayoutExecutedPayload(env) : null;
|
|
794
|
-
const payoutMatchesCurrentSwap = payoutExecuted !== null &&
|
|
1094
|
+
const payoutMatchesCurrentSwap = payoutExecuted !== null && context !== null;
|
|
795
1095
|
const refundExecuted = env.kind === EnvelopeKind.PUSH && env.type === CoordinatorMessageType.WithdrawExecuted ? protocol.asWithdrawExecutedPayload(env) : null;
|
|
796
1096
|
if (this.shouldBufferDepositConfirmed(env)) {
|
|
797
1097
|
this.bufferDepositConfirmed(env);
|
|
798
1098
|
return;
|
|
799
1099
|
}
|
|
800
|
-
const
|
|
801
|
-
const next =
|
|
802
|
-
|
|
1100
|
+
const stateForEnvelope = context?.swapState ?? this.swapState;
|
|
1101
|
+
const next = payoutExecuted !== null && !payoutMatchesCurrentSwap ? null : protocol.nextState(
|
|
1102
|
+
stateForEnvelope,
|
|
803
1103
|
env.kind,
|
|
804
1104
|
env.type,
|
|
805
1105
|
payoutExecuted ? { payoutFinal: payoutExecuted.final } : void 0
|
|
806
1106
|
);
|
|
807
1107
|
if (env.kind === EnvelopeKind.PUSH && next === null) {
|
|
808
1108
|
this.log(
|
|
809
|
-
|
|
1109
|
+
`ignored ${env.type} for swap ${env.swap_id ?? "null"} while client context is ${context?.swapId ?? "none"} in state ${stateForEnvelope}`,
|
|
810
1110
|
"info"
|
|
811
1111
|
);
|
|
812
1112
|
return;
|
|
813
1113
|
}
|
|
814
1114
|
if (next !== null) {
|
|
815
|
-
|
|
816
|
-
|
|
1115
|
+
if (context) {
|
|
1116
|
+
this.updateContextState(context, next);
|
|
1117
|
+
} else {
|
|
1118
|
+
this.swapState = next;
|
|
1119
|
+
this.emitStateChange(this.swapId, next);
|
|
1120
|
+
}
|
|
817
1121
|
}
|
|
818
1122
|
if (env.kind === EnvelopeKind.ERROR) {
|
|
819
1123
|
const coordinatorError = coordinatorErrorFromEnvelope(protocol, env);
|
|
@@ -825,26 +1129,25 @@ var UserCoordinatorSession = class {
|
|
|
825
1129
|
return;
|
|
826
1130
|
}
|
|
827
1131
|
if (env.kind === EnvelopeKind.PUSH) {
|
|
828
|
-
if (payoutExecuted && payoutMatchesCurrentSwap) {
|
|
829
|
-
this.emitPayoutExecuted(payoutExecuted,
|
|
1132
|
+
if (payoutExecuted && payoutMatchesCurrentSwap && context) {
|
|
1133
|
+
this.emitPayoutExecuted(payoutExecuted, context.swapId);
|
|
830
1134
|
}
|
|
831
|
-
if (refundExecuted) {
|
|
832
|
-
this.emitRefundExecuted(refundExecuted,
|
|
1135
|
+
if (refundExecuted && context) {
|
|
1136
|
+
this.emitRefundExecuted(refundExecuted, context.swapId);
|
|
833
1137
|
}
|
|
834
1138
|
this.resolvePushListener(env);
|
|
835
1139
|
}
|
|
836
1140
|
}
|
|
837
|
-
applySwapStatus(status, coordinatorTimestampMs) {
|
|
838
|
-
const keepTerminalState = TERMINAL_SWAP_STATES.has(
|
|
839
|
-
if (!keepTerminalState &&
|
|
840
|
-
this.
|
|
841
|
-
this.emitStateChange();
|
|
1141
|
+
applySwapStatus(context, status, coordinatorTimestampMs) {
|
|
1142
|
+
const keepTerminalState = TERMINAL_SWAP_STATES.has(context.swapState) && status.state !== context.swapState;
|
|
1143
|
+
if (!keepTerminalState && context.swapState !== status.state) {
|
|
1144
|
+
this.updateContextState(context, status.state);
|
|
842
1145
|
}
|
|
843
1146
|
for (const fragment of status.fragments_executed) {
|
|
844
|
-
this.emitPayoutExecuted(fragment,
|
|
1147
|
+
this.emitPayoutExecuted(fragment, context.swapId);
|
|
845
1148
|
}
|
|
846
1149
|
if (status.withdraw_execution) {
|
|
847
|
-
this.emitRefundExecuted(status.withdraw_execution,
|
|
1150
|
+
this.emitRefundExecuted(status.withdraw_execution, context.swapId);
|
|
848
1151
|
}
|
|
849
1152
|
if (status.actor_sync !== void 0) {
|
|
850
1153
|
for (const listener of this.normalUserActorSyncListeners) {
|
|
@@ -855,8 +1158,7 @@ var UserCoordinatorSession = class {
|
|
|
855
1158
|
emitPayoutExecuted(payload, swapId) {
|
|
856
1159
|
if (!swapId) return;
|
|
857
1160
|
const key = `${swapId}:${payload.index}:${payload.tx_signature}`;
|
|
858
|
-
if (this.emittedPayoutKeys
|
|
859
|
-
this.emittedPayoutKeys.add(key);
|
|
1161
|
+
if (!this.rememberEventKey(this.emittedPayoutKeys, key)) return;
|
|
860
1162
|
this.log(
|
|
861
1163
|
`payout executed tx=${payload.tx_signature} amount=${payload.amount_lamports} final=${payload.final}`,
|
|
862
1164
|
"in"
|
|
@@ -868,88 +1170,133 @@ var UserCoordinatorSession = class {
|
|
|
868
1170
|
emitRefundExecuted(payload, swapId) {
|
|
869
1171
|
if (!swapId) return;
|
|
870
1172
|
const key = `${swapId}:${payload.tx_signature}`;
|
|
871
|
-
if (this.emittedRefundKeys
|
|
872
|
-
this.emittedRefundKeys.add(key);
|
|
1173
|
+
if (!this.rememberEventKey(this.emittedRefundKeys, key)) return;
|
|
873
1174
|
this.log(`refund executed tx=${payload.tx_signature} amount=${payload.amount_lamports}`, "in");
|
|
874
1175
|
for (const listener of this.refundExecutedListeners) {
|
|
875
1176
|
listener(payload, swapId);
|
|
876
1177
|
}
|
|
877
1178
|
}
|
|
878
|
-
|
|
1179
|
+
rememberEventKey(keys, key) {
|
|
1180
|
+
if (keys.has(key)) return false;
|
|
1181
|
+
keys.add(key);
|
|
1182
|
+
if (keys.size > MAX_EVENT_DEDUP_ENTRIES_PER_KIND) {
|
|
1183
|
+
const oldest = keys.values().next().value;
|
|
1184
|
+
if (oldest !== void 0) keys.delete(oldest);
|
|
1185
|
+
}
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
request(type, payload, options = {}) {
|
|
879
1189
|
if (this.session === null) {
|
|
880
1190
|
throw new TransportError("CONNECTION_LOST", "coordinator session is not connected");
|
|
881
1191
|
}
|
|
882
1192
|
return requestEnvelope(this.session, type, payload, {
|
|
883
|
-
swapId: this.swapId,
|
|
884
|
-
timeoutMs,
|
|
1193
|
+
swapId: options.swapId === void 0 ? this.swapId : options.swapId,
|
|
1194
|
+
timeoutMs: options.timeoutMs,
|
|
1195
|
+
rejectMatchingOutOfBandErrors: options.rejectMatchingOutOfBandErrors,
|
|
1196
|
+
requireNonNullResponseSwapId: options.requireNonNullResponseSwapId,
|
|
885
1197
|
onLog: (message, direction) => this.log(message, direction)
|
|
886
1198
|
});
|
|
887
1199
|
}
|
|
888
|
-
awaitPush(type, timeoutMs = 3e4) {
|
|
1200
|
+
awaitPush(type, swapId, timeoutMs = 3e4) {
|
|
889
1201
|
return new Promise((resolve, reject) => {
|
|
890
1202
|
const timer = timeoutMs === null ? null : setTimeout(() => {
|
|
891
|
-
this.
|
|
1203
|
+
this.removePushListener(type, listener);
|
|
892
1204
|
reject(new CommandError("REJECTED_STALE_JOB", `timed out waiting for ${type} push`));
|
|
893
1205
|
}, timeoutMs);
|
|
894
|
-
|
|
1206
|
+
const listener = { swapId, resolve, reject, timer };
|
|
1207
|
+
const listeners = this.pushListeners.get(type) ?? [];
|
|
1208
|
+
listeners.push(listener);
|
|
1209
|
+
this.pushListeners.set(type, listeners);
|
|
895
1210
|
});
|
|
896
1211
|
}
|
|
897
1212
|
rejectAllPushListeners(err) {
|
|
898
|
-
for (const
|
|
899
|
-
|
|
900
|
-
|
|
1213
|
+
for (const listeners of this.pushListeners.values()) {
|
|
1214
|
+
for (const listener of listeners) {
|
|
1215
|
+
if (listener.timer) clearTimeout(listener.timer);
|
|
1216
|
+
listener.reject(err);
|
|
1217
|
+
}
|
|
901
1218
|
}
|
|
902
1219
|
this.pushListeners.clear();
|
|
903
|
-
this.
|
|
904
|
-
|
|
1220
|
+
for (const context of this.swapContexts.values()) {
|
|
1221
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
1222
|
+
context.bufferedDepositConfirmed = null;
|
|
1223
|
+
}
|
|
905
1224
|
}
|
|
906
1225
|
shouldBufferDepositConfirmed(env) {
|
|
907
|
-
|
|
1226
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) : null;
|
|
1227
|
+
return env.kind === EnvelopeKind.PUSH && env.type === CoordinatorMessageType.DepositConfirmed && context?.bufferDepositConfirmedUntilDelegateAck === true;
|
|
908
1228
|
}
|
|
909
1229
|
bufferDepositConfirmed(env) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1230
|
+
const context = env.swap_id ? this.swapContexts.get(env.swap_id) : null;
|
|
1231
|
+
if (!context) return;
|
|
1232
|
+
if (!context.bufferedDepositConfirmed) {
|
|
1233
|
+
context.bufferedDepositConfirmed = env;
|
|
1234
|
+
this.clearPushListenerTimer(CoordinatorMessageType.DepositConfirmed, context.swapId);
|
|
913
1235
|
this.log("buffered DepositConfirmed until DelegateAck is verified", "info");
|
|
914
1236
|
return;
|
|
915
1237
|
}
|
|
916
1238
|
this.log("ignored duplicate DepositConfirmed while waiting for DelegateAck", "info");
|
|
917
1239
|
}
|
|
918
|
-
releaseDepositConfirmedGate() {
|
|
1240
|
+
releaseDepositConfirmedGate(context) {
|
|
919
1241
|
const protocol = this.requireProtocol();
|
|
920
|
-
|
|
921
|
-
const buffered =
|
|
1242
|
+
context.bufferDepositConfirmedUntilDelegateAck = false;
|
|
1243
|
+
const buffered = context.bufferedDepositConfirmed;
|
|
922
1244
|
if (!buffered) return;
|
|
923
|
-
|
|
924
|
-
const next = protocol.nextState(
|
|
1245
|
+
context.bufferedDepositConfirmed = null;
|
|
1246
|
+
const next = protocol.nextState(context.swapState, buffered.kind, buffered.type);
|
|
925
1247
|
if (next === null) {
|
|
926
|
-
this.log(`ignored buffered DepositConfirmed in state ${
|
|
1248
|
+
this.log(`ignored buffered DepositConfirmed in state ${context.swapState}`, "info");
|
|
927
1249
|
return;
|
|
928
1250
|
}
|
|
929
|
-
this.
|
|
930
|
-
this.emitStateChange();
|
|
1251
|
+
this.updateContextState(context, next);
|
|
931
1252
|
this.resolvePushListener(buffered);
|
|
932
1253
|
}
|
|
933
1254
|
resolvePushListener(env) {
|
|
934
|
-
const
|
|
935
|
-
if (!
|
|
936
|
-
|
|
1255
|
+
const listeners = this.pushListeners.get(env.type);
|
|
1256
|
+
if (!listeners) return;
|
|
1257
|
+
const index = listeners.findIndex(
|
|
1258
|
+
(candidate) => candidate.swapId === void 0 || candidate.swapId === env.swap_id
|
|
1259
|
+
);
|
|
1260
|
+
if (index < 0) return;
|
|
1261
|
+
const listener = listeners.splice(index, 1)[0];
|
|
1262
|
+
if (listeners.length === 0) this.pushListeners.delete(env.type);
|
|
937
1263
|
if (listener.timer) clearTimeout(listener.timer);
|
|
938
1264
|
listener.resolve(env);
|
|
939
1265
|
}
|
|
940
|
-
clearPushListenerTimer(type) {
|
|
941
|
-
const listener = this.pushListeners.get(type);
|
|
1266
|
+
clearPushListenerTimer(type, swapId) {
|
|
1267
|
+
const listener = this.pushListeners.get(type)?.find((candidate) => candidate.swapId === void 0 || candidate.swapId === swapId);
|
|
942
1268
|
if (!listener?.timer) return;
|
|
943
1269
|
clearTimeout(listener.timer);
|
|
944
1270
|
listener.timer = null;
|
|
945
1271
|
}
|
|
1272
|
+
removePushListener(type, listener) {
|
|
1273
|
+
const listeners = this.pushListeners.get(type);
|
|
1274
|
+
if (!listeners) return;
|
|
1275
|
+
const index = listeners.indexOf(listener);
|
|
1276
|
+
if (index >= 0) listeners.splice(index, 1);
|
|
1277
|
+
if (listeners.length === 0) this.pushListeners.delete(type);
|
|
1278
|
+
}
|
|
1279
|
+
cancelPushWaitersForSwap(swapId) {
|
|
1280
|
+
for (const [type, listeners] of this.pushListeners.entries()) {
|
|
1281
|
+
const remaining = listeners.filter((listener) => {
|
|
1282
|
+
if (listener.swapId !== swapId) return true;
|
|
1283
|
+
if (listener.timer) clearTimeout(listener.timer);
|
|
1284
|
+
return false;
|
|
1285
|
+
});
|
|
1286
|
+
if (remaining.length === 0) {
|
|
1287
|
+
this.pushListeners.delete(type);
|
|
1288
|
+
} else if (remaining.length !== listeners.length) {
|
|
1289
|
+
this.pushListeners.set(type, remaining);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
946
1293
|
setSessionStatus(status) {
|
|
947
1294
|
this.sessionStatus = status;
|
|
948
1295
|
this.emitStateChange();
|
|
949
1296
|
}
|
|
950
|
-
emitStateChange() {
|
|
1297
|
+
emitStateChange(swapId = this.swapId, swapState = this.swapState) {
|
|
951
1298
|
for (const listener of this.stateListeners) {
|
|
952
|
-
listener(
|
|
1299
|
+
listener(swapState, this.sessionStatus, swapId);
|
|
953
1300
|
}
|
|
954
1301
|
}
|
|
955
1302
|
log(msg, direction) {
|
|
@@ -985,28 +1332,42 @@ function toContractRequestResult(prepared) {
|
|
|
985
1332
|
delegatedAtMs: prepared.delegatedAtMs,
|
|
986
1333
|
syncSecret: new Uint8Array(prepared.syncSecret),
|
|
987
1334
|
syncSecretHex: prepared.syncSecretHex,
|
|
1335
|
+
resumeSecret: new Uint8Array(prepared.resumeSecret),
|
|
1336
|
+
resumeSecretHex: prepared.resumeSecretHex,
|
|
988
1337
|
recoveryCodeHex: prepared.recoveryCodeHex,
|
|
989
1338
|
policySnapshot: prepared.policySnapshot,
|
|
990
1339
|
delegatePolicySnapshot: prepared.delegatePolicySnapshot
|
|
991
1340
|
};
|
|
992
1341
|
}
|
|
993
1342
|
async function contractRequestOnSession(session, params) {
|
|
994
|
-
if (activeFunctionalContractRequests.has(session)) {
|
|
995
|
-
throw new CommandError("REJECTED_STATE", ACTIVE_CONTRACT_REQUEST_MESSAGE);
|
|
996
|
-
}
|
|
997
|
-
activeFunctionalContractRequests.add(session);
|
|
998
1343
|
let client = null;
|
|
999
1344
|
try {
|
|
1000
|
-
client = new UserCoordinatorSession(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1345
|
+
client = new UserCoordinatorSession(
|
|
1346
|
+
{
|
|
1347
|
+
coordinator: getSessionState(session).pool,
|
|
1348
|
+
createSession: async () => session
|
|
1349
|
+
},
|
|
1350
|
+
false
|
|
1351
|
+
);
|
|
1004
1352
|
return await client.contractRequest(params);
|
|
1005
1353
|
} finally {
|
|
1006
|
-
activeFunctionalContractRequests.delete(session);
|
|
1007
1354
|
client?.releaseWithoutClosing();
|
|
1008
1355
|
}
|
|
1009
1356
|
}
|
|
1357
|
+
async function resumePreDelegationOnSession(session, params) {
|
|
1358
|
+
const client = new UserCoordinatorSession(
|
|
1359
|
+
{
|
|
1360
|
+
coordinator: getSessionState(session).pool,
|
|
1361
|
+
createSession: async () => session
|
|
1362
|
+
},
|
|
1363
|
+
false
|
|
1364
|
+
);
|
|
1365
|
+
try {
|
|
1366
|
+
return await client.resumePreDelegation(params);
|
|
1367
|
+
} finally {
|
|
1368
|
+
client.releaseWithoutClosing();
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1010
1371
|
async function requestRefundOnSession(session, params) {
|
|
1011
1372
|
const client = new UserCoordinatorSession(
|
|
1012
1373
|
{
|
|
@@ -1016,7 +1377,7 @@ async function requestRefundOnSession(session, params) {
|
|
|
1016
1377
|
false
|
|
1017
1378
|
);
|
|
1018
1379
|
try {
|
|
1019
|
-
await client.requestRefundIntent(params);
|
|
1380
|
+
const result = await client.requestRefundIntent(params);
|
|
1020
1381
|
const requestId = client.currentSwapId;
|
|
1021
1382
|
if (!requestId) {
|
|
1022
1383
|
throw new CommandError(
|
|
@@ -1024,7 +1385,19 @@ async function requestRefundOnSession(session, params) {
|
|
|
1024
1385
|
"refund response did not include the resolved contract id"
|
|
1025
1386
|
);
|
|
1026
1387
|
}
|
|
1027
|
-
|
|
1388
|
+
if (result.kind === "accepted") {
|
|
1389
|
+
return {
|
|
1390
|
+
accepted: true,
|
|
1391
|
+
requestId,
|
|
1392
|
+
amountLamports: result.accepted.amount_lamports,
|
|
1393
|
+
acceptedAtMs: result.accepted.accepted_at_ms
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return {
|
|
1397
|
+
accepted: true,
|
|
1398
|
+
requestId,
|
|
1399
|
+
amountLamports: result.refund.amount_lamports
|
|
1400
|
+
};
|
|
1028
1401
|
} finally {
|
|
1029
1402
|
client.releaseWithoutClosing();
|
|
1030
1403
|
}
|
|
@@ -1043,9 +1416,20 @@ async function requestUserSync(session, swapId, syncSecret, timeoutMs) {
|
|
|
1043
1416
|
actor: "user",
|
|
1044
1417
|
sync_secret: new Uint8Array(syncSecret)
|
|
1045
1418
|
},
|
|
1046
|
-
{ swapId, timeoutMs }
|
|
1419
|
+
{ swapId, timeoutMs, rejectMatchingOutOfBandErrors: true }
|
|
1047
1420
|
);
|
|
1048
|
-
|
|
1421
|
+
const payload = (await loadProtocol()).asSyncUserResponsePayload(env);
|
|
1422
|
+
assertSyncResponseContractId(env, payload, swapId);
|
|
1423
|
+
return {
|
|
1424
|
+
...payload,
|
|
1425
|
+
coordinatorTimestampMs: env.timestamp_ms
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
function assertSyncResponseContractId(envelope, payload, requestedSwapId) {
|
|
1429
|
+
const actorSyncContractId = payload.actor_sync?.snapshot.contract_id;
|
|
1430
|
+
if (envelope.swap_id !== requestedSwapId || actorSyncContractId !== void 0 && actorSyncContractId !== requestedSwapId) {
|
|
1431
|
+
throw new CommandError("REJECTED_STATE", SYNC_CONTRACT_ID_MISMATCH_MESSAGE);
|
|
1432
|
+
}
|
|
1049
1433
|
}
|
|
1050
1434
|
async function ensureFrostWasm() {
|
|
1051
1435
|
await (await loadFrostRuntime()).ensureFrostWasm();
|
|
@@ -1150,10 +1534,10 @@ async function contractRequest(session, args) {
|
|
|
1150
1534
|
assertCurrentUserContractPolicy(payoutPolicy);
|
|
1151
1535
|
assertAttested(session);
|
|
1152
1536
|
const destination = payoutPolicy.destinations[0];
|
|
1153
|
-
const
|
|
1537
|
+
const wirePayoutSpec = wirePayoutSpecFromPolicy(payoutPolicy, destination.address);
|
|
1154
1538
|
return contractRequestOnSession(session, {
|
|
1155
1539
|
amountLamports: args.amountLamports,
|
|
1156
|
-
payoutSpec,
|
|
1540
|
+
payoutSpec: wirePayoutSpec,
|
|
1157
1541
|
...args.syncSecret !== void 0 && { syncSecret: args.syncSecret },
|
|
1158
1542
|
...args.recoveryCommitment !== void 0 && {
|
|
1159
1543
|
recoveryCommitment: args.recoveryCommitment
|
|
@@ -1161,11 +1545,35 @@ async function contractRequest(session, args) {
|
|
|
1161
1545
|
...args.onSyncSecretReady !== void 0 && {
|
|
1162
1546
|
onSyncSecretReady: args.onSyncSecretReady
|
|
1163
1547
|
},
|
|
1548
|
+
...args.resumeSecret !== void 0 && { resumeSecret: args.resumeSecret },
|
|
1549
|
+
...args.onResumeSecretReady !== void 0 && {
|
|
1550
|
+
onResumeSecretReady: args.onResumeSecretReady
|
|
1551
|
+
},
|
|
1552
|
+
...args.onSwapAllocated !== void 0 && {
|
|
1553
|
+
onSwapAllocated: args.onSwapAllocated
|
|
1554
|
+
},
|
|
1164
1555
|
...args.onRecoveryCodeReady !== void 0 && {
|
|
1165
1556
|
onRecoveryCodeReady: args.onRecoveryCodeReady
|
|
1557
|
+
},
|
|
1558
|
+
...args.onDelegateShareReady !== void 0 && {
|
|
1559
|
+
onDelegateShareReady: args.onDelegateShareReady
|
|
1166
1560
|
}
|
|
1167
1561
|
});
|
|
1168
1562
|
}
|
|
1563
|
+
async function resumePreDelegation(session, args) {
|
|
1564
|
+
assertAttested(session);
|
|
1565
|
+
return resumePreDelegationOnSession(session, {
|
|
1566
|
+
swapId: args.swapId,
|
|
1567
|
+
resumeSecret: new Uint8Array(args.resumeSecret),
|
|
1568
|
+
...args.syncSecret !== void 0 && { syncSecret: new Uint8Array(args.syncSecret) },
|
|
1569
|
+
recoveryCommitment: new Uint8Array(args.recoveryCommitment),
|
|
1570
|
+
policySnapshot: args.policySnapshot,
|
|
1571
|
+
...args.onDelegateShareReady !== void 0 && {
|
|
1572
|
+
onDelegateShareReady: args.onDelegateShareReady
|
|
1573
|
+
},
|
|
1574
|
+
...args.delegateShare !== void 0 && { delegateShare: new Uint8Array(args.delegateShare) }
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1169
1577
|
async function sync(session, contractId, syncSecret, timeoutMs) {
|
|
1170
1578
|
assertAttested(session);
|
|
1171
1579
|
return requestUserSync(session, contractId, syncSecret, timeoutMs);
|
|
@@ -1188,7 +1596,7 @@ function assertCurrentUserContractPolicy(policy) {
|
|
|
1188
1596
|
}
|
|
1189
1597
|
}
|
|
1190
1598
|
}
|
|
1191
|
-
function
|
|
1599
|
+
function wirePayoutSpecFromPolicy(policy, destinationAddress) {
|
|
1192
1600
|
const totalDeadlineMs = policy.totalDeadlineMs;
|
|
1193
1601
|
if (totalDeadlineMs === void 0 || totalDeadlineMs === INSTANT_PAYOUT_WINDOW_MS) {
|
|
1194
1602
|
return {
|
|
@@ -1207,19 +1615,12 @@ async function requestRefund(session, args) {
|
|
|
1207
1615
|
assertAttested(session);
|
|
1208
1616
|
assertRecoveryCode(args.recoveryCode);
|
|
1209
1617
|
return requestRefundOnSession(session, {
|
|
1618
|
+
...args.swapId !== void 0 && { swapId: args.swapId },
|
|
1619
|
+
...args.syncSecret !== void 0 && { syncSecret: new Uint8Array(args.syncSecret) },
|
|
1210
1620
|
recoveryCode: new Uint8Array(args.recoveryCode)
|
|
1211
1621
|
});
|
|
1212
1622
|
}
|
|
1213
|
-
async function getDepositAddress(_session, _requestId, _syncSecret) {
|
|
1214
|
-
throw new NotImplementedError("user.getDepositAddress");
|
|
1215
|
-
}
|
|
1216
|
-
function subscribe(_session, _requestId, _syncSecret, _handler) {
|
|
1217
|
-
throw new NotImplementedError("user.subscribe");
|
|
1218
|
-
}
|
|
1219
|
-
async function replayFromStorage(_session, _storage) {
|
|
1220
|
-
throw new NotImplementedError("user.replayFromStorage");
|
|
1221
|
-
}
|
|
1222
1623
|
|
|
1223
|
-
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,
|
|
1624
|
+
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 };
|
|
1224
1625
|
//# sourceMappingURL=user.js.map
|
|
1225
1626
|
//# sourceMappingURL=user.js.map
|