@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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ConnectHost
3
- } from "./chunks/chunk-MA32MTCD.js";
3
+ } from "./chunks/chunk-OAAY2JEA.js";
4
4
  import {
5
5
  nftContentFromWire,
6
6
  nftContentToWire
@@ -9,7 +9,7 @@ import "./chunks/chunk-3CYK5SIU.js";
9
9
  import {
10
10
  ConnectClient,
11
11
  ConnectError
12
- } from "./chunks/chunk-BRYRG2TL.js";
12
+ } from "./chunks/chunk-JYBI36GK.js";
13
13
  import {
14
14
  ALL_PERMISSIONS,
15
15
  DEFAULT_PERMISSIONS,
@@ -19,7 +19,7 @@ import {
19
19
  hasIntentPermission,
20
20
  hasMethodPermission,
21
21
  validatePermissions
22
- } from "./chunks/chunk-T4DZCXWS.js";
22
+ } from "./chunks/chunk-FGQ6CKKK.js";
23
23
  import {
24
24
  AUTO_PUSHED_EVENTS,
25
25
  ERROR_CODES,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  ConnectHost
3
- } from "../chunks/chunk-MA32MTCD.js";
3
+ } from "../chunks/chunk-OAAY2JEA.js";
4
4
  import "../chunks/chunk-3CYK5SIU.js";
5
- import "../chunks/chunk-T4DZCXWS.js";
5
+ import "../chunks/chunk-FGQ6CKKK.js";
6
6
  import "../chunks/chunk-NPYX4JAI.js";
7
7
  export {
8
8
  ConnectHost
@@ -10373,6 +10373,19 @@ async function readTokenNft(token) {
10373
10373
  }
10374
10374
  }
10375
10375
 
10376
+ // token-engine/plugin-ops.ts
10377
+ function contextWithMintVerifiers(base, verifiers) {
10378
+ const registry = new import_MintJustificationVerifierService.MintJustificationVerifierService();
10379
+ for (const verifier of verifiers) registry.register(verifier);
10380
+ return new import_VerificationContext.VerificationContext(
10381
+ base.trustBase,
10382
+ base.predicateVerifier,
10383
+ base.unicityCertificateVerifier,
10384
+ registry,
10385
+ new import_TokenIssuanceVerifierService.TokenIssuanceVerifierService(false)
10386
+ );
10387
+ }
10388
+
10376
10389
  // token-engine/split-checkpoint.ts
10377
10390
  var CHECKPOINT_VERSION = 2;
10378
10391
  var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@3.0.1";
@@ -10519,6 +10532,10 @@ var SphereTokenEngine = class {
10519
10532
  const data = token.sdkToken.genesis.data;
10520
10533
  return data ? new Uint8Array(data) : null;
10521
10534
  }
10535
+ readTokenJustification(token) {
10536
+ const justification = token.sdkToken.genesis.justification;
10537
+ return justification ? new Uint8Array(justification) : null;
10538
+ }
10522
10539
  readNft(token) {
10523
10540
  return readTokenNft(token);
10524
10541
  }
@@ -10550,7 +10567,7 @@ var SphereTokenEngine = class {
10550
10567
  const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
10551
10568
  const tokenType = params.tokenType ? new import_TokenType.TokenType(params.tokenType) : import_TokenType.TokenType.generate();
10552
10569
  const salt = params.salt ? import_TokenSalt.TokenSalt.fromBytes(params.salt) : import_TokenSalt.TokenSalt.generate();
10553
- const mintTx = await import_MintTransaction.MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt });
10570
+ const mintTx = await import_MintTransaction.MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt, justification: params.justification ?? null });
10554
10571
  const certificationData = await import_CertificationData.CertificationData.fromMintTransaction(mintTx);
10555
10572
  const proof = await this.submitAndAwaitProof(
10556
10573
  certificationData,
@@ -10560,34 +10577,30 @@ var SphereTokenEngine = class {
10560
10577
  options
10561
10578
  );
10562
10579
  const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10563
- const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
10564
- return wrapToken(token);
10580
+ const verifiers = params.mintJustificationVerifiers;
10581
+ return wrapToken(await import_Token.Token.mint(certified, verifiers ? contextWithMintVerifiers(this.deps, verifiers) : this.deps.verificationContext));
10565
10582
  }
