@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
@@ -10287,6 +10287,19 @@ async function readTokenNft(token) {
10287
10287
  }
10288
10288
  }
10289
10289
 
10290
+ // token-engine/plugin-ops.ts
10291
+ function contextWithMintVerifiers(base, verifiers) {
10292
+ const registry = new MintJustificationVerifierService();
10293
+ for (const verifier of verifiers) registry.register(verifier);
10294
+ return new VerificationContext(
10295
+ base.trustBase,
10296
+ base.predicateVerifier,
10297
+ base.unicityCertificateVerifier,
10298
+ registry,
10299
+ new TokenIssuanceVerifierService(false)
10300
+ );
10301
+ }
10302
+
10290
10303
  // token-engine/split-checkpoint.ts
10291
10304
  var CHECKPOINT_VERSION = 2;
10292
10305
  var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@3.0.1";
@@ -10433,6 +10446,10 @@ var SphereTokenEngine = class {
10433
10446
  const data = token.sdkToken.genesis.data;
10434
10447
  return data ? new Uint8Array(data) : null;
10435
10448
  }
10449
+ readTokenJustification(token) {
10450
+ const justification = token.sdkToken.genesis.justification;
10451
+ return justification ? new Uint8Array(justification) : null;
10452
+ }
10436
10453
  readNft(token) {
10437
10454
  return readTokenNft(token);
10438
10455
  }
@@ -10464,7 +10481,7 @@ var SphereTokenEngine = class {
10464
10481
  const recipient = SignaturePredicate.create(params.recipientPubkey);
10465
10482
  const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
10466
10483
  const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
10467
- const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt });
10484
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt, justification: params.justification ?? null });
10468
10485
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
10469
10486
  const proof = await this.submitAndAwaitProof(
10470
10487
  certificationData,
@@ -10474,34 +10491,30 @@ var SphereTokenEngine = class {
10474
10491
  options
10475
10492
  );
10476
10493
  const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10477
- const token = await Token.mint(certified, this.deps.verificationContext);
10478
- return wrapToken(token);
10494
+ const verifiers = params.mintJustificationVerifiers;
10495
+ return wrapToken(await Token.mint(certified, verifiers ? contextWithMintVerifiers(this.deps, verifiers) : this.deps.verificationContext));
10479
10496
  }
10480
10497
  buildNftMint(params) {
10481
10498
  return planNftMint(this.deps, params);
10482
10499
  }
10483
10500
  async transfer(params, options) {
10484
10501
  this.assertOwned(params.token);
10485
- const transferId = this.resolveTransferId(options);
10486
- const recipient = SignaturePredicate.create(params.recipientPubkey);
10502
+ return this.spend(params.token, SignaturePredicate.create(params.recipientPubkey), params.data ?? null, options, "Transfer certification failed");
10503
+ }
10504
+ async burn(params, options) {
10505
+ this.assertOwned(params.token);
10506
+ return this.spend(params.token, BurnPredicate.create(sha256(params.reasonBytes)), params.reasonBytes, options, "Burn certification failed");
10507
+ }
10508
+ async spend(token, recipient, data, options, failLabel) {
10487
10509
  const stateMask = StateMask.fromBytes(
10488
- deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "stateMask")
10510
+ deriveRealization(this.privateKeyHex, this.resolveTransferId(options), this.resolveOpIndex(options), "stateMask")
10489
10511
  );
10490
- const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, {
10491
- data: params.data ?? null
10492
- });
10512
+ const transferTx = await TransferTransaction.create(token.sdkToken, recipient, stateMask, { data });
10493
10513
  const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
10494
10514
  const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
10495
- const proof = await this.submitAndAwaitProof(
10496
- certificationData,
10497
- transferTx,
10498
- "Transfer certification failed",
10499
- "TRANSFER_FAILED",
10500
- options
10501
- );
10515
+ const proof = await this.submitAndAwaitProof(certificationData, transferTx, failLabel, "TRANSFER_FAILED", options);
10502
10516
  const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10503
