@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
package/dist/index.js CHANGED
@@ -10321,6 +10321,19 @@ async function readTokenNft(token) {
10321
10321
  }
10322
10322
  }
10323
10323
 
10324
+ // token-engine/plugin-ops.ts
10325
+ function contextWithMintVerifiers(base, verifiers) {
10326
+ const registry = new MintJustificationVerifierService();
10327
+ for (const verifier of verifiers) registry.register(verifier);
10328
+ return new VerificationContext(
10329
+ base.trustBase,
10330
+ base.predicateVerifier,
10331
+ base.unicityCertificateVerifier,
10332
+ registry,
10333
+ new TokenIssuanceVerifierService(false)
10334
+ );
10335
+ }
10336
+
10324
10337
  // token-engine/split-checkpoint.ts
10325
10338
  var CHECKPOINT_VERSION = 2;
10326
10339
  var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@3.0.1";
@@ -10467,6 +10480,10 @@ var SphereTokenEngine = class {
10467
10480
  const data = token.sdkToken.genesis.data;
10468
10481
  return data ? new Uint8Array(data) : null;
10469
10482
  }
10483
+ readTokenJustification(token) {
10484
+ const justification = token.sdkToken.genesis.justification;
10485
+ return justification ? new Uint8Array(justification) : null;
10486
+ }
10470
10487
  readNft(token) {
10471
10488
  return readTokenNft(token);
10472
10489
  }
@@ -10498,7 +10515,7 @@ var SphereTokenEngine = class {
10498
10515
  const recipient = SignaturePredicate.create(params.recipientPubkey);
10499
10516
  const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
10500
10517
  const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
10501
- const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt });
10518
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt, justification: params.justification ?? null });
10502
10519
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
10503
10520
  const proof = await this.submitAndAwaitProof(
10504
10521
  certificationData,
@@ -10508,34 +10525,30 @@ var SphereTokenEngine = class {
10508
10525
  options
10509
10526
  );
10510
10527
  const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10511
- const token = await Token.mint(certified, this.deps.verificationContext);
10512
- return wrapToken(token);
10528
+ const verifiers = params.mintJustificationVerifiers;
10529
+ return wrapToken(await Token.mint(certified, verifiers ? contextWithMintVerifiers(this.deps, verifiers) : this.deps.verificationContext));
10513
10530
  }
10514
10531
  buildNftMint(params) {
10515
10532
  return planNftMint(this.deps, params);
10516
10533
  }
10517
10534
  async transfer(params, options) {
10518
10535
  this.assertOwned(params.token);
10519
- const transferId = this.resolveTransferId(options);
10520
- const recipient = SignaturePredicate.create(params.recipientPubkey);
10536
+ return this.spend(params.token, SignaturePredicate.create(params.recipientPubkey), params.data ?? null, options, "Transfer certification failed");
10537
+ }
10538
+ async burn(params, options) {
10539
+ this.assertOwned(params.token);
10540
+ return this.spend(params.token, BurnPredicate.create(sha256(params.reasonBytes)), params.reasonBytes, options, "Burn certification failed");
10541
+ }
10542
+ async spend(token, recipient, data, options, failLabel) {
10521
10543
  const stateMask = StateMask.fromBytes(
10522
- deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "stateMask")
10544
+ deriveRealization(this.privateKeyHex, this.resolveTransferId(options), this.resolveOpIndex(options), "stateMask")
10523
10545
  );
10524
- const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, {
10525
- data: params.data ?? null
10526
- });
10546
+ const transferTx = await TransferTransaction.create(token.sdkToken, recipient, stateMask, { data });
10527
10547
  const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
10528
10548
  const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
10529
- const proof = await this.submitAndAwaitProof(
10530
- certificationData,
10531
- transferTx,
10532
- "Transfer certification failed",
10533
- "TRANSFER_FAILED",
10534
- options
10535
- );
10549
+ const proof = await this.submitAndAwaitProof(certificationData, transferTx, failLabel, "TRANSFER_FAILED", options);
10536
10550
  const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10537