10566
10583
  buildNftMint(params) {
10567
10584
  return planNftMint(this.deps, params);
10568
10585
  }
10569
10586
  async transfer(params, options) {
10570
10587
  this.assertOwned(params.token);
10571
- const transferId = this.resolveTransferId(options);
10572
- const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
10588
+ return this.spend(params.token, import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey), params.data ?? null, options, "Transfer certification failed");
10589
+ }
10590
+ async burn(params, options) {
10591
+ this.assertOwned(params.token);
10592
+ return this.spend(params.token, import_BurnPredicate.BurnPredicate.create(sha256(params.reasonBytes)), params.reasonBytes, options, "Burn certification failed");
10593
+ }
10594
+ async spend(token, recipient, data, options, failLabel) {
10573
10595
  const stateMask = import_StateMask.StateMask.fromBytes(
10574
- deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "stateMask")
10596
+ deriveRealization(this.privateKeyHex, this.resolveTransferId(options), this.resolveOpIndex(options), "stateMask")
10575
10597
  );
10576
- const transferTx = await import_TransferTransaction.TransferTransaction.create(params.token.sdkToken, recipient, stateMask, {
10577
- data: params.data ?? null
10578
- });
10598
+ const transferTx = await import_TransferTransaction.TransferTransaction.create(token.sdkToken, recipient, stateMask, { data });
10579
10599
  const unlockScript = await import_SignaturePredicateUnlockScript.SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
10580
10600
  const certificationData = await import_CertificationData.CertificationData.fromTransaction(transferTx, unlockScript);
10581
- const proof = await this.submitAndAwaitProof(
10582
- certificationData,
10583
- transferTx,
10584
- "Transfer certification failed",
10585
- "TRANSFER_FAILED",
10586
- options
10587
- );
10601
+ const proof = await this.submitAndAwaitProof(certificationData, transferTx, failLabel, "TRANSFER_FAILED", options);
10588
10602
  const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
10589
- const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
10590
- return wrapToken(transferred);
10603
+ return wrapToken(await token.sdkToken.transfer(certified, this.deps.verificationContext));
10591
10604
  }