- const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
10504
- return wrapToken(transferred);
10517
+ return wrapToken(await token.sdkToken.transfer(certified, this.deps.verificationContext));
10505
10518
  }
10506
10519
  async split(params, options) {
10507
10520
  this.assertOwned(params.token);
@@ -10816,6 +10829,20 @@ var SphereTokenEngine = class {
10816
10829
 
10817
10830
  // token-engine/factory.ts
10818
10831
  var DEFAULT_VERIFICATION_POOL_SIZE = 4;
10832
+ function registerPluginVerifiers(registry, plugins) {
10833
+ for (const plugin of plugins ?? []) {
10834
+ for (const verifier of plugin.mintJustificationVerifiers ?? []) {
10835
+ try {
10836
+ registry.register(verifier);
10837
+ } catch (err) {
10838
+ throw new SphereError(
10839
+ `Token plugin '${plugin.id}' registers mint-reason tag ${verifier.tag} twice or over another plugin's: ` + (err instanceof Error ? err.message : String(err)),
10840
+ "INVALID_CONFIG"
10841
+ );
10842
+ }
10843
+ }
10844
+ }
10845
+ }
10819
10846
  function disposedError() {
10820
10847
  return new SphereError(
10821
10848
  "Verification worker pool disposed \u2014 the in-flight verification was cancelled",
@@ -10897,6 +10924,7 @@ async function createSphereTokenEngine(config) {
10897
10924
  mintJustificationVerifier.register(
10898
10925
  new SplitMintJustificationVerifier(decodeSpherePaymentData)
10899
10926
  );
10927
+ registerPluginVerifiers(mintJustificationVerifier, config.plugins);
10900
10928
  const deps = {
10901
10929
  client: new StateTransitionClient(new AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
10902
10930
  trustBase,
@@ -12014,6 +12042,8 @@ var STORE_KEYS = {
12014
12042
  deliveryJournal: "delivery-journal",
12015
12043
  mintJournal: "mint-journal",
12016
12044
  nftMintJournal: "nft-mint-journal",
12045
+ customMintJournal: "custom-mint-journal",
12046
+ burnJournal: "burn-journal",
12017
12047
  shortfalls: "shortfalls",
12018
12048
  settlingLinks: "settling",
12019
12049
  streamCursor: (s) => `cursor:${s}`,
@@ -12198,6 +12228,8 @@ function createMachineStores(kv) {
12198
12228
  deliveryJournal: new DeliveryJournal(kv),
12199
12229
  mintJournal: new ListStore(kv, STORE_KEYS.mintJournal, (e) => e.mintId),
12200
12230
  nftMintJournal: new ListStore(kv, STORE_KEYS.nftMintJournal, (e) => e.mintId),
12231
+ customMintJournal: new ListStore(kv, STORE_KEYS.customMintJournal, (e) => e.mintId),
12232
+ burnJournal: new ListStore(kv, STORE_KEYS.burnJournal, (e) => e.burnId),
12201
12233
  shortfalls: new ListStore(kv, STORE_KEYS.shortfalls, (e) => e.transferId)
12202
12234
  };
12203
12235
  }
@@ -12905,7 +12937,12 @@ var Converger = class {
12905
12937
  } catch (err) {
12906
12938
  retryAfterMs ??= retryAfterMsOf(err);
12907
12939
  }
12908
- for (const replay of [() => this.deps.replayMints(), () => this.deps.replayNftMints()]) {
12940
+ for (const replay of [
12941
+ () => this.deps.replayMints(),
12942
+ () => this.deps.replayNftMints(),
12943
+ () => this.deps.replayCustomMints(),
12944
+ () => this.deps.replayBurns()
12945
+ ]) {
12909
12946
  try {
12910
12947
  progress = await replay() > 0 || progress;
12911
12948
  } catch (err) {
@@ -12916,14 +12953,17 @@ var Converger = class {
12916
12953
  }
12917
12954
  /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
12918
12955
  async pendingWork() {
12919
- const [backstop, journal, mints, nftMints] = await Promise.all([
12956
+ const [backstop, journal, mints, nftMints, customMints, burns] = await Promise.all([
12920
12957
  this.deps.stores.backstop.list(),
12921
12958
  this.deps.stores.deliveryJournal.list(),
12922
12959
  this.deps.stores.mintJournal.list(),
12923
- this.deps.stores.nftMintJournal.list()
12960
+ this.deps.stores.nftMintJournal.list(),
12961
+ this.deps.stores.customMintJournal.list(),
12962
+ this.deps.stores.burnJournal.list()
12924
12963
  ]);
12925
- const mintsOpen = mints.length > 0 || nftMints.length > 0;
12926
- return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen;
12964
+ const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
12965
+ const burnsOpen = burns.some((e) => !e.settled);
12966
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
12927
12967
  }
12928
12968
  async resumePass() {
12929
12969
  const report = await resumeAll(this.filteredMachineDeps(), {
@@ -13155,6 +13195,12 @@ async function decodeView(deps, tokenId, bytes) {
13155
13195
 
13156
13196
  // modules/payments-v2/inventory/token-data.ts
13157
13197
  async function readTokenData(deps, tokenId) {
13198
+ return deps.engine.readTokenData(await heldToken(deps, tokenId));
13199
+ }
13200
+ async function readTokenJustification(deps, tokenId) {
13201
+ return deps.engine.readTokenJustification(await heldToken(deps, tokenId));
13202
+ }
13203
+ async function heldToken(deps, tokenId) {
13158
13204
  if (deps.view.stateHashOf(tokenId) === void 0) {
13159
13205
  throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
13160
13206
  }
@@ -13162,7 +13208,7 @@ async function readTokenData(deps, tokenId) {
13162
13208
  if (bytes === void 0) {
13163
13209
  throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
13164
13210
  }
13165
- return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
13211
+ return deps.engine.decodeToken({ tokenId, token: bytes });
13166
13212
  }
13167
13213
 
13168
13214
  // modules/payments-v2/history/History.ts
@@ -14998,6 +15044,7 @@ function mintParams(recipientPubkey, coinId, amount) {
14998
15044
 
14999
15045
  // modules/payments-v2/mint.ts
15000
15046
  var ATTENTION_MINT_UNRESOLVED = "mint:unresolved";
15047
+ var INVENTORY_SCAN_PAGE_LIMIT = 50;
15001
15048
  function runMintUnderJournal(deps, input) {
15002
15049
  return mintUnderJournal(deps, input);
15003
15050
  }
@@ -15050,6 +15097,15 @@ async function finalizeMint(deps, input) {
15050
15097
  deps.noteHeldState(token.blob.tokenId, (await deps.engine.deliveryKeys(bytes)).stateHash);
15051
15098
  deps.refreshView();
15052
15099
  }
15100
+ async function tokenInServerInventory(storagePort, tokenId) {
15101
+ let page = await storagePort.listInventory();
15102
+ for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15103
+ if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) return true;
15104
+ if (!page.more) return false;
15105
+ page = await storagePort.listInventory(page.cursor);
15106
+ }
15107
+ return false;
15108
+ }
15053
15109
  async function replayCoinMints(deps) {
15054
15110
  let resolved = 0;
15055
15111
  for (const entry of await deps.mintJournal.list()) {
@@ -15153,6 +15209,199 @@ async function mintJournaled(deps, entry) {
15153
15209
  await finalizeMint(deps, { mintId: entry.mintId, token, assets: [], journal: deps.nftMintJournal });
15154
15210
  }
15155
15211
 
15212
+ // modules/payments-v2/mint-custom.ts
15213
+ var HEX32 = /^[0-9a-f]{64}$/;
15214
+ function assertRequest(request) {
15215
+ if (request.tokenType.length !== 32) {
15216
+ throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
15217
+ }
15218
+ if (request.salt.length < 16 || request.salt.length > 64) {
15219
+ throw new SphereError("mintCustom: salt must be 16 to 64 bytes", "VALIDATION_ERROR");
15220
+ }
15221
+ if (request.data.length === 0) {
15222
+ throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
15223
+ }
15224
+ for (const asset of request.assets) {
15225
+ if (!HEX32.test(asset.coinId) || asset.amount <= 0n) {
15226
+ throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
15227
+ }
15228
+ }
15229
+ }
15230
+ async function runCustomMintUnderJournal(deps, input) {
15231
+ try {
15232
+ assertRequest(input.request);
15233
+ } catch (err) {
15234
+ return { success: false, error: messageOf(err) };
15235
+ }
15236
+ const entry = journalEntryOf2(input.mintId, input.request, deps.now());
15237
+ await deps.customMintJournal.upsert(entry);
15238
+ let tokenId;
15239
+ try {
15240
+ tokenId = await mintJournaled2(deps, entry, input.request.mintJustificationVerifiers);
15241
+ } catch (err) {
15242
+ deps.armHeartbeat();
15243
+ return { success: false, error: messageOf(err) };
15244
+ }
15245
+ return { success: true, tokenId };
15246
+ }
15247
+ async function replayCustomMints(deps) {
15248
+ let resolved = 0;
15249
+ for (const entry of await deps.customMintJournal.list()) {
15250
+ if (deps.isActiveOp(entry.mintId)) continue;
15251
+ try {
15252
+ if (entry.tokenId !== "" && await deps.tokenInServerInventory(entry.tokenId)) {
15253
+ await deps.customMintJournal.removeByKey(entry.mintId);
15254
+ } else {
15255
+ await mintJournaled2(deps, entry, void 0);
15256
+ }
15257
+ resolved += 1;
15258
+ } catch {
15259
+ continue;
15260
+ }
15261
+ }
15262
+ return resolved;
15263
+ }
15264
+ function journalEntryOf2(mintId, request, createdAt) {
15265
+ return {
15266
+ mintId,
15267
+ tokenId: "",
15268
+ dataHex: bytesToHex3(request.data),
15269
+ saltHex: bytesToHex3(request.salt),
15270
+ tokenTypeHex: bytesToHex3(request.tokenType),
15271
+ justificationHex: request.justification ? bytesToHex3(request.justification) : null,
15272
+ assets: request.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
15273
+ createdAt
15274
+ };
15275
+ }
15276
+ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
15277
+ const token = await deps.engine.mintDataToken(
15278
+ {
15279
+ recipientPubkey: deps.ownPubkeyBytes,
15280
+ data: hexToBytes4(entry.dataHex),
15281
+ tokenType: hexToBytes4(entry.tokenTypeHex),
15282
+ salt: hexToBytes4(entry.saltHex),
15283
+ ...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
15284
+ ...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
15285
+ },
15286
+ { transferId: entry.mintId }
15287
+ );
15288
+ if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
15289
+ throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
15290
+ }
15291
+ if (entry.tokenId === "") {
15292
+ await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
15293
+ }
15294
+ await finalizeMint(deps, {
15295
+ mintId: entry.mintId,
15296
+ token,
15297
+ assets: entry.assets,
15298
+ journal: deps.customMintJournal
15299
+ });
15300
+ return token.blob.tokenId;
15301
+ }
15302
+
15303
+ // modules/payments-v2/burn.ts
15304
+ var HEX322 = /^[0-9a-f]{64}$/;
15305
+ async function heldToken2(deps, tokenId) {
15306
+ const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
15307
+ if (bytes === void 0) {
15308
+ throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
15309
+ }
15310
+ return deps.engine.decodeToken({ tokenId, token: bytes });
15311
+ }
15312
+ function assetsOf(token) {
15313
+ return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
15314
+ }
15315
+ async function runBurnUnderJournal(deps, input) {
15316
+ const { burnId } = input;
15317
+ const { tokenId, reasonBytes } = input.request;
15318
+ if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
15319
+ return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
15320
+ }
15321
+ let token;
15322
+ try {
15323
+ token = await heldToken2(deps, tokenId);
15324
+ if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
15325
+ throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
15326
+ }
15327
+ } catch (err) {
15328
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15329
+ }
15330
+ const entry = {
15331
+ burnId,
15332
+ tokenId,
15333
+ reasonHex: bytesToHex3(reasonBytes),
15334
+ burnedTokenHex: null,
15335
+ settled: false,
15336
+ assets: assetsOf(token),
15337
+ createdAt: deps.now()
15338
+ };
15339
+ await deps.burnJournal.upsert(entry);
15340
+ try {
15341
+ const settled = await burnJournaled(deps, entry, token, { applied: false });
15342
+ return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
15343
+ } catch (err) {
15344
+ deps.armHeartbeat();
15345
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15346
+ }
15347
+ }
15348
+ async function replayBurns(deps) {
15349
+ let progressed = 0;
15350
+ for (const entry of await deps.burnJournal.list()) {
15351
+ if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
15352
+ try {
15353
+ const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
15354
+ const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
15355
+ await burnJournaled(deps, entry, token, { applied });
15356
+ progressed += 1;
15357
+ } catch {
15358
+ continue;
15359
+ }
15360
+ }
15361
+ return progressed;
15362
+ }
15363
+ async function pendingBurns(deps) {
15364
+ return (await deps.burnJournal.list()).map((entry) => ({
15365
+ burnId: entry.burnId,
15366
+ tokenId: entry.tokenId,
15367
+ reasonBytes: hexToBytes4(entry.reasonHex),
15368
+ burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
15369
+ settled: entry.settled,
15370
+ createdAt: entry.createdAt
15371
+ }));
15372
+ }
15373
+ async function acknowledgeBurn(deps, burnId) {
15374
+ const entry = await deps.burnJournal.getByKey(burnId);
15375
+ if (entry === void 0) return;
15376
+ if (!entry.settled) {
15377
+ throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
15378
+ }
15379
+ await deps.burnJournal.removeByKey(burnId);
15380
+ }
15381
+ async function burnJournaled(deps, entry, token, state) {
15382
+ let current = entry;
15383
+ if (current.burnedTokenHex === null) {
15384
+ if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
15385
+ const burned = await deps.engine.burn(
15386
+ { token, reasonBytes: hexToBytes4(current.reasonHex) },
15387
+ { transferId: current.burnId }
15388
+ );
15389
+ if (burned.blob.tokenId !== current.tokenId) {
15390
+ throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
15391
+ }
15392
+ current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
15393
+ await deps.burnJournal.upsert(current);
15394
+ }
15395
+ if (!state.applied) {
15396
+ await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
15397
+ }
15398
+ await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
15399
+ current = { ...current, settled: true };
15400
+ await deps.burnJournal.upsert(current);
15401
+ deps.refreshView();
15402
+ return current;
15403
+ }
15404
+
15156
15405
  // modules/payments-v2/send-whole.ts
15157
15406
  async function materializeWholeSpend(deps, input) {
15158
15407
  const { engine } = deps;
@@ -15338,7 +15587,6 @@ async function takeSourceBlobs(deps, sourceIds) {
15338
15587
 
15339
15588
  // modules/payments-v2/PaymentsFacade.ts
15340
15589
  var MAX_RESELECT = 8;
15341
- var INVENTORY_SCAN_PAGE_LIMIT = 50;
15342
15590
  var PaymentsFacade = class {
15343
15591
  constructor(deps) {
15344
15592
  this.deps = deps;
@@ -15375,6 +15623,8 @@ var PaymentsFacade = class {
15375
15623
  refreshView: () => this.view.delta(),
15376
15624
  replayMints: () => replayCoinMints(this.mintDeps()),
15377
15625
  replayNftMints: () => replayNftMints(this.mintDeps()),
15626
+ replayCustomMints: () => replayCustomMints(this.mintDeps()),
15627
+ replayBurns: () => replayBurns(this.mintDeps()),
15378
15628
  isActiveOp: (id) => this.activeMoneyOps.has(id),
15379
15629
  emit: deps.emit,
15380
15630
  now: () => this.nowMs()
@@ -15470,6 +15720,9 @@ var PaymentsFacade = class {
15470
15720
  tokenData(tokenId) {
15471
15721
  return readTokenData(this.readDeps(), tokenId);
15472
15722
  }
15723
+ tokenJustification(tokenId) {
15724
+ return readTokenJustification(this.readDeps(), tokenId);
15725
+ }
15473
15726
  nft(tokenId) {
15474
15727
  return readNft(this.readDeps(), tokenId);
15475
15728
  }
@@ -15518,8 +15771,12 @@ var PaymentsFacade = class {
15518
15771
  return this.track(this.mintInner(coinId, amount));
15519
15772
  }
15520
15773
  mintNft(request) {
15521
- return this.track(this.ownedMint((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15774
+ return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15522
15775
  }
15776
+ mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
15777
+ burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
15778
+ pendingBurns = () => this.track(pendingBurns(this.mintDeps()));
15779
+ acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.mintDeps(), burnId));
15523
15780
  // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
15524
15781
  async resumeNow() {
15525
15782
  if (!this.started) return;
@@ -15543,12 +15800,15 @@ var PaymentsFacade = class {
15543
15800
  nowMs() {
15544
15801
  return (this.deps.now ?? Date.now)();
15545
15802
  }
15546
- /** One snapshot serves the coin and the NFT mint, live and replayed. */
15803
+ /** One snapshot serves the coin, NFT and custom mints and the burn, live and replayed. */
15547
15804
  mintDeps() {
15548
15805
  return {
15549
15806
  engine: this.engine(),
15550
15807
  mintJournal: this.machineStores.mintJournal,
15551
15808
  nftMintJournal: this.machineStores.nftMintJournal,
15809
+ customMintJournal: this.machineStores.customMintJournal,
15810
+ burnJournal: this.machineStores.burnJournal,
15811
+ recordSent: (input) => this.historyStore.recordSent(input),
15552
15812
  storagePort: this.deps.storagePort,
15553
15813
  recordMint: (input) => this.historyStore.recordMint(input),
15554
15814
  armHeartbeat: () => this.heartbeat.arm(),
@@ -15557,7 +15817,7 @@ var PaymentsFacade = class {
15557
15817
  ownPubkeyBytes: this.ownPubkeyBytes,
15558
15818
  nftTokenType: this.deps.nftTokenType,
15559
15819
  isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
15560
- tokenInServerInventory: (tokenId) => this.tokenInServerInventory(tokenId),
15820
+ tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
15561
15821
  emit: this.deps.emit,
15562
15822
  now: () => this.nowMs()
15563
15823
  };
@@ -15878,28 +16138,17 @@ var PaymentsFacade = class {
15878
16138
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
15879
16139
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
15880
16140
  }
15881
- return this.ownedMint((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
16141
+ return this.ownedOp((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
15882
16142
  }
15883
- /** A live mint under a fresh id — its replay stays hands-off while this attempt runs. */
15884
- async ownedMint(run) {
15885
- const mintId = this.newId();
15886
- this.activeMoneyOps.add(mintId);
16143
+ /** A live money op under a fresh id — its replay stays hands-off while this attempt runs (§7). */
16144
+ async ownedOp(run) {
16145
+ const id = this.newId();
16146
+ this.activeMoneyOps.add(id);
15887
16147
  try {
15888
- return await run(mintId);
16148
+ return await run(id);
15889
16149
  } finally {
15890
- this.activeMoneyOps.delete(mintId);
15891
- }
15892
- }
15893
- async tokenInServerInventory(tokenId) {
15894
- let page = await this.deps.storagePort.listInventory();
15895
- for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15896
- if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) {
15897
- return true;
15898
- }
15899
- if (!page.more) return false;
15900
- page = await this.deps.storagePort.listInventory(page.cursor);
16150
+ this.activeMoneyOps.delete(id);
15901
16151
  }
15902
- return false;
15903
16152
  }
15904
16153
  // ── wiring internals ───────────────────────────────────────────────────────
15905
16154
  engine() {
@@ -17291,6 +17540,8 @@ var Sphere = class _Sphere {
17291
17540
  * the rebuild after an api-key change share one pool configuration.
17292
17541
  */
17293
17542
  _verification;
17543
+ /** Token plugins (SphereInitOptions.plugins), applied to every engine like _verification. */
17544
+ _plugins;
17294
17545
  /** This Sphere's OWN token registry. Disposed by destroy(); never the process global. */
17295
17546
  _registry = null;
17296
17547
  // Events
@@ -17404,6 +17655,7 @@ var Sphere = class _Sphere {
17404
17655
  communications: options.communications,
17405
17656
  password: options.password,
17406
17657
  verification: options.verification,
17658
+ plugins: options.plugins,
17407
17659
  discoverAddresses: options.discoverAddresses,
17408
17660
  onProgress: options.onProgress
17409
17661
  });
@@ -17441,6 +17693,7 @@ var Sphere = class _Sphere {
17441
17693
  communications: options.communications,
17442
17694
  password: options.password,
17443
17695
  verification: options.verification,
17696
+ plugins: options.plugins,
17444
17697
  discoverAddresses: options.discoverAddresses,
17445
17698
  onProgress: options.onProgress
17446
17699
  });
@@ -17619,6 +17872,7 @@ var Sphere = class _Sphere {
17619
17872
  options.communications
17620
17873
  );
17621
17874
  sphere._verification = options.verification;
17875
+ sphere._plugins = options.plugins;
17622
17876
  sphere._paymentsV2Composition = composition;
17623
17877
  sphere._password = options.password ?? null;
17624
17878
  sphere._passwordProtected = sphere._password !== null;
@@ -17683,6 +17937,7 @@ var Sphere = class _Sphere {
17683
17937
  options.communications
17684
17938
  );
17685
17939
  sphere._verification = options.verification;
17940
+ sphere._plugins = options.plugins;
17686
17941
  sphere._paymentsV2Composition = composition;
17687
17942
  sphere._password = options.password ?? null;
17688
17943
  sphere._passwordProtected = sphere._password !== null;
@@ -17769,6 +18024,7 @@ var Sphere = class _Sphere {
17769
18024
  options.communications
17770
18025
  );
17771
18026
  sphere._verification = options.verification;
18027
+ sphere._plugins = options.plugins;
17772
18028
  sphere._paymentsV2Composition = composition;
17773
18029
  sphere._password = options.password ?? null;
17774
18030
  sphere._passwordProtected = sphere._password !== null;
@@ -20079,7 +20335,8 @@ var Sphere = class _Sphere {
20079
20335
  apiKey: oracle.getApiKey?.(),
20080
20336
  privateKey: hexToBytes4(privateKey),
20081
20337
  trustBaseJson,
20082
- ...this._verification ? { verification: this._verification } : {}
20338
+ ...this._verification ? { verification: this._verification } : {},
20339
+ ...this._plugins ? { plugins: this._plugins } : {}
20083
20340
  });
20084
20341
  } catch (err) {
20085
20342
  logger.warn(