@unicitylabs/sphere-sdk 0.17.6 → 0.18.0-dev.2

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.
Files changed (44) hide show
  1. package/dist/connect/chunks/{chunk-T4DZCXWS.js → chunk-FGQ6CKKK.js} +2 -2
  2. package/dist/connect/chunks/{chunk-T4DZCXWS.js.map → chunk-FGQ6CKKK.js.map} +1 -1
  3. package/dist/connect/chunks/{chunk-BRYRG2TL.js → chunk-JYBI36GK.js} +2 -2
  4. package/dist/connect/chunks/{chunk-MA32MTCD.js → chunk-OAAY2JEA.js} +2 -2
  5. package/dist/connect/index.cjs +1 -1
  6. package/dist/connect/index.cjs.map +1 -1
  7. package/dist/connect/index.js +3 -3
  8. package/dist/connect/internal/host.js +2 -2
  9. package/dist/core/index.cjs +304 -47
  10. package/dist/core/index.cjs.map +1 -1
  11. package/dist/core/index.d.cts +392 -320
  12. package/dist/core/index.d.ts +392 -320
  13. package/dist/core/index.js +304 -47
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/impl/browser/connect/index.cjs +1 -1
  16. package/dist/impl/browser/connect/index.cjs.map +1 -1
  17. package/dist/impl/browser/connect/index.js +2 -2
  18. package/dist/impl/wallet-api-v2/index.cjs +2 -0
  19. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  20. package/dist/impl/wallet-api-v2/index.d.cts +30 -30
  21. package/dist/impl/wallet-api-v2/index.d.ts +30 -30
  22. package/dist/impl/wallet-api-v2/index.js +2 -0
  23. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  24. package/dist/index.cjs +304 -47
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +279 -154
  27. package/dist/index.d.ts +279 -154
  28. package/dist/index.js +304 -47
  29. package/dist/index.js.map +1 -1
  30. package/dist/modules/payments-v2/index.cjs +249 -28
  31. package/dist/modules/payments-v2/index.cjs.map +1 -1
  32. package/dist/modules/payments-v2/index.d.cts +252 -166
  33. package/dist/modules/payments-v2/index.d.ts +252 -166
  34. package/dist/modules/payments-v2/index.js +249 -28
  35. package/dist/modules/payments-v2/index.js.map +1 -1
  36. package/dist/token-engine/index.cjs +455 -427
  37. package/dist/token-engine/index.cjs.map +1 -1
  38. package/dist/token-engine/index.d.cts +26 -1
  39. package/dist/token-engine/index.d.ts +26 -1
  40. package/dist/token-engine/index.js +455 -427
  41. package/dist/token-engine/index.js.map +1 -1
  42. package/package.json +1 -1
  43. /package/dist/connect/chunks/{chunk-BRYRG2TL.js.map → chunk-JYBI36GK.js.map} +0 -0
  44. /package/dist/connect/chunks/{chunk-MA32MTCD.js.map → chunk-OAAY2JEA.js.map} +0 -0
@@ -2036,6 +2036,8 @@ var STORE_KEYS = {
2036
2036
  deliveryJournal: "delivery-journal",
2037
2037
  mintJournal: "mint-journal",
2038
2038
  nftMintJournal: "nft-mint-journal",
2039
+ customMintJournal: "custom-mint-journal",
2040
+ burnJournal: "burn-journal",
2039
2041
  shortfalls: "shortfalls",
2040
2042
  settlingLinks: "settling",
2041
2043
  streamCursor: (s) => `cursor:${s}`,
@@ -3301,6 +3303,8 @@ function createMachineStores(kv) {
3301
3303
  deliveryJournal: new DeliveryJournal(kv),
3302
3304
  mintJournal: new ListStore(kv, STORE_KEYS.mintJournal, (e) => e.mintId),
3303
3305
  nftMintJournal: new ListStore(kv, STORE_KEYS.nftMintJournal, (e) => e.mintId),
3306
+ customMintJournal: new ListStore(kv, STORE_KEYS.customMintJournal, (e) => e.mintId),
3307
+ burnJournal: new ListStore(kv, STORE_KEYS.burnJournal, (e) => e.burnId),
3304
3308
  shortfalls: new ListStore(kv, STORE_KEYS.shortfalls, (e) => e.transferId)
3305
3309
  };
3306
3310
  }
