@unicitylabs/sphere-sdk 0.14.11-dev.6 → 0.14.11-dev.8
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/dist/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +201 -46
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +201 -46
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/wallet-api-v2/index.cjs +73 -3
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +25 -1
- package/dist/impl/wallet-api-v2/index.d.ts +25 -1
- package/dist/impl/wallet-api-v2/index.js +73 -3
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +201 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -46
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +128 -43
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +14 -0
- package/dist/modules/payments-v2/index.d.ts +14 -0
- package/dist/modules/payments-v2/index.js +128 -43
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -340,8 +340,22 @@ interface DeliveryPort {
|
|
|
340
340
|
*/
|
|
341
341
|
incomingEpoch(): string | null;
|
|
342
342
|
ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
|
|
343
|
+
/** Settle many acks at once (#757). Contract pinned by the delivery-port contract suite. */
|
|
344
|
+
ackBatch?(acks: readonly AckRequest[]): Promise<readonly AckOutcome[]>;
|
|
343
345
|
onWake?(cb: () => void): () => void;
|
|
344
346
|
}
|
|
347
|
+
interface AckRequest {
|
|
348
|
+
readonly deliveryId: string;
|
|
349
|
+
readonly disposition: 'claimed' | 'rejected';
|
|
350
|
+
readonly reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other';
|
|
351
|
+
}
|
|
352
|
+
type AckOutcome = {
|
|
353
|
+
readonly deliveryId: string;
|
|
354
|
+
readonly status: 'settled';
|
|
355
|
+
} | {
|
|
356
|
+
readonly deliveryId: string;
|
|
357
|
+
readonly status: 'conflict';
|
|
358
|
+
};
|
|
345
359
|
|
|
346
360
|
interface IntentPayload {
|
|
347
361
|
v: 2;
|
|
@@ -340,8 +340,22 @@ interface DeliveryPort {
|
|
|
340
340
|
*/
|
|
341
341
|
incomingEpoch(): string | null;
|
|
342
342
|
ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
|
|
343
|
+
/** Settle many acks at once (#757). Contract pinned by the delivery-port contract suite. */
|
|
344
|
+
ackBatch?(acks: readonly AckRequest[]): Promise<readonly AckOutcome[]>;
|
|
343
345
|
onWake?(cb: () => void): () => void;
|
|
344
346
|
}
|
|
347
|
+
interface AckRequest {
|
|
348
|
+
readonly deliveryId: string;
|
|
349
|
+
readonly disposition: 'claimed' | 'rejected';
|
|
350
|
+
readonly reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other';
|
|
351
|
+
}
|
|
352
|
+
type AckOutcome = {
|
|
353
|
+
readonly deliveryId: string;
|
|
354
|
+
readonly status: 'settled';
|
|
355
|
+
} | {
|
|
356
|
+
readonly deliveryId: string;
|
|
357
|
+
readonly status: 'conflict';
|
|
358
|
+
};
|
|
345
359
|
|
|
346
360
|
interface IntentPayload {
|
|
347
361
|
v: 2;
|
|
@@ -536,6 +536,33 @@ var SerialChain = class {
|
|
|
536
536
|
return run;
|
|
537
537
|
}
|
|
538
538
|
};
|
|
539
|
+
function coalesced(run, observe) {
|
|
540
|
+
let inFlight = false;
|
|
541
|
+
let queued = false;
|
|
542
|
+
const start = () => {
|
|
543
|
+
inFlight = true;
|
|
544
|
+
let running;
|
|
545
|
+
try {
|
|
546
|
+
running = run();
|
|
547
|
+
} catch {
|
|
548
|
+
running = Promise.resolve();
|
|
549
|
+
}
|
|
550
|
+
observe?.(running);
|
|
551
|
+
void running.catch(() => void 0).finally(() => {
|
|
552
|
+
inFlight = false;
|
|
553
|
+
if (!queued) return;
|
|
554
|
+
queued = false;
|
|
555
|
+
start();
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
return () => {
|
|
559
|
+
if (inFlight) {
|
|
560
|
+
queued = true;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
start();
|
|
564
|
+
};
|
|
565
|
+
}
|
|
539
566
|
|
|
540
567
|
// modules/payments-v2/machine/journal.ts
|
|
541
568
|
var MAX_DELIVERY_ATTEMPTS = 6;
|
|
@@ -2841,6 +2868,16 @@ var InventoryView = class {
|
|
|
2841
2868
|
this.inFlight.add(tokenId);
|
|
2842
2869
|
this.deps.emit("inventory:updated");
|
|
2843
2870
|
}
|
|
2871
|
+
/** Reserve a whole plan as ONE change — per token emitted 54 events per send. */
|
|
2872
|
+
markInFlightMany(tokenIds) {
|
|
2873
|
+
let changed = false;
|
|
2874
|
+
for (const tokenId of tokenIds) {
|
|
2875
|
+
if (this.inFlight.has(tokenId)) continue;
|
|
2876
|
+
this.inFlight.add(tokenId);
|
|
2877
|
+
changed = true;
|
|
2878
|
+
}
|
|
2879
|
+
if (changed) this.deps.emit("inventory:updated");
|
|
2880
|
+
}
|
|
2844
2881
|
release(tokenId) {
|
|
2845
2882
|
if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
|
|
2846
2883
|
}
|
|
@@ -3157,6 +3194,11 @@ var logger = {
|
|
|
3157
3194
|
}
|
|
3158
3195
|
};
|
|
3159
3196
|
|
|
3197
|
+
// modules/payments-v2/ports.ts
|
|
3198
|
+
function isRetryableAckError(err) {
|
|
3199
|
+
return err !== null && typeof err === "object" && err.retryable === true;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3160
3202
|
// modules/payments-v2/receive/Receive.ts
|
|
3161
3203
|
var ACK_BATCH_SIZE = 200;
|
|
3162
3204
|
var REFRESH_INTERVAL_MS = 2500;
|
|
@@ -3213,6 +3255,17 @@ var Receive = class {
|
|
|
3213
3255
|
await flushAcks(deps, pending, epochOf);
|
|
3214
3256
|
deps.refreshView();
|
|
3215
3257
|
}
|
|
3258
|
+
/** One listing pass: process each entry, flushing and refreshing as it goes. */
|
|
3259
|
+
async drainPages(ctx, basis) {
|
|
3260
|
+
const { deps, engine, pending, stored, pageEpoch } = ctx;
|
|
3261
|
+
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
3262
|
+
const storedBefore = stored.length;
|
|
3263
|
+
await this.processEntry(deps, engine, entry, pending, stored);
|
|
3264
|
+
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
3265
|
+
if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
|
|
3266
|
+
}
|
|
3267
|
+
await flushAcks(deps, pending, pageEpoch);
|
|
3268
|
+
}
|
|
3216
3269
|
async doDrain() {
|
|
3217
3270
|
const deps = this.deps;
|
|
3218
3271
|
this.lastRefreshAt = deps.now?.() ?? Date.now();
|
|
@@ -3224,13 +3277,7 @@ var Receive = class {
|
|
|
3224
3277
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
3225
3278
|
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
3226
3279
|
for (let pass = 0; pass < 2; pass++) {
|
|
3227
|
-
|
|
3228
|
-
const storedBefore = stored.length;
|
|
3229
|
-
await this.processEntry(deps, engine, entry, pending, stored);
|
|
3230
|
-
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
3231
|
-
if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
|
|
3232
|
-
}
|
|
3233
|
-
await flushAcks(deps, pending, pageEpoch);
|
|
3280
|
+
await this.drainPages({ deps, engine, pending, stored, pageEpoch }, basis);
|
|
3234
3281
|
const served = deps.delivery.incomingEpoch();
|
|
3235
3282
|
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
3236
3283
|
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
@@ -3238,10 +3285,12 @@ var Receive = class {
|
|
|
3238
3285
|
}
|
|
3239
3286
|
} catch (err) {
|
|
3240
3287
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
3288
|
+
if (isRetryableAckError(err)) return stored;
|
|
3241
3289
|
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
3242
3290
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
3243
3291
|
});
|
|
3244
3292
|
}
|
|
3293
|
+
if (stored.length > 0) deps.refreshView?.();
|
|
3245
3294
|
return stored;
|
|
3246
3295
|
}
|
|
3247
3296
|
async processEntry(deps, engine, entry, pending, stored) {
|
|
@@ -3312,14 +3361,18 @@ async function announce(deps, entry, record) {
|
|
|
3312
3361
|
};
|
|
3313
3362
|
}
|
|
3314
3363
|
async function flushAcks(deps, pending, epochOf) {
|
|
3364
|
+
if (pending.length === 0) return;
|
|
3315
3365
|
let lastAcked = null;
|
|
3316
3366
|
try {
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
lastAcked =
|
|
3321
|
-
|
|
3367
|
+
const settled = await settleAcks(deps, pending);
|
|
3368
|
+
let i = 0;
|
|
3369
|
+
while (i < pending.length && settled.has(pending[i].deliveryId)) {
|
|
3370
|
+
lastAcked = pending[i].cursor;
|
|
3371
|
+
i += 1;
|
|
3322
3372
|
}
|
|
3373
|
+
const stuck = pending.filter((p) => !settled.has(p.deliveryId));
|
|
3374
|
+
pending.length = 0;
|
|
3375
|
+
pending.push(...stuck);
|
|
3323
3376
|
} finally {
|
|
3324
3377
|
if (lastAcked !== null) {
|
|
3325
3378
|
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
@@ -3327,14 +3380,65 @@ async function flushAcks(deps, pending, epochOf) {
|
|
|
3327
3380
|
}
|
|
3328
3381
|
}
|
|
3329
3382
|
}
|
|
3383
|
+
async function settleAcks(deps, pending) {
|
|
3384
|
+
const batch = deps.delivery.ackBatch?.bind(deps.delivery);
|
|
3385
|
+
if (batch === void 0) return settleOneByOne(deps, pending);
|
|
3386
|
+
let outcomes;
|
|
3387
|
+
try {
|
|
3388
|
+
outcomes = await batch(pending.map(toAckRequest));
|
|
3389
|
+
} catch (err) {
|
|
3390
|
+
if (isRetryableAckError(err)) throw err;
|
|
3391
|
+
logger.warn("PaymentsV2", "batched mailbox ack failed \u2014 settling one at a time:", err);
|
|
3392
|
+
return settleOneByOne(deps, pending);
|
|
3393
|
+
}
|
|
3394
|
+
const settled = new Set(outcomes.filter((o) => o.status === "settled").map((o) => o.deliveryId));
|
|
3395
|
+
const conflicts = outcomes.filter((o) => o.status === "conflict").map((o) => o.deliveryId);
|
|
3396
|
+
if (conflicts.length > 0) await resolveConflicts(deps, pending, conflicts, settled);
|
|
3397
|
+
return settled;
|
|
3398
|
+
}
|
|
3399
|
+
async function resolveConflicts(deps, pending, conflicts, settled) {
|
|
3400
|
+
for (const deliveryId of conflicts) {
|
|
3401
|
+
const entry = pending.find((p) => p.deliveryId === deliveryId);
|
|
3402
|
+
try {
|
|
3403
|
+
await rejectStaleClaim(deps, deliveryId, entry?.transferId);
|
|
3404
|
+
} catch (err) {
|
|
3405
|
+
logger.warn("PaymentsV2", `stale-reject failed for ${deliveryId} \u2014 cursor holds here:`, err);
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
settled.add(deliveryId);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
async function rejectStaleClaim(deps, deliveryId, transferId) {
|
|
3412
|
+
logger.warn("PaymentsV2", `mailbox claim CONFLICT for ${deliveryId} \u2014 rejected('other') as stale`);
|
|
3413
|
+
await deps.delivery.ack(deliveryId, "rejected", "other");
|
|
3414
|
+
deps.attention(transferId ?? "", ATTENTION_CLAIM_CONFLICT, deliveryId);
|
|
3415
|
+
}
|
|
3416
|
+
async function settleOneByOne(deps, pending) {
|
|
3417
|
+
const settled = /* @__PURE__ */ new Set();
|
|
3418
|
+
for (const ack of pending) {
|
|
3419
|
+
try {
|
|
3420
|
+
await ackOne(deps, ack);
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
logger.warn("PaymentsV2", `mailbox ack failed for ${ack.deliveryId} \u2014 cursor holds here:`, err);
|
|
3423
|
+
break;
|
|
3424
|
+
}
|
|
3425
|
+
settled.add(ack.deliveryId);
|
|
3426
|
+
}
|
|
3427
|
+
return settled;
|
|
3428
|
+
}
|
|
3429
|
+
function toAckRequest(ack) {
|
|
3430
|
+
return {
|
|
3431
|
+
deliveryId: ack.deliveryId,
|
|
3432
|
+
disposition: ack.disposition,
|
|
3433
|
+
...ack.reason !== void 0 ? { reason: ack.reason } : {}
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3330
3436
|
async function ackOne(deps, ack) {
|
|
3331
3437
|
try {
|
|
3332
3438
|
await deps.delivery.ack(ack.deliveryId, ack.disposition, ack.reason);
|
|
3333
3439
|
} catch (err) {
|
|
3334
3440
|
if (ack.disposition !== "claimed" || !isClaimConflict(err)) throw err;
|
|
3335
|
-
|
|
3336
|
-
await deps.delivery.ack(ack.deliveryId, "rejected", "other");
|
|
3337
|
-
deps.attention(ack.transferId ?? "", ATTENTION_CLAIM_CONFLICT, ack.deliveryId);
|
|
3441
|
+
await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
|
|
3338
3442
|
}
|
|
3339
3443
|
}
|
|
3340
3444
|
function claimAck(entry) {
|
|
@@ -4088,26 +4192,6 @@ function parseAmount(raw) {
|
|
|
4088
4192
|
function supportsDeterministicMint(engine) {
|
|
4089
4193
|
return engine.deterministicMint === true;
|
|
4090
4194
|
}
|
|
4091
|
-
function makeCoalescedRefresh(refresh) {
|
|
4092
|
-
let inFlight = false;
|
|
4093
|
-
let queued = false;
|
|
4094
|
-
const run = () => {
|
|
4095
|
-
inFlight = true;
|
|
4096
|
-
void refresh().catch(() => void 0).finally(() => {
|
|
4097
|
-
inFlight = false;
|
|
4098
|
-
if (!queued) return;
|
|
4099
|
-
queued = false;
|
|
4100
|
-
run();
|
|
4101
|
-
});
|
|
4102
|
-
};
|
|
4103
|
-
return () => {
|
|
4104
|
-
if (inFlight) {
|
|
4105
|
-
queued = true;
|
|
4106
|
-
return;
|
|
4107
|
-
}
|
|
4108
|
-
run();
|
|
4109
|
-
};
|
|
4110
|
-
}
|
|
4111
4195
|
function composeFacadeParts(deps, hooks) {
|
|
4112
4196
|
const ownPubkeyBytes = hexToBytes(deps.ownPubkey);
|
|
4113
4197
|
const ledger = new ReservationLedger();
|
|
@@ -4137,9 +4221,11 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4137
4221
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
4138
4222
|
const machineStores = createMachineStores(deps.kv);
|
|
4139
4223
|
const heldStates = /* @__PURE__ */ new Map();
|
|
4224
|
+
const refreshView = coalesced(() => view.delta(), hooks.track);
|
|
4140
4225
|
return {
|
|
4141
4226
|
ownPubkeyBytes,
|
|
4142
4227
|
view,
|
|
4228
|
+
refreshView,
|
|
4143
4229
|
ledger,
|
|
4144
4230
|
pins: buildPins(deps, hooks, { ledger, view, machineStores, machineDeps }),
|
|
4145
4231
|
queue,
|
|
@@ -4148,7 +4234,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4148
4234
|
machineDeps,
|
|
4149
4235
|
machine: new TransferMachine(machineDeps),
|
|
4150
4236
|
heldStates,
|
|
4151
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates,
|
|
4237
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
|
|
4152
4238
|
requests: buildRequests(deps, hooks),
|
|
4153
4239
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
4154
4240
|
};
|
|
@@ -4215,7 +4301,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4215
4301
|
}
|
|
4216
4302
|
};
|
|
4217
4303
|
}
|
|
4218
|
-
function buildReceive(deps, hooks, historyStore, heldStates,
|
|
4304
|
+
function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
4219
4305
|
return new Receive({
|
|
4220
4306
|
delivery: deps.deliveryPort,
|
|
4221
4307
|
engine: () => hooks.engine(),
|
|
@@ -4229,8 +4315,7 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
|
4229
4315
|
registry: deps.registry,
|
|
4230
4316
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
4231
4317
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
4232
|
-
|
|
4233
|
-
refreshView: makeCoalescedRefresh(() => view.delta()),
|
|
4318
|
+
refreshView,
|
|
4234
4319
|
attention: (transferId, code, detail) => {
|
|
4235
4320
|
deps.emit(
|
|
4236
4321
|
"transfer:attention",
|
|
@@ -4279,10 +4364,11 @@ var PaymentsFacade = class {
|
|
|
4279
4364
|
const parts = composeFacadeParts(deps, {
|
|
4280
4365
|
engine: () => this.engine(),
|
|
4281
4366
|
send: (request) => this.send(request),
|
|
4282
|
-
isActiveOp: (id) => this.activeMoneyOps.has(id)
|
|
4367
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
4368
|
+
track: (op) => this.trackTail(op)
|
|
4283
4369
|
});
|
|
4284
4370
|
this.view = parts.view;
|
|
4285
|
-
this.wakeRefresh =
|
|
4371
|
+
this.wakeRefresh = parts.refreshView;
|
|
4286
4372
|
this.ledger = parts.ledger;
|
|
4287
4373
|
this.queue = parts.queue;
|
|
4288
4374
|
this.machine = parts.machine;
|
|
@@ -4419,7 +4505,6 @@ var PaymentsFacade = class {
|
|
|
4419
4505
|
}
|
|
4420
4506
|
async receive() {
|
|
4421
4507
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
4422
|
-
if (transfers.length > 0) this.trackTail(this.view.delta());
|
|
4423
4508
|
return { transfers };
|
|
4424
4509
|
}
|
|
4425
4510
|
mint(coinId, amount) {
|
|
@@ -4615,7 +4700,7 @@ var PaymentsFacade = class {
|
|
|
4615
4700
|
...spend.plan.direct,
|
|
4616
4701
|
...spend.plan.split !== void 0 ? [spend.plan.split.tokenId] : []
|
|
4617
4702
|
];
|
|
4618
|
-
|
|
4703
|
+
this.view.markInFlightMany(sourceIds);
|
|
4619
4704
|
if (spend.plan.split !== void 0) {
|
|
4620
4705
|
this.queue.declareExpectedChange(transferId, coinId, spend.plan.split.remainderAmount);
|
|
4621
4706
|
}
|