@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
@@ -1978,6 +1978,8 @@ var STORE_KEYS = {
1978
1978
  deliveryJournal: "delivery-journal",
1979
1979
  mintJournal: "mint-journal",
1980
1980
  nftMintJournal: "nft-mint-journal",
1981
+ customMintJournal: "custom-mint-journal",
1982
+ burnJournal: "burn-journal",
1981
1983
  shortfalls: "shortfalls",
1982
1984
  settlingLinks: "settling",
1983
1985
  streamCursor: (s) => `cursor:${s}`,
@@ -3243,6 +3245,8 @@ function createMachineStores(kv) {
3243
3245
  deliveryJournal: new DeliveryJournal(kv),
3244
3246
  mintJournal: new ListStore(kv, STORE_KEYS.mintJournal, (e) => e.mintId),
3245
3247
  nftMintJournal: new ListStore(kv, STORE_KEYS.nftMintJournal, (e) => e.mintId),
3248
+ customMintJournal: new ListStore(kv, STORE_KEYS.customMintJournal, (e) => e.mintId),
3249
+ burnJournal: new ListStore(kv, STORE_KEYS.burnJournal, (e) => e.burnId),
3246
3250
  shortfalls: new ListStore(kv, STORE_KEYS.shortfalls, (e) => e.transferId)
3247
3251
  };
3248
3252
  }
@@ -3978,7 +3982,12 @@ var Converger = class {
3978
3982
  } catch (err) {
3979
3983
  retryAfterMs ??= retryAfterMsOf(err);
3980
3984
  }