@@ -4036,7 +4040,12 @@ var Converger = class {
4036
4040
  } catch (err) {
4037
4041
  retryAfterMs ??= retryAfterMsOf(err);
4038
4042
  }
4039
- for (const replay of [() => this.deps.replayMints(), () => this.deps.replayNftMints()]) {
4043
+ for (const replay of [
4044
+ () => this.deps.replayMints(),
4045
+ () => this.deps.replayNftMints(),
4046
+ () => this.deps.replayCustomMints(),
4047
+ () => this.deps.replayBurns()
4048
+ ]) {
4040
4049
  try {
4041
4050
  progress = await replay() > 0 || progress;
4042
4051
  } catch (err) {
@@ -4047,14 +4056,17 @@ var Converger = class {
4047
4056
  }
4048
4057
  /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
4049
4058
  async pendingWork() {
4050
- const [backstop, journal, mints, nftMints] = await Promise.all([
4059
+ const [backstop, journal, mints, nftMints, customMints, burns] = await Promise.all([
4051
4060
  this.deps.stores.backstop.list(),
4052
4061
  this.deps.stores.deliveryJournal.list(),
4053
4062
  this.deps.stores.mintJournal.list(),
4054
- this.deps.stores.nftMintJournal.list()
4063
+ this.deps.stores.nftMintJournal.list(),
4064
+ this.deps.stores.customMintJournal.list(),
4065
+ this.deps.stores.burnJournal.list()
4055
4066
  ]);
4056
- const mintsOpen = mints.length > 0 || nftMints.length > 0;
4057
- return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen;
4067
+ const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
4068
+ const burnsOpen = burns.some((e) => !e.settled);
4069
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
4058
4070
  }
4059
4071
  async resumePass() {
4060
4072
  const report = await resumeAll(this.filteredMachineDeps(), {
@@ -4802,6 +4814,7 @@ function mintParams(recipientPubkey, coinId, amount) {
4802
4814
 
4803
4815
  // modules/payments-v2/mint.ts
4804
4816
  var ATTENTION_MINT_UNRESOLVED = "mint:unresolved";
4817
+ var INVENTORY_SCAN_PAGE_LIMIT = 50;
4805
4818
  function runMintUnderJournal(deps, input) {
4806
4819
  return mintUnderJournal(deps, input);
4807
4820
  }
@@ -4854,6 +4867,15 @@ async function finalizeMint(deps, input) {
4854
4867
  deps.noteHeldState(token.blob.tokenId, (await deps.engine.deliveryKeys(bytes)).stateHash);
4855
4868
  deps.refreshView();
4856
4869
  }
4870
+ async function tokenInServerInventory(storagePort, tokenId) {
4871
+ let page = await storagePort.listInventory();
4872
+ for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
4873
+ if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) return true;
4874
+ if (!page.more) return false;
4875
+ page = await storagePort.listInventory(page.cursor);
4876
+ }
4877
+ return false;
4878
+ }
4857
4879
  async function replayCoinMints(deps) {
4858
4880
  let resolved = 0;
4859
4881
  for (const entry of await deps.mintJournal.list()) {
@@ -5031,6 +5053,12 @@ async function decodeView(deps, tokenId, bytes) {
5031
5053
 
5032
5054
  // modules/payments-v2/inventory/token-data.ts
5033
5055
  async function readTokenData(deps, tokenId) {
5056
+ return deps.engine.readTokenData(await heldToken(deps, tokenId));
5057
+ }
5058
+ async function readTokenJustification(deps, tokenId) {
5059
+ return deps.engine.readTokenJustification(await heldToken(deps, tokenId));
5060
+ }
5061
+ async function heldToken(deps, tokenId) {
5034
5062
  if (deps.view.stateHashOf(tokenId) === void 0) {
5035
5063
  throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
5036
5064
  }
@@ -5038,7 +5066,200 @@ async function readTokenData(deps, tokenId) {
5038
5066
  if (bytes === void 0) {
5039
5067
  throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
5040
5068
  }
5041
- return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
5069
+ return deps.engine.decodeToken({ tokenId, token: bytes });
5070
+ }
5071
+
5072
+ // modules/payments-v2/mint-custom.ts
5073
+ var HEX32 = /^[0-9a-f]{64}$/;
5074
+ function assertRequest(request) {
5075
+ if (request.tokenType.length !== 32) {
5076
+ throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
5077
+ }
5078
+ if (request.salt.length < 16 || request.salt.length > 64) {
5079
+ throw new SphereError("mintCustom: salt must be 16 to 64 bytes", "VALIDATION_ERROR");
5080
+ }
5081
+ if (request.data.length === 0) {
5082
+ throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
5083
+ }
5084
+ for (const asset of request.assets) {
5085
+ if (!HEX32.test(asset.coinId) || asset.amount <= 0n) {
5086
+ throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
5087
+ }
5088
+ }
5089
+ }
5090
+ async function runCustomMintUnderJournal(deps, input) {
5091
+ try {
5092
+ assertRequest(input.request);
5093
+ } catch (err) {
5094
+ return { success: false, error: messageOf(err) };
5095
+ }
5096
+ const entry = journalEntryOf2(input.mintId, input.request, deps.now());
5097
+ await deps.customMintJournal.upsert(entry);
5098
+ let tokenId;
5099
+ try {
5100
+ tokenId = await mintJournaled2(deps, entry, input.request.mintJustificationVerifiers);
5101
+ } catch (err) {
5102
+ deps.armHeartbeat();
5103
+ return { success: false, error: messageOf(err) };
5104
+ }
5105
+ return { success: true, tokenId };
5106
+ }
5107
+ async function replayCustomMints(deps) {
5108
+ let resolved = 0;
5109
+ for (const entry of await deps.customMintJournal.list()) {
5110
+ if (deps.isActiveOp(entry.mintId)) continue;
5111
+ try {
5112
+ if (entry.tokenId !== "" && await deps.tokenInServerInventory(entry.tokenId)) {
5113
+ await deps.customMintJournal.removeByKey(entry.mintId);
5114
+ } else {
5115
+ await mintJournaled2(deps, entry, void 0);
5116
+ }
5117
+ resolved += 1;
5118
+ } catch {
5119
+ continue;
5120
+ }
5121
+ }
5122
+ return resolved;
5123
+ }
5124
+ function journalEntryOf2(mintId, request, createdAt) {
5125
+ return {
5126
+ mintId,
5127
+ tokenId: "",
5128
+ dataHex: bytesToHex(request.data),
5129
+ saltHex: bytesToHex(request.salt),
5130
+ tokenTypeHex: bytesToHex(request.tokenType),
5131
+ justificationHex: request.justification ? bytesToHex(request.justification) : null,
5132
+ assets: request.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
5133
+ createdAt
5134
+ };
5135
+ }
5136
+ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
5137
+ const token = await deps.engine.mintDataToken(
5138
+ {
5139
+ recipientPubkey: deps.ownPubkeyBytes,
5140
+ data: hexToBytes(entry.dataHex),
5141
+ tokenType: hexToBytes(entry.tokenTypeHex),
5142
+ salt: hexToBytes(entry.saltHex),
5143
+ ...entry.justificationHex !== null ? { justification: hexToBytes(entry.justificationHex) } : {},
5144
+ ...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
5145
+ },
5146
+ { transferId: entry.mintId }
5147
+ );
5148
+ if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
5149
+ throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
5150
+ }
5151
+ if (entry.tokenId === "") {
5152
+ await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
5153
+ }
5154
+ await finalizeMint(deps, {
5155
+ mintId: entry.mintId,
5156
+ token,
5157
+ assets: entry.assets,
5158
+ journal: deps.customMintJournal
5159
+ });
5160
+ return token.blob.tokenId;
5161
+ }
5162
+
5163
+ // modules/payments-v2/burn.ts
5164
+ var HEX322 = /^[0-9a-f]{64}$/;
5165
+ async function heldToken2(deps, tokenId) {
5166
+ const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
5167
+ if (bytes === void 0) {
5168
+ throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
5169
+ }
5170
+ return deps.engine.decodeToken({ tokenId, token: bytes });
5171
+ }
5172
+ function assetsOf(token) {
5173
+ return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
5174
+ }
5175
+ async function runBurnUnderJournal(deps, input) {
5176
+ const { burnId } = input;
5177
+ const { tokenId, reasonBytes } = input.request;
5178
+ if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
5179
+ return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
5180
+ }
5181
+ let token;
5182
+ try {
5183
+ token = await heldToken2(deps, tokenId);
5184
+ if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
5185
+ throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
5186
+ }
5187
+ } catch (err) {
5188
+ return { success: false, burnId, tokenId, error: messageOf(err) };
5189
+ }
5190
+ const entry = {
5191
+ burnId,
5192
+ tokenId,
5193
+ reasonHex: bytesToHex(reasonBytes),
5194
+ burnedTokenHex: null,
5195
+ settled: false,
5196
+ assets: assetsOf(token),
5197
+ createdAt: deps.now()
5198
+ };
5199
+ await deps.burnJournal.upsert(entry);
5200
+ try {
5201
+ const settled = await burnJournaled(deps, entry, token, { applied: false });
5202
+ return { success: true, burnId, tokenId, burnedToken: hexToBytes(settled.burnedTokenHex ?? "") };
5203
+ } catch (err) {
5204
+ deps.armHeartbeat();
5205
+ return { success: false, burnId, tokenId, error: messageOf(err) };
5206
+ }
5207
+ }
5208
+ async function replayBurns(deps) {
5209
+ let progressed = 0;
5210
+ for (const entry of await deps.burnJournal.list()) {
5211
+ if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
5212
+ try {
5213
+ const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
5214
+ const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
5215
+ await burnJournaled(deps, entry, token, { applied });
5216
+ progressed += 1;
5217
+ } catch {
5218
+ continue;
5219
+ }
5220
+ }
5221
+ return progressed;
5222
+ }
5223
+ async function pendingBurns(deps) {
5224
+ return (await deps.burnJournal.list()).map((entry) => ({
5225
+ burnId: entry.burnId,
5226
+ tokenId: entry.tokenId,
5227
+ reasonBytes: hexToBytes(entry.reasonHex),
5228
+ burnedToken: entry.burnedTokenHex === null ? null : hexToBytes(entry.burnedTokenHex),
5229
+ settled: entry.settled,
5230
+ createdAt: entry.createdAt
5231
+ }));
5232
+ }
5233
+ async function acknowledgeBurn(deps, burnId) {
5234
+ const entry = await deps.burnJournal.getByKey(burnId);
5235
+ if (entry === void 0) return;
5236
+ if (!entry.settled) {
5237
+ throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
5238
+ }
5239
+ await deps.burnJournal.removeByKey(burnId);
5240
+ }
5241
+ async function burnJournaled(deps, entry, token, state) {
5242
+ let current = entry;
5243
+ if (current.burnedTokenHex === null) {
5244
+ if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
5245
+ const burned = await deps.engine.burn(
5246
+ { token, reasonBytes: hexToBytes(current.reasonHex) },
5247
+ { transferId: current.burnId }
5248
+ );
5249
+ if (burned.blob.tokenId !== current.tokenId) {
5250
+ throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
5251
+ }
5252
+ current = { ...current, burnedTokenHex: bytesToHex(burned.blob.token) };
5253
+ await deps.burnJournal.upsert(current);
5254
+ }
5255
+ if (!state.applied) {
5256
+ await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
5257
+ }
5258
+ await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
5259
+ current = { ...current, settled: true };
5260
+ await deps.burnJournal.upsert(current);
5261
+ deps.refreshView();
5262
+ return current;
5042
5263
  }
5043
5264
 
5044
5265
  // modules/payments-v2/send-whole.ts
@@ -5226,7 +5447,6 @@ async function takeSourceBlobs(deps, sourceIds) {
5226
5447
 
5227
5448
  // modules/payments-v2/PaymentsFacade.ts
5228
5449
  var MAX_RESELECT = 8;
5229
- var INVENTORY_SCAN_PAGE_LIMIT = 50;
5230
5450
  var PaymentsFacade = class {
5231
5451
  constructor(deps) {
5232
5452
  this.deps = deps;
@@ -5263,6 +5483,8 @@ var PaymentsFacade = class {
5263
5483
  refreshView: () => this.view.delta(),
5264
5484
  replayMints: () => replayCoinMints(this.mintDeps()),
5265
5485
  replayNftMints: () => replayNftMints(this.mintDeps()),
5486
+ replayCustomMints: () => replayCustomMints(this.mintDeps()),
5487
+ replayBurns: () => replayBurns(this.mintDeps()),
5266
5488
  isActiveOp: (id) => this.activeMoneyOps.has(id),
5267
5489
  emit: deps.emit,
5268
5490
  now: () => this.nowMs()
@@ -5358,6 +5580,9 @@ var PaymentsFacade = class {
5358
5580
  tokenData(tokenId) {
5359
5581
  return readTokenData(this.readDeps(), tokenId);
5360
5582
  }
5583
+ tokenJustification(tokenId) {
5584
+ return readTokenJustification(this.readDeps(), tokenId);
5585
+ }
5361
5586
  nft(tokenId) {
5362
5587
  return readNft(this.readDeps(), tokenId);
5363
5588
  }
@@ -5406,8 +5631,12 @@ var PaymentsFacade = class {
5406
5631
  return this.track(this.mintInner(coinId, amount));
5407
5632
  }
5408
5633
  mintNft(request) {
5409
- return this.track(this.ownedMint((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
5634
+ return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
5410
5635
  }
5636
+ mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
5637
+ burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
5638
+ pendingBurns = () => this.track(pendingBurns(this.mintDeps()));
5639
+ acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.mintDeps(), burnId));
5411
5640
  // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
5412
5641
  async resumeNow() {
5413
5642
  if (!this.started) return;
@@ -5431,12 +5660,15 @@ var PaymentsFacade = class {
5431
5660
  nowMs() {
5432
5661
  return (this.deps.now ?? Date.now)();
5433
5662
  }
5434
- /** One snapshot serves the coin and the NFT mint, live and replayed. */
5663
+ /** One snapshot serves the coin, NFT and custom mints and the burn, live and replayed. */
5435
5664
  mintDeps() {
5436
5665
  return {
5437
5666
  engine: this.engine(),
5438
5667
  mintJournal: this.machineStores.mintJournal,
5439
5668
  nftMintJournal: this.machineStores.nftMintJournal,
5669
+ customMintJournal: this.machineStores.customMintJournal,
5670
+ burnJournal: this.machineStores.burnJournal,
5671
+ recordSent: (input) => this.historyStore.recordSent(input),
5440
5672
  storagePort: this.deps.storagePort,
5441
5673
  recordMint: (input) => this.historyStore.recordMint(input),
5442
5674
  armHeartbeat: () => this.heartbeat.arm(),
@@ -5445,7 +5677,7 @@ var PaymentsFacade = class {
5445
5677
  ownPubkeyBytes: this.ownPubkeyBytes,
5446
5678
  nftTokenType: this.deps.nftTokenType,
5447
5679
  isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
5448
- tokenInServerInventory: (tokenId) => this.tokenInServerInventory(tokenId),
5680
+ tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
5449
5681
  emit: this.deps.emit,
5450
5682
  now: () => this.nowMs()
5451
5683
  };
@@ -5766,29 +5998,18 @@ var PaymentsFacade = class {
5766
5998
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
5767
5999
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
5768
6000
  }
5769
- return this.ownedMint((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
6001
+ return this.ownedOp((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
5770
6002
  }
5771
- /** A live mint under a fresh id — its replay stays hands-off while this attempt runs. */
5772
- async ownedMint(run) {
5773
- const mintId = this.newId();
5774
- this.activeMoneyOps.add(mintId);
6003
+ /** A live money op under a fresh id — its replay stays hands-off while this attempt runs (§7). */
6004
+ async ownedOp(run) {
6005
+ const id = this.newId();
6006
+ this.activeMoneyOps.add(id);
5775
6007
  try {
5776
- return await run(mintId);
6008
+ return await run(id);
5777
6009
  } finally {
5778
- this.activeMoneyOps.delete(mintId);
6010
+ this.activeMoneyOps.delete(id);
5779
6011
  }
5780
6012
  }
5781
- async tokenInServerInventory(tokenId) {
5782
- let page = await this.deps.storagePort.listInventory();
5783
- for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
5784
- if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) {
5785
- return true;
5786
- }
5787
- if (!page.more) return false;
5788
- page = await this.deps.storagePort.listInventory(page.cursor);
5789
- }
5790
- return false;
5791
- }
5792
6013
  // ── wiring internals ───────────────────────────────────────────────────────
5793
6014
  engine() {
5794
6015
  return this.currentEngine ?? this.deps.engineRef();