10592
10605
  async split(params, options) {
10593
10606
  this.assertOwned(params.token);
@@ -10902,6 +10915,20 @@ var SphereTokenEngine = class {
10902
10915
 
10903
10916
  // token-engine/factory.ts
10904
10917
  var DEFAULT_VERIFICATION_POOL_SIZE = 4;
10918
+ function registerPluginVerifiers(registry, plugins) {
10919
+ for (const plugin of plugins ?? []) {
10920
+ for (const verifier of plugin.mintJustificationVerifiers ?? []) {
10921
+ try {
10922
+ registry.register(verifier);
10923
+ } catch (err) {
10924
+ throw new SphereError(
10925
+ `Token plugin '${plugin.id}' registers mint-reason tag ${verifier.tag} twice or over another plugin's: ` + (err instanceof Error ? err.message : String(err)),
10926
+ "INVALID_CONFIG"
10927
+ );
10928
+ }
10929
+ }
10930
+ }
10931
+ }
10905
10932
  function disposedError() {
10906
10933
  return new SphereError(
10907
10934
  "Verification worker pool disposed \u2014 the in-flight verification was cancelled",
@@ -10983,6 +11010,7 @@ async function createSphereTokenEngine(config) {
10983
11010
  mintJustificationVerifier.register(
10984
11011
  new import_SplitMintJustificationVerifier.SplitMintJustificationVerifier(decodeSpherePaymentData)
10985
11012
  );
11013
+ registerPluginVerifiers(mintJustificationVerifier, config.plugins);
10986
11014
  const deps = {
10987
11015
  client: new import_StateTransitionClient.StateTransitionClient(new import_AggregatorClient.AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
10988
11016
  trustBase,
@@ -12100,6 +12128,8 @@ var STORE_KEYS = {
12100
12128
  deliveryJournal: "delivery-journal",
12101
12129
  mintJournal: "mint-journal",
12102
12130
  nftMintJournal: "nft-mint-journal",
12131
+ customMintJournal: "custom-mint-journal",
12132
+ burnJournal: "burn-journal",
12103
12133
  shortfalls: "shortfalls",
12104
12134
  settlingLinks: "settling",
12105
12135
  streamCursor: (s) => `cursor:${s}`,
@@ -12284,6 +12314,8 @@ function createMachineStores(kv) {
12284
12314
  deliveryJournal: new DeliveryJournal(kv),
12285
12315
  mintJournal: new ListStore(kv, STORE_KEYS.mintJournal, (e) => e.mintId),
12286
12316
  nftMintJournal: new ListStore(kv, STORE_KEYS.nftMintJournal, (e) => e.mintId),
12317
+ customMintJournal: new ListStore(kv, STORE_KEYS.customMintJournal, (e) => e.mintId),
12318
+ burnJournal: new ListStore(kv, STORE_KEYS.burnJournal, (e) => e.burnId),
12287
12319
  shortfalls: new ListStore(kv, STORE_KEYS.shortfalls, (e) => e.transferId)
12288
12320
  };
12289
12321
  }
@@ -12991,7 +13023,12 @@ var Converger = class {
12991
13023
  } catch (err) {
12992
13024
  retryAfterMs ??= retryAfterMsOf(err);
12993
13025
  }
12994
- for (const replay of [() => this.deps.replayMints(), () => this.deps.replayNftMints()]) {
13026
+ for (const replay of [
13027
+ () => this.deps.replayMints(),
13028
+ () => this.deps.replayNftMints(),
13029
+ () => this.deps.replayCustomMints(),
13030
+ () => this.deps.replayBurns()
13031
+ ]) {
12995
13032
  try {
12996
13033
  progress = await replay() > 0 || progress;
12997
13034
  } catch (err) {
@@ -13002,14 +13039,17 @@ var Converger = class {
13002
13039
  }
13003
13040
  /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
13004
13041
  async pendingWork() {
13005
- const [backstop, journal, mints, nftMints] = await Promise.all([
13042
+ const [backstop, journal, mints, nftMints, customMints, burns] = await Promise.all([
13006
13043
  this.deps.stores.backstop.list(),
13007
13044
  this.deps.stores.deliveryJournal.list(),
13008
13045
  this.deps.stores.mintJournal.list(),
13009
- this.deps.stores.nftMintJournal.list()
13046
+ this.deps.stores.nftMintJournal.list(),
13047
+ this.deps.stores.customMintJournal.list(),
13048
+ this.deps.stores.burnJournal.list()
13010
13049
  ]);
13011
- const mintsOpen = mints.length > 0 || nftMints.length > 0;
13012
- return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen;
13050
+ const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
13051
+ const burnsOpen = burns.some((e) => !e.settled);
13052
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
13013
13053
  }
13014
13054
  async resumePass() {
13015
13055
  const report = await resumeAll(this.filteredMachineDeps(), {
@@ -13241,6 +13281,12 @@ async function decodeView(deps, tokenId, bytes) {
13241
13281
 
13242
13282
  // modules/payments-v2/inventory/token-data.ts
13243
13283
  async function readTokenData(deps, tokenId) {
13284
+ return deps.engine.readTokenData(await heldToken(deps, tokenId));
13285
+ }
13286
+ async function readTokenJustification(deps, tokenId) {
13287
+ return deps.engine.readTokenJustification(await heldToken(deps, tokenId));
13288
+ }
13289
+ async function heldToken(deps, tokenId) {
13244
13290
  if (deps.view.stateHashOf(tokenId) === void 0) {
13245
13291
  throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
13246
13292
  }
@@ -13248,7 +13294,7 @@ async function readTokenData(deps, tokenId) {
13248
13294
  if (bytes === void 0) {
13249
13295
  throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
13250
13296
  }
13251
- return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
13297
+ return deps.engine.decodeToken({ tokenId, token: bytes });
13252
13298
  }
13253
13299
 
13254
13300
  // modules/payments-v2/history/History.ts
@@ -15084,6 +15130,7 @@ function mintParams(recipientPubkey, coinId, amount) {
15084
15130
 
15085
15131
  // modules/payments-v2/mint.ts
15086
15132
  var ATTENTION_MINT_UNRESOLVED = "mint:unresolved";
15133
+ var INVENTORY_SCAN_PAGE_LIMIT = 50;
15087
15134
  function runMintUnderJournal(deps, input) {
15088
15135
  return mintUnderJournal(deps, input);
15089
15136
  }
@@ -15136,6 +15183,15 @@ async function finalizeMint(deps, input) {
15136
15183
  deps.noteHeldState(token.blob.tokenId, (await deps.engine.deliveryKeys(bytes)).stateHash);
15137
15184
  deps.refreshView();
15138
15185
  }
15186
+ async function tokenInServerInventory(storagePort, tokenId) {
15187
+ let page = await storagePort.listInventory();
15188
+ for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15189
+ if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) return true;
15190
+ if (!page.more) return false;
15191
+ page = await storagePort.listInventory(page.cursor);
15192
+ }
15193
+ return false;
15194
+ }
15139
15195
  async function replayCoinMints(deps) {
15140
15196
  let resolved = 0;
15141
15197
  for (const entry of await deps.mintJournal.list()) {
@@ -15239,6 +15295,199 @@ async function mintJournaled(deps, entry) {
15239
15295
  await finalizeMint(deps, { mintId: entry.mintId, token, assets: [], journal: deps.nftMintJournal });
15240
15296
  }
15241
15297
 
15298
+ // modules/payments-v2/mint-custom.ts
15299
+ var HEX32 = /^[0-9a-f]{64}$/;
15300
+ function assertRequest(request) {
15301
+ if (request.tokenType.length !== 32) {
15302
+ throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
15303
+ }
15304
+ if (request.salt.length < 16 || request.salt.length > 64) {
15305
+ throw new SphereError("mintCustom: salt must be 16 to 64 bytes", "VALIDATION_ERROR");
15306
+ }
15307
+ if (request.data.length === 0) {
15308
+ throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
15309
+ }
15310
+ for (const asset of request.assets) {
15311
+ if (!HEX32.test(asset.coinId) || asset.amount <= 0n) {
15312
+ throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
15313
+ }
15314
+ }
15315
+ }
15316
+ async function runCustomMintUnderJournal(deps, input) {
15317
+ try {
15318
+ assertRequest(input.request);
15319
+ } catch (err) {
15320
+ return { success: false, error: messageOf(err) };
15321
+ }
15322
+ const entry = journalEntryOf2(input.mintId, input.request, deps.now());
15323
+ await deps.customMintJournal.upsert(entry);
15324
+ let tokenId;
15325
+ try {
15326
+ tokenId = await mintJournaled2(deps, entry, input.request.mintJustificationVerifiers);
15327
+ } catch (err) {
15328
+ deps.armHeartbeat();
15329
+ return { success: false, error: messageOf(err) };
15330
+ }
15331
+ return { success: true, tokenId };
15332
+ }
15333
+ async function replayCustomMints(deps) {
15334
+ let resolved = 0;
15335
+ for (const entry of await deps.customMintJournal.list()) {
15336
+ if (deps.isActiveOp(entry.mintId)) continue;
15337
+ try {
15338
+ if (entry.tokenId !== "" && await deps.tokenInServerInventory(entry.tokenId)) {
15339
+ await deps.customMintJournal.removeByKey(entry.mintId);
15340
+ } else {
15341
+ await mintJournaled2(deps, entry, void 0);
15342
+ }
15343
+ resolved += 1;
15344
+ } catch {
15345
+ continue;
15346
+ }
15347
+ }
15348
+ return resolved;
15349
+ }
15350
+ function journalEntryOf2(mintId, request, createdAt) {
15351
+ return {
15352
+ mintId,
15353
+ tokenId: "",
15354
+ dataHex: bytesToHex3(request.data),
15355
+ saltHex: bytesToHex3(request.salt),
15356
+ tokenTypeHex: bytesToHex3(request.tokenType),
15357
+ justificationHex: request.justification ? bytesToHex3(request.justification) : null,
15358
+ assets: request.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
15359
+ createdAt
15360
+ };
15361
+ }
15362
+ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
15363
+ const token = await deps.engine.mintDataToken(
15364
+ {
15365
+ recipientPubkey: deps.ownPubkeyBytes,
15366
+ data: hexToBytes4(entry.dataHex),
15367
+ tokenType: hexToBytes4(entry.tokenTypeHex),
15368
+ salt: hexToBytes4(entry.saltHex),
15369
+ ...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
15370
+ ...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
15371
+ },
15372
+ { transferId: entry.mintId }
15373
+ );
15374
+ if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
15375
+ throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
15376
+ }
15377
+ if (entry.tokenId === "") {
15378
+ await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
15379
+ }
15380
+ await finalizeMint(deps, {
15381
+ mintId: entry.mintId,
15382
+ token,
15383
+ assets: entry.assets,
15384
+ journal: deps.customMintJournal
15385
+ });
15386
+ return token.blob.tokenId;
15387
+ }
15388
+
15389
+ // modules/payments-v2/burn.ts
15390
+ var HEX322 = /^[0-9a-f]{64}$/;
15391
+ async function heldToken2(deps, tokenId) {
15392
+ const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
15393
+ if (bytes === void 0) {
15394
+ throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
15395
+ }
15396
+ return deps.engine.decodeToken({ tokenId, token: bytes });
15397
+ }
15398
+ function assetsOf(token) {
15399
+ return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
15400
+ }
15401
+ async function runBurnUnderJournal(deps, input) {
15402
+ const { burnId } = input;
15403
+ const { tokenId, reasonBytes } = input.request;
15404
+ if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
15405
+ return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
15406
+ }
15407
+ let token;
15408
+ try {
15409
+ token = await heldToken2(deps, tokenId);
15410
+ if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
15411
+ throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
15412
+ }
15413
+ } catch (err) {
15414
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15415
+ }
15416
+ const entry = {
15417
+ burnId,
15418
+ tokenId,
15419
+ reasonHex: bytesToHex3(reasonBytes),
15420
+ burnedTokenHex: null,
15421
+ settled: false,
15422
+ assets: assetsOf(token),
15423
+ createdAt: deps.now()
15424
+ };
15425
+ await deps.burnJournal.upsert(entry);
15426
+ try {
15427
+ const settled = await burnJournaled(deps, entry, token, { applied: false });
15428
+ return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
15429
+ } catch (err) {
15430
+ deps.armHeartbeat();
15431
+ return { success: false, burnId, tokenId, error: messageOf(err) };
15432
+ }
15433
+ }
15434
+ async function replayBurns(deps) {
15435
+ let progressed = 0;
15436
+ for (const entry of await deps.burnJournal.list()) {
15437
+ if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
15438
+ try {
15439
+ const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
15440
+ const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
15441
+ await burnJournaled(deps, entry, token, { applied });
15442
+ progressed += 1;
15443
+ } catch {
15444
+ continue;
15445
+ }
15446
+ }
15447
+ return progressed;
15448
+ }
15449
+ async function pendingBurns(deps) {
15450
+ return (await deps.burnJournal.list()).map((entry) => ({
15451
+ burnId: entry.burnId,
15452
+ tokenId: entry.tokenId,
15453
+ reasonBytes: hexToBytes4(entry.reasonHex),
15454
+ burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
15455
+ settled: entry.settled,
15456
+ createdAt: entry.createdAt
15457
+ }));
15458
+ }
15459
+ async function acknowledgeBurn(deps, burnId) {
15460
+ const entry = await deps.burnJournal.getByKey(burnId);
15461
+ if (entry === void 0) return;
15462
+ if (!entry.settled) {
15463
+ throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
15464
+ }
15465
+ await deps.burnJournal.removeByKey(burnId);
15466
+ }
15467
+ async function burnJournaled(deps, entry, token, state) {
15468
+ let current = entry;
15469
+ if (current.burnedTokenHex === null) {
15470
+ if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
15471
+ const burned = await deps.engine.burn(
15472
+ { token, reasonBytes: hexToBytes4(current.reasonHex) },
15473
+ { transferId: current.burnId }
15474
+ );
15475
+ if (burned.blob.tokenId !== current.tokenId) {
15476
+ throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
15477
+ }
15478
+ current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
15479
+ await deps.burnJournal.upsert(current);
15480
+ }
15481
+ if (!state.applied) {
15482
+ await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
15483
+ }
15484
+ await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
15485
+ current = { ...current, settled: true };
15486
+ await deps.burnJournal.upsert(current);
15487
+ deps.refreshView();
15488
+ return current;
15489
+ }
15490
+
15242
15491
  // modules/payments-v2/send-whole.ts
15243
15492
  async function materializeWholeSpend(deps, input) {
15244
15493
  const { engine } = deps;
@@ -15424,7 +15673,6 @@ async function takeSourceBlobs(deps, sourceIds) {
15424
15673
 
15425
15674
  // modules/payments-v2/PaymentsFacade.ts
15426
15675
  var MAX_RESELECT = 8;
15427
- var INVENTORY_SCAN_PAGE_LIMIT = 50;
15428
15676
  var PaymentsFacade = class {
15429
15677
  constructor(deps) {
15430
15678
  this.deps = deps;
@@ -15461,6 +15709,8 @@ var PaymentsFacade = class {
15461
15709
  refreshView: () => this.view.delta(),
15462
15710
  replayMints: () => replayCoinMints(this.mintDeps()),
15463
15711
  replayNftMints: () => replayNftMints(this.mintDeps()),
15712
+ replayCustomMints: () => replayCustomMints(this.mintDeps()),
15713
+ replayBurns: () => replayBurns(this.mintDeps()),
15464
15714
  isActiveOp: (id) => this.activeMoneyOps.has(id),
15465
15715
  emit: deps.emit,
15466
15716
  now: () => this.nowMs()
@@ -15556,6 +15806,9 @@ var PaymentsFacade = class {
15556
15806
  tokenData(tokenId) {
15557
15807
  return readTokenData(this.readDeps(), tokenId);
15558
15808
  }
15809
+ tokenJustification(tokenId) {
15810
+ return readTokenJustification(this.readDeps(), tokenId);
15811
+ }
15559
15812
  nft(tokenId) {
15560
15813
  return readNft(this.readDeps(), tokenId);
15561
15814
  }
@@ -15604,8 +15857,12 @@ var PaymentsFacade = class {
15604
15857
  return this.track(this.mintInner(coinId, amount));
15605
15858
  }
15606
15859
  mintNft(request) {
15607
- return this.track(this.ownedMint((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15860
+ return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
15608
15861
  }
15862
+ mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
15863
+ burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
15864
+ pendingBurns = () => this.track(pendingBurns(this.mintDeps()));
15865
+ acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.mintDeps(), burnId));
15609
15866
  // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
15610
15867
  async resumeNow() {
15611
15868
  if (!this.started) return;
@@ -15629,12 +15886,15 @@ var PaymentsFacade = class {
15629
15886
  nowMs() {
15630
15887
  return (this.deps.now ?? Date.now)();
15631
15888
  }
15632
- /** One snapshot serves the coin and the NFT mint, live and replayed. */
15889
+ /** One snapshot serves the coin, NFT and custom mints and the burn, live and replayed. */
15633
15890
  mintDeps() {
15634
15891
  return {
15635
15892
  engine: this.engine(),
15636
15893
  mintJournal: this.machineStores.mintJournal,
15637
15894
  nftMintJournal: this.machineStores.nftMintJournal,
15895
+ customMintJournal: this.machineStores.customMintJournal,
15896
+ burnJournal: this.machineStores.burnJournal,
15897
+ recordSent: (input) => this.historyStore.recordSent(input),
15638
15898
  storagePort: this.deps.storagePort,
15639
15899
  recordMint: (input) => this.historyStore.recordMint(input),
15640
15900
  armHeartbeat: () => this.heartbeat.arm(),
@@ -15643,7 +15903,7 @@ var PaymentsFacade = class {
15643
15903
  ownPubkeyBytes: this.ownPubkeyBytes,
15644
15904
  nftTokenType: this.deps.nftTokenType,
15645
15905
  isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
15646
- tokenInServerInventory: (tokenId) => this.tokenInServerInventory(tokenId),
15906
+ tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
15647
15907
  emit: this.deps.emit,
15648
15908
  now: () => this.nowMs()
15649
15909
  };
@@ -15964,28 +16224,17 @@ var PaymentsFacade = class {
15964
16224
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
15965
16225
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
15966
16226
  }
15967
- return this.ownedMint((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
16227
+ return this.ownedOp((mintId) => runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount }));
15968
16228
  }
15969
- /** A live mint under a fresh id — its replay stays hands-off while this attempt runs. */
15970
- async ownedMint(run) {
15971
- const mintId = this.newId();
15972
- this.activeMoneyOps.add(mintId);
16229
+ /** A live money op under a fresh id — its replay stays hands-off while this attempt runs (§7). */
16230
+ async ownedOp(run) {
16231
+ const id = this.newId();
16232
+ this.activeMoneyOps.add(id);
15973
16233
  try {
15974
- return await run(mintId);
16234
+ return await run(id);
15975
16235
  } finally {
15976
- this.activeMoneyOps.delete(mintId);
15977
- }
15978
- }
15979
- async tokenInServerInventory(tokenId) {
15980
- let page = await this.deps.storagePort.listInventory();
15981
- for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
15982
- if (page.items.some((item) => item.tokenId === tokenId && item.status === "active")) {
15983
- return true;
15984
- }
15985
- if (!page.more) return false;
15986
- page = await this.deps.storagePort.listInventory(page.cursor);
16236
+ this.activeMoneyOps.delete(id);
15987
16237
  }
15988
- return false;
15989
16238
  }
15990
16239
  // ── wiring internals ───────────────────────────────────────────────────────
15991
16240
  engine() {
@@ -17377,6 +17626,8 @@ var Sphere = class _Sphere {
17377
17626
  * the rebuild after an api-key change share one pool configuration.
17378
17627
  */
17379
17628
  _verification;
17629
+ /** Token plugins (SphereInitOptions.plugins), applied to every engine like _verification. */
17630
+ _plugins;
17380
17631
  /** This Sphere's OWN token registry. Disposed by destroy(); never the process global. */
17381
17632
  _registry = null;
17382
17633
  // Events
@@ -17490,6 +17741,7 @@ var Sphere = class _Sphere {
17490
17741
  communications: options.communications,
17491
17742
  password: options.password,
17492
17743
  verification: options.verification,
17744
+ plugins: options.plugins,
17493
17745
  discoverAddresses: options.discoverAddresses,
17494
17746
  onProgress: options.onProgress
17495
17747
  });
@@ -17527,6 +17779,7 @@ var Sphere = class _Sphere {
17527
17779
  communications: options.communications,
17528
17780
  password: options.password,
17529
17781
  verification: options.verification,
17782
+ plugins: options.plugins,
17530
17783
  discoverAddresses: options.discoverAddresses,
17531
17784
  onProgress: options.onProgress
17532
17785
  });
@@ -17705,6 +17958,7 @@ var Sphere = class _Sphere {
17705
17958
  options.communications
17706
17959
  );
17707
17960
  sphere._verification = options.verification;
17961
+ sphere._plugins = options.plugins;
17708
17962
  sphere._paymentsV2Composition = composition;
17709
17963
  sphere._password = options.password ?? null;
17710
17964
  sphere._passwordProtected = sphere._password !== null;
@@ -17769,6 +18023,7 @@ var Sphere = class _Sphere {
17769
18023
  options.communications
17770
18024
  );
17771
18025
  sphere._verification = options.verification;
18026
+ sphere._plugins = options.plugins;
17772
18027
  sphere._paymentsV2Composition = composition;
17773
18028
  sphere._password = options.password ?? null;
17774
18029
  sphere._passwordProtected = sphere._password !== null;
@@ -17855,6 +18110,7 @@ var Sphere = class _Sphere {
17855
18110
  options.communications
17856
18111
  );
17857
18112
  sphere._verification = options.verification;
18113
+ sphere._plugins = options.plugins;
17858
18114
  sphere._paymentsV2Composition = composition;
17859
18115
  sphere._password = options.password ?? null;
17860
18116
  sphere._passwordProtected = sphere._password !== null;
@@ -20165,7 +20421,8 @@ var Sphere = class _Sphere {
20165
20421
  apiKey: oracle.getApiKey?.(),
20166
20422
  privateKey: hexToBytes4(privateKey),
20167
20423
  trustBaseJson,
20168
- ...this._verification ? { verification: this._verification } : {}
20424
+ ...this._verification ? { verification: this._verification } : {},
20425
+ ...this._plugins ? { plugins: this._plugins } : {}
20169
20426
  });
20170
20427
  } catch (err) {
20171
20428
  logger.warn(