3981
- for (const replay of [() => this.deps.replayMints(), () => this.deps.replayNftMints()]) {
3985
+ for (const replay of [
3986
+ () => this.deps.replayMints(),
3987
+ () => this.deps.replayNftMints(),
3988
+ () => this.deps.replayCustomMints(),
3989
+ () => this.deps.replayBurns()
3990
+ ]) {
3982
3991
  try {
3983
3992
  progress = await replay() > 0 || progress;
3984
3993
  } catch (err) {
@@ -3989,14 +3998,17 @@ var Converger = class {
3989
3998
  }
3990
3999
  /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
3991
4000
  async pendingWork() {
3992
- const [backstop, journal, mints, nftMints] = await Promise.all([
4001
+ const [backstop, journal, mints, nftMints, customMints, burns] = await Promise.all([
3993
4002
  this.deps.stores.backstop.list(),
3994
4003
  this.deps.stores.deliveryJournal.list(),
3995
4004
  this.deps.stores.mintJournal.list(),
3996
- this.deps.stores.nftMintJournal.list()
4005
+ this.deps.stores.nftMintJournal.list(),
4006
+ this.deps.stores.customMintJournal.list(),
4007
+ this.deps.stores.burnJournal.list()
3997
4008
  ]);
3998
- const mintsOpen = mints.length > 0 || nftMints.length > 0;
3999
- return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen;
4009
+ const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
4010
+ const burnsOpen = burns.some((e) => !e.settled);
4011
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
4000
4012
  }
4001
4013
  async resumePass() {
4002
4014
  const report = await resumeAll(this.filteredMachineDeps(), {
@@ -4744,6 +4756,7 @@ function mintParams(recipientPubkey, coinId, amount) {
4744
4756
 
4745
4757
  // modules/payments-v2/mint.ts
4746
4758
  var ATTENTION_MINT_UNRESOLVED = "mint:unresolved";
4759
+ var INVENTORY_SCAN_PAGE_LIMIT = 50;
4747
4760
  function runMintUnderJournal(deps, input) {
4748
4761
  return mintUnderJournal(deps, input);
4749
4762
  }
@@ -4796,6 +4809,15 @@ async function finalizeMint(deps, input) {
4796
4809
  deps.noteHeldState(token.blob.tokenId, (await deps.engine.deliveryKeys(bytes)).stateHash);
4797
4810
  deps.refreshView();
4798
4811
  }
4812
+ async function tokenInServerInventory(storagePort, tokenId) {
4813
+ let page = await storagePort.listInventory();
4814
+ for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
4815
+ if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) return true;
4816
+ if (!page.more) return false;
4817
+ page = await storagePort.listInventory(page.cursor);
4818
+ }
4819
+ return false;
4820
+ }
4799
4821
  async function replayCoinMints(deps) {
4800
4822
  let resolved = 0;
4801
4823
  for (const entry of await deps.mintJournal.list()) {
@@ -4973,6 +4995,12 @@ async function decodeView(deps, tokenId, bytes) {
4973
4995
 
4974
4996
  // modules/payments-v2/inventory/token-data.ts
4975
4997
  async function readTokenData(deps, tokenId) {
4998
+ return deps.engine.readTokenData(await heldToken(deps, tokenId));
4999
+ }
5000
+ async function readTokenJustification(deps, tokenId) {
5001
+ return deps.engine.readTokenJustification(await heldToken(deps, tokenId));
5002
+ }
5003
+ async function heldToken(deps, tokenId) {
4976
5004
  if (deps.view.stateHashOf(tokenId) === void 0) {
4977
5005
  throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
4978
5006
  }
@@ -4980,7 +5008,200 @@ async function readTokenData(deps, tokenId) {
4980
5008
  if (bytes === void 0) {
4981
5009
  throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
4982
5010
  }
4983
- return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
5011
+ return deps.engine.decodeToken({ tokenId, token: bytes });
5012
+ }
5013
+
5014
+ // modules/payments-v2/mint-custom.ts
5015
+ var HEX32 = /^[0-9a-f]{64}$/;
5016
+ function assertRequest(request) {
5017
+ if (request.tokenType.length !== 32) {
5018
+ throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
5019
+ }
5020
+ if (request.salt.length < 16 || request.salt.length > 64) {
5021
+ throw new SphereError("mintCustom: salt must be 16 to 64 bytes", "VALIDATION_ERROR");
5022
+ }
5023
+ if (request.data.length === 0) {
5024
+ throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
5025
+ }
5026
+ for (const asset of request.assets) {
5027
+ if (!HEX32.test(asset.coinId) || asset.amount <= 0n) {
5028
+ throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
5029
+ }
5030
+ }
5031
+ }
5032
+ async function runCustomMintUnderJournal(deps, input) {
5033
+ try {
5034
+ assertRequest(input.request);
5035
+ } catch (err) {
5036
+ return { success: false, error: messageOf(err) };
5037
+ }
5038
+ const entry = journalEntryOf2(input.mintId, input.request, deps.now());
5039
+ await deps.customMintJournal.upsert(entry);
5040
+ let tokenId;
5041
+ try {
5042
+ tokenId = await mintJournaled2(deps, entry, input.request.mintJustificationVerifiers);
5043
+ } catch (err) {
5044
+ deps.armHeartbeat();
5045
+ return { success: false, error: messageOf(err) };
5046
+ }
5047
+ return { success: true, tokenId };
5048
+ }
5049
+ async function replayCustomMints(deps) {
5050
+ let resolved = 0;
5051
+ for (const entry of await deps.customMintJournal.list()) {
5052
+ if (deps.isActiveOp(entry.mintId)) continue;
5053
+ try {
5054
+ if (entry.tokenId !== "" && await deps.tokenInServerInventory(entry.tokenId)) {
5055
+ await deps.customMintJournal.removeByKey(entry.mintId);
5056
+ } else {
5057
+ await mintJournaled2(deps, entry, void 0);
5058
+ }
5059
+ resolved += 1;
5060
+ } catch {
5061
+ continue;
5062
+ }
5063
+ }
5064
+ return resolved;
5065
+ }
5066
+ function journalEntryOf2(mintId, request, createdAt) {
5067
+ return {
5068
+ mintId,
5069
+ tokenId: "",
5070
+ dataHex: bytesToHex(request.data),
5071
+ saltHex: bytesToHex(request.salt),
5072
+ tokenTypeHex: bytesToHex(request.tokenType),
5073
+ justificationHex: request.justification ? bytesToHex(request.justification) : null,
5074
+ assets: request.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
5075
+ createdAt
5076
+ };
5077
+ }
5078
+ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
5079
+ const token = await deps.engine.mintDataToken(
5080
+ {
5081
+ recipientPubkey: deps.ownPubkeyBytes,
5082
+ data: hexToBytes(entry.dataHex),
5083
+ tokenType: hexToBytes(entry.tokenTypeHex),
5084
+ salt: hexToBytes(entry.saltHex),
5085
+ ...entry.justificationHex !== null ? { justification: hexToBytes(entry.justificationHex) } : {},
5086
+ ...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
5087
+ },
5088
+ { transferId: entry.mintId }
5089
+ );
5090
+ if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
5091
+ throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
5092
+ }
5093
+ if (entry.tokenId === "") {
5094
+ await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
5095
+ }
5096
+ await finalizeMint(deps, {
5097
+ mintId: entry.mintId,
5098
+ token,
5099
+ assets: entry.assets,
5100
+ journal: deps.customMintJournal
5101
+ });
5102
+ return token.blob.tokenId;
5103
+ }
5104
+
5105
+ // modules/payments-v2/burn.ts
5106
+ var HEX322 = /^[0-9a-f]{64}$/;
5107
+ async function heldToken2(deps, tokenId) {
5108
+ const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
5109
+ if (bytes === void 0) {
5110
+ throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
5111
+ }
5112
+ return deps.engine.decodeToken({ tokenId, token: bytes });
5113
+ }
5114
+ function assetsOf(token) {
5115
+ return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
5116
+ }
5117
+ async function runBurnUnderJournal(deps, input) {
5118
+ const { burnId } = input;
5119
+ const { tokenId, reasonBytes } = input.request;
5120
+ if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
5121
+ return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
5122
+ }
5123
+ let token;
5124
+ try {
5125
+ token = await heldToken2(deps, tokenId);
5126
+ if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
5127
+ throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
5128
+ }
5129
+ } catch (err) {
5130
+ return { success: false, burnId, tokenId, error: messageOf(err) };
5131
+ }
5132
+ const entry = {
5133
+ burnId,
5134
+ tokenId,
5135
+ reasonHex: bytesToHex(reasonBytes),
5136
+ burnedTokenHex: null,
5137
+ settled: false,
5138
+ assets: assetsOf(token),
5139
+ createdAt: deps.now()
5140
+ };
5141
+ await deps.burnJournal.upsert(entry);
5142
+ try {
5143
+ const settled = await burnJournaled(deps, entry, token, { applied: false });
5144
+ return { success: true, burnId, tokenId, burnedToken: hexToBytes(settled.burnedTokenHex ?? "") };
5145
+ } catch (err) {
5146
+ deps.armHeartbeat();
5147
+ return { success: false, burnId, tokenId, error: messageOf(err) };
5148
+ }
5149
+ }
5150
+ async function replayBurns(deps) {
5151
+ let progressed = 0;
5152
+ for (const entry of await deps.burnJournal.list()) {
5153
+ if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
5154
+ try {
5155
+ const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
5156
+ const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
5157
+ await burnJournaled(deps, entry, token, { applied });
5158
+ progressed += 1;
5159
+ } catch {
5160
+ continue;
5161
+ }
5162
+ }
5163
+ return progressed;
5164
+ }
5165
+ async function pendingBurns(deps) {
5166
+ return (await deps.burnJournal.list()).map((entry) => ({
5167
+ burnId: entry.burnId,
5168
+ tokenId: entry.tokenId,
5169
+ reasonBytes: hexToBytes(entry.reasonHex),
5170
+ burnedToken: entry.burnedTokenHex === null ? null : hexToBytes(entry.burnedTokenHex),
5171
+ settled: entry.settled,
5172
+ createdAt: entry.createdAt
5173
+ }));
5174
+ }
5175
+ async function acknowledgeBurn(deps, burnId) {
5176
+ const entry = await deps.burnJournal.getByKey(burnId);
5177
+ if (entry === void 0) return;
5178
+ if (!entry.settled) {
5179
+ throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
5180
+ }
5181
+ await deps.burnJournal.removeByKey(burnId);
5182
+ }
5183
+ async function burnJournaled(deps, entry, token, state) {
5184
+ let current = entry;
5185
+ if (current.burnedTokenHex === null) {
5186
+ if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
5187
+ const burned = await deps.engine.burn(
5188
+ { token, reasonBytes: hexToBytes(current.reasonHex) },
5189
+ { transferId: current.burnId }
5190
+ );
5191
+ if (burned.blob.tokenId !== current.tokenId) {
5192
+ throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
5193
+ }
5194
+ current = { ...current, burnedTokenHex: bytesToHex(burned.blob.token) };
5195
+ await deps.burnJournal.upsert(current);
5196
+ }
5197
+ if (!state.applied) {
5198
+ await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
5199
+ }
5200
+ await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
5201
+ current = { ...current, settled: true };
5202
+ await deps.burnJournal.upsert(current);
5203
+ deps.refreshView();
5204
+ return current;
4984
5205
  }
4985
5206
 
4986
5207
  // modules/payments-v2/send-whole.ts
@@ -5168,7 +5389,6 @@ async function takeSourceBlobs(deps, sourceIds) {
5168
5389
 
5169
5390
  // modules/payments-v2/PaymentsFacade.ts
5170
5391
  var MAX_RESELECT = 8;
5171
- var INVENTORY_SCAN_PAGE_LIMIT = 50;
5172
5392
  var PaymentsFacade = class {
5173
5393
  constructor(deps) {
5174
5394
  this.deps = deps;
@@ -5205,6 +5425,8 @@ var PaymentsFacade = class {
5205
5425
  refreshView: () => this.view.delta(),
5206
5426
  replayMints: () => replayCoinMints(this.mintDeps()),
5207
5427
  replayNftMints: () => replayNftMints(this.mintDeps()),
5428
+ replayCustomMints: () => replayCustomMints(this.mintDeps()),
5429
+ replayBurns: () => replayBurns(this.mintDeps()),
5208
5430
  isActiveOp: (id) => this.activeMoneyOps.has(id),
5209
5431
  emit: deps.emit,
5210
5432
  now: () => this.nowMs()
@@ -5300,6 +5522,9 @@ var PaymentsFacade = class {
5300
5522
  tokenData(tokenId) {
5301
5523
  return readTokenData(this.readDeps(), tokenId);
5302
5524
  }
5525
+ tokenJustification(tokenId) {
5526
+ return readTokenJustification(this.readDeps(), tokenId);
5527
+ }
5303
5528
  nft(tokenId) {
5304
5529
  return readNft(this.readDeps(), tokenId);
5305
5530
  }
@@ -5348,8 +5573,12 @@ var PaymentsFacade = class {
5348
5573
  return this.track(this.mintInner(coinId, amount));
5349
5574
  }
5350
5575
  mintNft(request) {
5351
- return this.track(this.ownedMint((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
5576
+ return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
5352
5577
  }
5578
+ mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
5579
+ burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
5580
+ pendingBurns = () => this.track(pendingBurns(this.mintDeps()));
5581
+ acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.mintDeps(), burnId));
5353
5582
  // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
5354
5583
  async resumeNow() {
5355
5584
  if (!this.started) return;
@@ -5373,12 +5602,15 @@ var PaymentsFacade = class {
5373
5602
  nowMs() {
5374
5603
  return (this.deps.now ?? Date.now)();
5375
5604
  }
5376
- /** One snapshot serves the coin and the NFT mint, live and replayed. */
5605
+ /** One snapshot serves the coin, NFT and custom mints and the burn, live and replayed. */
5377
5606
  mintDeps() {
5378
5607
  return {
5379
5608
  engine: this.engine(),
5380
5609
  mintJournal: this.machineStores.mintJournal,
5381
5610
  nftMintJournal: this.machineStores.nftMintJournal,
5611
+ customMintJournal: this.machineStores.customMintJournal,
5612
+ burnJournal: this.machineStores.burnJournal,
5613
+ recordSent: (input) => this.historyStore.recordSent(input),
5382
5614
  storagePort: this.deps.storagePort,
5383
5615
  recordMint: (input) => this.historyStore.recordMint(input),
5384
5616
  armHeartbeat: () => this.heartbeat.arm(),
@@ -5387,7 +5619,7 @@ var PaymentsFacade = class {
5387
5619
  ownPubkeyBytes: this.ownPubkeyBytes,
5388
5620
  nftTokenType: this.deps.nftTokenType,
5389
5621
  isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
5390
- tokenInServerInventory: (tokenId) => this.tokenInServerInventory(tokenId),
5622
+ tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
5391
5623
  emit: this.deps.emit,
5392
5624
  now: () => this.nowMs()
5393
5625
  };
@@ -5708,29 +5940,18 @@ var PaymentsFacade = class {
5708
5940
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
5709
5941
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
5710
5942
  }
5711
- return this.ownedMint((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
5943
+ return this.ownedOp((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
5712
5944
  }
5713
- /** A live mint under a fresh id — its replay stays hands-off while this attempt runs. */
5714
- async ownedMint(run) {
5715
- const mintId = this.newId();
5716
- this.activeMoneyOps.add(mintId);
5945
+ /** A live money op under a fresh id — its replay stays hands-off while this attempt runs (§7). */
5946
+ async ownedOp(run) {
5947
+ const id = this.newId();
5948
+ this.activeMoneyOps.add(id);
5717
5949
  try {
5718
- return await run(mintId);
5950
+ return await run(id);
5719
5951
  } finally {
5720
- this.activeMoneyOps.delete(mintId);
5952
+ this.activeMoneyOps.delete(id);
5721
5953
  }
5722
5954
  }
5723
- async tokenInServerInventory(tokenId) {
5724
- let page = await this.deps.storagePort.listInventory();
5725
- for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
5726
- if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) {
5727
- return true;
5728
- }
5729
- if (!page.more) return false;
5730
- page = await this.deps.storagePort.listInventory(page.cursor);
5731
- }
5732
- return false;
5733
- }
5734
5955
  // ── wiring internals ───────────────────────────────────────────────────────
5735
5956
  engine() {
5736
5957
  return this.currentEngine ?? this.deps.engineRef();