- const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
10538
- return wrapToken(transferred);
10551
+ return wrapToken(await token.sdkToken.transfer(certified, this.deps.verificationContext));
10539
10552
  }
10540
10553
  async split(params, options) {
10541
10554
  this.assertOwned(params.token);
@@ -10850,6 +10863,20 @@ var SphereTokenEngine = class {
10850
10863
 
10851
10864
  // token-engine/factory.ts
10852
10865
  var DEFAULT_VERIFICATION_POOL_SIZE = 4;
10866
+ function registerPluginVerifiers(registry, plugins) {
10867
+ for (const plugin of plugins ?? []) {
10868
+ for (const verifier of plugin.mintJustificationVerifiers ?? []) {
10869
+ try {
10870
+ registry.register(verifier);
10871
+ } catch (err) {
10872
+ throw new SphereError(
10873
+ `Token plugin '${plugin.id}' registers mint-reason tag ${verifier.tag} twice or over another plugin's: ` + (err instanceof Error ? err.message : String(err)),
10874
+ "INVALID_CONFIG"
10875
+ );
10876
+ }
10877
+ }
10878
+ }
10879
+ }
10853
10880
  function disposedError() {
10854
10881
  return new SphereError(
10855
10882
  "Verification worker pool disposed \u2014 the in-flight verification was cancelled",
@@ -10931,6 +10958,7 @@ async function createSphereTokenEngine(config) {
10931
10958
  mintJustificationVerifier.register(
10932
10959
  new SplitMintJustificationVerifier(decodeSpherePaymentData)
10933
10960
  );
10961
+ registerPluginVerifiers(mintJustificationVerifier, config.plugins);
10934
10962
  const deps = {
10935
10963
  client: new StateTransitionClient(new AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
10936
10964
  trustBase,
@@ -12048,6 +12076,8 @@ var STORE_KEYS = {
12048
12076
  deliveryJournal: "delivery-journal",
12049
12077
  mintJournal: "mint-journal",
12050
12078
  nftMintJournal: "nft-mint-journal",
12079
+ customMintJournal: "custom-mint-journal",
12080
+ burnJournal: "burn-journal",
12051
12081
  shortfalls: "shortfalls",
12052
12082
  settlingLinks: "settling",
12053
12083
  streamCursor: (s) => `cursor:${s}`,
@@ -12232,6 +12262,8 @@ function createMachineStores(kv) {
12232
12262
  deliveryJournal: new DeliveryJournal(kv),
12233
12263
  mintJournal: new ListStore(kv, STORE_KEYS.mintJournal, (e) => e.mintId),
12234
12264
  nftMintJournal: new ListStore(kv, STORE_KEYS.nftMintJournal, (e) => e.mintId),
12265
+ customMintJournal: new ListStore(kv, STORE_KEYS.customMintJournal, (e) => e.mintId),
12266
+ burnJournal: new ListStore(kv, STORE_KEYS.burnJournal, (e) => e.burnId),
12235
12267
  shortfalls: new ListStore(kv, STORE_KEYS.shortfalls, (e) => e.transferId)
12236
12268
  };
12237
12269
  }
@@ -12939,7 +12971,12 @@ var Converger = class {
12939
12971
  } catch (err) {
12940
12972
  retryAfterMs ??= retryAfterMsOf(err);
12941
12973
  }
12942
- for (const replay of [() => this.deps.replayMints(), () => this.deps.replayNftMints()]) {
12974
+ for (const replay of [
12975
+ () => this.deps.replayMints(),
12976
+ () => this.deps.replayNftMints(),
12977
+ () => this.deps.replayCustomMints(),
12978
+ () => this.deps.replayBurns()
12979
+ ]) {
12943
12980
  try {
12944
12981
  progress = await replay() > 0 || progress;
12945
12982
  } catch (err) {
@@ -12950,14 +12987,17 @@ var Converger = class {
12950
12987
  }
12951
12988
  /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
12952
12989
  async pendingWork() {
12953
- const [backstop, journal, mints, nftMints] = await Promise.all([
12990
+ const [backstop, journal, mints, nftMints, customMints, burns] = await Promise.all([
12954
12991
  this.deps.stores.backstop.list(),
12955
12992
  this.deps.stores.deliveryJournal.list(),
12956
12993
  this.deps.stores.mintJournal.list(),
12957
- this.deps.stores.nftMintJournal.list()
12994
+ this.deps.stores.nftMintJournal.list(),
12995
+ this.deps.stores.customMintJournal.list(),
12996
+ this.deps.stores.burnJournal.list()
12958
12997
  ]);
12959
- const mintsOpen = mints.length > 0 || nftMints.length > 0;
12960
- return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen;
12998
+ const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
12999
+ const burnsOpen = burns.some((e) => !e.settled);
13000
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
12961
13001
  }
12962
13002
  async resumePass() {
12963
13003
  const report = await resumeAll(this.filteredMachineDeps(), {
@@ -13189,6 +13229,12 @@ async function decodeView(deps, tokenId, bytes) {
13189
13229
 
13190
13230
  // modules/payments-v2/inventory/token-data.ts
13191
13231
  async function readTokenData(deps, tokenId) {
13232
+ return deps.engine.readTokenData(await heldToken(deps, tokenId));
13233
+ }
13234
+ async function readTokenJustification(deps, tokenId) {
13235
+ return deps.engine.readTokenJustification(await heldToken(deps, tokenId));
13236
+ }
13237
+ async function heldToken(deps, tokenId) {
13192
13238
  if (deps.view.stateHashOf(tokenId) === void 0) {
13193
13239
  throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
13194
13240
  }
@@ -13196,7 +13242,7 @@ async function readTokenData(deps, tokenId) {
13196
13242
  if (bytes === void 0) {
13197
13243
  throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
13198
13244
  }
13199
- return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
13245
+ return deps.engine.decodeToken({ tokenId, token: bytes });
13200
13246
  }
13201
13247
 
13202
13248
  // modules/payments-v2/history/History.ts
@@ -15032,6 +15078,7 @@ function mintParams(recipientPubkey, coinId, amount) {
15032
15078
 
15033
15079
  // modules/payments-v2/mint.ts
15034
15080
  var ATTENTION_MINT_UNRESOLVED = "mint:unresolved";
15081
+ var INVENTORY_SCAN_PAGE_LIMIT = 50;
15035
15082
  function runMintUnderJournal(deps, input) {
15036
15083
  return mintUnderJournal(deps, input);
15037
15084
  }
@@ -15084,6 +15131,15 @@ async function finalizeMint(deps, input) {
15084
15131
  deps.noteHeldState(token.blob.tokenId, (await deps.engine.deliveryKeys(bytes)).stateHash);
15085
15132
  deps.refreshView();
15086
15133
  }
15134
+ async function tokenInServerInventory(storagePort, tokenId) {
15135
+ let page = await storagePort.listInventory();
15136
+ for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15137
+ if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) return true;
15138
+ if (!page.more) return false;
15139
+ page = await storagePort.listInventory(page.cursor);
15140
+ }
15141
+ return false;
15142
+ }
15087
15143
  async function replayCoinMints(deps) {
15088
15144
  let resolved = 0;
15089
15145
  for (const entry of await deps.mintJournal.list()) {
@@ -15187,6 +15243,199 @@ async function mintJournaled(deps, entry) {
15187
15243
  await finalizeMint(deps, { mintId: entry.mintId, token, assets: [], journal: deps.nftMintJournal });
15188
15244
  }
15189
15245
 
15246
+ // modules/payments-v2/mint-custom.ts
15247
+ var HEX32 = /^[0-9a-f]{64}$/;
15248
+ function assertRequest(request) {
15249
+ if (request.tokenType.length !== 32) {
15250
+ throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
15251
+ }
15252
+ if (request.salt.length < 16 || request.salt.length > 64) {
15253
+ throw new SphereError("mintCustom: salt must be 16 to 64 bytes", "VALIDATION_ERROR");
15254
+ }
15255
+ if (request.data.length === 0) {
15256
+ throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
15257
+ }
15258
+ for (const asset of request.assets) {
15259
+ if (!HEX32.test(asset.coinId) || asset.amount <= 0n) {
15260
+ throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
15261
+ }
15262
+ }
15263
+ }
15264
+ async function runCustomMintUnderJournal(deps, input) {
15265
+ try {
15266
+ assertRequest(input.request);
15267
+ } catch (err) {
15268
+ return { success: false, error: messageOf(err) };
15269
+ }
15270
+ const entry = journalEntryOf2(input.mintId, input.request, deps.now());
15271
+ await deps.customMintJournal.upsert(entry);
15272
+ let tokenId;
15273
+ try {
15274
+ tokenId = await mintJournaled2(deps, entry, input.request.mintJustificationVerifiers);
15275
+ } catch (err) {
15276
+ deps.armHeartbeat();
15277
+ return { success: false, error: messageOf(err) };
15278
+ }
15279
+ return { success: true, tokenId };
15280
+ }
15281
+ async function replayCustomMints(deps) {
15282
+ let resolved = 0;
15283
+ for (const entry of await deps.customMintJournal.list()) {
15284
+ if (deps.isActiveOp(entry.mintId)) continue;
15285
+ try {
15286
+ if (entry.tokenId !== "" && await deps.tokenInServerInventory(entry.tokenId)) {
15287
+ await deps.customMintJournal.removeByKey(entry.mintId);
15288
+ } else {
15289
+ await mintJournaled2(deps, entry, void 0);
15290
+ }
15291
+ resolved += 1;
15292
+ } catch {
15293
+ continue;
15294
+ }
15295
+ }
15296
+ return resolved;
15297
+ }
15298
+ function journalEntryOf2(mintId, request, createdAt) {
15299
+ return {
15300
+ mintId,
15301
+ tokenId: "",
15302
+ dataHex: bytesToHex3(request.data),
15303
+ saltHex: bytesToHex3(request.salt),
15304
+ tokenTypeHex: bytesToHex3(request.tokenType),
15305
+ justificationHex: request.justification ? bytesToHex3(request.justification) : null,
15306
+ assets: request.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
15307
+ createdAt
15308
+ };
15309
+ }
15310
+ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
15311
+ const token = await deps.engine.mintDataToken(
15312
+ {
15313
+ recipientPubkey: deps.ownPubkeyBytes,
15314
+ data: hexToBytes4(entry.dataHex),
15315
+ tokenType: hexToBytes4(entry.tokenTypeHex),
15316
+ salt: hexToBytes4(entry.saltHex),
15317
+ ...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
15318
+ ...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
15319
+ },
15320
+ { transferId: entry.mintId }
15321
+ );
15322
+ if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
15323
+ throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
15324
+ }
15325
+ if (entry.tokenId === "") {
15326
+ await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
15327
+ }
15328
+ await finalizeMint(deps, {
15329
+ mintId: entry.mintId,
15330
+ token,
15331
+ assets: entry.assets,
15332
+ journal: deps.customMintJournal
15333
+ });
15334
+ return token.blob.tokenId;
15335
+ }
15336
+
15337
+ // modules/payments-v2/burn.ts
15338
+ var HEX322 = /^[0-9a-f]{64}$/;
15339
+ async function heldToken2(deps, tokenId) {
15340
+ const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
15341
+ if (bytes === void 0) {
15342
+ throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
15343
+ }
15344
+ return deps.engine.decodeToken({ tokenId, token: bytes });
15345
+ }
15346
+ function assetsOf(token) {
15347
+ return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
15348
+ }
15349
+ async function runBurnUnderJournal(deps, input) {
15350
+ const { burnId } = input;
15351
+ const { tokenId, reasonBytes } = input.request;
15352
+ if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
15353
+ return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
15354
+ }
15355
+ let token;
15356
+ try {
15357
+ token = await heldToken2(deps, tokenId);
15358
+ if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
15359
+ throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
15360
+ }
15361
+ } catch (err) {
15362
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15363
+ }
15364
+ const entry = {
15365
+ burnId,
15366
+ tokenId,
15367
+ reasonHex: bytesToHex3(reasonBytes),
15368
+ burnedTokenHex: null,
15369
+ settled: false,
15370
+ assets: assetsOf(token),
15371
+ createdAt: deps.now()
15372
+ };
15373
+ await deps.burnJournal.upsert(entry);
15374
+ try {
15375
+ const settled = await burnJournaled(deps, entry, token, { applied: false });
15376
+ return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
15377
+ } catch (err) {
15378
+ deps.armHeartbeat();
15379
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15380
+ }
15381
+ }
15382
+ async function replayBurns(deps) {
15383
+ let progressed = 0;
15384
+ for (const entry of await deps.burnJournal.list()) {
15385
+ if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
15386
+ try {
15387
+ const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
15388
+ const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
15389
+ await burnJournaled(deps, entry, token, { applied });
15390
+ progressed += 1;
15391
+ } catch {
15392
+ continue;
15393
+ }
15394
+ }
15395
+ return progressed;
15396
+ }
15397
+ async function pendingBurns(deps) {
15398
+ return (await deps.burnJournal.list()).map((entry) => ({
15399
+ burnId: entry.burnId,
15400
+ tokenId: entry.tokenId,
15401
+ reasonBytes: hexToBytes4(entry.reasonHex),
15402
+ burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
15403
+ settled: entry.settled,
15404
+ createdAt: entry.createdAt
15405
+ }));
15406
+ }
15407
+ async function acknowledgeBurn(deps, burnId) {
15408
+ const entry = await deps.burnJournal.getByKey(burnId);
15409
+ if (entry === void 0) return;
15410
+ if (!entry.settled) {
15411
+ throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
15412
+ }
15413
+ await deps.burnJournal.removeByKey(burnId);
15414
+ }
15415
+ async function burnJournaled(deps, entry, token, state) {
15416
+ let current = entry;
15417
+ if (current.burnedTokenHex === null) {
15418
+ if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
15419
+ const burned = await deps.engine.burn(
15420
+ { token, reasonBytes: hexToBytes4(current.reasonHex) },
15421
+ { transferId: current.burnId }
15422
+ );
15423
+ if (burned.blob.tokenId !== current.tokenId) {
15424
+ throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
15425
+ }
15426
+ current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
15427
+ await deps.burnJournal.upsert(current);
15428
+ }
15429
+ if (!state.applied) {
15430
+ await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
15431
+ }
15432
+ await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
15433
+ current = { ...current, settled: true };
15434
+ await deps.burnJournal.upsert(current);
15435
+ deps.refreshView();
15436
+ return current;
15437
+ }
15438
+
15190
15439
  // modules/payments-v2/send-whole.ts
15191
15440
  async function materializeWholeSpend(deps, input) {
15192
15441
  const { engine } = deps;
@@ -15372,7 +15621,6 @@ async function takeSourceBlobs(deps, sourceIds) {
15372
15621
 
15373
15622
  // modules/payments-v2/PaymentsFacade.ts
15374
15623
  var MAX_RESELECT = 8;
15375
- var INVENTORY_SCAN_PAGE_LIMIT = 50;
15376
15624
  var PaymentsFacade = class {
15377
15625
  constructor(deps) {
15378
15626
  this.deps = deps;
@@ -15409,6 +15657,8 @@ var PaymentsFacade = class {
15409
15657
  refreshView: () => this.view.delta(),
15410
15658
  replayMints: () => replayCoinMints(this.mintDeps()),
15411
15659
  replayNftMints: () => replayNftMints(this.mintDeps()),
15660
+ replayCustomMints: () => replayCustomMints(this.mintDeps()),
15661
+ replayBurns: () => replayBurns(this.mintDeps()),
15412
15662
  isActiveOp: (id) => this.activeMoneyOps.has(id),
15413
15663
  emit: deps.emit,
15414
15664
  now: () => this.nowMs()
@@ -15504,6 +15754,9 @@ var PaymentsFacade = class {
15504
15754
  tokenData(tokenId) {
15505
15755
  return readTokenData(this.readDeps(), tokenId);
15506
15756
  }
15757
+ tokenJustification(tokenId) {
15758
+ return readTokenJustification(this.readDeps(), tokenId);
15759
+ }
15507
15760
  nft(tokenId) {
15508
15761
  return readNft(this.readDeps(), tokenId);
15509
15762
  }
@@ -15552,8 +15805,12 @@ var PaymentsFacade = class {
15552
15805
  return this.track(this.mintInner(coinId, amount));
15553
15806
  }
15554
15807
  mintNft(request) {
15555
- return this.track(this.ownedMint((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15808
+ return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15556
15809
  }
15810
+ mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
15811
+ burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
15812
+ pendingBurns = () => this.track(pendingBurns(this.mintDeps()));
15813
+ acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.mintDeps(), burnId));
15557
15814
  // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
15558
15815
  async resumeNow() {
15559
15816
  if (!this.started) return;
@@ -15577,12 +15834,15 @@ var PaymentsFacade = class {
15577
15834
  nowMs() {
15578
15835
  return (this.deps.now ?? Date.now)();
15579
15836
  }
15580
- /** One snapshot serves the coin and the NFT mint, live and replayed. */
15837
+ /** One snapshot serves the coin, NFT and custom mints and the burn, live and replayed. */
15581
15838
  mintDeps() {
15582
15839
  return {
15583
15840
  engine: this.engine(),
15584
15841
  mintJournal: this.machineStores.mintJournal,
15585
15842
  nftMintJournal: this.machineStores.nftMintJournal,
15843
+ customMintJournal: this.machineStores.customMintJournal,
15844
+ burnJournal: this.machineStores.burnJournal,
15845
+ recordSent: (input) => this.historyStore.recordSent(input),
15586
15846
  storagePort: this.deps.storagePort,
15587
15847
  recordMint: (input) => this.historyStore.recordMint(input),
15588
15848
  armHeartbeat: () => this.heartbeat.arm(),
@@ -15591,7 +15851,7 @@ var PaymentsFacade = class {
15591
15851
  ownPubkeyBytes: this.ownPubkeyBytes,
15592
15852
  nftTokenType: this.deps.nftTokenType,
15593
15853
  isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
15594
- tokenInServerInventory: (tokenId) => this.tokenInServerInventory(tokenId),
15854
+ tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
15595
15855
  emit: this.deps.emit,
15596
15856
  now: () => this.nowMs()
15597
15857
  };
@@ -15912,28 +16172,17 @@ var PaymentsFacade = class {
15912
16172
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
15913
16173
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
15914
16174
  }
15915
- return this.ownedMint((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
16175
+ return this.ownedOp((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
15916
16176
  }
15917
- /** A live mint under a fresh id — its replay stays hands-off while this attempt runs. */
15918
- async ownedMint(run) {
15919
- const mintId = this.newId();
15920
- this.activeMoneyOps.add(mintId);
16177
+ /** A live money op under a fresh id — its replay stays hands-off while this attempt runs (§7). */
16178
+ async ownedOp(run) {
16179
+ const id = this.newId();
16180
+ this.activeMoneyOps.add(id);
15921
16181
  try {
15922
- return await run(mintId);
16182
+ return await run(id);
15923
16183
  } finally {
15924
- this.activeMoneyOps.delete(mintId);
15925
- }
15926
- }
15927
- async tokenInServerInventory(tokenId) {
15928
- let page = await this.deps.storagePort.listInventory();
15929
- for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15930
- if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) {
15931
- return true;
15932
- }
15933
- if (!page.more) return false;
15934
- page = await this.deps.storagePort.listInventory(page.cursor);
16184
+ this.activeMoneyOps.delete(id);
15935
16185
  }
15936
- return false;
15937
16186
  }
15938
16187
  // ── wiring internals ───────────────────────────────────────────────────────
15939
16188
  engine() {
@@ -17325,6 +17574,8 @@ var Sphere = class _Sphere {
17325
17574
  * the rebuild after an api-key change share one pool configuration.
17326
17575
  */
17327
17576
  _verification;
17577
+ /** Token plugins (SphereInitOptions.plugins), applied to every engine like _verification. */
17578
+ _plugins;
17328
17579
  /** This Sphere's OWN token registry. Disposed by destroy(); never the process global. */
17329
17580
  _registry = null;
17330
17581
  // Events
@@ -17438,6 +17689,7 @@ var Sphere = class _Sphere {
17438
17689
  communications: options.communications,
17439
17690
  password: options.password,
17440
17691
  verification: options.verification,
17692
+ plugins: options.plugins,
17441
17693
  discoverAddresses: options.discoverAddresses,
17442
17694
  onProgress: options.onProgress
17443
17695
  });
@@ -17475,6 +17727,7 @@ var Sphere = class _Sphere {
17475
17727
  communications: options.communications,
17476
17728
  password: options.password,
17477
17729
  verification: options.verification,
17730
+ plugins: options.plugins,
17478
17731
  discoverAddresses: options.discoverAddresses,
17479
17732
  onProgress: options.onProgress
17480
17733
  });
@@ -17653,6 +17906,7 @@ var Sphere = class _Sphere {
17653
17906
  options.communications
17654
17907
  );
17655
17908
  sphere._verification = options.verification;
17909
+ sphere._plugins = options.plugins;
17656
17910
  sphere._paymentsV2Composition = composition;
17657
17911
  sphere._password = options.password ?? null;
17658
17912
  sphere._passwordProtected = sphere._password !== null;
@@ -17717,6 +17971,7 @@ var Sphere = class _Sphere {
17717
17971
  options.communications
17718
17972
  );
17719
17973
  sphere._verification = options.verification;
17974
+ sphere._plugins = options.plugins;
17720
17975
  sphere._paymentsV2Composition = composition;
17721
17976
  sphere._password = options.password ?? null;
17722
17977
  sphere._passwordProtected = sphere._password !== null;
@@ -17803,6 +18058,7 @@ var Sphere = class _Sphere {
17803
18058
  options.communications
17804
18059
  );
17805
18060
  sphere._verification = options.verification;
18061
+ sphere._plugins = options.plugins;
17806
18062
  sphere._paymentsV2Composition = composition;
17807
18063
  sphere._password = options.password ?? null;
17808
18064
  sphere._passwordProtected = sphere._password !== null;
@@ -20113,7 +20369,8 @@ var Sphere = class _Sphere {
20113
20369
  apiKey: oracle.getApiKey?.(),
20114
20370
  privateKey: hexToBytes4(privateKey),
20115
20371
  trustBaseJson,
20116
- ...this._verification ? { verification: this._verification } : {}
20372
+ ...this._verification ? { verification: this._verification } : {},
20373
+ ...this._plugins ? { plugins: this._plugins } : {}
20117
20374
  });
20118
20375
  } catch (err) {
20119
20376
  logger.warn(