@unicitylabs/sphere-sdk 0.14.11-dev.6 → 0.14.11-dev.7
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 +178 -37
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +178 -37
- 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 +72 -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 +72 -3
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +178 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +178 -37
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +106 -34
- 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 +106 -34
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11361,6 +11361,26 @@ var SerialChain = class {
|
|
|
11361
11361
|
return run;
|
|
11362
11362
|
}
|
|
11363
11363
|
};
|
|
11364
|
+
function coalesced(run) {
|
|
11365
|
+
let inFlight = false;
|
|
11366
|
+
let queued = false;
|
|
11367
|
+
const start = () => {
|
|
11368
|
+
inFlight = true;
|
|
11369
|
+
void run().catch(() => void 0).finally(() => {
|
|
11370
|
+
inFlight = false;
|
|
11371
|
+
if (!queued) return;
|
|
11372
|
+
queued = false;
|
|
11373
|
+
start();
|
|
11374
|
+
});
|
|
11375
|
+
};
|
|
11376
|
+
return () => {
|
|
11377
|
+
if (inFlight) {
|
|
11378
|
+
queued = true;
|
|
11379
|
+
return;
|
|
11380
|
+
}
|
|
11381
|
+
start();
|
|
11382
|
+
};
|
|
11383
|
+
}
|
|
11364
11384
|
|
|
11365
11385
|
// modules/payments-v2/stores.ts
|
|
11366
11386
|
function createScopedKV(storage, network, chainPubkey) {
|
|
@@ -12808,6 +12828,16 @@ var InventoryView = class {
|
|
|
12808
12828
|
this.inFlight.add(tokenId);
|
|
12809
12829
|
this.deps.emit("inventory:updated");
|
|
12810
12830
|
}
|
|
12831
|
+
/** Reserve a whole plan as ONE change — per token emitted 54 events per send. */
|
|
12832
|
+
markInFlightMany(tokenIds) {
|
|
12833
|
+
let changed = false;
|
|
12834
|
+
for (const tokenId of tokenIds) {
|
|
12835
|
+
if (this.inFlight.has(tokenId)) continue;
|
|
12836
|
+
this.inFlight.add(tokenId);
|
|
12837
|
+
changed = true;
|
|
12838
|
+
}
|
|
12839
|
+
if (changed) this.deps.emit("inventory:updated");
|
|
12840
|
+
}
|
|
12811
12841
|
release(tokenId) {
|
|
12812
12842
|
if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
|
|
12813
12843
|
}
|
|
@@ -13032,6 +13062,11 @@ var InventoryView = class {
|
|
|
13032
13062
|
}
|
|
13033
13063
|
};
|
|
13034
13064
|
|
|
13065
|
+
// modules/payments-v2/ports.ts
|
|
13066
|
+
function isRetryableAckError(err) {
|
|
13067
|
+
return err !== null && typeof err === "object" && err.retryable === true;
|
|
13068
|
+
}
|
|
13069
|
+
|
|
13035
13070
|
// modules/payments-v2/receive/Receive.ts
|
|
13036
13071
|
var ACK_BATCH_SIZE = 200;
|
|
13037
13072
|
var REFRESH_INTERVAL_MS = 2500;
|
|
@@ -13113,6 +13148,7 @@ var Receive = class {
|
|
|
13113
13148
|
}
|
|
13114
13149
|
} catch (err) {
|
|
13115
13150
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
13151
|
+
if (isRetryableAckError(err)) return stored;
|
|
13116
13152
|
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
13117
13153
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
13118
13154
|
});
|
|
@@ -13187,14 +13223,18 @@ async function announce(deps, entry, record) {
|
|
|
13187
13223
|
};
|
|
13188
13224
|
}
|
|
13189
13225
|
async function flushAcks(deps, pending, epochOf) {
|
|
13226
|
+
if (pending.length === 0) return;
|
|
13190
13227
|
let lastAcked = null;
|
|
13191
13228
|
try {
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
lastAcked =
|
|
13196
|
-
|
|
13229
|
+
const settled = await settleAcks(deps, pending);
|
|
13230
|
+
let i = 0;
|
|
13231
|
+
while (i < pending.length && settled.has(pending[i].deliveryId)) {
|
|
13232
|
+
lastAcked = pending[i].cursor;
|
|
13233
|
+
i += 1;
|
|
13197
13234
|
}
|
|
13235
|
+
const stuck = pending.filter((p) => !settled.has(p.deliveryId));
|
|
13236
|
+
pending.length = 0;
|
|
13237
|
+
pending.push(...stuck);
|
|
13198
13238
|
} finally {
|
|
13199
13239
|
if (lastAcked !== null) {
|
|
13200
13240
|
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
@@ -13202,14 +13242,65 @@ async function flushAcks(deps, pending, epochOf) {
|
|
|
13202
13242
|
}
|
|
13203
13243
|
}
|
|
13204
13244
|
}
|
|
13245
|
+
async function settleAcks(deps, pending) {
|
|
13246
|
+
const batch = deps.delivery.ackBatch?.bind(deps.delivery);
|
|
13247
|
+
if (batch === void 0) return settleOneByOne(deps, pending);
|
|
13248
|
+
let outcomes;
|
|
13249
|
+
try {
|
|
13250
|
+
outcomes = await batch(pending.map(toAckRequest));
|
|
13251
|
+
} catch (err) {
|
|
13252
|
+
if (isRetryableAckError(err)) throw err;
|
|
13253
|
+
logger.warn("PaymentsV2", "batched mailbox ack failed \u2014 settling one at a time:", err);
|
|
13254
|
+
return settleOneByOne(deps, pending);
|
|
13255
|
+
}
|
|
13256
|
+
const settled = new Set(outcomes.filter((o) => o.status === "settled").map((o) => o.deliveryId));
|
|
13257
|
+
const conflicts = outcomes.filter((o) => o.status === "conflict").map((o) => o.deliveryId);
|
|
13258
|
+
if (conflicts.length > 0) await resolveConflicts(deps, pending, conflicts, settled);
|
|
13259
|
+
return settled;
|
|
13260
|
+
}
|
|
13261
|
+
async function resolveConflicts(deps, pending, conflicts, settled) {
|
|
13262
|
+
for (const deliveryId of conflicts) {
|
|
13263
|
+
const entry = pending.find((p) => p.deliveryId === deliveryId);
|
|
13264
|
+
try {
|
|
13265
|
+
await rejectStaleClaim(deps, deliveryId, entry?.transferId);
|
|
13266
|
+
} catch (err) {
|
|
13267
|
+
logger.warn("PaymentsV2", `stale-reject failed for ${deliveryId} \u2014 cursor holds here:`, err);
|
|
13268
|
+
continue;
|
|
13269
|
+
}
|
|
13270
|
+
settled.add(deliveryId);
|
|
13271
|
+
}
|
|
13272
|
+
}
|
|
13273
|
+
async function rejectStaleClaim(deps, deliveryId, transferId) {
|
|
13274
|
+
logger.warn("PaymentsV2", `mailbox claim CONFLICT for ${deliveryId} \u2014 rejected('other') as stale`);
|
|
13275
|
+
await deps.delivery.ack(deliveryId, "rejected", "other");
|
|
13276
|
+
deps.attention(transferId ?? "", ATTENTION_CLAIM_CONFLICT, deliveryId);
|
|
13277
|
+
}
|
|
13278
|
+
async function settleOneByOne(deps, pending) {
|
|
13279
|
+
const settled = /* @__PURE__ */ new Set();
|
|
13280
|
+
for (const ack of pending) {
|
|
13281
|
+
try {
|
|
13282
|
+
await ackOne(deps, ack);
|
|
13283
|
+
} catch (err) {
|
|
13284
|
+
logger.warn("PaymentsV2", `mailbox ack failed for ${ack.deliveryId} \u2014 cursor holds here:`, err);
|
|
13285
|
+
break;
|
|
13286
|
+
}
|
|
13287
|
+
settled.add(ack.deliveryId);
|
|
13288
|
+
}
|
|
13289
|
+
return settled;
|
|
13290
|
+
}
|
|
13291
|
+
function toAckRequest(ack) {
|
|
13292
|
+
return {
|
|
13293
|
+
deliveryId: ack.deliveryId,
|
|
13294
|
+
disposition: ack.disposition,
|
|
13295
|
+
...ack.reason !== void 0 ? { reason: ack.reason } : {}
|
|
13296
|
+
};
|
|
13297
|
+
}
|
|
13205
13298
|
async function ackOne(deps, ack) {
|
|
13206
13299
|
try {
|
|
13207
13300
|
await deps.delivery.ack(ack.deliveryId, ack.disposition, ack.reason);
|
|
13208
13301
|
} catch (err) {
|
|
13209
13302
|
if (ack.disposition !== "claimed" || !isClaimConflict(err)) throw err;
|
|
13210
|
-
|
|
13211
|
-
await deps.delivery.ack(ack.deliveryId, "rejected", "other");
|
|
13212
|
-
deps.attention(ack.transferId ?? "", ATTENTION_CLAIM_CONFLICT, ack.deliveryId);
|
|
13303
|
+
await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
|
|
13213
13304
|
}
|
|
13214
13305
|
}
|
|
13215
13306
|
function claimAck(entry) {
|
|
@@ -13963,26 +14054,6 @@ function parseAmount(raw) {
|
|
|
13963
14054
|
function supportsDeterministicMint(engine) {
|
|
13964
14055
|
return engine.deterministicMint === true;
|
|
13965
14056
|
}
|
|
13966
|
-
function makeCoalescedRefresh(refresh) {
|
|
13967
|
-
let inFlight = false;
|
|
13968
|
-
let queued = false;
|
|
13969
|
-
const run = () => {
|
|
13970
|
-
inFlight = true;
|
|
13971
|
-
void refresh().catch(() => void 0).finally(() => {
|
|
13972
|
-
inFlight = false;
|
|
13973
|
-
if (!queued) return;
|
|
13974
|
-
queued = false;
|
|
13975
|
-
run();
|
|
13976
|
-
});
|
|
13977
|
-
};
|
|
13978
|
-
return () => {
|
|
13979
|
-
if (inFlight) {
|
|
13980
|
-
queued = true;
|
|
13981
|
-
return;
|
|
13982
|
-
}
|
|
13983
|
-
run();
|
|
13984
|
-
};
|
|
13985
|
-
}
|
|
13986
14057
|
function composeFacadeParts(deps, hooks) {
|
|
13987
14058
|
const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
|
|
13988
14059
|
const ledger = new ReservationLedger();
|
|
@@ -14012,9 +14083,11 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14012
14083
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
14013
14084
|
const machineStores = createMachineStores(deps.kv);
|
|
14014
14085
|
const heldStates = /* @__PURE__ */ new Map();
|
|
14086
|
+
const refreshView = coalesced(() => view.delta());
|
|
14015
14087
|
return {
|
|
14016
14088
|
ownPubkeyBytes,
|
|
14017
14089
|
view,
|
|
14090
|
+
refreshView,
|
|
14018
14091
|
ledger,
|
|
14019
14092
|
pins: buildPins(deps, hooks, { ledger, view, machineStores, machineDeps }),
|
|
14020
14093
|
queue,
|
|
@@ -14023,7 +14096,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14023
14096
|
machineDeps,
|
|
14024
14097
|
machine: new TransferMachine(machineDeps),
|
|
14025
14098
|
heldStates,
|
|
14026
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates,
|
|
14099
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
|
|
14027
14100
|
requests: buildRequests(deps, hooks),
|
|
14028
14101
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
14029
14102
|
};
|
|
@@ -14090,7 +14163,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14090
14163
|
}
|
|
14091
14164
|
};
|
|
14092
14165
|
}
|
|
14093
|
-
function buildReceive(deps, hooks, historyStore, heldStates,
|
|
14166
|
+
function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
14094
14167
|
return new Receive({
|
|
14095
14168
|
delivery: deps.deliveryPort,
|
|
14096
14169
|
engine: () => hooks.engine(),
|
|
@@ -14104,8 +14177,7 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
|
14104
14177
|
registry: deps.registry,
|
|
14105
14178
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
14106
14179
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
14107
|
-
|
|
14108
|
-
refreshView: makeCoalescedRefresh(() => view.delta()),
|
|
14180
|
+
refreshView,
|
|
14109
14181
|
attention: (transferId, code, detail) => {
|
|
14110
14182
|
deps.emit(
|
|
14111
14183
|
"transfer:attention",
|
|
@@ -14157,7 +14229,7 @@ var PaymentsFacade = class {
|
|
|
14157
14229
|
isActiveOp: (id) => this.activeMoneyOps.has(id)
|
|
14158
14230
|
});
|
|
14159
14231
|
this.view = parts.view;
|
|
14160
|
-
this.wakeRefresh =
|
|
14232
|
+
this.wakeRefresh = parts.refreshView;
|
|
14161
14233
|
this.ledger = parts.ledger;
|
|
14162
14234
|
this.queue = parts.queue;
|
|
14163
14235
|
this.machine = parts.machine;
|
|
@@ -14490,7 +14562,7 @@ var PaymentsFacade = class {
|
|
|
14490
14562
|
...spend.plan.direct,
|
|
14491
14563
|
...spend.plan.split !== void 0 ? [spend.plan.split.tokenId] : []
|
|
14492
14564
|
];
|
|
14493
|
-
|
|
14565
|
+
this.view.markInFlightMany(sourceIds);
|
|
14494
14566
|
if (spend.plan.split !== void 0) {
|
|
14495
14567
|
this.queue.declareExpectedChange(transferId, coinId, spend.plan.split.remainderAmount);
|
|
14496
14568
|
}
|
|
@@ -14776,6 +14848,10 @@ var WalletApiHttpError = class extends Error {
|
|
|
14776
14848
|
if (retryAfter !== void 0) this.retryAfter = retryAfter;
|
|
14777
14849
|
}
|
|
14778
14850
|
};
|
|
14851
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([0, 408, 425, 429, 500, 502, 503, 504]);
|
|
14852
|
+
function isRetryableStatus(err) {
|
|
14853
|
+
return err instanceof WalletApiHttpError && RETRYABLE_STATUSES.has(err.status);
|
|
14854
|
+
}
|
|
14779
14855
|
function isWalletApiHttpError(err, status) {
|
|
14780
14856
|
return err instanceof WalletApiHttpError && (status === void 0 || err.status === status);
|
|
14781
14857
|
}
|
|
@@ -15488,6 +15564,23 @@ function computeDeliveryId(tokenIdHex, stateHashHex) {
|
|
|
15488
15564
|
return bytesToHex3(sha256(joined));
|
|
15489
15565
|
}
|
|
15490
15566
|
var DELIVERY_SEEN_KEY = "delivery:seen";
|
|
15567
|
+
var ACK_CHUNK = 50;
|
|
15568
|
+
function chunked(items, size) {
|
|
15569
|
+
const out = [];
|
|
15570
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
15571
|
+
return out;
|
|
15572
|
+
}
|
|
15573
|
+
var RetryableAckFailure = class extends Error {
|
|
15574
|
+
retryable = true;
|
|
15575
|
+
constructor(cause) {
|
|
15576
|
+
super(`mailbox ack batch could not be attempted: ${String(cause)}`);
|
|
15577
|
+
this.name = "RetryableAckFailure";
|
|
15578
|
+
this.cause = cause;
|
|
15579
|
+
}
|
|
15580
|
+
};
|
|
15581
|
+
function asRetryable(err) {
|
|
15582
|
+
return isRetryableStatus(err) ? new RetryableAckFailure(err) : err;
|
|
15583
|
+
}
|
|
15491
15584
|
var WalletApiDeliveryPort = class {
|
|
15492
15585
|
custody;
|
|
15493
15586
|
client;
|
|
@@ -15653,7 +15746,53 @@ var WalletApiDeliveryPort = class {
|
|
|
15653
15746
|
} else {
|
|
15654
15747
|
await this.client.reject([deliveryId], reason);
|
|
15655
15748
|
}
|
|
15656
|
-
await this.
|
|
15749
|
+
await this.addSeenAll([deliveryId]);
|
|
15750
|
+
}
|
|
15751
|
+
/**
|
|
15752
|
+
* §5.7/#757 batched settle. Claims go out grouped, rejects grouped by reason
|
|
15753
|
+
* (the endpoint takes one reason per call), and the seen-set is written ONCE
|
|
15754
|
+
* after every HTTP call has succeeded — never on a throw, so an aborted batch
|
|
15755
|
+
* re-lists rather than being silently forgotten.
|
|
15756
|
+
*/
|
|
15757
|
+
async ackBatch(acks) {
|
|
15758
|
+
const settled = [];
|
|
15759
|
+
const outcomes = [];
|
|
15760
|
+
try {
|
|
15761
|
+
await this.settleClaims(acks, settled, outcomes);
|
|
15762
|
+
await this.settleRejects(acks, settled);
|
|
15763
|
+
} catch (err) {
|
|
15764
|
+
throw asRetryable(err);
|
|
15765
|
+
}
|
|
15766
|
+
await this.addSeenAll(settled);
|
|
15767
|
+
return [...outcomes, ...settled.map((deliveryId) => ({ deliveryId, status: "settled" }))];
|
|
15768
|
+
}
|
|
15769
|
+
async settleClaims(acks, settled, outcomes) {
|
|
15770
|
+
const ids = acks.filter((a) => a.disposition === "claimed").map((a) => a.deliveryId);
|
|
15771
|
+
for (const chunk of chunked(ids, ACK_CHUNK)) {
|
|
15772
|
+
const result = await this.client.claim(chunk, this.custody === "inventory");
|
|
15773
|
+
settled.push(...result.claimed);
|
|
15774
|
+
settled.push(...result.alreadyClaimed.map((e) => e.entryId));
|
|
15775
|
+
for (const f of result.failed) {
|
|
15776
|
+
if (f.code === "CONFLICT") outcomes.push({ deliveryId: f.entryId, status: "conflict" });
|
|
15777
|
+
else throw new MailboxClaimFailedError(f.entryId, f.code);
|
|
15778
|
+
}
|
|
15779
|
+
}
|
|
15780
|
+
}
|
|
15781
|
+
async settleRejects(acks, settled) {
|
|
15782
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
15783
|
+
for (const a of acks) {
|
|
15784
|
+
if (a.disposition !== "rejected") continue;
|
|
15785
|
+
const key = a.reason ?? "";
|
|
15786
|
+
const group = byReason.get(key) ?? [];
|
|
15787
|
+
group.push(a.deliveryId);
|
|
15788
|
+
byReason.set(key, group);
|
|
15789
|
+
}
|
|
15790
|
+
for (const [reason, group] of byReason) {
|
|
15791
|
+
for (const chunk of chunked(group, ACK_CHUNK)) {
|
|
15792
|
+
await this.client.reject(chunk, reason === "" ? void 0 : reason);
|
|
15793
|
+
settled.push(...chunk);
|
|
15794
|
+
}
|
|
15795
|
+
}
|
|
15657
15796
|
}
|
|
15658
15797
|
onWake(cb) {
|
|
15659
15798
|
if (this.wake === null) return () => void 0;
|
|
@@ -15664,10 +15803,12 @@ var WalletApiDeliveryPort = class {
|
|
|
15664
15803
|
const raw = await this.kv.get(DELIVERY_SEEN_KEY);
|
|
15665
15804
|
return new Set(raw ?? []);
|
|
15666
15805
|
}
|
|
15667
|
-
|
|
15806
|
+
/** SerialChain settles-not-fulfills: a rejected write never bricks later acks. */
|
|
15807
|
+
addSeenAll(deliveryIds) {
|
|
15808
|
+
if (deliveryIds.length === 0) return Promise.resolve();
|
|
15668
15809
|
return this.seenChain.enqueue(async () => {
|
|
15669
15810
|
const seen = await this.readSeen();
|
|
15670
|
-
seen.add(
|
|
15811
|
+
for (const id of deliveryIds) seen.add(id);
|
|
15671
15812
|
await this.kv.set(DELIVERY_SEEN_KEY, [...seen]);
|
|
15672
15813
|
});
|
|
15673
15814
|
}
|