@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.js
CHANGED
|
@@ -11233,6 +11233,26 @@ var SerialChain = class {
|
|
|
11233
11233
|
return run;
|
|
11234
11234
|
}
|
|
11235
11235
|
};
|
|
11236
|
+
function coalesced(run) {
|
|
11237
|
+
let inFlight = false;
|
|
11238
|
+
let queued = false;
|
|
11239
|
+
const start = () => {
|
|
11240
|
+
inFlight = true;
|
|
11241
|
+
void run().catch(() => void 0).finally(() => {
|
|
11242
|
+
inFlight = false;
|
|
11243
|
+
if (!queued) return;
|
|
11244
|
+
queued = false;
|
|
11245
|
+
start();
|
|
11246
|
+
});
|
|
11247
|
+
};
|
|
11248
|
+
return () => {
|
|
11249
|
+
if (inFlight) {
|
|
11250
|
+
queued = true;
|
|
11251
|
+
return;
|
|
11252
|
+
}
|
|
11253
|
+
start();
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11236
11256
|
|
|
11237
11257
|
// modules/payments-v2/stores.ts
|
|
11238
11258
|
function createScopedKV(storage, network, chainPubkey) {
|
|
@@ -12680,6 +12700,16 @@ var InventoryView = class {
|
|
|
12680
12700
|
this.inFlight.add(tokenId);
|
|
12681
12701
|
this.deps.emit("inventory:updated");
|
|
12682
12702
|
}
|
|
12703
|
+
/** Reserve a whole plan as ONE change — per token emitted 54 events per send. */
|
|
12704
|
+
markInFlightMany(tokenIds) {
|
|
12705
|
+
let changed = false;
|
|
12706
|
+
for (const tokenId of tokenIds) {
|
|
12707
|
+
if (this.inFlight.has(tokenId)) continue;
|
|
12708
|
+
this.inFlight.add(tokenId);
|
|
12709
|
+
changed = true;
|
|
12710
|
+
}
|
|
12711
|
+
if (changed) this.deps.emit("inventory:updated");
|
|
12712
|
+
}
|
|
12683
12713
|
release(tokenId) {
|
|
12684
12714
|
if (this.inFlight.delete(tokenId)) this.deps.emit("inventory:updated");
|
|
12685
12715
|
}
|
|
@@ -12904,6 +12934,11 @@ var InventoryView = class {
|
|
|
12904
12934
|
}
|
|
12905
12935
|
};
|
|
12906
12936
|
|
|
12937
|
+
// modules/payments-v2/ports.ts
|
|
12938
|
+
function isRetryableAckError(err) {
|
|
12939
|
+
return err !== null && typeof err === "object" && err.retryable === true;
|
|
12940
|
+
}
|
|
12941
|
+
|
|
12907
12942
|
// modules/payments-v2/receive/Receive.ts
|
|
12908
12943
|
var ACK_BATCH_SIZE = 200;
|
|
12909
12944
|
var REFRESH_INTERVAL_MS = 2500;
|
|
@@ -12985,6 +13020,7 @@ var Receive = class {
|
|
|
12985
13020
|
}
|
|
12986
13021
|
} catch (err) {
|
|
12987
13022
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
13023
|
+
if (isRetryableAckError(err)) return stored;
|
|
12988
13024
|
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
12989
13025
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
12990
13026
|
});
|
|
@@ -13059,14 +13095,18 @@ async function announce(deps, entry, record) {
|
|
|
13059
13095
|
};
|
|
13060
13096
|
}
|
|
13061
13097
|
async function flushAcks(deps, pending, epochOf) {
|
|
13098
|
+
if (pending.length === 0) return;
|
|
13062
13099
|
let lastAcked = null;
|
|
13063
13100
|
try {
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
lastAcked =
|
|
13068
|
-
|
|
13101
|
+
const settled = await settleAcks(deps, pending);
|
|
13102
|
+
let i = 0;
|
|
13103
|
+
while (i < pending.length && settled.has(pending[i].deliveryId)) {
|
|
13104
|
+
lastAcked = pending[i].cursor;
|
|
13105
|
+
i += 1;
|
|
13069
13106
|
}
|
|
13107
|
+
const stuck = pending.filter((p) => !settled.has(p.deliveryId));
|
|
13108
|
+
pending.length = 0;
|
|
13109
|
+
pending.push(...stuck);
|
|
13070
13110
|
} finally {
|
|
13071
13111
|
if (lastAcked !== null) {
|
|
13072
13112
|
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
@@ -13074,14 +13114,65 @@ async function flushAcks(deps, pending, epochOf) {
|
|
|
13074
13114
|
}
|
|
13075
13115
|
}
|
|
13076
13116
|
}
|
|
13117
|
+
async function settleAcks(deps, pending) {
|
|
13118
|
+
const batch = deps.delivery.ackBatch?.bind(deps.delivery);
|
|
13119
|
+
if (batch === void 0) return settleOneByOne(deps, pending);
|
|
13120
|
+
let outcomes;
|
|
13121
|
+
try {
|
|
13122
|
+
outcomes = await batch(pending.map(toAckRequest));
|
|
13123
|
+
} catch (err) {
|
|
13124
|
+
if (isRetryableAckError(err)) throw err;
|
|
13125
|
+
logger.warn("PaymentsV2", "batched mailbox ack failed \u2014 settling one at a time:", err);
|
|
13126
|
+
return settleOneByOne(deps, pending);
|
|
13127
|
+
}
|
|
13128
|
+
const settled = new Set(outcomes.filter((o) => o.status === "settled").map((o) => o.deliveryId));
|
|
13129
|
+
const conflicts = outcomes.filter((o) => o.status === "conflict").map((o) => o.deliveryId);
|
|
13130
|
+
if (conflicts.length > 0) await resolveConflicts(deps, pending, conflicts, settled);
|
|
13131
|
+
return settled;
|
|
13132
|
+
}
|
|
13133
|
+
async function resolveConflicts(deps, pending, conflicts, settled) {
|
|
13134
|
+
for (const deliveryId of conflicts) {
|
|
13135
|
+
const entry = pending.find((p) => p.deliveryId === deliveryId);
|
|
13136
|
+
try {
|
|
13137
|
+
await rejectStaleClaim(deps, deliveryId, entry?.transferId);
|
|
13138
|
+
} catch (err) {
|
|
13139
|
+
logger.warn("PaymentsV2", `stale-reject failed for ${deliveryId} \u2014 cursor holds here:`, err);
|
|
13140
|
+
continue;
|
|
13141
|
+
}
|
|
13142
|
+
settled.add(deliveryId);
|
|
13143
|
+
}
|
|
13144
|
+
}
|
|
13145
|
+
async function rejectStaleClaim(deps, deliveryId, transferId) {
|
|
13146
|
+
logger.warn("PaymentsV2", `mailbox claim CONFLICT for ${deliveryId} \u2014 rejected('other') as stale`);
|
|
13147
|
+
await deps.delivery.ack(deliveryId, "rejected", "other");
|
|
13148
|
+
deps.attention(transferId ?? "", ATTENTION_CLAIM_CONFLICT, deliveryId);
|
|
13149
|
+
}
|
|
13150
|
+
async function settleOneByOne(deps, pending) {
|
|
13151
|
+
const settled = /* @__PURE__ */ new Set();
|
|
13152
|
+
for (const ack of pending) {
|
|
13153
|
+
try {
|
|
13154
|
+
await ackOne(deps, ack);
|
|
13155
|
+
} catch (err) {
|
|
13156
|
+
logger.warn("PaymentsV2", `mailbox ack failed for ${ack.deliveryId} \u2014 cursor holds here:`, err);
|
|
13157
|
+
break;
|
|
13158
|
+
}
|
|
13159
|
+
settled.add(ack.deliveryId);
|
|
13160
|
+
}
|
|
13161
|
+
return settled;
|
|
13162
|
+
}
|
|
13163
|
+
function toAckRequest(ack) {
|
|
13164
|
+
return {
|
|
13165
|
+
deliveryId: ack.deliveryId,
|
|
13166
|
+
disposition: ack.disposition,
|
|
13167
|
+
...ack.reason !== void 0 ? { reason: ack.reason } : {}
|
|
13168
|
+
};
|
|
13169
|
+
}
|
|
13077
13170
|
async function ackOne(deps, ack) {
|
|
13078
13171
|
try {
|
|
13079
13172
|
await deps.delivery.ack(ack.deliveryId, ack.disposition, ack.reason);
|
|
13080
13173
|
} catch (err) {
|
|
13081
13174
|
if (ack.disposition !== "claimed" || !isClaimConflict(err)) throw err;
|
|
13082
|
-
|
|
13083
|
-
await deps.delivery.ack(ack.deliveryId, "rejected", "other");
|
|
13084
|
-
deps.attention(ack.transferId ?? "", ATTENTION_CLAIM_CONFLICT, ack.deliveryId);
|
|
13175
|
+
await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
|
|
13085
13176
|
}
|
|
13086
13177
|
}
|
|
13087
13178
|
function claimAck(entry) {
|
|
@@ -13835,26 +13926,6 @@ function parseAmount(raw) {
|
|
|
13835
13926
|
function supportsDeterministicMint(engine) {
|
|
13836
13927
|
return engine.deterministicMint === true;
|
|
13837
13928
|
}
|
|
13838
|
-
function makeCoalescedRefresh(refresh) {
|
|
13839
|
-
let inFlight = false;
|
|
13840
|
-
let queued = false;
|
|
13841
|
-
const run = () => {
|
|
13842
|
-
inFlight = true;
|
|
13843
|
-
void refresh().catch(() => void 0).finally(() => {
|
|
13844
|
-
inFlight = false;
|
|
13845
|
-
if (!queued) return;
|
|
13846
|
-
queued = false;
|
|
13847
|
-
run();
|
|
13848
|
-
});
|
|
13849
|
-
};
|
|
13850
|
-
return () => {
|
|
13851
|
-
if (inFlight) {
|
|
13852
|
-
queued = true;
|
|
13853
|
-
return;
|
|
13854
|
-
}
|
|
13855
|
-
run();
|
|
13856
|
-
};
|
|
13857
|
-
}
|
|
13858
13929
|
function composeFacadeParts(deps, hooks) {
|
|
13859
13930
|
const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
|
|
13860
13931
|
const ledger = new ReservationLedger();
|
|
@@ -13884,9 +13955,11 @@ function composeFacadeParts(deps, hooks) {
|
|
|
13884
13955
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
13885
13956
|
const machineStores = createMachineStores(deps.kv);
|
|
13886
13957
|
const heldStates = /* @__PURE__ */ new Map();
|
|
13958
|
+
const refreshView = coalesced(() => view.delta());
|
|
13887
13959
|
return {
|
|
13888
13960
|
ownPubkeyBytes,
|
|
13889
13961
|
view,
|
|
13962
|
+
refreshView,
|
|
13890
13963
|
ledger,
|
|
13891
13964
|
pins: buildPins(deps, hooks, { ledger, view, machineStores, machineDeps }),
|
|
13892
13965
|
queue,
|
|
@@ -13895,7 +13968,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
13895
13968
|
machineDeps,
|
|
13896
13969
|
machine: new TransferMachine(machineDeps),
|
|
13897
13970
|
heldStates,
|
|
13898
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates,
|
|
13971
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
|
|
13899
13972
|
requests: buildRequests(deps, hooks),
|
|
13900
13973
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
13901
13974
|
};
|
|
@@ -13962,7 +14035,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
13962
14035
|
}
|
|
13963
14036
|
};
|
|
13964
14037
|
}
|
|
13965
|
-
function buildReceive(deps, hooks, historyStore, heldStates,
|
|
14038
|
+
function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
13966
14039
|
return new Receive({
|
|
13967
14040
|
delivery: deps.deliveryPort,
|
|
13968
14041
|
engine: () => hooks.engine(),
|
|
@@ -13976,8 +14049,7 @@ function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
|
13976
14049
|
registry: deps.registry,
|
|
13977
14050
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
13978
14051
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
13979
|
-
|
|
13980
|
-
refreshView: makeCoalescedRefresh(() => view.delta()),
|
|
14052
|
+
refreshView,
|
|
13981
14053
|
attention: (transferId, code, detail) => {
|
|
13982
14054
|
deps.emit(
|
|
13983
14055
|
"transfer:attention",
|
|
@@ -14029,7 +14101,7 @@ var PaymentsFacade = class {
|
|
|
14029
14101
|
isActiveOp: (id) => this.activeMoneyOps.has(id)
|
|
14030
14102
|
});
|
|
14031
14103
|
this.view = parts.view;
|
|
14032
|
-
this.wakeRefresh =
|
|
14104
|
+
this.wakeRefresh = parts.refreshView;
|
|
14033
14105
|
this.ledger = parts.ledger;
|
|
14034
14106
|
this.queue = parts.queue;
|
|
14035
14107
|
this.machine = parts.machine;
|
|
@@ -14362,7 +14434,7 @@ var PaymentsFacade = class {
|
|
|
14362
14434
|
...spend.plan.direct,
|
|
14363
14435
|
...spend.plan.split !== void 0 ? [spend.plan.split.tokenId] : []
|
|
14364
14436
|
];
|
|
14365
|
-
|
|
14437
|
+
this.view.markInFlightMany(sourceIds);
|
|
14366
14438
|
if (spend.plan.split !== void 0) {
|
|
14367
14439
|
this.queue.declareExpectedChange(transferId, coinId, spend.plan.split.remainderAmount);
|
|
14368
14440
|
}
|
|
@@ -14648,6 +14720,10 @@ var WalletApiHttpError = class extends Error {
|
|
|
14648
14720
|
if (retryAfter !== void 0) this.retryAfter = retryAfter;
|
|
14649
14721
|
}
|
|
14650
14722
|
};
|
|
14723
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([0, 408, 425, 429, 500, 502, 503, 504]);
|
|
14724
|
+
function isRetryableStatus(err) {
|
|
14725
|
+
return err instanceof WalletApiHttpError && RETRYABLE_STATUSES.has(err.status);
|
|
14726
|
+
}
|
|
14651
14727
|
function isWalletApiHttpError(err, status) {
|
|
14652
14728
|
return err instanceof WalletApiHttpError && (status === void 0 || err.status === status);
|
|
14653
14729
|
}
|
|
@@ -15360,6 +15436,23 @@ function computeDeliveryId(tokenIdHex, stateHashHex) {
|
|
|
15360
15436
|
return bytesToHex3(sha256(joined));
|
|
15361
15437
|
}
|
|
15362
15438
|
var DELIVERY_SEEN_KEY = "delivery:seen";
|
|
15439
|
+
var ACK_CHUNK = 50;
|
|
15440
|
+
function chunked(items, size) {
|
|
15441
|
+
const out = [];
|
|
15442
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
15443
|
+
return out;
|
|
15444
|
+
}
|
|
15445
|
+
var RetryableAckFailure = class extends Error {
|
|
15446
|
+
retryable = true;
|
|
15447
|
+
constructor(cause) {
|
|
15448
|
+
super(`mailbox ack batch could not be attempted: ${String(cause)}`);
|
|
15449
|
+
this.name = "RetryableAckFailure";
|
|
15450
|
+
this.cause = cause;
|
|
15451
|
+
}
|
|
15452
|
+
};
|
|
15453
|
+
function asRetryable(err) {
|
|
15454
|
+
return isRetryableStatus(err) ? new RetryableAckFailure(err) : err;
|
|
15455
|
+
}
|
|
15363
15456
|
var WalletApiDeliveryPort = class {
|
|
15364
15457
|
custody;
|
|
15365
15458
|
client;
|
|
@@ -15525,7 +15618,53 @@ var WalletApiDeliveryPort = class {
|
|
|
15525
15618
|
} else {
|
|
15526
15619
|
await this.client.reject([deliveryId], reason);
|
|
15527
15620
|
}
|
|
15528
|
-
await this.
|
|
15621
|
+
await this.addSeenAll([deliveryId]);
|
|
15622
|
+
}
|
|
15623
|
+
/**
|
|
15624
|
+
* §5.7/#757 batched settle. Claims go out grouped, rejects grouped by reason
|
|
15625
|
+
* (the endpoint takes one reason per call), and the seen-set is written ONCE
|
|
15626
|
+
* after every HTTP call has succeeded — never on a throw, so an aborted batch
|
|
15627
|
+
* re-lists rather than being silently forgotten.
|
|
15628
|
+
*/
|
|
15629
|
+
async ackBatch(acks) {
|
|
15630
|
+
const settled = [];
|
|
15631
|
+
const outcomes = [];
|
|
15632
|
+
try {
|
|
15633
|
+
await this.settleClaims(acks, settled, outcomes);
|
|
15634
|
+
await this.settleRejects(acks, settled);
|
|
15635
|
+
} catch (err) {
|
|
15636
|
+
throw asRetryable(err);
|
|
15637
|
+
}
|
|
15638
|
+
await this.addSeenAll(settled);
|
|
15639
|
+
return [...outcomes, ...settled.map((deliveryId) => ({ deliveryId, status: "settled" }))];
|
|
15640
|
+
}
|
|
15641
|
+
async settleClaims(acks, settled, outcomes) {
|
|
15642
|
+
const ids = acks.filter((a) => a.disposition === "claimed").map((a) => a.deliveryId);
|
|
15643
|
+
for (const chunk of chunked(ids, ACK_CHUNK)) {
|
|
15644
|
+
const result = await this.client.claim(chunk, this.custody === "inventory");
|
|
15645
|
+
settled.push(...result.claimed);
|
|
15646
|
+
settled.push(...result.alreadyClaimed.map((e) => e.entryId));
|
|
15647
|
+
for (const f of result.failed) {
|
|
15648
|
+
if (f.code === "CONFLICT") outcomes.push({ deliveryId: f.entryId, status: "conflict" });
|
|
15649
|
+
else throw new MailboxClaimFailedError(f.entryId, f.code);
|
|
15650
|
+
}
|
|
15651
|
+
}
|
|
15652
|
+
}
|
|
15653
|
+
async settleRejects(acks, settled) {
|
|
15654
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
15655
|
+
for (const a of acks) {
|
|
15656
|
+
if (a.disposition !== "rejected") continue;
|
|
15657
|
+
const key = a.reason ?? "";
|
|
15658
|
+
const group = byReason.get(key) ?? [];
|
|
15659
|
+
group.push(a.deliveryId);
|
|
15660
|
+
byReason.set(key, group);
|
|
15661
|
+
}
|
|
15662
|
+
for (const [reason, group] of byReason) {
|
|
15663
|
+
for (const chunk of chunked(group, ACK_CHUNK)) {
|
|
15664
|
+
await this.client.reject(chunk, reason === "" ? void 0 : reason);
|
|
15665
|
+
settled.push(...chunk);
|
|
15666
|
+
}
|
|
15667
|
+
}
|
|
15529
15668
|
}
|
|
15530
15669
|
onWake(cb) {
|
|
15531
15670
|
if (this.wake === null) return () => void 0;
|
|
@@ -15536,10 +15675,12 @@ var WalletApiDeliveryPort = class {
|
|
|
15536
15675
|
const raw = await this.kv.get(DELIVERY_SEEN_KEY);
|
|
15537
15676
|
return new Set(raw ?? []);
|
|
15538
15677
|
}
|
|
15539
|
-
|
|
15678
|
+
/** SerialChain settles-not-fulfills: a rejected write never bricks later acks. */
|
|
15679
|
+
addSeenAll(deliveryIds) {
|
|
15680
|
+
if (deliveryIds.length === 0) return Promise.resolve();
|
|
15540
15681
|
return this.seenChain.enqueue(async () => {
|
|
15541
15682
|
const seen = await this.readSeen();
|
|
15542
|
-
seen.add(
|
|
15683
|
+
for (const id of deliveryIds) seen.add(id);
|
|
15543
15684
|
await this.kv.set(DELIVERY_SEEN_KEY, [...seen]);
|
|
15544
15685
|
});
|
|
15545
15686
|
}
|