@unicitylabs/sphere-sdk 0.7.2 → 0.8.0-dev.0

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.
package/dist/index.cjs CHANGED
@@ -917,8 +917,8 @@ __export(index_exports, {
917
917
  NETWORKS: () => NETWORKS,
918
918
  NIP29_KINDS: () => NIP29_KINDS,
919
919
  NOSTR_EVENT_KINDS: () => NOSTR_EVENT_KINDS,
920
- NostrClient: () => import_nostr_js_sdk7.NostrClient,
921
- NostrKeyManager: () => import_nostr_js_sdk7.NostrKeyManager,
920
+ NostrClient: () => import_nostr_js_sdk6.NostrClient,
921
+ NostrKeyManager: () => import_nostr_js_sdk6.NostrKeyManager,
922
922
  PaymentsModule: () => PaymentsModule,
923
923
  SIGN_MESSAGE_PREFIX: () => SIGN_MESSAGE_PREFIX,
924
924
  STORAGE_KEYS: () => STORAGE_KEYS,
@@ -936,7 +936,7 @@ __export(index_exports, {
936
936
  TokenValidator: () => TokenValidator,
937
937
  addressesMatch: () => addressesMatch,
938
938
  archivedKeyFromTokenId: () => archivedKeyFromTokenId,
939
- areSameNametag: () => import_nostr_js_sdk6.areSameNametag,
939
+ areSameNametag: () => import_nostr_js_sdk5.areSameNametag,
940
940
  base58Decode: () => base58Decode,
941
941
  base58Encode: () => base58Encode2,
942
942
  buildManifest: () => buildManifest,
@@ -966,7 +966,7 @@ __export(index_exports, {
966
966
  decryptCMasterKey: () => decryptCMasterKey,
967
967
  decryptJson: () => decryptJson,
968
968
  decryptMnemonic: () => decryptMnemonic,
969
- decryptNametag: () => import_nostr_js_sdk6.decryptNametag,
969
+ decryptNametag: () => import_nostr_js_sdk5.decryptNametag,
970
970
  decryptPrivateKey: () => decryptPrivateKey,
971
971
  decryptSimple: () => decryptSimple,
972
972
  decryptTextFormatKey: () => decryptTextFormatKey,
@@ -979,7 +979,7 @@ __export(index_exports, {
979
979
  encodeBech32: () => encodeBech32,
980
980
  encrypt: () => encrypt2,
981
981
  encryptMnemonic: () => encryptMnemonic,
982
- encryptNametag: () => import_nostr_js_sdk6.encryptNametag,
982
+ encryptNametag: () => import_nostr_js_sdk5.encryptNametag,
983
983
  encryptSimple: () => encryptSimple,
984
984
  encryptWallet: () => encryptWallet,
985
985
  extractFromText: () => extractFromText,
@@ -1008,8 +1008,8 @@ __export(index_exports, {
1008
1008
  hasUncommittedTransactions: () => hasUncommittedTransactions,
1009
1009
  hasValidTxfData: () => hasValidTxfData,
1010
1010
  hash160: () => hash160,
1011
- hashAddressForTag: () => import_nostr_js_sdk6.hashAddressForTag,
1012
- hashNametag: () => import_nostr_js_sdk6.hashNametag,
1011
+ hashAddressForTag: () => import_nostr_js_sdk5.hashAddressForTag,
1012
+ hashNametag: () => import_nostr_js_sdk5.hashNametag,
1013
1013
  hashSignMessage: () => hashSignMessage,
1014
1014
  hexToBytes: () => hexToBytes2,
1015
1015
  hexToWIF: () => hexToWIF,
@@ -1024,7 +1024,7 @@ __export(index_exports, {
1024
1024
  isKnownToken: () => isKnownToken,
1025
1025
  isPaymentSessionTerminal: () => isPaymentSessionTerminal,
1026
1026
  isPaymentSessionTimedOut: () => isPaymentSessionTimedOut,
1027
- isPhoneNumber: () => import_nostr_js_sdk6.isPhoneNumber,
1027
+ isPhoneNumber: () => import_nostr_js_sdk5.isPhoneNumber,
1028
1028
  isSQLiteDatabase: () => isSQLiteDatabase,
1029
1029
  isSphereError: () => isSphereError,
1030
1030
  isTextWalletEncrypted: () => isTextWalletEncrypted,
@@ -1043,7 +1043,7 @@ __export(index_exports, {
1043
1043
  mnemonicToSeedSync: () => mnemonicToSeedSync2,
1044
1044
  normalizeAddress: () => normalizeAddress,
1045
1045
  normalizeCoinId: () => normalizeCoinId,
1046
- normalizeNametag: () => import_nostr_js_sdk6.normalizeNametag,
1046
+ normalizeNametag: () => import_nostr_js_sdk5.normalizeNametag,
1047
1047
  normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
1048
1048
  objectToTxf: () => objectToTxf,
1049
1049
  parseAddress: () => parseAddress,
@@ -2096,26 +2096,14 @@ var NostrTransportProvider = class _NostrTransportProvider {
2096
2096
  }
2097
2097
  /**
2098
2098
  * Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
2099
- * Computes PROXY address from nametag if available.
2100
2099
  */
2101
2100
  async bindingInfoToPeerInfo(binding, nametag) {
2102
- const nametagValue = nametag || binding.nametag;
2103
- let proxyAddress = binding.proxyAddress;
2104
- if (nametagValue && !proxyAddress) {
2105
- try {
2106
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
2107
- const proxyAddr = await ProxyAddress.fromNameTag(nametagValue);
2108
- proxyAddress = proxyAddr.toString();
2109
- } catch {
2110
- }
2111
- }
2112
2101
  return {
2113
- nametag: nametagValue,
2102
+ nametag: nametag || binding.nametag,
2114
2103
  transportPubkey: binding.transportPubkey,
2115
2104
  chainPubkey: binding.publicKey || "",
2116
2105
  l1Address: binding.l1Address || "",
2117
2106
  directAddress: binding.directAddress || "",
2118
- proxyAddress,
2119
2107
  timestamp: binding.timestamp
2120
2108
  };
2121
2109
  }
@@ -2141,7 +2129,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2141
2129
  chainPubkey: content.public_key || "",
2142
2130
  l1Address: content.l1_address || "",
2143
2131
  directAddress: content.direct_address || "",
2144
- proxyAddress: content.proxy_address || void 0,
2145
2132
  timestamp: bindingEvent.created_at * 1e3
2146
2133
  };
2147
2134
  } catch {
@@ -2184,7 +2171,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2184
2171
  chainPubkey: content.public_key || "",
2185
2172
  l1Address: content.l1_address || "",
2186
2173
  directAddress: content.direct_address || "",
2187
- proxyAddress: content.proxy_address || void 0,
2188
2174
  timestamp: event.created_at * 1e3
2189
2175
  });
2190
2176
  } catch {
@@ -2254,8 +2240,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2254
2240
  }
2255
2241
  const nostrPubkey = this.getNostrPubkey();
2256
2242
  if (nametag) {
2257
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
2258
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
2259
2243
  try {
2260
2244
  const success2 = await this.nostrClient.publishNametagBinding(
2261
2245
  nametag,
@@ -2263,8 +2247,7 @@ var NostrTransportProvider = class _NostrTransportProvider {
2263
2247
  {
2264
2248
  publicKey: chainPubkey,
2265
2249
  l1Address,
2266
- directAddress,
2267
- proxyAddress: proxyAddr.toString()
2250
+ directAddress
2268
2251
  }
2269
2252
  );
2270
2253
  if (success2) {
@@ -6468,6 +6451,13 @@ function createL1PaymentsModule(config) {
6468
6451
  return new L1PaymentsModule(config);
6469
6452
  }
6470
6453
 
6454
+ // types/v2-transfer.ts
6455
+ function isV2TransferPayload(obj) {
6456
+ if (!obj || typeof obj !== "object") return false;
6457
+ const p = obj;
6458
+ return p.type === "V2_TRANSFER" && typeof p.tokenBlob === "string" && p.tokenBlob.length > 0;
6459
+ }
6460
+
6471
6461
  // modules/payments/TokenSplitExecutor.ts
6472
6462
  init_logger();
6473
6463
  init_errors();
@@ -6841,18 +6831,117 @@ var TokenReservationLedger = class {
6841
6831
  // modules/payments/SpendQueue.ts
6842
6832
  init_logger();
6843
6833
  init_errors();
6844
- var import_Token2 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
6834
+ var import_Token3 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
6845
6835
  var import_CoinId2 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
6836
+
6837
+ // token-engine/sdk.ts
6838
+ var import_StateTransitionClient = require("state-transition-sdk-v2/lib/StateTransitionClient.js");
6839
+ var import_AggregatorClient = require("state-transition-sdk-v2/lib/api/AggregatorClient.js");
6840
+ var import_NetworkId = require("state-transition-sdk-v2/lib/api/NetworkId.js");
6841
+ var import_CertificationData = require("state-transition-sdk-v2/lib/api/CertificationData.js");
6842
+ var import_CertificationResponse = require("state-transition-sdk-v2/lib/api/CertificationResponse.js");
6843
+ var import_StateId = require("state-transition-sdk-v2/lib/api/StateId.js");
6844
+ var import_InclusionProof = require("state-transition-sdk-v2/lib/api/InclusionProof.js");
6845
+ var import_InclusionProofResponse = require("state-transition-sdk-v2/lib/api/InclusionProofResponse.js");
6846
+ var import_RootTrustBase = require("state-transition-sdk-v2/lib/api/bft/RootTrustBase.js");
6847
+ var import_InclusionProofUtils2 = require("state-transition-sdk-v2/lib/util/InclusionProofUtils.js");
6848
+ var import_Token2 = require("state-transition-sdk-v2/lib/transaction/Token.js");
6849
+ var import_MintTransaction = require("state-transition-sdk-v2/lib/transaction/MintTransaction.js");
6850
+ var import_TransferTransaction = require("state-transition-sdk-v2/lib/transaction/TransferTransaction.js");
6851
+ var import_CertifiedMintTransaction = require("state-transition-sdk-v2/lib/transaction/CertifiedMintTransaction.js");
6852
+ var import_CertifiedTransferTransaction = require("state-transition-sdk-v2/lib/transaction/CertifiedTransferTransaction.js");
6853
+ var import_TokenId2 = require("state-transition-sdk-v2/lib/transaction/TokenId.js");
6854
+ var import_TokenType = require("state-transition-sdk-v2/lib/transaction/TokenType.js");
6855
+ var import_TokenSalt = require("state-transition-sdk-v2/lib/transaction/TokenSalt.js");
6856
+ var import_MintJustificationVerifierService = require("state-transition-sdk-v2/lib/transaction/verification/MintJustificationVerifierService.js");
6857
+ var import_EncodedPredicate = require("state-transition-sdk-v2/lib/predicate/EncodedPredicate.js");
6858
+ var import_SignaturePredicate = require("state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicate.js");
6859
+ var import_SignaturePredicateUnlockScript = require("state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
6860
+ var import_BurnPredicate = require("state-transition-sdk-v2/lib/predicate/builtin/BurnPredicate.js");
6861
+ var import_PredicateVerifierService = require("state-transition-sdk-v2/lib/predicate/verification/PredicateVerifierService.js");
6862
+ var import_SigningService = require("state-transition-sdk-v2/lib/crypto/secp256k1/SigningService.js");
6863
+ var import_Signature = require("state-transition-sdk-v2/lib/crypto/secp256k1/Signature.js");
6864
+ var import_MintSigningService = require("state-transition-sdk-v2/lib/crypto/MintSigningService.js");
6865
+ var import_HashAlgorithm2 = require("state-transition-sdk-v2/lib/crypto/hash/HashAlgorithm.js");
6866
+ var import_DataHash = require("state-transition-sdk-v2/lib/crypto/hash/DataHash.js");
6867
+ var import_DataHasher = require("state-transition-sdk-v2/lib/crypto/hash/DataHasher.js");
6868
+ var import_DataHasherFactory = require("state-transition-sdk-v2/lib/crypto/hash/DataHasherFactory.js");
6869
+ var import_CborSerializer = require("state-transition-sdk-v2/lib/serialization/cbor/CborSerializer.js");
6870
+ var import_CborDeserializer = require("state-transition-sdk-v2/lib/serialization/cbor/CborDeserializer.js");
6871
+ var import_CborError = require("state-transition-sdk-v2/lib/serialization/cbor/CborError.js");
6872
+ var import_Asset = require("state-transition-sdk-v2/lib/payment/asset/Asset.js");
6873
+ var import_AssetId = require("state-transition-sdk-v2/lib/payment/asset/AssetId.js");
6874
+ var import_PaymentAssetCollection = require("state-transition-sdk-v2/lib/payment/asset/PaymentAssetCollection.js");
6875
+ var import_TokenSplit = require("state-transition-sdk-v2/lib/payment/TokenSplit.js");
6876
+ var import_SplitTokenRequest = require("state-transition-sdk-v2/lib/payment/SplitTokenRequest.js");
6877
+ var import_SplitToken = require("state-transition-sdk-v2/lib/payment/SplitToken.js");
6878
+ var import_SplitAssetProof = require("state-transition-sdk-v2/lib/payment/SplitAssetProof.js");
6879
+ var import_SplitMintJustification = require("state-transition-sdk-v2/lib/payment/SplitMintJustification.js");
6880
+ var import_SplitMintJustificationVerifier = require("state-transition-sdk-v2/lib/payment/SplitMintJustificationVerifier.js");
6881
+ var import_VerificationStatus = require("state-transition-sdk-v2/lib/verification/VerificationStatus.js");
6882
+ var import_VerificationResult = require("state-transition-sdk-v2/lib/verification/VerificationResult.js");
6883
+ var import_HexConverter = require("state-transition-sdk-v2/lib/util/HexConverter.js");
6884
+ var import_BigintConverter = require("state-transition-sdk-v2/lib/util/BigintConverter.js");
6885
+ var import_BitString = require("state-transition-sdk-v2/lib/util/BitString.js");
6886
+
6887
+ // token-engine/token-blob.ts
6888
+ var TOKEN_BLOB_TAG = 39051n;
6889
+ var TOKEN_BLOB_VERSION = 1;
6890
+ function encodeTokenBlob(blob) {
6891
+ return import_CborSerializer.CborSerializer.encodeTag(
6892
+ TOKEN_BLOB_TAG,
6893
+ import_CborSerializer.CborSerializer.encodeArray(
6894
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(BigInt(blob.v)),
6895
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(BigInt(blob.network)),
6896
+ import_CborSerializer.CborSerializer.encodeTextString(blob.tokenId),
6897
+ import_CborSerializer.CborSerializer.encodeByteString(blob.token)
6898
+ )
6899
+ );
6900
+ }
6901
+ function decodeTokenBlob(bytes) {
6902
+ const tag = import_CborDeserializer.CborDeserializer.decodeTag(bytes);
6903
+ if (tag.tag !== TOKEN_BLOB_TAG) {
6904
+ throw new import_CborError.CborError(`Invalid TokenBlob tag: ${tag.tag}`);
6905
+ }
6906
+ const fields = import_CborDeserializer.CborDeserializer.decodeArray(tag.data, 4);
6907
+ const v = Number(import_CborDeserializer.CborDeserializer.decodeUnsignedInteger(fields[0]));
6908
+ if (v !== TOKEN_BLOB_VERSION) {
6909
+ throw new import_CborError.CborError(`Unsupported TokenBlob version: ${v}`);
6910
+ }
6911
+ return {
6912
+ v,
6913
+ network: Number(import_CborDeserializer.CborDeserializer.decodeUnsignedInteger(fields[1])),
6914
+ tokenId: import_CborDeserializer.CborDeserializer.decodeTextString(fields[2]),
6915
+ token: import_CborDeserializer.CborDeserializer.decodeByteString(fields[3])
6916
+ };
6917
+ }
6918
+
6919
+ // modules/payments/SpendQueue.ts
6846
6920
  var QUEUE_TIMEOUT_MS = 3e4;
6847
6921
  var QUEUE_MAX_SIZE = 100;
6848
6922
  var TAG2 = "SpendQueue";
6849
6923
  var SpendPlanner = class {
6924
+ /**
6925
+ * Token engine (path B). When injected, value reads go through the v2 engine
6926
+ * (sdkData = engine blob); otherwise the legacy v1 SdkToken path is used. The
6927
+ * engine is wired after construction via {@link setEngine} (it is bound to the
6928
+ * payments deps, which arrive after the planner is built).
6929
+ */
6930
+ engine;
6931
+ constructor(engine) {
6932
+ this.engine = engine;
6933
+ }
6934
+ /** Inject (or clear) the token engine. */
6935
+ setEngine(engine) {
6936
+ this.engine = engine;
6937
+ }
6850
6938
  /**
6851
6939
  * Async pre-computation: parse all tokens for a given coinId.
6852
6940
  * Called BEFORE the synchronous critical section.
6853
6941
  *
6854
- * Filters to confirmed tokens matching the coinId, parses each
6855
- * token's sdkData into an SdkToken, and extracts the bigint amount.
6942
+ * Filters to confirmed tokens matching the coinId, decodes each token's
6943
+ * sdkData and extracts the bigint amount — via the v2 engine when injected,
6944
+ * else the legacy v1 SdkToken path.
6856
6945
  */
6857
6946
  async buildParsedPool(tokens, coinId) {
6858
6947
  const pool = /* @__PURE__ */ new Map();
@@ -6861,20 +6950,28 @@ var SpendPlanner = class {
6861
6950
  if (t.status !== "confirmed") continue;
6862
6951
  if (!t.sdkData) continue;
6863
6952
  try {
6864
- const parsed = JSON.parse(t.sdkData);
6865
- const sdkToken = await import_Token2.Token.fromJSON(parsed);
6866
- const realAmount = this.getTokenBalance(sdkToken, coinId);
6867
- if (realAmount <= 0n) {
6953
+ const { sdkToken, amount } = this.engine ? await this.parseViaEngine(t.sdkData, coinId) : await this.parseViaSdk(t.sdkData, coinId);
6954
+ if (amount <= 0n) {
6868
6955
  logger.warn(TAG2, `Token ${t.id} has 0 balance for coinId ${coinId}`);
6869
6956
  continue;
6870
6957
  }
6871
- pool.set(t.id, { token: t, sdkToken, amount: realAmount });
6958
+ pool.set(t.id, { token: t, sdkToken, amount });
6872
6959
  } catch (e) {
6873
6960
  logger.warn(TAG2, "Failed to parse token", t.id, e);
6874
6961
  }
6875
6962
  }
6876
6963
  return pool;
6877
6964
  }
6965
+ /** Engine path (v2): sdkData is the engine blob (hex of CBOR(TokenBlob)). */
6966
+ async parseViaEngine(sdkData, coinId) {
6967
+ const token = await this.engine.decodeToken(decodeTokenBlob(hexToBytes2(sdkData)));
6968
+ return { sdkToken: token, amount: this.engine.balanceOf(token, coinId) };
6969
+ }
6970
+ /** Legacy v1 path: sdkData is TXF JSON. */
6971
+ async parseViaSdk(sdkData, coinId) {
6972
+ const sdkToken = await import_Token3.Token.fromJSON(JSON.parse(sdkData));
6973
+ return { sdkToken, amount: this.getTokenBalance(sdkToken, coinId) };
6974
+ }
6878
6975
  /**
6879
6976
  * SYNCHRONOUS critical section. NO await allowed anywhere in this method.
6880
6977
  *
@@ -7285,174 +7382,6 @@ var SpendQueue = class {
7285
7382
  }
7286
7383
  };
7287
7384
 
7288
- // modules/payments/NametagMinter.ts
7289
- init_logger();
7290
- var import_Token3 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
7291
- var import_TokenId2 = require("@unicitylabs/state-transition-sdk/lib/token/TokenId");
7292
- var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
7293
- var import_TokenState2 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
7294
- var import_MintTransactionData = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
7295
- var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
7296
- var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
7297
- var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
7298
- var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
7299
- var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
7300
- var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
7301
- var NametagMinter = class {
7302
- client;
7303
- trustBase;
7304
- signingService;
7305
- skipVerification;
7306
- debug;
7307
- constructor(config) {
7308
- this.client = config.stateTransitionClient;
7309
- this.trustBase = config.trustBase;
7310
- this.signingService = config.signingService;
7311
- this.skipVerification = config.skipVerification ?? false;
7312
- this.debug = config.debug ?? false;
7313
- }
7314
- log(message, ...args) {
7315
- logger.debug("NametagMinter", message, ...args);
7316
- }
7317
- /**
7318
- * Check if a nametag is available (not already minted)
7319
- */
7320
- async isNametagAvailable(nametag) {
7321
- try {
7322
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
7323
- const cleanNametag = (0, import_nostr_js_sdk3.normalizeNametag)(stripped);
7324
- const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
7325
- const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
7326
- return !isMinted;
7327
- } catch (error) {
7328
- this.log("Error checking nametag availability:", error);
7329
- return false;
7330
- }
7331
- }
7332
- /**
7333
- * Mint a nametag token on-chain
7334
- *
7335
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
7336
- * @param ownerAddress - The owner's direct address
7337
- * @returns MintNametagResult with token if successful
7338
- */
7339
- async mintNametag(nametag, ownerAddress) {
7340
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
7341
- const cleanNametag = (0, import_nostr_js_sdk3.normalizeNametag)(stripped);
7342
- this.log(`Starting mint for nametag: ${cleanNametag}`);
7343
- try {
7344
- const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
7345
- const nametagTokenType = new import_TokenType.TokenType(
7346
- Buffer.from(UNICITY_TOKEN_TYPE_HEX, "hex")
7347
- );
7348
- const nametagBytes = new TextEncoder().encode(cleanNametag);
7349
- const pubKey = this.signingService.publicKey;
7350
- const saltInput = new Uint8Array(pubKey.length + nametagBytes.length);
7351
- saltInput.set(pubKey, 0);
7352
- saltInput.set(nametagBytes, pubKey.length);
7353
- const saltBuffer = await crypto.subtle.digest("SHA-256", saltInput);
7354
- const salt = new Uint8Array(saltBuffer);
7355
- this.log("Generated deterministic salt");
7356
- const mintData = await import_MintTransactionData.MintTransactionData.createFromNametag(
7357
- cleanNametag,
7358
- nametagTokenType,
7359
- ownerAddress,
7360
- salt,
7361
- ownerAddress
7362
- );
7363
- this.log("Created MintTransactionData");
7364
- const commitment = await import_MintCommitment.MintCommitment.create(mintData);
7365
- this.log("Created MintCommitment");
7366
- const MAX_RETRIES = 3;
7367
- let submitSuccess = false;
7368
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
7369
- try {
7370
- this.log(`Submitting commitment (attempt ${attempt}/${MAX_RETRIES})...`);
7371
- const response = await this.client.submitMintCommitment(commitment);
7372
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
7373
- this.log(`Commitment ${response.status === "REQUEST_ID_EXISTS" ? "already exists" : "submitted successfully"}`);
7374
- submitSuccess = true;
7375
- break;
7376
- } else {
7377
- this.log(`Commitment failed: ${response.status}`);
7378
- if (attempt === MAX_RETRIES) {
7379
- return {
7380
- success: false,
7381
- error: `Failed to submit commitment after ${MAX_RETRIES} attempts: ${response.status}`
7382
- };
7383
- }
7384
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7385
- }
7386
- } catch (error) {
7387
- this.log(`Attempt ${attempt} error:`, error);
7388
- if (attempt === MAX_RETRIES) {
7389
- return {
7390
- success: false,
7391
- error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`
7392
- };
7393
- }
7394
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7395
- }
7396
- }
7397
- if (!submitSuccess) {
7398
- return {
7399
- success: false,
7400
- error: "Failed to submit commitment after retries"
7401
- };
7402
- }
7403
- this.log("Waiting for inclusion proof...");
7404
- const inclusionProof = await (0, import_InclusionProofUtils2.waitInclusionProof)(this.trustBase, this.client, commitment);
7405
- this.log("Received inclusion proof");
7406
- const genesisTransaction = commitment.toTransaction(inclusionProof);
7407
- const nametagPredicate = await import_UnmaskedPredicate2.UnmaskedPredicate.create(
7408
- nametagTokenId,
7409
- nametagTokenType,
7410
- this.signingService,
7411
- import_HashAlgorithm2.HashAlgorithm.SHA256,
7412
- salt
7413
- );
7414
- const tokenState = new import_TokenState2.TokenState(nametagPredicate, null);
7415
- let token;
7416
- if (this.skipVerification) {
7417
- this.log("Creating token WITHOUT verification (dev mode)");
7418
- const tokenJson = {
7419
- version: "2.0",
7420
- state: tokenState.toJSON(),
7421
- genesis: genesisTransaction.toJSON(),
7422
- transactions: [],
7423
- nametags: []
7424
- };
7425
- token = await import_Token3.Token.fromJSON(tokenJson);
7426
- } else {
7427
- token = await import_Token3.Token.mint(
7428
- this.trustBase,
7429
- tokenState,
7430
- genesisTransaction
7431
- );
7432
- }
7433
- this.log(`Nametag minted successfully: ${cleanNametag}`);
7434
- const nametagData = {
7435
- name: cleanNametag,
7436
- token: token.toJSON(),
7437
- timestamp: Date.now(),
7438
- format: "txf",
7439
- version: "2.0"
7440
- };
7441
- return {
7442
- success: true,
7443
- token,
7444
- nametagData
7445
- };
7446
- } catch (error) {
7447
- this.log("Minting failed:", error);
7448
- return {
7449
- success: false,
7450
- error: error instanceof Error ? error.message : String(error)
7451
- };
7452
- }
7453
- }
7454
- };
7455
-
7456
7385
  // modules/payments/PaymentsModule.ts
7457
7386
  init_constants();
7458
7387
 
@@ -8069,6 +7998,15 @@ function txfToToken(tokenId, txf) {
8069
7998
  sdkData: JSON.stringify(txf)
8070
7999
  };
8071
8000
  }
8001
+ function isV2TokenBlob(sdkData) {
8002
+ return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
8003
+ }
8004
+ function v2TokenId(token) {
8005
+ return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
8006
+ }
8007
+ function isV2TokenEntry(entry) {
8008
+ return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
8009
+ }
8072
8010
  async function buildTxfStorageData(tokens, meta, options) {
8073
8011
  const storageData = {
8074
8012
  _meta: {
@@ -8099,6 +8037,8 @@ async function buildTxfStorageData(tokens, meta, options) {
8099
8037
  if (txf) {
8100
8038
  const actualTokenId = txf.genesis.data.tokenId;
8101
8039
  storageData[keyFromTokenId(actualTokenId)] = txf;
8040
+ } else if (isV2TokenBlob(token.sdkData)) {
8041
+ storageData[keyFromTokenId(v2TokenId(token))] = token;
8102
8042
  }
8103
8043
  }
8104
8044
  if (options?.archivedTokens && options.archivedTokens.size > 0) {
@@ -8196,6 +8136,8 @@ function parseTxfStorageData(data) {
8196
8136
  if (txfToken?.genesis?.data?.tokenId) {
8197
8137
  const token = txfToToken(tokenId, txfToken);
8198
8138
  result.tokens.push(token);
8139
+ } else if (isV2TokenEntry(storageData[key])) {
8140
+ result.tokens.push(storageData[key]);
8199
8141
  }
8200
8142
  } catch (err) {
8201
8143
  result.validationErrors.push(`Token ${tokenId}: ${err}`);
@@ -8471,12 +8413,12 @@ init_logger();
8471
8413
  init_errors();
8472
8414
  var import_Token4 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
8473
8415
  var import_TokenId3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenId");
8474
- var import_TokenState3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
8416
+ var import_TokenState2 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
8475
8417
  var import_CoinId3 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
8476
8418
  var import_TokenCoinData2 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
8477
8419
  var import_TokenSplitBuilder2 = require("@unicitylabs/state-transition-sdk/lib/transaction/split/TokenSplitBuilder");
8478
8420
  var import_HashAlgorithm3 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
8479
- var import_UnmaskedPredicate3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
8421
+ var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
8480
8422
  var import_UnmaskedPredicateReference2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
8481
8423
  var import_TransferCommitment2 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment");
8482
8424
  var import_InclusionProofUtils3 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
@@ -8598,14 +8540,14 @@ var InstantSplitExecutor = class {
8598
8540
  options?.message
8599
8541
  // on-chain message (invoice memo bytes, or null)
8600
8542
  );
8601
- const mintedPredicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
8543
+ const mintedPredicate = await import_UnmaskedPredicate2.UnmaskedPredicate.create(
8602
8544
  recipientTokenId,
8603
8545
  tokenToSplit.type,
8604
8546
  this.signingService,
8605
8547
  import_HashAlgorithm3.HashAlgorithm.SHA256,
8606
8548
  recipientSalt
8607
8549
  );
8608
- const mintedState = new import_TokenState3.TokenState(mintedPredicate, null);
8550
+ const mintedState = new import_TokenState2.TokenState(mintedPredicate, null);
8609
8551
  logger.debug("InstantSplit", "Step 5: Packaging V5 bundle...");
8610
8552
  const senderPubkey = toHex2(this.signingService.publicKey);
8611
8553
  let nametagTokenJson;
@@ -8720,14 +8662,14 @@ var InstantSplitExecutor = class {
8720
8662
  * It does NOT need the genesis transaction or mint proof.
8721
8663
  */
8722
8664
  async createTransferCommitmentFromMintData(mintData, recipientAddress, transferSalt, signingService, nametagTokens, message) {
8723
- const predicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
8665
+ const predicate = await import_UnmaskedPredicate2.UnmaskedPredicate.create(
8724
8666
  mintData.tokenId,
8725
8667
  mintData.tokenType,
8726
8668
  signingService,
8727
8669
  import_HashAlgorithm3.HashAlgorithm.SHA256,
8728
8670
  mintData.salt
8729
8671
  );
8730
- const state = new import_TokenState3.TokenState(predicate, null);
8672
+ const state = new import_TokenState2.TokenState(predicate, null);
8731
8673
  const minimalToken = {
8732
8674
  state,
8733
8675
  nametagTokens: nametagTokens || [],
@@ -8788,14 +8730,14 @@ var InstantSplitExecutor = class {
8788
8730
  message: `Mint proof received in ${proofDuration.toFixed(0)}ms`
8789
8731
  });
8790
8732
  const mintTransaction = senderMintCommitment.toTransaction(senderMintProof);
8791
- const predicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
8733
+ const predicate = await import_UnmaskedPredicate2.UnmaskedPredicate.create(
8792
8734
  context.senderTokenId,
8793
8735
  context.tokenType,
8794
8736
  context.signingService,
8795
8737
  import_HashAlgorithm3.HashAlgorithm.SHA256,
8796
8738
  context.senderSalt
8797
8739
  );
8798
- const state = new import_TokenState3.TokenState(predicate, null);
8740
+ const state = new import_TokenState2.TokenState(predicate, null);
8799
8741
  const changeToken = await import_Token4.Token.mint(this.trustBase, state, mintTransaction);
8800
8742
  if (!this.devMode) {
8801
8743
  const verification = await changeToken.verify(this.trustBase);
@@ -8875,14 +8817,14 @@ var InstantSplitExecutor = class {
8875
8817
  init_logger();
8876
8818
  init_errors();
8877
8819
  var import_Token5 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
8878
- var import_TokenState4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
8820
+ var import_TokenState3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
8879
8821
  var import_TokenType2 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
8880
8822
  var import_HashAlgorithm4 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
8881
- var import_UnmaskedPredicate4 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
8823
+ var import_UnmaskedPredicate3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
8882
8824
  var import_TransferCommitment3 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment");
8883
- var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction");
8884
- var import_MintCommitment2 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
8885
- var import_MintTransactionData2 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
8825
+ var import_TransferTransaction2 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction");
8826
+ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
8827
+ var import_MintTransactionData = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
8886
8828
  var import_InclusionProofUtils4 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
8887
8829
 
8888
8830
  // types/instant-split.ts
@@ -8975,11 +8917,11 @@ var InstantSplitProcessor = class {
8975
8917
  logger.warn("InstantSplit", "Sender pubkey mismatch (non-fatal)");
8976
8918
  }
8977
8919
  const burnTxJson = JSON.parse(bundle.burnTransaction);
8978
- const _burnTransaction = await import_TransferTransaction.TransferTransaction.fromJSON(burnTxJson);
8920
+ const _burnTransaction = await import_TransferTransaction2.TransferTransaction.fromJSON(burnTxJson);
8979
8921
  logger.debug("InstantSplit", "Burn transaction validated");
8980
8922
  const mintDataJson = JSON.parse(bundle.recipientMintData);
8981
- const mintData = await import_MintTransactionData2.MintTransactionData.fromJSON(mintDataJson);
8982
- const mintCommitment = await import_MintCommitment2.MintCommitment.create(mintData);
8923
+ const mintData = await import_MintTransactionData.MintTransactionData.fromJSON(mintDataJson);
8924
+ const mintCommitment = await import_MintCommitment.MintCommitment.create(mintData);
8983
8925
  logger.debug("InstantSplit", "Mint commitment recreated");
8984
8926
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
8985
8927
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -9011,14 +8953,14 @@ var InstantSplitProcessor = class {
9011
8953
  const transferTransaction = transferCommitment.toTransaction(transferProof);
9012
8954
  logger.debug("InstantSplit", "Transfer proof received");
9013
8955
  const transferSalt = fromHex3(bundle.transferSaltHex);
9014
- const finalRecipientPredicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
8956
+ const finalRecipientPredicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
9015
8957
  mintData.tokenId,
9016
8958
  tokenType,
9017
8959
  signingService,
9018
8960
  import_HashAlgorithm4.HashAlgorithm.SHA256,
9019
8961
  transferSalt
9020
8962
  );
9021
- const finalRecipientState = new import_TokenState4.TokenState(finalRecipientPredicate, null);
8963
+ const finalRecipientState = new import_TokenState3.TokenState(finalRecipientPredicate, null);
9022
8964
  logger.debug("InstantSplit", "Final recipient state created");
9023
8965
  let nametagTokens = [];
9024
8966
  const recipientAddressStr = bundle.recipientAddressJson;
@@ -9125,8 +9067,8 @@ var InstantSplitProcessor = class {
9125
9067
  await this.waitInclusionProofWithDevBypass(burnCommitment, options?.proofTimeoutMs);
9126
9068
  logger.debug("InstantSplit", "V4: Burn proof received");
9127
9069
  const mintDataJson = JSON.parse(bundle.recipientMintData);
9128
- const mintData = await import_MintTransactionData2.MintTransactionData.fromJSON(mintDataJson);
9129
- const mintCommitment = await import_MintCommitment2.MintCommitment.create(mintData);
9070
+ const mintData = await import_MintTransactionData.MintTransactionData.fromJSON(mintDataJson);
9071
+ const mintCommitment = await import_MintCommitment.MintCommitment.create(mintData);
9130
9072
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
9131
9073
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
9132
9074
  throw new SphereError(`Mint submission failed: ${mintResponse.status}`, "TRANSFER_FAILED");
@@ -9139,14 +9081,14 @@ var InstantSplitProcessor = class {
9139
9081
  logger.debug("InstantSplit", "V4: Mint proof received");
9140
9082
  const tokenType = new import_TokenType2.TokenType(fromHex3(bundle.tokenTypeHex));
9141
9083
  const recipientSalt = fromHex3(bundle.recipientSaltHex);
9142
- const recipientPredicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
9084
+ const recipientPredicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
9143
9085
  mintData.tokenId,
9144
9086
  tokenType,
9145
9087
  signingService,
9146
9088
  import_HashAlgorithm4.HashAlgorithm.SHA256,
9147
9089
  recipientSalt
9148
9090
  );
9149
- const recipientState = new import_TokenState4.TokenState(recipientPredicate, null);
9091
+ const recipientState = new import_TokenState3.TokenState(recipientPredicate, null);
9150
9092
  const tokenJson = {
9151
9093
  version: "2.0",
9152
9094
  state: recipientState.toJSON(),
@@ -9169,14 +9111,14 @@ var InstantSplitProcessor = class {
9169
9111
  const transferTransaction = transferCommitment.toTransaction(transferProof);
9170
9112
  logger.debug("InstantSplit", "V4: Transfer proof received");
9171
9113
  const transferSalt = fromHex3(bundle.transferSaltHex);
9172
- const finalPredicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
9114
+ const finalPredicate = await import_UnmaskedPredicate3.UnmaskedPredicate.create(
9173
9115
  mintData.tokenId,
9174
9116
  tokenType,
9175
9117
  signingService,
9176
9118
  import_HashAlgorithm4.HashAlgorithm.SHA256,
9177
9119
  transferSalt
9178
9120
  );
9179
- const finalState = new import_TokenState4.TokenState(finalPredicate, null);
9121
+ const finalState = new import_TokenState3.TokenState(finalPredicate, null);
9180
9122
  const finalTokenJson = mintedToken.toJSON();
9181
9123
  finalTokenJson.state = finalState.toJSON();
9182
9124
  finalTokenJson.transactions = [transferTransaction.toJSON()];
@@ -9227,17 +9169,17 @@ var InstantSplitProcessor = class {
9227
9169
  var import_Token6 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
9228
9170
  var import_CoinId4 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
9229
9171
  var import_TransferCommitment4 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment");
9230
- var import_TransferTransaction2 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction");
9231
- var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
9172
+ var import_TransferTransaction3 = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction");
9173
+ var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
9232
9174
  var import_AddressScheme = require("@unicitylabs/state-transition-sdk/lib/address/AddressScheme");
9233
- var import_UnmaskedPredicate5 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
9234
- var import_TokenState5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
9175
+ var import_UnmaskedPredicate4 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
9176
+ var import_TokenState4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
9235
9177
  var import_HashAlgorithm5 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
9236
9178
  var import_TokenType3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
9237
- var import_MintCommitment3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
9238
- var import_MintTransactionData3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
9179
+ var import_MintCommitment2 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
9180
+ var import_MintTransactionData2 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
9239
9181
  var import_InclusionProofUtils5 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
9240
- var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof");
9182
+ var import_InclusionProof2 = require("@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof");
9241
9183
  function computeHistoryDedupKey(type, tokenId, transferId) {
9242
9184
  if (type === "SENT" && transferId) return `${type}_transfer_${transferId}`;
9243
9185
  if (tokenId) return `${type}_${tokenId}`;
@@ -9258,7 +9200,7 @@ function enrichWithRegistry(info) {
9258
9200
  }
9259
9201
  return info;
9260
9202
  }
9261
- async function parseTokenInfo(tokenData) {
9203
+ async function parseTokenInfo(tokenData, engine) {
9262
9204
  const defaultInfo = {
9263
9205
  coinId: "ALPHA",
9264
9206
  symbol: "ALPHA",
@@ -9266,6 +9208,25 @@ async function parseTokenInfo(tokenData) {
9266
9208
  decimals: 0,
9267
9209
  amount: "0"
9268
9210
  };
9211
+ if (engine && typeof tokenData === "string" && looksLikeTokenBlob(tokenData)) {
9212
+ try {
9213
+ const token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(tokenData)));
9214
+ const first = engine.readValue(token)?.assets[0];
9215
+ if (first) {
9216
+ return enrichWithRegistry({
9217
+ coinId: first.coinId,
9218
+ symbol: first.coinId.slice(0, 8),
9219
+ name: `Token ${first.coinId.slice(0, 8)}`,
9220
+ decimals: 0,
9221
+ amount: String(first.amount),
9222
+ tokenId: engine.tokenId(token)
9223
+ });
9224
+ }
9225
+ return { ...defaultInfo, tokenId: engine.tokenId(token) };
9226
+ } catch (error) {
9227
+ logger.warn("Payments", "Failed to parse token info via engine:", error);
9228
+ }
9229
+ }
9269
9230
  try {
9270
9231
  const data = typeof tokenData === "string" ? JSON.parse(tokenData) : tokenData;
9271
9232
  try {
@@ -9404,27 +9365,41 @@ async function parseTokenInfo(tokenData) {
9404
9365
  }
9405
9366
  var sdkDataCache = /* @__PURE__ */ new Map();
9406
9367
  var SDK_DATA_CACHE_MAX = 2e3;
9368
+ function looksLikeTokenBlob(sdkData) {
9369
+ return sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
9370
+ }
9371
+ function tryParseBlobKeys(sdkData) {
9372
+ try {
9373
+ const blob = decodeTokenBlob(hexToBytes2(sdkData));
9374
+ return { tokenId: blob.tokenId, stateHash: sha2562(bytesToHex3(blob.token), "hex") };
9375
+ } catch {
9376
+ return null;
9377
+ }
9378
+ }
9407
9379
  function parseSdkDataCached(sdkData) {
9408
9380
  const cached = sdkDataCache.get(sdkData);
9409
9381
  if (cached) return cached;
9410
- let tokenId = null;
9411
- let stateHash = "";
9412
- try {
9413
- const txf = JSON.parse(sdkData);
9414
- tokenId = txf.genesis?.data?.tokenId || null;
9415
- stateHash = getCurrentStateHash(txf) || "";
9416
- if (!stateHash) {
9417
- if (txf.state?.hash) {
9418
- stateHash = txf.state.hash;
9419
- } else if (txf.stateHash) {
9420
- stateHash = txf.stateHash;
9421
- } else if (txf.currentStateHash) {
9422
- stateHash = txf.currentStateHash;
9382
+ let entry = looksLikeTokenBlob(sdkData) ? tryParseBlobKeys(sdkData) : null;
9383
+ if (!entry) {
9384
+ let tokenId = null;
9385
+ let stateHash = "";
9386
+ try {
9387
+ const txf = JSON.parse(sdkData);
9388
+ tokenId = txf.genesis?.data?.tokenId || null;
9389
+ stateHash = getCurrentStateHash(txf) || "";
9390
+ if (!stateHash) {
9391
+ if (txf.state?.hash) {
9392
+ stateHash = txf.state.hash;
9393
+ } else if (txf.stateHash) {
9394
+ stateHash = txf.stateHash;
9395
+ } else if (txf.currentStateHash) {
9396
+ stateHash = txf.currentStateHash;
9397
+ }
9423
9398
  }
9399
+ } catch {
9424
9400
  }
9425
- } catch {
9401
+ entry = { tokenId, stateHash };
9426
9402
  }
9427
- const entry = { tokenId, stateHash };
9428
9403
  if (sdkDataCache.size >= SDK_DATA_CACHE_MAX) {
9429
9404
  sdkDataCache.clear();
9430
9405
  }
@@ -9701,6 +9676,7 @@ var PaymentsModule = class _PaymentsModule {
9701
9676
  );
9702
9677
  this.deps = deps;
9703
9678
  this.priceProvider = deps.price ?? null;
9679
+ this.spendPlanner.setEngine(deps.tokenEngine);
9704
9680
  if (this.l1) {
9705
9681
  this.l1.initialize({
9706
9682
  identity: deps.identity,
@@ -9925,7 +9901,59 @@ var PaymentsModule = class _PaymentsModule {
9925
9901
  request.invoiceRefundAddress,
9926
9902
  request.invoiceContact
9927
9903
  );
9928
- if (transferMode === "conservative") {
9904
+ if (this.deps?.tokenEngine && peerInfo?.chainPubkey) {
9905
+ const engine = this.deps.tokenEngine;
9906
+ const recipientChainPubkey = hexToBytes2(peerInfo.chainPubkey);
9907
+ const memoData = onChainMessage ?? void 0;
9908
+ const handToRecipient = async (finished) => {
9909
+ const tokenBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(finished)));
9910
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
9911
+ type: "V2_TRANSFER",
9912
+ version: "2.0",
9913
+ tokenBlob,
9914
+ memo: request.memo
9915
+ });
9916
+ };
9917
+ for (const tw of splitPlan.tokensToTransferDirectly) {
9918
+ const finished = await engine.transfer({
9919
+ token: tw.sdkToken,
9920
+ recipientPubkey: recipientChainPubkey,
9921
+ data: memoData
9922
+ });
9923
+ await handToRecipient(finished);
9924
+ result.tokenTransfers.push({ sourceTokenId: tw.uiToken.id, method: "direct" });
9925
+ await this.removeToken(tw.uiToken.id, result.id);
9926
+ }
9927
+ if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9928
+ const selfChainPubkey = hexToBytes2(this.deps.identity.chainPubkey);
9929
+ const { outputs } = await engine.split({
9930
+ token: splitPlan.tokenToSplit.sdkToken,
9931
+ outputs: [
9932
+ { recipientPubkey: recipientChainPubkey, coinId: request.coinId, amount: splitPlan.splitAmount, data: memoData },
9933
+ { recipientPubkey: selfChainPubkey, coinId: request.coinId, amount: splitPlan.remainderAmount }
9934
+ ]
9935
+ });
9936
+ await handToRecipient(outputs[0]);
9937
+ const changeToken = outputs[1];
9938
+ const changeBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(changeToken)));
9939
+ const changeInfo = await parseTokenInfo(changeBlob, engine);
9940
+ const registry = TokenRegistry.getInstance();
9941
+ await this.addToken({
9942
+ id: `v2_${engine.tokenId(changeToken)}`,
9943
+ coinId: changeInfo.coinId,
9944
+ symbol: registry.getSymbol(changeInfo.coinId) || changeInfo.symbol,
9945
+ name: registry.getName(changeInfo.coinId) || changeInfo.name,
9946
+ decimals: registry.getDecimals(changeInfo.coinId) ?? changeInfo.decimals,
9947
+ amount: changeInfo.amount,
9948
+ status: "confirmed",
9949
+ createdAt: Date.now(),
9950
+ updatedAt: Date.now(),
9951
+ sdkData: changeBlob
9952
+ });
9953
+ result.tokenTransfers.push({ sourceTokenId: splitPlan.tokenToSplit.uiToken.id, method: "split" });
9954
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, result.id);
9955
+ }
9956
+ } else if (transferMode === "conservative") {
9929
9957
  if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9930
9958
  logger.debug("Payments", "Executing conservative split...");
9931
9959
  const splitExecutor = new TokenSplitExecutor({
@@ -10521,7 +10549,7 @@ var PaymentsModule = class _PaymentsModule {
10521
10549
  * because bundle-level dedup protects against replays, and split children share genesis IDs.
10522
10550
  */
10523
10551
  async saveCommitmentOnlyToken(sourceTokenInput, commitmentInput, senderPubkey, deferPersistence = false, skipGenesisDedup = false) {
10524
- const tokenInfo = await parseTokenInfo(sourceTokenInput);
10552
+ const tokenInfo = await parseTokenInfo(sourceTokenInput, this.deps?.tokenEngine);
10525
10553
  const sdkData = typeof sourceTokenInput === "string" ? sourceTokenInput : JSON.stringify(sourceTokenInput);
10526
10554
  const nostrTokenId = extractTokenIdFromSdkData(sdkData);
10527
10555
  const nostrStateHash = extractStateHashFromSdkData(sdkData);
@@ -11613,8 +11641,8 @@ var PaymentsModule = class _PaymentsModule {
11613
11641
  if (pending2.stage === "RECEIVED") {
11614
11642
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
11615
11643
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11616
- const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
11617
- const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
11644
+ const mintData = await import_MintTransactionData2.MintTransactionData.fromJSON(mintDataJson);
11645
+ const mintCommitment = await import_MintCommitment2.MintCommitment.create(mintData);
11618
11646
  const mintResponse = await stClient.submitMintCommitment(mintCommitment);
11619
11647
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
11620
11648
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -11626,8 +11654,8 @@ var PaymentsModule = class _PaymentsModule {
11626
11654
  if (pending2.stage === "MINT_SUBMITTED") {
11627
11655
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
11628
11656
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11629
- const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
11630
- const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
11657
+ const mintData = await import_MintTransactionData2.MintTransactionData.fromJSON(mintDataJson);
11658
+ const mintCommitment = await import_MintCommitment2.MintCommitment.create(mintData);
11631
11659
  const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
11632
11660
  if (!proof) {
11633
11661
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
@@ -11724,10 +11752,10 @@ var PaymentsModule = class _PaymentsModule {
11724
11752
  */
11725
11753
  async finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase) {
11726
11754
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11727
- const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
11728
- const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
11755
+ const mintData = await import_MintTransactionData2.MintTransactionData.fromJSON(mintDataJson);
11756
+ const mintCommitment = await import_MintCommitment2.MintCommitment.create(mintData);
11729
11757
  const mintProofJson = JSON.parse(pending2.mintProofJson);
11730
- const mintProof = import_InclusionProof.InclusionProof.fromJSON(mintProofJson);
11758
+ const mintProof = import_InclusionProof2.InclusionProof.fromJSON(mintProofJson);
11731
11759
  const mintTransaction = mintCommitment.toTransaction(mintProof);
11732
11760
  const tokenType = new import_TokenType3.TokenType(fromHex4(bundle.tokenTypeHex));
11733
11761
  const senderMintedStateJson = JSON.parse(bundle.mintedTokenStateJson);
@@ -11744,14 +11772,14 @@ var PaymentsModule = class _PaymentsModule {
11744
11772
  const transferProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, transferCommitment);
11745
11773
  const transferTransaction = transferCommitment.toTransaction(transferProof);
11746
11774
  const transferSalt = fromHex4(bundle.transferSaltHex);
11747
- const recipientPredicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
11775
+ const recipientPredicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
11748
11776
  mintData.tokenId,
11749
11777
  tokenType,
11750
11778
  signingService,
11751
11779
  import_HashAlgorithm5.HashAlgorithm.SHA256,
11752
11780
  transferSalt
11753
11781
  );
11754
- const recipientState = new import_TokenState5.TokenState(recipientPredicate, null);
11782
+ const recipientState = new import_TokenState4.TokenState(recipientPredicate, null);
11755
11783
  let nametagTokens = [];
11756
11784
  const recipientAddressStr = bundle.recipientAddressJson;
11757
11785
  if (recipientAddressStr.startsWith("PROXY://")) {
@@ -12488,68 +12516,6 @@ var PaymentsModule = class _PaymentsModule {
12488
12516
  }
12489
12517
  }
12490
12518
  }
12491
- /**
12492
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
12493
- * This creates the nametag token required for receiving tokens via PROXY addresses
12494
- *
12495
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
12496
- * @returns MintNametagResult with success status and token if successful
12497
- */
12498
- async mintNametag(nametag) {
12499
- this.ensureInitialized();
12500
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12501
- if (!stClient) {
12502
- return {
12503
- success: false,
12504
- error: "State transition client not available. Oracle provider must implement getStateTransitionClient()"
12505
- };
12506
- }
12507
- const trustBase = this.deps.oracle.getTrustBase?.();
12508
- if (!trustBase) {
12509
- return {
12510
- success: false,
12511
- error: "Trust base not available. Oracle provider must implement getTrustBase()"
12512
- };
12513
- }
12514
- try {
12515
- const signingService = await this.createSigningService();
12516
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12517
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12518
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12519
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12520
- const addressRef = await UnmaskedPredicateReference4.create(
12521
- tokenType,
12522
- signingService.algorithm,
12523
- signingService.publicKey,
12524
- import_HashAlgorithm5.HashAlgorithm.SHA256
12525
- );
12526
- const ownerAddress = await addressRef.toAddress();
12527
- const minter = new NametagMinter({
12528
- stateTransitionClient: stClient,
12529
- trustBase,
12530
- signingService,
12531
- debug: this.moduleConfig.debug
12532
- });
12533
- const result = await minter.mintNametag(nametag, ownerAddress);
12534
- if (result.success && result.nametagData) {
12535
- await this.setNametag(result.nametagData);
12536
- logger.debug("Payments", `Unicity ID minted and saved: ${result.nametagData.name}`);
12537
- this.deps.emitEvent("nametag:registered", {
12538
- nametag: result.nametagData.name,
12539
- addressIndex: 0
12540
- // Primary address
12541
- });
12542
- }
12543
- return result;
12544
- } catch (error) {
12545
- const errorMsg = error instanceof Error ? error.message : String(error);
12546
- logger.debug("Payments", "mintNametag failed:", errorMsg);
12547
- return {
12548
- success: false,
12549
- error: errorMsg
12550
- };
12551
- }
12552
- }
12553
12519
  /**
12554
12520
  * Mint a fungible token directly to this wallet (genesis mint).
12555
12521
  *
@@ -12590,18 +12556,18 @@ var PaymentsModule = class _PaymentsModule {
12590
12556
  }
12591
12557
  try {
12592
12558
  const signingService = await this.createSigningService();
12593
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12559
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12594
12560
  const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
12595
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12561
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12596
12562
  const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
12597
12563
  const tokenType = new import_TokenType3.TokenType(tokenTypeBytes);
12598
12564
  const tokenIdBytes = new Uint8Array(32);
12599
12565
  crypto.getRandomValues(tokenIdBytes);
12600
- const tokenId = new TokenId5(tokenIdBytes);
12566
+ const tokenId = new TokenId4(tokenIdBytes);
12601
12567
  const coinIdBytes = fromHex4(coinIdHex);
12602
12568
  const coinId = new import_CoinId4.CoinId(coinIdBytes);
12603
12569
  const coinData = TokenCoinData3.create([[coinId, amount]]);
12604
- const addressRef = await UnmaskedPredicateReference4.create(
12570
+ const addressRef = await UnmaskedPredicateReference3.create(
12605
12571
  tokenType,
12606
12572
  signingService.algorithm,
12607
12573
  signingService.publicKey,
@@ -12610,7 +12576,7 @@ var PaymentsModule = class _PaymentsModule {
12610
12576
  const ownerAddress = await addressRef.toAddress();
12611
12577
  const salt = new Uint8Array(32);
12612
12578
  crypto.getRandomValues(salt);
12613
- const mintData = await import_MintTransactionData3.MintTransactionData.create(
12579
+ const mintData = await import_MintTransactionData2.MintTransactionData.create(
12614
12580
  tokenId,
12615
12581
  tokenType,
12616
12582
  null,
@@ -12625,7 +12591,7 @@ var PaymentsModule = class _PaymentsModule {
12625
12591
  null
12626
12592
  // reason: null (genesis, no burn predecessor)
12627
12593
  );
12628
- const commitment = await import_MintCommitment3.MintCommitment.create(mintData);
12594
+ const commitment = await import_MintCommitment2.MintCommitment.create(mintData);
12629
12595
  const MAX_RETRIES = 3;
12630
12596
  let lastStatus;
12631
12597
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
@@ -12642,14 +12608,14 @@ var PaymentsModule = class _PaymentsModule {
12642
12608
  }
12643
12609
  const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
12644
12610
  const genesisTransaction = commitment.toTransaction(inclusionProof);
12645
- const predicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
12611
+ const predicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
12646
12612
  tokenId,
12647
12613
  tokenType,
12648
12614
  signingService,
12649
12615
  import_HashAlgorithm5.HashAlgorithm.SHA256,
12650
12616
  salt
12651
12617
  );
12652
- const tokenState = new import_TokenState5.TokenState(predicate, null);
12618
+ const tokenState = new import_TokenState4.TokenState(predicate, null);
12653
12619
  const sdkToken = await import_Token6.Token.mint(trustBase, tokenState, genesisTransaction);
12654
12620
  const tokenIdHex = tokenId.toJSON();
12655
12621
  const symbol = this.getCoinSymbol(coinIdHex);
@@ -12676,29 +12642,6 @@ var PaymentsModule = class _PaymentsModule {
12676
12642
  return { success: false, error: `Local mint failed: ${msg}` };
12677
12643
  }
12678
12644
  }
12679
- /**
12680
- * Check if a nametag is available for minting
12681
- * @param nametag - The nametag to check (e.g., "alice" or "@alice")
12682
- */
12683
- async isNametagAvailable(nametag) {
12684
- this.ensureInitialized();
12685
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12686
- const trustBase = this.deps.oracle.getTrustBase?.();
12687
- if (!stClient || !trustBase) {
12688
- return false;
12689
- }
12690
- try {
12691
- const signingService = await this.createSigningService();
12692
- const minter = new NametagMinter({
12693
- stateTransitionClient: stClient,
12694
- trustBase,
12695
- signingService
12696
- });
12697
- return await minter.isNametagAvailable(nametag);
12698
- } catch {
12699
- return false;
12700
- }
12701
- }
12702
12645
  // ===========================================================================
12703
12646
  // Public API - Sync & Validate
12704
12647
  // ===========================================================================
@@ -13014,7 +12957,7 @@ var PaymentsModule = class _PaymentsModule {
13014
12957
  const privateKeyBytes = new Uint8Array(
13015
12958
  privateKeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
13016
12959
  );
13017
- return import_SigningService.SigningService.createFromSecret(privateKeyBytes);
12960
+ return import_SigningService2.SigningService.createFromSecret(privateKeyBytes);
13018
12961
  }
13019
12962
  /**
13020
12963
  * Get the wallet's signing public key (used for token ownership predicates).
@@ -13029,14 +12972,14 @@ var PaymentsModule = class _PaymentsModule {
13029
12972
  * Create DirectAddress from a public key using UnmaskedPredicateReference
13030
12973
  */
13031
12974
  async createDirectAddressFromPubkey(pubkeyHex) {
13032
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
13033
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
13034
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
13035
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12975
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12976
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12977
+ const UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12978
+ const tokenType = new TokenType4(Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex"));
13036
12979
  const pubkeyBytes = new Uint8Array(
13037
12980
  pubkeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
13038
12981
  );
13039
- const addressRef = await UnmaskedPredicateReference4.create(
12982
+ const addressRef = await UnmaskedPredicateReference3.create(
13040
12983
  tokenType,
13041
12984
  "secp256k1",
13042
12985
  pubkeyBytes,
@@ -13048,10 +12991,9 @@ var PaymentsModule = class _PaymentsModule {
13048
12991
  * Resolve recipient to IAddress for L3 transfers.
13049
12992
  * Uses pre-resolved PeerInfo when available to avoid redundant network queries.
13050
12993
  */
13051
- async resolveRecipientAddress(recipient, addressMode = "auto", peerInfo) {
12994
+ async resolveRecipientAddress(recipient, _addressMode = "auto", peerInfo) {
13052
12995
  const { AddressFactory } = await import("@unicitylabs/state-transition-sdk/lib/address/AddressFactory");
13053
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
13054
- if (recipient.startsWith("PROXY:") || recipient.startsWith("DIRECT:")) {
12996
+ if (recipient.startsWith("DIRECT:")) {
13055
12997
  return AddressFactory.createAddress(recipient);
13056
12998
  }
13057
12999
  if (recipient.length === 66 && /^[0-9a-fA-F]+$/.test(recipient)) {
@@ -13061,28 +13003,19 @@ var PaymentsModule = class _PaymentsModule {
13061
13003
  const info = peerInfo ?? await this.deps?.transport.resolve?.(recipient) ?? null;
13062
13004
  if (!info) {
13063
13005
  throw new SphereError(
13064
- `Recipient "${recipient}" not found. Use @nametag, a valid PROXY:/DIRECT: address, or a 33-byte hex pubkey.`,
13006
+ `Recipient "${recipient}" not found. Use @nametag, a DIRECT: address, or a 33-byte hex pubkey.`,
13065
13007
  "INVALID_RECIPIENT"
13066
13008
  );
13067
13009
  }
13068
13010
  const nametag = recipient.startsWith("@") ? recipient.slice(1) : info.nametag || recipient;
13069
- if (addressMode === "proxy") {
13070
- logger.debug("Payments", `Using PROXY address for "${nametag}" (forced)`);
13071
- return ProxyAddress.fromNameTag(nametag);
13072
- }
13073
- if (addressMode === "direct") {
13074
- if (!info.directAddress) {
13075
- throw new SphereError(`"${nametag}" has no DirectAddress stored. It may be a legacy registration.`, "INVALID_RECIPIENT");
13076
- }
13077
- logger.debug("Payments", `Using DirectAddress for "${nametag}" (forced): ${info.directAddress.slice(0, 30)}...`);
13078
- return AddressFactory.createAddress(info.directAddress);
13079
- }
13080
- if (info.directAddress) {
13081
- logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
13082
- return AddressFactory.createAddress(info.directAddress);
13011
+ if (!info.directAddress) {
13012
+ throw new SphereError(
13013
+ `"${nametag}" has no DirectAddress \u2014 the recipient must publish a key-based identity binding.`,
13014
+ "INVALID_RECIPIENT"
13015
+ );
13083
13016
  }
13084
- logger.debug("Payments", `Using PROXY address for legacy nametag "${nametag}"`);
13085
- return ProxyAddress.fromNameTag(nametag);
13017
+ logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
13018
+ return AddressFactory.createAddress(info.directAddress);
13086
13019
  }
13087
13020
  /**
13088
13021
  * Handle NOSTR-FIRST commitment-only transfer (recipient side)
@@ -13137,14 +13070,14 @@ var PaymentsModule = class _PaymentsModule {
13137
13070
  const addressScheme = recipientAddress.scheme;
13138
13071
  const signingService = await this.createSigningService();
13139
13072
  const transferSalt = transferTx.data.salt;
13140
- const recipientPredicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
13073
+ const recipientPredicate = await import_UnmaskedPredicate4.UnmaskedPredicate.create(
13141
13074
  sourceToken.id,
13142
13075
  sourceToken.type,
13143
13076
  signingService,
13144
13077
  import_HashAlgorithm5.HashAlgorithm.SHA256,
13145
13078
  transferSalt
13146
13079
  );
13147
- const recipientState = new import_TokenState5.TokenState(recipientPredicate, null);
13080
+ const recipientState = new import_TokenState4.TokenState(recipientPredicate, null);
13148
13081
  let nametagTokens = [];
13149
13082
  if (addressScheme === import_AddressScheme.AddressScheme.PROXY) {
13150
13083
  const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
@@ -13241,6 +13174,67 @@ var PaymentsModule = class _PaymentsModule {
13241
13174
  }
13242
13175
  }
13243
13176
  }
13177
+ /**
13178
+ * v2 engine transfer (sender-driven): the sender handed us a FINISHED token.
13179
+ * Decode the blob, dedup by the genesis-stable token id, store it as a
13180
+ * confirmed token, and emit/record the receipt. No commitment / inclusion-proof
13181
+ * / finalization round-trip (contrast the v1 sourceToken+transferTx path).
13182
+ */
13183
+ async handleV2Transfer(payload, senderPubkey) {
13184
+ this.ensureInitialized();
13185
+ if (!this.loaded && this.loadedPromise) {
13186
+ await this.loadedPromise;
13187
+ }
13188
+ const engine = this.deps.tokenEngine;
13189
+ if (!engine) return;
13190
+ let token;
13191
+ try {
13192
+ token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(payload.tokenBlob)));
13193
+ } catch (err) {
13194
+ logger.error("Payments", "V2 transfer: failed to decode token blob:", err);
13195
+ return;
13196
+ }
13197
+ const id = `v2_${engine.tokenId(token)}`;
13198
+ if (this.tokens.has(id)) {
13199
+ logger.debug("Payments", `V2 transfer ${id.slice(0, 16)}... already present, skipping`);
13200
+ return;
13201
+ }
13202
+ const info = await parseTokenInfo(payload.tokenBlob, engine);
13203
+ const registry = TokenRegistry.getInstance();
13204
+ const uiToken = {
13205
+ id,
13206
+ coinId: info.coinId,
13207
+ symbol: registry.getSymbol(info.coinId) || info.symbol,
13208
+ name: registry.getName(info.coinId) || info.name,
13209
+ decimals: registry.getDecimals(info.coinId) ?? info.decimals,
13210
+ amount: info.amount,
13211
+ status: "confirmed",
13212
+ createdAt: Date.now(),
13213
+ updatedAt: Date.now(),
13214
+ sdkData: payload.tokenBlob
13215
+ };
13216
+ await this.addToken(uiToken);
13217
+ const senderInfo = await this.resolveSenderInfo(senderPubkey);
13218
+ this.deps.emitEvent("transfer:incoming", {
13219
+ id,
13220
+ senderPubkey,
13221
+ senderNametag: senderInfo.senderNametag,
13222
+ tokens: [uiToken],
13223
+ memo: payload.memo,
13224
+ receivedAt: Date.now()
13225
+ });
13226
+ await this.addToHistory({
13227
+ type: "RECEIVED",
13228
+ amount: info.amount,
13229
+ coinId: info.coinId,
13230
+ symbol: uiToken.symbol,
13231
+ timestamp: Date.now(),
13232
+ senderPubkey,
13233
+ ...senderInfo,
13234
+ memo: payload.memo,
13235
+ tokenId: id
13236
+ });
13237
+ }
13244
13238
  async handleIncomingTransfer(transfer) {
13245
13239
  if (!this.loaded && this.loadedPromise) {
13246
13240
  await this.loadedPromise;
@@ -13248,6 +13242,10 @@ var PaymentsModule = class _PaymentsModule {
13248
13242
  try {
13249
13243
  const payload = transfer.payload;
13250
13244
  logger.debug("Payments", "handleIncomingTransfer: keys=", Object.keys(payload).join(","));
13245
+ if (this.deps.tokenEngine && isV2TransferPayload(transfer.payload)) {
13246
+ await this.handleV2Transfer(transfer.payload, transfer.senderTransportPubkey);
13247
+ return;
13248
+ }
13251
13249
  let combinedBundle = null;
13252
13250
  if (isCombinedTransferBundleV6(payload)) {
13253
13251
  combinedBundle = payload;
@@ -13329,7 +13327,7 @@ var PaymentsModule = class _PaymentsModule {
13329
13327
  const hasTransactionData = transferTxInput.transactionData !== void 0;
13330
13328
  const hasAuthenticator = transferTxInput.authenticator !== void 0;
13331
13329
  if (hasData && hasInclusionProof) {
13332
- transferTx = await import_TransferTransaction2.TransferTransaction.fromJSON(transferTxInput);
13330
+ transferTx = await import_TransferTransaction3.TransferTransaction.fromJSON(transferTxInput);
13333
13331
  } else if (hasTransactionData && hasAuthenticator) {
13334
13332
  const commitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferTxInput);
13335
13333
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -13350,7 +13348,7 @@ var PaymentsModule = class _PaymentsModule {
13350
13348
  transferTx = commitment.toTransaction(inclusionProof);
13351
13349
  } else {
13352
13350
  try {
13353
- transferTx = await import_TransferTransaction2.TransferTransaction.fromJSON(transferTxInput);
13351
+ transferTx = await import_TransferTransaction3.TransferTransaction.fromJSON(transferTxInput);
13354
13352
  } catch {
13355
13353
  const commitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferTxInput);
13356
13354
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -13392,7 +13390,7 @@ var PaymentsModule = class _PaymentsModule {
13392
13390
  logger.warn("Payments", "Received invalid token");
13393
13391
  return;
13394
13392
  }
13395
- const tokenInfo = await parseTokenInfo(tokenData);
13393
+ const tokenInfo = await parseTokenInfo(tokenData, this.deps?.tokenEngine);
13396
13394
  const token = {
13397
13395
  id: tokenInfo.tokenId ?? crypto.randomUUID(),
13398
13396
  coinId: tokenInfo.coinId,
@@ -13698,25 +13696,7 @@ function createPaymentsModule(config) {
13698
13696
  return new PaymentsModule(config);
13699
13697
  }
13700
13698
 
13701
- // modules/payments/TokenSplitCalculator.ts
13702
- init_logger();
13703
- var import_Token7 = require("@unicitylabs/state-transition-sdk/lib/token/Token");
13704
- var import_CoinId5 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
13705
-
13706
- // modules/payments/BackgroundCommitmentService.ts
13707
- init_logger();
13708
- init_errors();
13709
-
13710
- // modules/payments/TokenRecoveryService.ts
13711
- init_logger();
13712
- var import_TokenId4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenId");
13713
- var import_TokenState6 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
13714
- var import_TokenType4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
13715
- var import_CoinId6 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
13716
- var import_HashAlgorithm6 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
13717
- var import_UnmaskedPredicate6 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
13718
-
13719
- // modules/communications/CommunicationsModule.ts
13699
+ // modules/communications/CommunicationsModule.ts
13720
13700
  init_logger();
13721
13701
  init_errors();
13722
13702
 
@@ -14397,7 +14377,7 @@ function createCommunicationsModule(config) {
14397
14377
  }
14398
14378
 
14399
14379
  // modules/groupchat/GroupChatModule.ts
14400
- var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
14380
+ var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
14401
14381
  init_logger();
14402
14382
  init_errors();
14403
14383
  init_constants();
@@ -14415,7 +14395,7 @@ var GroupVisibility = {
14415
14395
 
14416
14396
  // modules/groupchat/GroupChatModule.ts
14417
14397
  function createNip29Filter(data) {
14418
- return new import_nostr_js_sdk4.Filter(data);
14398
+ return new import_nostr_js_sdk3.Filter(data);
14419
14399
  }
14420
14400
  var GroupChatModule = class {
14421
14401
  config;
@@ -14464,7 +14444,7 @@ var GroupChatModule = class {
14464
14444
  }
14465
14445
  this.deps = deps;
14466
14446
  const secretKey = Buffer.from(deps.identity.privateKey, "hex");
14467
- this.keyManager = import_nostr_js_sdk4.NostrKeyManager.fromPrivateKey(secretKey);
14447
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
14468
14448
  }
14469
14449
  async load() {
14470
14450
  this.ensureInitialized();
@@ -14605,7 +14585,7 @@ var GroupChatModule = class {
14605
14585
  }
14606
14586
  this.subscriptionIds = [];
14607
14587
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
14608
- this.keyManager = import_nostr_js_sdk4.NostrKeyManager.fromPrivateKey(secretKey);
14588
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
14609
14589
  if (this.groups.size === 0) {
14610
14590
  await this.restoreJoinedGroups();
14611
14591
  } else {
@@ -14617,13 +14597,13 @@ var GroupChatModule = class {
14617
14597
  this.ensureInitialized();
14618
14598
  if (!this.keyManager) {
14619
14599
  const secretKey = Buffer.from(this.deps.identity.privateKey, "hex");
14620
- this.keyManager = import_nostr_js_sdk4.NostrKeyManager.fromPrivateKey(secretKey);
14600
+ this.keyManager = import_nostr_js_sdk3.NostrKeyManager.fromPrivateKey(secretKey);
14621
14601
  }
14622
14602
  const primaryRelay = this.config.relays[0];
14623
14603
  if (primaryRelay) {
14624
14604
  await this.checkAndClearOnRelayChange(primaryRelay);
14625
14605
  }
14626
- this.client = new import_nostr_js_sdk4.NostrClient(this.keyManager);
14606
+ this.client = new import_nostr_js_sdk3.NostrClient(this.keyManager);
14627
14607
  try {
14628
14608
  await this.client.connect(...this.config.relays);
14629
14609
  this.connected = true;
@@ -14910,7 +14890,7 @@ var GroupChatModule = class {
14910
14890
  if (!this.client) return [];
14911
14891
  const groupsMap = /* @__PURE__ */ new Map();
14912
14892
  await this.oneshotSubscription(
14913
- new import_nostr_js_sdk4.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
14893
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA] }),
14914
14894
  {
14915
14895
  onEvent: (event) => {
14916
14896
  const group = this.parseGroupMetadata(event);
@@ -15410,7 +15390,7 @@ var GroupChatModule = class {
15410
15390
  if (!this.client) return /* @__PURE__ */ new Set();
15411
15391
  const adminPubkeys = /* @__PURE__ */ new Set();
15412
15392
  return this.oneshotSubscription(
15413
- new import_nostr_js_sdk4.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
15393
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": ["", "_"] }),
15414
15394
  {
15415
15395
  onEvent: (event) => {
15416
15396
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -15432,7 +15412,7 @@ var GroupChatModule = class {
15432
15412
  if (!this.client) return null;
15433
15413
  let result = null;
15434
15414
  return this.oneshotSubscription(
15435
- new import_nostr_js_sdk4.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
15415
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_METADATA], "#d": [groupId] }),
15436
15416
  {
15437
15417
  onEvent: (event) => {
15438
15418
  if (!result) result = this.parseGroupMetadata(event);
@@ -15469,7 +15449,7 @@ var GroupChatModule = class {
15469
15449
  if (!this.client) return [];
15470
15450
  const members = [];
15471
15451
  return this.oneshotSubscription(
15472
- new import_nostr_js_sdk4.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
15452
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_MEMBERS], "#d": [groupId] }),
15473
15453
  {
15474
15454
  onEvent: (event) => {
15475
15455
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -15490,7 +15470,7 @@ var GroupChatModule = class {
15490
15470
  if (!this.client) return [];
15491
15471
  const adminPubkeys = [];
15492
15472
  return this.oneshotSubscription(
15493
- new import_nostr_js_sdk4.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
15473
+ new import_nostr_js_sdk3.Filter({ kinds: [NIP29_KINDS.GROUP_ADMINS], "#d": [groupId] }),
15494
15474
  {
15495
15475
  onEvent: (event) => {
15496
15476
  const pTags = event.tags.filter((t) => t[0] === "p");
@@ -17353,7 +17333,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17353
17333
  const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : void 0;
17354
17334
  return abytes(bytes, sizer);
17355
17335
  }
17356
- class Signature {
17336
+ class Signature2 {
17357
17337
  r;
17358
17338
  s;
17359
17339
  recovery;
@@ -17373,7 +17353,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17373
17353
  let recid;
17374
17354
  if (format === "der") {
17375
17355
  const { r: r2, s: s2 } = DER.toSig(abytes(bytes));
17376
- return new Signature(r2, s2);
17356
+ return new Signature2(r2, s2);
17377
17357
  }
17378
17358
  if (format === "recovered") {
17379
17359
  recid = bytes[0];
@@ -17383,7 +17363,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17383
17363
  const L = lengths.signature / 2;
17384
17364
  const r = bytes.subarray(0, L);
17385
17365
  const s = bytes.subarray(L, L * 2);
17386
- return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);
17366
+ return new Signature2(Fn.fromBytes(r), Fn.fromBytes(s), recid);
17387
17367
  }
17388
17368
  static fromHex(hex, format) {
17389
17369
  return this.fromBytes(hexToBytes(hex), format);
@@ -17395,7 +17375,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17395
17375
  return recovery;
17396
17376
  }
17397
17377
  addRecoveryBit(recovery) {
17398
- return new Signature(this.r, this.s, recovery);
17378
+ return new Signature2(this.r, this.s, recovery);
17399
17379
  }
17400
17380
  recoverPublicKey(messageHash) {
17401
17381
  const { r, s } = this;
@@ -17487,7 +17467,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17487
17467
  normS = Fn.neg(s);
17488
17468
  recovery ^= 1;
17489
17469
  }
17490
- return new Signature(r, normS, hasLargeCofactor ? void 0 : recovery);
17470
+ return new Signature2(r, normS, hasLargeCofactor ? void 0 : recovery);
17491
17471
  }
17492
17472
  return { seed, k2sig };
17493
17473
  }
@@ -17502,12 +17482,12 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17502
17482
  publicKey = abytes(publicKey, void 0, "publicKey");
17503
17483
  message = validateMsgAndHash(message, prehash);
17504
17484
  if (!isBytes(signature)) {
17505
- const end = signature instanceof Signature ? ", use sig.toBytes()" : "";
17485
+ const end = signature instanceof Signature2 ? ", use sig.toBytes()" : "";
17506
17486
  throw new Error("verify expects Uint8Array signature" + end);
17507
17487
  }
17508
17488
  validateSigLength(signature, format);
17509
17489
  try {
17510
- const sig = Signature.fromBytes(signature, format);
17490
+ const sig = Signature2.fromBytes(signature, format);
17511
17491
  const P = Point.fromBytes(publicKey);
17512
17492
  if (lowS && sig.hasHighS())
17513
17493
  return false;
@@ -17528,7 +17508,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17528
17508
  function recoverPublicKey(signature, message, opts = {}) {
17529
17509
  const { prehash } = validateSigOpts(opts, defaultSigOpts);
17530
17510
  message = validateMsgAndHash(message, prehash);
17531
- return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17511
+ return Signature2.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17532
17512
  }
17533
17513
  return Object.freeze({
17534
17514
  keygen,
@@ -17540,7 +17520,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17540
17520
  sign,
17541
17521
  verify,
17542
17522
  recoverPublicKey,
17543
- Signature,
17523
+ Signature: Signature2,
17544
17524
  hash
17545
17525
  });
17546
17526
  }
@@ -18812,7 +18792,7 @@ function freezeCoinAsset(coinAsset, state, latestSender) {
18812
18792
  }
18813
18793
 
18814
18794
  // modules/accounting/AccountingModule.ts
18815
- var import_Token8 = require("@unicitylabs/state-transition-sdk/lib/token/Token.js");
18795
+ var import_Token7 = require("@unicitylabs/state-transition-sdk/lib/token/Token.js");
18816
18796
  var LOG_TAG2 = "Accounting";
18817
18797
  var INV_LEDGER_PREFIX = "inv_ledger:";
18818
18798
  var AccountingModule = class _AccountingModule {
@@ -19465,129 +19445,146 @@ var AccountingModule = class _AccountingModule {
19465
19445
  );
19466
19446
  }
19467
19447
  try {
19468
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19469
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19470
- const { MintTransactionData: MintTransactionData4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19471
- const { MintCommitment: MintCommitment4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19472
- const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19473
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19474
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19475
- const { UnmaskedPredicate: UnmaskedPredicate7 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19476
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19477
- const { TokenState: TokenState7 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19478
- const { Token: SdkToken5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19479
- const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19480
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(invoiceBytesEncoded).digest();
19481
- const invoiceTokenId = new TokenId5(hash.imprint);
19482
- const invoiceId = invoiceTokenId.toJSON();
19483
- if (this.invoiceTermsCache.has(invoiceId)) {
19484
- throw new SphereError(
19485
- `Invoice already exists locally: ${invoiceId}`,
19486
- "INVOICE_ALREADY_EXISTS"
19448
+ let invoiceId;
19449
+ let sdkData;
19450
+ const engine = deps.tokenEngine;
19451
+ if (engine) {
19452
+ const invoiceToken = await engine.mintDataToken({
19453
+ recipientPubkey: hexToBytes(deps.identity.chainPubkey),
19454
+ data: invoiceBytesEncoded,
19455
+ tokenType: hexToBytes(INVOICE_TOKEN_TYPE_HEX),
19456
+ salt
19457
+ });
19458
+ invoiceId = engine.tokenId(invoiceToken);
19459
+ if (this.invoiceTermsCache.has(invoiceId)) {
19460
+ throw new SphereError(`Invoice already exists locally: ${invoiceId}`, "INVOICE_ALREADY_EXISTS");
19461
+ }
19462
+ sdkData = bytesToHex(encodeTokenBlob(engine.encodeToken(invoiceToken)));
19463
+ } else {
19464
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19465
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19466
+ const { MintTransactionData: MintTransactionData3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19467
+ const { MintCommitment: MintCommitment3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19468
+ const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19469
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19470
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19471
+ const { UnmaskedPredicate: UnmaskedPredicate5 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19472
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19473
+ const { TokenState: TokenState5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19474
+ const { Token: SdkToken4 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19475
+ const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19476
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(invoiceBytesEncoded).digest();
19477
+ const invoiceTokenId = new TokenId4(hash.imprint);
19478
+ invoiceId = invoiceTokenId.toJSON();
19479
+ if (this.invoiceTermsCache.has(invoiceId)) {
19480
+ throw new SphereError(
19481
+ `Invoice already exists locally: ${invoiceId}`,
19482
+ "INVOICE_ALREADY_EXISTS"
19483
+ );
19484
+ }
19485
+ const invoiceTokenType = new TokenType4(
19486
+ Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19487
19487
  );
19488
- }
19489
- const invoiceTokenType = new TokenType6(
19490
- Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19491
- );
19492
- const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19493
- const addressRef = await UnmaskedPredicateReference4.create(
19494
- invoiceTokenType,
19495
- signingService.algorithm,
19496
- signingService.publicKey,
19497
- HashAlgorithm8.SHA256
19498
- );
19499
- const ownerAddress = await addressRef.toAddress();
19500
- const mintData = await MintTransactionData4.create(
19501
- invoiceTokenId,
19502
- invoiceTokenType,
19503
- invoiceBytesEncoded,
19504
- // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19505
- null,
19506
- // coinData: null (non-fungible invoice token)
19507
- ownerAddress,
19508
- salt,
19509
- null,
19510
- // recipientDataHash: null
19511
- null
19512
- // reason: null
19513
- );
19514
- if (this.config.debug) {
19515
- logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19516
- }
19517
- const commitment = await MintCommitment4.create(mintData);
19518
- if (this.config.debug) {
19519
- logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19520
- }
19521
- const MAX_RETRIES = 3;
19522
- let submitSuccess = false;
19523
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19524
- try {
19525
- if (this.config.debug) {
19526
- logger.debug(
19527
- LOG_TAG2,
19528
- `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19529
- );
19530
- }
19531
- const response = await stClient.submitMintCommitment(commitment);
19532
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19488
+ const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19489
+ const addressRef = await UnmaskedPredicateReference3.create(
19490
+ invoiceTokenType,
19491
+ signingService.algorithm,
19492
+ signingService.publicKey,
19493
+ HashAlgorithm6.SHA256
19494
+ );
19495
+ const ownerAddress = await addressRef.toAddress();
19496
+ const mintData = await MintTransactionData3.create(
19497
+ invoiceTokenId,
19498
+ invoiceTokenType,
19499
+ invoiceBytesEncoded,
19500
+ // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19501
+ null,
19502
+ // coinData: null (non-fungible invoice token)
19503
+ ownerAddress,
19504
+ salt,
19505
+ null,
19506
+ // recipientDataHash: null
19507
+ null
19508
+ // reason: null
19509
+ );
19510
+ if (this.config.debug) {
19511
+ logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19512
+ }
19513
+ const commitment = await MintCommitment3.create(mintData);
19514
+ if (this.config.debug) {
19515
+ logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19516
+ }
19517
+ const MAX_RETRIES = 3;
19518
+ let submitSuccess = false;
19519
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19520
+ try {
19533
19521
  if (this.config.debug) {
19534
19522
  logger.debug(
19535
19523
  LOG_TAG2,
19536
- response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19524
+ `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19537
19525
  );
19538
19526
  }
19539
- submitSuccess = true;
19540
- break;
19541
- } else {
19542
- logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19527
+ const response = await stClient.submitMintCommitment(commitment);
19528
+ if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19529
+ if (this.config.debug) {
19530
+ logger.debug(
19531
+ LOG_TAG2,
19532
+ response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19533
+ );
19534
+ }
19535
+ submitSuccess = true;
19536
+ break;
19537
+ } else {
19538
+ logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19539
+ if (attempt === MAX_RETRIES) {
19540
+ throw new SphereError(
19541
+ `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19542
+ "INVOICE_MINT_FAILED"
19543
+ );
19544
+ }
19545
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
19546
+ }
19547
+ } catch (retryErr) {
19548
+ if (retryErr instanceof SphereError && (retryErr.code === "INVOICE_ORACLE_REQUIRED" || retryErr.code === "INVOICE_INVALID_PROOF" || retryErr.code === "INVOICE_MINT_FAILED" || retryErr.code === "NOT_INITIALIZED" || retryErr.code === "MODULE_DESTROYED")) throw retryErr;
19549
+ logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19543
19550
  if (attempt === MAX_RETRIES) {
19544
19551
  throw new SphereError(
19545
- `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19546
- "INVOICE_MINT_FAILED"
19552
+ `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19553
+ "INVOICE_MINT_FAILED",
19554
+ retryErr
19547
19555
  );
19548
19556
  }
19549
19557
  await new Promise((r) => setTimeout(r, 1e3 * attempt));
19550
19558
  }
19551
- } catch (retryErr) {
19552
- if (retryErr instanceof SphereError && (retryErr.code === "INVOICE_ORACLE_REQUIRED" || retryErr.code === "INVOICE_INVALID_PROOF" || retryErr.code === "INVOICE_MINT_FAILED" || retryErr.code === "NOT_INITIALIZED" || retryErr.code === "MODULE_DESTROYED")) throw retryErr;
19553
- logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19554
- if (attempt === MAX_RETRIES) {
19555
- throw new SphereError(
19556
- `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19557
- "INVOICE_MINT_FAILED",
19558
- retryErr
19559
- );
19560
- }
19561
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
19562
19559
  }
19563
- }
19564
- if (!submitSuccess) {
19565
- throw new SphereError(
19566
- "Failed to mint invoice token: commitment submission failed after retries",
19567
- "INVOICE_MINT_FAILED"
19560
+ if (!submitSuccess) {
19561
+ throw new SphereError(
19562
+ "Failed to mint invoice token: commitment submission failed after retries",
19563
+ "INVOICE_MINT_FAILED"
19564
+ );
19565
+ }
19566
+ if (this.config.debug) {
19567
+ logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19568
+ }
19569
+ const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19570
+ if (this.config.debug) {
19571
+ logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19572
+ }
19573
+ const genesisTransaction = commitment.toTransaction(inclusionProof);
19574
+ const invoicePredicate = await UnmaskedPredicate5.create(
19575
+ invoiceTokenId,
19576
+ invoiceTokenType,
19577
+ signingService,
19578
+ HashAlgorithm6.SHA256,
19579
+ salt
19568
19580
  );
19581
+ const tokenState = new TokenState5(invoicePredicate, null);
19582
+ const sdkToken = await SdkToken4.mint(trustBase, tokenState, genesisTransaction);
19583
+ if (this.config.debug) {
19584
+ logger.debug(LOG_TAG2, "Invoice token minted successfully");
19585
+ }
19586
+ sdkData = JSON.stringify(sdkToken.toJSON());
19569
19587
  }
19570
- if (this.config.debug) {
19571
- logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19572
- }
19573
- const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19574
- if (this.config.debug) {
19575
- logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19576
- }
19577
- const genesisTransaction = commitment.toTransaction(inclusionProof);
19578
- const invoicePredicate = await UnmaskedPredicate7.create(
19579
- invoiceTokenId,
19580
- invoiceTokenType,
19581
- signingService,
19582
- HashAlgorithm8.SHA256,
19583
- salt
19584
- );
19585
- const tokenState = new TokenState7(invoicePredicate, null);
19586
- const sdkToken = await SdkToken5.mint(trustBase, tokenState, genesisTransaction);
19587
- if (this.config.debug) {
19588
- logger.debug(LOG_TAG2, "Invoice token minted successfully");
19589
- }
19590
- const sdkTokenJson = sdkToken.toJSON();
19591
19588
  const uiToken = {
19592
19589
  id: invoiceId,
19593
19590
  coinId: INVOICE_TOKEN_TYPE_HEX,
@@ -19598,7 +19595,7 @@ var AccountingModule = class _AccountingModule {
19598
19595
  status: "confirmed",
19599
19596
  createdAt: terms.createdAt,
19600
19597
  updatedAt: terms.createdAt,
19601
- sdkData: JSON.stringify(sdkTokenJson)
19598
+ sdkData
19602
19599
  };
19603
19600
  await deps.payments.addToken(uiToken);
19604
19601
  this.invoiceTermsCache.set(invoiceId, this._normalizeInvoiceTerms(terms));
@@ -19609,17 +19606,7 @@ var AccountingModule = class _AccountingModule {
19609
19606
  const allTokens = deps.payments.getTokens();
19610
19607
  let anyScanDirty = false;
19611
19608
  for (const token of allTokens) {
19612
- if (!token.sdkData) continue;
19613
- let txf;
19614
- try {
19615
- txf = JSON.parse(token.sdkData);
19616
- } catch {
19617
- continue;
19618
- }
19619
- const txCount = txf.transactions?.length ?? 0;
19620
- if (txCount === 0) continue;
19621
- this._processTokenTransactions(token.id, txf, 0);
19622
- anyScanDirty = true;
19609
+ if (await this._scanTokenForAttribution(token, 0)) anyScanDirty = true;
19623
19610
  }
19624
19611
  const archivedTokensForScan = deps.payments.getArchivedTokens();
19625
19612
  for (const [archivedId, txf] of archivedTokensForScan) {
@@ -19635,7 +19622,7 @@ var AccountingModule = class _AccountingModule {
19635
19622
  if (this.config.debug) {
19636
19623
  logger.debug(LOG_TAG2, `Invoice created and stored: ${invoiceId}`);
19637
19624
  }
19638
- const txfToken = sdkTokenJson;
19625
+ const txfToken = engine ? sdkData : JSON.parse(sdkData);
19639
19626
  return {
19640
19627
  success: true,
19641
19628
  invoiceId,
@@ -19676,36 +19663,60 @@ var AccountingModule = class _AccountingModule {
19676
19663
  this.ensureNotDestroyed();
19677
19664
  this.ensureInitialized();
19678
19665
  const deps = this.deps;
19679
- const tokenType = token.genesis?.data?.tokenType;
19680
- if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19681
- throw new SphereError(
19682
- `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19683
- "INVOICE_WRONG_TOKEN_TYPE"
19684
- );
19685
- }
19686
- const tokenData = token.genesis?.data?.tokenData;
19687
- if (!tokenData || typeof tokenData !== "string") {
19688
- throw new SphereError(
19689
- "Invoice import failed: missing or invalid tokenData field.",
19690
- "INVOICE_INVALID_DATA"
19691
- );
19692
- }
19693
- let jsonString = tokenData;
19694
- if (!/^\s*[\[{"]/.test(tokenData)) {
19666
+ let terms;
19667
+ let tokenId;
19668
+ let sdkDataForStore;
19669
+ const engine = deps.tokenEngine;
19670
+ const isV2 = !!engine && typeof token === "string";
19671
+ if (isV2) {
19672
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token)));
19673
+ const verifyResult = await engine.verify(sphereToken);
19674
+ if (!verifyResult.ok) {
19675
+ throw new SphereError("Invoice import failed: inclusion proof is invalid.", "INVOICE_INVALID_PROOF");
19676
+ }
19677
+ tokenId = engine.tokenId(sphereToken);
19678
+ const data = engine.readTokenData(sphereToken);
19679
+ if (!data) {
19680
+ throw new SphereError("Invoice import failed: missing or invalid tokenData field.", "INVOICE_INVALID_DATA");
19681
+ }
19695
19682
  try {
19696
- const bytes = hexToBytes(tokenData);
19697
- jsonString = new TextDecoder().decode(bytes);
19683
+ terms = JSON.parse(new TextDecoder().decode(data));
19698
19684
  } catch {
19685
+ throw new SphereError("Invoice import failed: tokenData is not valid JSON.", "INVOICE_INVALID_DATA");
19699
19686
  }
19700
- }
19701
- let terms;
19702
- try {
19703
- terms = JSON.parse(jsonString);
19704
- } catch {
19705
- throw new SphereError(
19706
- "Invoice import failed: tokenData is not valid JSON.",
19707
- "INVOICE_INVALID_DATA"
19708
- );
19687
+ sdkDataForStore = token;
19688
+ } else {
19689
+ const tokenType = token.genesis?.data?.tokenType;
19690
+ if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19691
+ throw new SphereError(
19692
+ `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19693
+ "INVOICE_WRONG_TOKEN_TYPE"
19694
+ );
19695
+ }
19696
+ const tokenData = token.genesis?.data?.tokenData;
19697
+ if (!tokenData || typeof tokenData !== "string") {
19698
+ throw new SphereError(
19699
+ "Invoice import failed: missing or invalid tokenData field.",
19700
+ "INVOICE_INVALID_DATA"
19701
+ );
19702
+ }
19703
+ let jsonString = tokenData;
19704
+ if (!/^\s*[[{"]/.test(tokenData)) {
19705
+ try {
19706
+ const bytes = hexToBytes(tokenData);
19707
+ jsonString = new TextDecoder().decode(bytes);
19708
+ } catch {
19709
+ }
19710
+ }
19711
+ try {
19712
+ terms = JSON.parse(jsonString);
19713
+ } catch {
19714
+ throw new SphereError(
19715
+ "Invoice import failed: tokenData is not valid JSON.",
19716
+ "INVOICE_INVALID_DATA"
19717
+ );
19718
+ }
19719
+ sdkDataForStore = JSON.stringify(token);
19709
19720
  }
19710
19721
  if (!terms || typeof terms !== "object") {
19711
19722
  throw new SphereError(
@@ -19792,7 +19803,7 @@ var AccountingModule = class _AccountingModule {
19792
19803
  }
19793
19804
  }
19794
19805
  }
19795
- const tokenId = token.genesis?.data?.tokenId;
19806
+ if (!isV2) tokenId = token.genesis?.data?.tokenId;
19796
19807
  if (!tokenId || typeof tokenId !== "string") {
19797
19808
  throw new SphereError(
19798
19809
  "Invoice import failed: missing tokenId in genesis data.",
@@ -19805,13 +19816,13 @@ var AccountingModule = class _AccountingModule {
19805
19816
  "INVOICE_ALREADY_EXISTS"
19806
19817
  );
19807
19818
  }
19808
- {
19809
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19810
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19811
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19819
+ if (!isV2) {
19820
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19821
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19822
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19812
19823
  const reSerializedBytes = new TextEncoder().encode(canonicalSerialize(terms));
19813
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(reSerializedBytes).digest();
19814
- const reTokenId = new TokenId5(hash.imprint).toJSON();
19824
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(reSerializedBytes).digest();
19825
+ const reTokenId = new TokenId4(hash.imprint).toJSON();
19815
19826
  if (reTokenId !== tokenId) {
19816
19827
  throw new SphereError(
19817
19828
  "Invoice import failed: parsed terms do not match on-chain token ID (canonical hash mismatch).",
@@ -19819,35 +19830,37 @@ var AccountingModule = class _AccountingModule {
19819
19830
  );
19820
19831
  }
19821
19832
  }
19822
- if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19823
- throw new SphereError(
19824
- "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19825
- "INVOICE_INVALID_PROOF"
19826
- );
19827
- }
19828
- try {
19829
- const sdkToken = await import_Token8.Token.fromJSON(token);
19830
- const verifyResult = await sdkToken.verify(deps.trustBase);
19831
- const verifyOk = verifyResult.isSuccessful === true;
19832
- if (!verifyOk) {
19833
+ if (!isV2) {
19834
+ if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19833
19835
  throw new SphereError(
19834
- "Invoice import failed: inclusion proof is invalid.",
19836
+ "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19835
19837
  "INVOICE_INVALID_PROOF"
19836
19838
  );
19837
19839
  }
19838
- const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19839
- if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19840
+ try {
19841
+ const sdkToken = await import_Token7.Token.fromJSON(token);
19842
+ const verifyResult = await sdkToken.verify(deps.trustBase);
19843
+ const verifyOk = verifyResult.isSuccessful === true;
19844
+ if (!verifyOk) {
19845
+ throw new SphereError(
19846
+ "Invoice import failed: inclusion proof is invalid.",
19847
+ "INVOICE_INVALID_PROOF"
19848
+ );
19849
+ }
19850
+ const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19851
+ if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19852
+ throw new SphereError(
19853
+ `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19854
+ "INVOICE_INVALID_DATA"
19855
+ );
19856
+ }
19857
+ } catch (err) {
19858
+ if (err instanceof SphereError) throw err;
19840
19859
  throw new SphereError(
19841
- `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19842
- "INVOICE_INVALID_DATA"
19860
+ `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19861
+ "INVOICE_INVALID_PROOF"
19843
19862
  );
19844
19863
  }
19845
- } catch (err) {
19846
- if (err instanceof SphereError) throw err;
19847
- throw new SphereError(
19848
- `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19849
- "INVOICE_INVALID_PROOF"
19850
- );
19851
19864
  }
19852
19865
  try {
19853
19866
  const uiToken = {
@@ -19860,7 +19873,7 @@ var AccountingModule = class _AccountingModule {
19860
19873
  status: "confirmed",
19861
19874
  createdAt: terms.createdAt,
19862
19875
  updatedAt: terms.createdAt,
19863
- sdkData: JSON.stringify(token)
19876
+ sdkData: sdkDataForStore
19864
19877
  };
19865
19878
  await deps.payments.addToken(uiToken);
19866
19879
  } catch (err) {
@@ -19915,17 +19928,8 @@ var AccountingModule = class _AccountingModule {
19915
19928
  const allTokens = deps.payments.getTokens();
19916
19929
  let anyDirty = false;
19917
19930
  for (const existingToken of allTokens) {
19918
- if (!existingToken.sdkData) continue;
19919
- let txf;
19920
- try {
19921
- txf = JSON.parse(existingToken.sdkData);
19922
- } catch {
19923
- continue;
19924
- }
19925
- const transactions = txf.transactions ?? [];
19926
19931
  const startIndex = this.tokenScanState.get(existingToken.id) ?? 0;
19927
- if (transactions.length > startIndex) {
19928
- this._processTokenTransactions(existingToken.id, txf, startIndex);
19932
+ if (await this._scanTokenForAttribution(existingToken, startIndex)) {
19929
19933
  anyDirty = true;
19930
19934
  }
19931
19935
  }
@@ -21815,17 +21819,8 @@ var AccountingModule = class _AccountingModule {
21815
21819
  const allTokens = deps.payments.getTokens();
21816
21820
  let anyDirty = false;
21817
21821
  for (const token of allTokens) {
21818
- if (!token.sdkData) continue;
21819
- let txf;
21820
- try {
21821
- txf = JSON.parse(token.sdkData);
21822
- } catch {
21823
- continue;
21824
- }
21825
- const transactions = txf.transactions ?? [];
21826
21822
  const startIndex = this.tokenScanState.get(token.id) ?? 0;
21827
- if (transactions.length > startIndex) {
21828
- this._processTokenTransactions(token.id, txf, startIndex);
21823
+ if (await this._scanTokenForAttribution(token, startIndex)) {
21829
21824
  anyDirty = true;
21830
21825
  }
21831
21826
  }
@@ -21880,6 +21875,53 @@ var AccountingModule = class _AccountingModule {
21880
21875
  * @param txf - Parsed TxfToken.
21881
21876
  * @param startIndex - First unprocessed transaction index.
21882
21877
  */
21878
+ /**
21879
+ * Attribute one payment token's invoice memo(s) to the ledger.
21880
+ *
21881
+ * v2 (engine blob): the token carries a single on-chain memo. We decode it and
21882
+ * shim the token into a v1-shaped `txf` (coinData ← engine.readValue, the memo
21883
+ * ← engine.readMemo) so the battle-hardened `_processTokenTransactions` runs
21884
+ * UNCHANGED — same dedup, direction, provisional/synthetic/orphan handling.
21885
+ * v1 (TXF JSON): parse and scan transactions directly.
21886
+ *
21887
+ * `startIndex` is the per-token watermark (0 for a full retroactive scan).
21888
+ * Returns true when the token was scanned. Async because engine.decodeToken is.
21889
+ */
21890
+ async _scanTokenForAttribution(token, startIndex) {
21891
+ if (!token.sdkData) return false;
21892
+ const engine = this.deps?.tokenEngine;
21893
+ const isBlob = token.sdkData.length >= 2 && token.sdkData.length % 2 === 0 && token.sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(token.sdkData);
21894
+ if (engine && isBlob) {
21895
+ let syntheticTxf;
21896
+ try {
21897
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token.sdkData)));
21898
+ const memo = engine.readMemo(sphereToken);
21899
+ if (!memo) return false;
21900
+ const coinData = (engine.readValue(sphereToken)?.assets ?? []).map(
21901
+ (a) => [a.coinId, a.amount.toString()]
21902
+ );
21903
+ syntheticTxf = {
21904
+ genesis: { data: { coinData } },
21905
+ transactions: [{ data: { message: bytesToHex(memo) }, inclusionProof: {} }]
21906
+ };
21907
+ } catch {
21908
+ return false;
21909
+ }
21910
+ this._processTokenTransactions(token.id, syntheticTxf, startIndex);
21911
+ return true;
21912
+ }
21913
+ let txf;
21914
+ try {
21915
+ txf = JSON.parse(token.sdkData);
21916
+ } catch {
21917
+ return false;
21918
+ }
21919
+ if ((txf.transactions?.length ?? 0) > startIndex) {
21920
+ this._processTokenTransactions(token.id, txf, startIndex);
21921
+ return true;
21922
+ }
21923
+ return false;
21924
+ }
21883
21925
  _processTokenTransactions(tokenId, txf, startIndex) {
21884
21926
  const transactions = txf.transactions ?? [];
21885
21927
  let lastSuccessIdx = startIndex;
@@ -22159,17 +22201,7 @@ var AccountingModule = class _AccountingModule {
22159
22201
  async _handleIncomingTransfer(transfer) {
22160
22202
  if (this.destroyed) return;
22161
22203
  for (const token of transfer.tokens) {
22162
- if (!token.sdkData) continue;
22163
- let txf;
22164
- try {
22165
- txf = JSON.parse(token.sdkData);
22166
- } catch {
22167
- continue;
22168
- }
22169
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
22170
- if ((txf.transactions?.length ?? 0) > startIndex) {
22171
- this._processTokenTransactions(token.id, txf, startIndex);
22172
- }
22204
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
22173
22205
  }
22174
22206
  if (this.destroyed) return;
22175
22207
  await this._flushDirtyLedgerEntries();
@@ -22307,16 +22339,7 @@ var AccountingModule = class _AccountingModule {
22307
22339
  if (this.destroyed) return;
22308
22340
  for (const token of result.tokens) {
22309
22341
  if (!token.sdkData) continue;
22310
- let txf;
22311
- try {
22312
- txf = JSON.parse(token.sdkData);
22313
- } catch {
22314
- continue;
22315
- }
22316
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
22317
- if ((txf.transactions?.length ?? 0) > startIndex) {
22318
- this._processTokenTransactions(token.id, txf, startIndex);
22319
- }
22342
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
22320
22343
  const relatedInvoices = this.tokenInvoiceMap.get(token.id);
22321
22344
  if (relatedInvoices) {
22322
22345
  for (const invoiceId of relatedInvoices) {
@@ -22387,15 +22410,8 @@ var AccountingModule = class _AccountingModule {
22387
22410
  const tokens = this.deps.payments.getTokens();
22388
22411
  const token = tokens.find((t) => t.id === tokenId);
22389
22412
  if (!token?.sdkData) return;
22390
- let txf;
22391
- try {
22392
- txf = JSON.parse(token.sdkData);
22393
- } catch {
22394
- return;
22395
- }
22396
22413
  const startIndex = this.tokenScanState.get(tokenId) ?? 0;
22397
- if ((txf.transactions?.length ?? 0) > startIndex) {
22398
- this._processTokenTransactions(tokenId, txf, startIndex);
22414
+ if (await this._scanTokenForAttribution(token, startIndex)) {
22399
22415
  if (this.destroyed) return;
22400
22416
  await this._flushDirtyLedgerEntries();
22401
22417
  }
@@ -27828,29 +27844,421 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
27828
27844
  };
27829
27845
  }
27830
27846
 
27847
+ // token-engine/identity.ts
27848
+ var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27849
+ var SIGNING_ALGORITHM = "secp256k1";
27850
+ var EMBEDDED_PREDICATE_UNMASKED = 0;
27851
+ var HASH_ALGORITHM_SHA256 = 0n;
27852
+ var SHA256_IMPRINT_PREFIX = new Uint8Array([0, 0]);
27853
+ function hexToBytes4(hex) {
27854
+ const bytes = new Uint8Array(hex.length / 2);
27855
+ for (let i = 0; i < bytes.length; i++) {
27856
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
27857
+ }
27858
+ return bytes;
27859
+ }
27860
+ function sha2565(data) {
27861
+ return new import_DataHasher.DataHasher(import_HashAlgorithm2.HashAlgorithm.SHA256).update(data).digest().then((h) => h.data);
27862
+ }
27863
+ async function deriveDirectAddress(publicKey) {
27864
+ const tokenTypeCbor = import_CborSerializer.CborSerializer.encodeByteString(hexToBytes4(UNICITY_TOKEN_TYPE_HEX));
27865
+ const reference = import_CborSerializer.CborSerializer.encodeArray(
27866
+ import_CborSerializer.CborSerializer.encodeByteString(new Uint8Array([EMBEDDED_PREDICATE_UNMASKED])),
27867
+ import_CborSerializer.CborSerializer.encodeByteString(tokenTypeCbor),
27868
+ import_CborSerializer.CborSerializer.encodeTextString(SIGNING_ALGORITHM),
27869
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(HASH_ALGORITHM_SHA256),
27870
+ import_CborSerializer.CborSerializer.encodeByteString(publicKey)
27871
+ );
27872
+ const refHash = await sha2565(reference);
27873
+ const imprint = new Uint8Array([...SHA256_IMPRINT_PREFIX, ...refHash]);
27874
+ const checksum = (await sha2565(imprint)).slice(0, 4);
27875
+ return `DIRECT://${import_HexConverter.HexConverter.encode(imprint)}${import_HexConverter.HexConverter.encode(checksum)}`;
27876
+ }
27877
+
27878
+ // token-engine/factory.ts
27879
+ init_errors();
27880
+
27881
+ // token-engine/SpherePaymentData.ts
27882
+ init_errors();
27883
+ var COIN_ID_PATTERN = /^([0-9a-f]{2})+$/;
27884
+ function assertAsset(coinId, amount) {
27885
+ if (!COIN_ID_PATTERN.test(coinId)) {
27886
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27887
+ }
27888
+ if (amount < 0n) {
27889
+ throw new SphereError(`Asset amount must be non-negative: ${amount.toString()}`, "VALIDATION_ERROR");
27890
+ }
27891
+ }
27892
+ function sphereAssetToSdk(coinId, amount) {
27893
+ assertAsset(coinId, amount);
27894
+ return new import_Asset.Asset(new import_AssetId.AssetId(import_HexConverter.HexConverter.decode(coinId)), amount);
27895
+ }
27896
+ var SpherePaymentData = class _SpherePaymentData {
27897
+ constructor(assets, _memo = null) {
27898
+ this.assets = assets;
27899
+ this._memo = _memo;
27900
+ }
27901
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
27902
+ static CBOR_TAG = 39050n;
27903
+ /** Envelope version; bump when the structure changes. */
27904
+ static VERSION = 1n;
27905
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
27906
+ get memo() {
27907
+ return this._memo ? new Uint8Array(this._memo) : null;
27908
+ }
27909
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
27910
+ static create(assets, memo = null) {
27911
+ return new _SpherePaymentData(assets, memo);
27912
+ }
27913
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
27914
+ static fromValue(value, memo = null) {
27915
+ const assets = value.assets.map((a) => sphereAssetToSdk(a.coinId, a.amount));
27916
+ return new _SpherePaymentData(import_PaymentAssetCollection.PaymentAssetCollection.create(...assets), memo);
27917
+ }
27918
+ /** Decode from the CBOR envelope produced by {@link encode}. */
27919
+ static fromCBOR(bytes) {
27920
+ const tag = import_CborDeserializer.CborDeserializer.decodeTag(bytes);
27921
+ if (tag.tag !== _SpherePaymentData.CBOR_TAG) {
27922
+ throw new import_CborError.CborError(`Invalid SpherePaymentData tag: ${tag.tag}`);
27923
+ }
27924
+ const fields = import_CborDeserializer.CborDeserializer.decodeArray(tag.data, 3);
27925
+ const version = import_CborDeserializer.CborDeserializer.decodeUnsignedInteger(fields[0]);
27926
+ if (version !== _SpherePaymentData.VERSION) {
27927
+ throw new import_CborError.CborError(`Unsupported SpherePaymentData version: ${version}`);
27928
+ }
27929
+ const memo = import_CborDeserializer.CborDeserializer.decodeNullable(fields[2], import_CborDeserializer.CborDeserializer.decodeByteString);
27930
+ return new _SpherePaymentData(import_PaymentAssetCollection.PaymentAssetCollection.fromCBOR(fields[1]), memo);
27931
+ }
27932
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
27933
+ encode() {
27934
+ return Promise.resolve(
27935
+ import_CborSerializer.CborSerializer.encodeTag(
27936
+ _SpherePaymentData.CBOR_TAG,
27937
+ import_CborSerializer.CborSerializer.encodeArray(
27938
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(_SpherePaymentData.VERSION),
27939
+ this.assets.toCBOR(),
27940
+ import_CborSerializer.CborSerializer.encodeNullable(this._memo, import_CborSerializer.CborSerializer.encodeByteString)
27941
+ )
27942
+ )
27943
+ );
27944
+ }
27945
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
27946
+ toValue() {
27947
+ return {
27948
+ assets: this.assets.toArray().map((a) => ({
27949
+ coinId: import_HexConverter.HexConverter.encode(a.id.bytes),
27950
+ amount: a.value
27951
+ }))
27952
+ };
27953
+ }
27954
+ /** Balance of a single coin within this payload (0n when absent). */
27955
+ balanceOf(coinId) {
27956
+ if (!COIN_ID_PATTERN.test(coinId)) {
27957
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27958
+ }
27959
+ const asset = this.assets.get(new import_AssetId.AssetId(import_HexConverter.HexConverter.decode(coinId)));
27960
+ return asset ? asset.value : 0n;
27961
+ }
27962
+ };
27963
+ function decodeSpherePaymentData(bytes) {
27964
+ return Promise.resolve(SpherePaymentData.fromCBOR(bytes));
27965
+ }
27966
+
27967
+ // token-engine/SphereTokenEngine.ts
27968
+ init_errors();
27969
+ var SphereTokenEngine = class {
27970
+ constructor(deps) {
27971
+ this.deps = deps;
27972
+ }
27973
+ // ── identity ────────────────────────────────────────────────────────────────
27974
+ getIdentity() {
27975
+ return { chainPubkey: new Uint8Array(this.deps.signingService.publicKey) };
27976
+ }
27977
+ /** Legacy DIRECT:// address (Path A). Async: the derivation hashes via the SDK. */
27978
+ deriveIdentityAddress(pubkey) {
27979
+ return deriveDirectAddress(pubkey ?? this.deps.signingService.publicKey);
27980
+ }
27981
+ // ── value (read) ─────────────────────────────────────────────────────────────
27982
+ readValue(token) {
27983
+ return token.value;
27984
+ }
27985
+ balanceOf(token, coinId) {
27986
+ let sum = 0n;
27987
+ for (const asset of token.value?.assets ?? []) {
27988
+ if (asset.coinId === coinId) sum += asset.amount;
27989
+ }
27990
+ return sum;
27991
+ }
27992
+ tokenId(token) {
27993
+ return token.blob.tokenId;
27994
+ }
27995
+ readMemo(token) {
27996
+ const sdkToken = token.sdkToken;
27997
+ if (sdkToken.transactions.length > 0) {
27998
+ return sdkToken.latestTransaction.data;
27999
+ }
28000
+ const data = sdkToken.genesis.data;
28001
+ if (data && this.isSpherePaymentData(data)) {
28002
+ return SpherePaymentData.fromCBOR(data).memo;
28003
+ }
28004
+ return null;
28005
+ }
28006
+ readTokenData(token) {
28007
+ const data = token.sdkToken.genesis.data;
28008
+ return data ? new Uint8Array(data) : null;
28009
+ }
28010
+ // ── lifecycle ────────────────────────────────────────────────────────────────
28011
+ async mint(params, options) {
28012
+ const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
28013
+ const data = params.value ? await SpherePaymentData.fromValue(params.value).encode() : null;
28014
+ const mintTx = await import_MintTransaction.MintTransaction.create(this.deps.networkId, recipient, data);
28015
+ const certificationData = await import_CertificationData.CertificationData.fromMintTransaction(mintTx);
28016
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
28017
+ if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
28018
+ throw new SphereError(`Mint certification failed: ${response.status}`, "AGGREGATOR_ERROR");
28019
+ }
28020
+ const proof = await (0, import_InclusionProofUtils2.waitInclusionProof)(
28021
+ this.deps.client,
28022
+ this.deps.trustBase,
28023
+ this.deps.predicateVerifier,
28024
+ mintTx,
28025
+ options?.signal
28026
+ );
28027
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
28028
+ const token = await import_Token2.Token.mint(
28029
+ this.deps.trustBase,
28030
+ this.deps.predicateVerifier,
28031
+ this.deps.mintJustificationVerifier,
28032
+ certified
28033
+ );
28034
+ return this.wrapToken(token);
28035
+ }
28036
+ async mintDataToken(params, options) {
28037
+ const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
28038
+ const tokenType = params.tokenType ? new import_TokenType.TokenType(params.tokenType) : import_TokenType.TokenType.generate();
28039
+ const salt = params.salt ? import_TokenSalt.TokenSalt.fromBytes(params.salt) : import_TokenSalt.TokenSalt.generate();
28040
+ const mintTx = await import_MintTransaction.MintTransaction.create(this.deps.networkId, recipient, params.data, tokenType, salt);
28041
+ const certificationData = await import_CertificationData.CertificationData.fromMintTransaction(mintTx);
28042
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
28043
+ if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
28044
+ throw new SphereError(`Data-token mint failed: ${response.status}`, "AGGREGATOR_ERROR");
28045
+ }
28046
+ const proof = await (0, import_InclusionProofUtils2.waitInclusionProof)(
28047
+ this.deps.client,
28048
+ this.deps.trustBase,
28049
+ this.deps.predicateVerifier,
28050
+ mintTx,
28051
+ options?.signal
28052
+ );
28053
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
28054
+ const token = await import_Token2.Token.mint(
28055
+ this.deps.trustBase,
28056
+ this.deps.predicateVerifier,
28057
+ this.deps.mintJustificationVerifier,
28058
+ certified
28059
+ );
28060
+ return this.wrapToken(token);
28061
+ }
28062
+ async transfer(params, options) {
28063
+ this.assertOwned(params.token);
28064
+ const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
28065
+ const stateMask = crypto.getRandomValues(new Uint8Array(32));
28066
+ const transferTx = await import_TransferTransaction.TransferTransaction.create(params.token.sdkToken, recipient, stateMask, params.data ?? null);
28067
+ const unlockScript = await import_SignaturePredicateUnlockScript.SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
28068
+ const certificationData = await import_CertificationData.CertificationData.fromTransaction(transferTx, unlockScript);
28069
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
28070
+ if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
28071
+ throw new SphereError(`Transfer certification failed: ${response.status}`, "TRANSFER_FAILED");
28072
+ }
28073
+ const proof = await (0, import_InclusionProofUtils2.waitInclusionProof)(
28074
+ this.deps.client,
28075
+ this.deps.trustBase,
28076
+ this.deps.predicateVerifier,
28077
+ transferTx,
28078
+ options?.signal
28079
+ );
28080
+ const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
28081
+ const transferred = await params.token.sdkToken.transfer(this.deps.trustBase, this.deps.predicateVerifier, certified);
28082
+ return this.wrapToken(transferred);
28083
+ }
28084
+ async split(params, options) {
28085
+ this.assertOwned(params.token);
28086
+ if (params.outputs.length === 0) {
28087
+ throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
28088
+ }
28089
+ const requests = params.outputs.map(
28090
+ (o) => import_SplitTokenRequest.SplitTokenRequest.create(
28091
+ import_SignaturePredicate.SignaturePredicate.create(o.recipientPubkey),
28092
+ import_PaymentAssetCollection.PaymentAssetCollection.create(sphereAssetToSdk(o.coinId, o.amount))
28093
+ )
28094
+ );
28095
+ const split = await import_TokenSplit.TokenSplit.split(params.token.sdkToken, decodeSpherePaymentData, requests);
28096
+ const burnUnlock = await import_SignaturePredicateUnlockScript.SignaturePredicateUnlockScript.create(split.burn.transaction, this.deps.signingService);
28097
+ const burnCert = await import_CertificationData.CertificationData.fromTransaction(split.burn.transaction, burnUnlock);
28098
+ const burnResponse = await this.deps.client.submitCertificationRequest(burnCert);
28099
+ if (burnResponse.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
28100
+ throw new SphereError(`Split burn failed: ${burnResponse.status}`, "TRANSFER_FAILED");
28101
+ }
28102
+ const burnProof = await (0, import_InclusionProofUtils2.waitInclusionProof)(
28103
+ this.deps.client,
28104
+ this.deps.trustBase,
28105
+ this.deps.predicateVerifier,
28106
+ split.burn.transaction,
28107
+ options?.signal
28108
+ );
28109
+ const burnCertified = await split.burn.transaction.toCertifiedTransaction(
28110
+ this.deps.trustBase,
28111
+ this.deps.predicateVerifier,
28112
+ burnProof
28113
+ );
28114
+ const burntToken = await params.token.sdkToken.transfer(
28115
+ this.deps.trustBase,
28116
+ this.deps.predicateVerifier,
28117
+ burnCertified
28118
+ );
28119
+ const outputs = [];
28120
+ for (let i = 0; i < split.tokens.length; i++) {
28121
+ const splitToken = split.tokens[i];
28122
+ const data = await SpherePaymentData.create(splitToken.assets, params.outputs[i].data ?? null).encode();
28123
+ const justification = import_SplitMintJustification.SplitMintJustification.create(burntToken, splitToken.proofs).toCBOR();
28124
+ const mintTx = await import_MintTransaction.MintTransaction.create(
28125
+ splitToken.networkId,
28126
+ splitToken.recipient,
28127
+ data,
28128
+ splitToken.tokenType,
28129
+ splitToken.salt,
28130
+ justification
28131
+ );
28132
+ const certData = await import_CertificationData.CertificationData.fromMintTransaction(mintTx);
28133
+ const response = await this.deps.client.submitCertificationRequest(certData);
28134
+ if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
28135
+ throw new SphereError(`Split mint failed: ${response.status}`, "AGGREGATOR_ERROR");
28136
+ }
28137
+ const proof = await (0, import_InclusionProofUtils2.waitInclusionProof)(
28138
+ this.deps.client,
28139
+ this.deps.trustBase,
28140
+ this.deps.predicateVerifier,
28141
+ mintTx,
28142
+ options?.signal
28143
+ );
28144
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
28145
+ const token = await import_Token2.Token.mint(
28146
+ this.deps.trustBase,
28147
+ this.deps.predicateVerifier,
28148
+ this.deps.mintJustificationVerifier,
28149
+ certified
28150
+ );
28151
+ outputs.push(this.wrapToken(token));
28152
+ }
28153
+ return { outputs };
28154
+ }
28155
+ // ── verification ─────────────────────────────────────────────────────────────
28156
+ async verify(token, _options) {
28157
+ const result = await token.sdkToken.verify(
28158
+ this.deps.trustBase,
28159
+ this.deps.predicateVerifier,
28160
+ this.deps.mintJustificationVerifier
28161
+ );
28162
+ return result.status === import_VerificationStatus.VerificationStatus.OK ? { ok: true } : { ok: false, reason: String(result.status) };
28163
+ }
28164
+ async isSpent(token, _options) {
28165
+ const probe = await import_TransferTransaction.TransferTransaction.create(
28166
+ token.sdkToken,
28167
+ import_SignaturePredicate.SignaturePredicate.create(this.deps.signingService.publicKey),
28168
+ new Uint8Array(32)
28169
+ );
28170
+ const stateId = await import_StateId.StateId.fromTransaction(probe);
28171
+ const response = await this.deps.client.getInclusionProof(stateId);
28172
+ return response.inclusionProof.inclusionCertificate !== null;
28173
+ }
28174
+ // ── serialization ────────────────────────────────────────────────────────────
28175
+ encodeToken(token) {
28176
+ return token.blob;
28177
+ }
28178
+ async decodeToken(blob) {
28179
+ const sdkToken = await import_Token2.Token.fromCBOR(blob.token);
28180
+ if (sdkToken.genesis.networkId.id !== this.deps.networkId.id) {
28181
+ throw new SphereError(
28182
+ `Token network mismatch: token is on network ${sdkToken.genesis.networkId.id}, engine on ${this.deps.networkId.id}`,
28183
+ "VALIDATION_ERROR"
28184
+ );
28185
+ }
28186
+ return this.wrapToken(sdkToken);
28187
+ }
28188
+ // ── internals ────────────────────────────────────────────────────────────────
28189
+ /** Fail fast if this engine's key does not own the token's current state. */
28190
+ assertOwned(token) {
28191
+ const owner = token.sdkToken.latestTransaction.recipient;
28192
+ const mine = import_EncodedPredicate.EncodedPredicate.fromPredicate(import_SignaturePredicate.SignaturePredicate.create(this.deps.signingService.publicKey));
28193
+ if (!import_EncodedPredicate.EncodedPredicate.equals(owner, mine)) {
28194
+ throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
28195
+ }
28196
+ }
28197
+ /** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
28198
+ wrapToken(sdkToken) {
28199
+ const data = sdkToken.genesis.data;
28200
+ let value = null;
28201
+ if (data && this.isSpherePaymentData(data)) {
28202
+ try {
28203
+ value = SpherePaymentData.fromCBOR(data).toValue();
28204
+ } catch (err) {
28205
+ throw new SphereError(
28206
+ `Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
28207
+ "VALIDATION_ERROR"
28208
+ );
28209
+ }
28210
+ }
28211
+ const blob = {
28212
+ v: TOKEN_BLOB_VERSION,
28213
+ network: sdkToken.genesis.networkId.id,
28214
+ tokenId: import_HexConverter.HexConverter.encode(sdkToken.id.bytes),
28215
+ token: sdkToken.toCBOR()
28216
+ };
28217
+ return { sdkToken, blob, value };
28218
+ }
28219
+ /** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
28220
+ isSpherePaymentData(data) {
28221
+ try {
28222
+ return import_CborDeserializer.CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
28223
+ } catch {
28224
+ return false;
28225
+ }
28226
+ }
28227
+ };
28228
+
28229
+ // token-engine/factory.ts
28230
+ async function createSphereTokenEngine(config) {
28231
+ if (config.trustBaseJson == null) {
28232
+ throw new SphereError("Engine config requires a trust base (trustBaseJson)", "INVALID_CONFIG");
28233
+ }
28234
+ const trustBase = import_RootTrustBase.RootTrustBase.fromJSON(config.trustBaseJson);
28235
+ const predicateVerifier = import_PredicateVerifierService.PredicateVerifierService.create();
28236
+ const mintJustificationVerifier = new import_MintJustificationVerifierService.MintJustificationVerifierService();
28237
+ mintJustificationVerifier.register(
28238
+ new import_SplitMintJustificationVerifier.SplitMintJustificationVerifier(trustBase, predicateVerifier, decodeSpherePaymentData)
28239
+ );
28240
+ const deps = {
28241
+ client: new import_StateTransitionClient.StateTransitionClient(new import_AggregatorClient.AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
28242
+ trustBase,
28243
+ predicateVerifier,
28244
+ mintJustificationVerifier,
28245
+ signingService: new import_SigningService.SigningService(config.privateKey),
28246
+ // The trust base is the single source of truth for the network id (it carries
28247
+ // NetworkId.fromId, so any id works — e.g. testnet2 = 4 — with no enum entry).
28248
+ networkId: trustBase.networkId
28249
+ };
28250
+ return new SphereTokenEngine(deps);
28251
+ }
28252
+
27831
28253
  // core/Sphere.ts
27832
- var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
27833
- var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
27834
- var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
27835
- var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
27836
- var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
28254
+ var import_nostr_js_sdk4 = require("@unicitylabs/nostr-js-sdk");
27837
28255
  function isValidNametag2(nametag) {
27838
- if ((0, import_nostr_js_sdk5.isPhoneNumber)(nametag)) return true;
28256
+ if ((0, import_nostr_js_sdk4.isPhoneNumber)(nametag)) return true;
27839
28257
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
27840
28258
  }
27841
- var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27842
28259
  async function deriveL3PredicateAddress(privateKey) {
27843
- const secret = Buffer.from(privateKey, "hex");
27844
- const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
27845
- const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
27846
- const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
27847
- const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
27848
- tokenType,
27849
- signingService.algorithm,
27850
- signingService.publicKey,
27851
- import_HashAlgorithm7.HashAlgorithm.SHA256
27852
- );
27853
- return (await (await predicateRef).toAddress()).toString();
28260
+ const prehashedPublicKey = getPublicKey(sha2562(privateKey, "hex"));
28261
+ return deriveDirectAddress(hexToBytes2(prehashedPublicKey));
27854
28262
  }
27855
28263
  var Sphere = class _Sphere {
27856
28264
  // Singleton
@@ -27872,14 +28280,14 @@ var Sphere = class _Sphere {
27872
28280
  _addressIdToIndex = /* @__PURE__ */ new Map();
27873
28281
  /** Nametag cache: addressId -> (nametagIndex -> nametag). Separate from tracked addresses. */
27874
28282
  _addressNametags = /* @__PURE__ */ new Map();
27875
- /** Cached PROXY address (computed once when nametag is set) */
27876
- _cachedProxyAddress = void 0;
27877
28283
  // Providers
27878
28284
  _storage;
27879
28285
  _tokenStorageProviders = /* @__PURE__ */ new Map();
27880
28286
  _transport;
27881
28287
  _oracle;
27882
28288
  _priceProvider;
28289
+ /** v2 token engine (built per active address from the oracle); injected into modules. */
28290
+ _tokenEngine;
27883
28291
  // Modules (single-instance — backward compat, delegates to active address)
27884
28292
  _payments;
27885
28293
  _communications;
@@ -28220,20 +28628,6 @@ var Sphere = class _Sphere {
28220
28628
  await sphere.syncIdentityWithTransport();
28221
28629
  sphere._initialized = true;
28222
28630
  _Sphere.instance = sphere;
28223
- if (sphere._identity?.nametag && !sphere._payments.hasNametag()) {
28224
- progress?.({ step: "registering_nametag", message: "Restoring nametag token..." });
28225
- logger.debug("Sphere", `Unicity ID @${sphere._identity.nametag} has no token, attempting to mint...`);
28226
- try {
28227
- const result = await sphere.mintNametag(sphere._identity.nametag);
28228
- if (result.success) {
28229
- logger.debug("Sphere", `Nametag token minted successfully on load`);
28230
- } else {
28231
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
28232
- }
28233
- } catch (err) {
28234
- logger.warn("Sphere", `Nametag token mint failed:`, err);
28235
- }
28236
- }
28237
28631
  if (options.discoverAddresses !== false && sphere._transport.discoverAddresses && sphere._masterKey) {
28238
28632
  progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
28239
28633
  try {
@@ -29285,13 +29679,13 @@ var Sphere = class _Sphere {
29285
29679
  oracle: this._oracle,
29286
29680
  emitEvent: this.emitEvent.bind(this),
29287
29681
  chainCode: this._masterKey?.chainCode || void 0,
29288
- price: this._priceProvider ?? void 0
29682
+ price: this._priceProvider ?? void 0,
29683
+ tokenEngine: moduleSet.tokenEngine
29289
29684
  });
29290
29685
  }
29291
29686
  }
29292
29687
  this._identity = newIdentity;
29293
29688
  this._currentAddressIndex = index;
29294
- await this._updateCachedProxyAddress();
29295
29689
  const activeModules = this._addressModules.get(index);
29296
29690
  this._payments = activeModules.payments;
29297
29691
  this._communications = activeModules.communications;
@@ -29329,35 +29723,10 @@ var Sphere = class _Sphere {
29329
29723
  }
29330
29724
  if (newNametag) {
29331
29725
  await this.persistAddressNametags();
29332
- if (!this._payments.hasNametag()) {
29333
- logger.debug("Sphere", `Minting nametag token for @${newNametag}...`);
29334
- try {
29335
- const result = await this.mintNametag(newNametag);
29336
- if (result.success) {
29337
- logger.debug("Sphere", `Nametag token minted successfully`);
29338
- } else {
29339
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
29340
- }
29341
- } catch (err) {
29342
- logger.warn("Sphere", `Nametag token mint failed:`, err);
29343
- }
29344
- }
29345
29726
  this.emitEvent("nametag:registered", {
29346
29727
  nametag: newNametag,
29347
29728
  addressIndex: index
29348
29729
  });
29349
- } else if (this._identity?.nametag && !this._payments.hasNametag()) {
29350
- logger.debug("Sphere", `Unicity ID @${this._identity.nametag} has no token after switch, minting...`);
29351
- try {
29352
- const result = await this.mintNametag(this._identity.nametag);
29353
- if (result.success) {
29354
- logger.debug("Sphere", `Nametag token minted successfully after switch`);
29355
- } else {
29356
- logger.warn("Sphere", `Could not mint nametag token after switch: ${result.error}`);
29357
- }
29358
- } catch (err) {
29359
- logger.warn("Sphere", `Nametag token mint failed after switch:`, err);
29360
- }
29361
29730
  }
29362
29731
  }
29363
29732
  /**
@@ -29387,6 +29756,7 @@ var Sphere = class _Sphere {
29387
29756
  const communications = createCommunicationsModule(this._communicationsConfig);
29388
29757
  const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
29389
29758
  const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
29759
+ const tokenEngine = await this.buildTokenEngine(identity);
29390
29760
  payments.initialize({
29391
29761
  identity,
29392
29762
  storage: this._storage,
@@ -29395,7 +29765,8 @@ var Sphere = class _Sphere {
29395
29765
  oracle: this._oracle,
29396
29766
  emitEvent,
29397
29767
  chainCode: this._masterKey?.chainCode || void 0,
29398
- price: this._priceProvider ?? void 0
29768
+ price: this._priceProvider ?? void 0,
29769
+ tokenEngine
29399
29770
  });
29400
29771
  communications.initialize({
29401
29772
  identity,
@@ -29431,7 +29802,8 @@ var Sphere = class _Sphere {
29431
29802
  emitEvent,
29432
29803
  on: this.on.bind(this),
29433
29804
  storage: this._storage,
29434
- communications
29805
+ communications,
29806
+ tokenEngine
29435
29807
  });
29436
29808
  } else {
29437
29809
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -29491,6 +29863,7 @@ var Sphere = class _Sphere {
29491
29863
  market,
29492
29864
  transportAdapter: adapter,
29493
29865
  tokenStorageProviders: new Map(tokenStorageProviders),
29866
+ tokenEngine,
29494
29867
  initialized: true
29495
29868
  };
29496
29869
  this._addressModules.set(index, moduleSet);
@@ -30046,15 +30419,6 @@ var Sphere = class _Sphere {
30046
30419
  hasNametag() {
30047
30420
  return !!this._identity?.nametag;
30048
30421
  }
30049
- /**
30050
- * Get the PROXY address for the current nametag
30051
- * PROXY addresses are derived from the nametag hash and require
30052
- * the nametag token to claim funds sent to them
30053
- * @returns PROXY address string or undefined if no nametag
30054
- */
30055
- getProxyAddress() {
30056
- return this._cachedProxyAddress;
30057
- }
30058
30422
  /**
30059
30423
  * Resolve any identifier to full peer information.
30060
30424
  * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, or transport pubkey.
@@ -30089,17 +30453,6 @@ var Sphere = class _Sphere {
30089
30453
  throw new SphereError(`Cannot resolve address: ${address.slice(0, 30)}`, "INVALID_RECIPIENT");
30090
30454
  }
30091
30455
  }
30092
- /** Compute and cache the PROXY address from the current nametag */
30093
- async _updateCachedProxyAddress() {
30094
- const nametag = this._identity?.nametag;
30095
- if (!nametag) {
30096
- this._cachedProxyAddress = void 0;
30097
- return;
30098
- }
30099
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
30100
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
30101
- this._cachedProxyAddress = proxyAddr.toString();
30102
- }
30103
30456
  /**
30104
30457
  * Register a nametag for the current active address
30105
30458
  * Each address can have its own independent nametag
@@ -30127,17 +30480,6 @@ var Sphere = class _Sphere {
30127
30480
  if (this._identity?.nametag) {
30128
30481
  throw new SphereError(`Unicity ID already registered for address ${this._currentAddressIndex}: @${this._identity.nametag}`, "ALREADY_INITIALIZED");
30129
30482
  }
30130
- if (!this._payments.hasNametag()) {
30131
- logger.debug("Sphere", `Minting nametag token for @${cleanNametag}...`);
30132
- const result = await this.mintNametag(cleanNametag);
30133
- if (!result.success) {
30134
- throw new SphereError(
30135
- `Failed to mint nametag token: ${result.error}`,
30136
- "AGGREGATOR_ERROR"
30137
- );
30138
- }
30139
- logger.debug("Sphere", `Nametag token minted successfully`);
30140
- }
30141
30483
  if (this._transport.publishIdentityBinding) {
30142
30484
  const success = await this._transport.publishIdentityBinding(
30143
30485
  this._identity.chainPubkey,
@@ -30150,7 +30492,6 @@ var Sphere = class _Sphere {
30150
30492
  }
30151
30493
  }
30152
30494
  this._identity.nametag = cleanNametag;
30153
- await this._updateCachedProxyAddress();
30154
30495
  const currentAddressId = this._trackedAddresses.get(this._currentAddressIndex)?.addressId;
30155
30496
  if (currentAddressId) {
30156
30497
  let nametags = this._addressNametags.get(currentAddressId);
@@ -30183,35 +30524,19 @@ var Sphere = class _Sphere {
30183
30524
  await this._storage.saveTrackedAddresses(entries);
30184
30525
  }
30185
30526
  /**
30186
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
30187
- * This creates the nametag token required for receiving tokens via PROXY addresses (@nametag)
30527
+ * Check whether a nametag is available to register.
30188
30528
  *
30189
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
30190
- * @returns MintNametagResult with success status and token if successful
30529
+ * D5: nametags are Nostr bindings (name chainPubkey), not on-chain tokens. Availability is
30530
+ * first-seen-wins a name is available iff no binding resolves for it.
30191
30531
  *
30192
- * @example
30193
- * ```typescript
30194
- * // Mint nametag token for receiving via @alice
30195
- * const result = await sphere.mintNametag('alice');
30196
- * if (result.success) {
30197
- * console.log('Nametag minted:', result.nametagData?.name);
30198
- * } else {
30199
- * console.error('Mint failed:', result.error);
30200
- * }
30201
- * ```
30202
- */
30203
- async mintNametag(nametag) {
30204
- this.ensureReady();
30205
- return this._payments.mintNametag(nametag);
30206
- }
30207
- /**
30208
- * Check if a nametag is available for minting
30209
30532
  * @param nametag - The nametag to check (e.g., "alice" or "@alice")
30210
- * @returns true if available, false if taken or error
30533
+ * @returns true if available, false if already taken
30211
30534
  */
30212
30535
  async isNametagAvailable(nametag) {
30213
30536
  this.ensureReady();
30214
- return this._payments.isNametagAvailable(nametag);
30537
+ if (!this._transport.resolveNametag) return true;
30538
+ const bound = await this._transport.resolveNametag(this.cleanNametag(nametag));
30539
+ return bound == null;
30215
30540
  }
30216
30541
  /**
30217
30542
  * Load tracked addresses from storage.
@@ -30386,7 +30711,6 @@ var Sphere = class _Sphere {
30386
30711
  }
30387
30712
  if (recoveredNametag && !this._identity?.nametag) {
30388
30713
  this._identity.nametag = recoveredNametag;
30389
- await this._updateCachedProxyAddress();
30390
30714
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
30391
30715
  let nametags = this._addressNametags.get(entry.addressId);
30392
30716
  if (!nametags) {
@@ -30475,7 +30799,6 @@ var Sphere = class _Sphere {
30475
30799
  try {
30476
30800
  if (this._identity) {
30477
30801
  this._identity.nametag = recoveredNametag;
30478
- await this._updateCachedProxyAddress();
30479
30802
  }
30480
30803
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
30481
30804
  let nametags = this._addressNametags.get(entry.addressId);
@@ -30495,7 +30818,7 @@ var Sphere = class _Sphere {
30495
30818
  */
30496
30819
  cleanNametag(raw) {
30497
30820
  const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
30498
- return (0, import_nostr_js_sdk5.normalizeNametag)(stripped);
30821
+ return (0, import_nostr_js_sdk4.normalizeNametag)(stripped);
30499
30822
  }
30500
30823
  // ===========================================================================
30501
30824
  // Public Methods - Lifecycle
@@ -30675,7 +30998,6 @@ var Sphere = class _Sphere {
30675
30998
  } else if (this._identity && nametag) {
30676
30999
  this._identity.nametag = nametag;
30677
31000
  }
30678
- await this._updateCachedProxyAddress();
30679
31001
  }
30680
31002
  async initializeIdentityFromMnemonic(mnemonic, derivationPath) {
30681
31003
  const basePath = derivationPath ?? DEFAULT_BASE_PATH;
@@ -30826,10 +31148,45 @@ var Sphere = class _Sphere {
30826
31148
  this._providerEventCleanups = [];
30827
31149
  this._lastProviderConnected.clear();
30828
31150
  }
31151
+ /**
31152
+ * Construct the v2 token engine for a given address identity (defaults to the
31153
+ * active one) from the oracle's gateway URL + trust base and that address's
31154
+ * signing key. The engine is per-address — each address signs with its own key.
31155
+ * The trust base is the single source of truth for the network id (so any id
31156
+ * works — e.g. testnet2 = 4 — with no enum entry). Returns undefined (modules
31157
+ * keep their legacy path) when the oracle can't supply a trust base / url, or
31158
+ * construction fails — a misconfigured oracle never breaks initialization.
31159
+ */
31160
+ async buildTokenEngine(identity) {
31161
+ const oracle = this._oracle;
31162
+ const privateKey = (identity ?? this._identity)?.privateKey;
31163
+ const trustBaseJson = oracle.getTrustBaseJson?.() ?? null;
31164
+ const aggregatorUrl = oracle.getAggregatorUrl?.();
31165
+ if (!trustBaseJson || !aggregatorUrl || !privateKey) {
31166
+ logger.warn("Sphere", "v2 token engine not constructed (oracle has no trust base / url, or no identity) \u2014 legacy path");
31167
+ return void 0;
31168
+ }
31169
+ try {
31170
+ return await createSphereTokenEngine({
31171
+ aggregatorUrl,
31172
+ apiKey: oracle.getApiKey?.(),
31173
+ privateKey: hexToBytes2(privateKey),
31174
+ trustBaseJson
31175
+ });
31176
+ } catch (err) {
31177
+ logger.warn(
31178
+ "Sphere",
31179
+ `Failed to construct v2 token engine \u2014 modules use the legacy path: ${err instanceof Error ? err.message : String(err)}`
31180
+ );
31181
+ return void 0;
31182
+ }
31183
+ }
30829
31184
  async initializeModules() {
30830
31185
  const emitEvent = this.emitEvent.bind(this);
30831
31186
  const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
30832
31187
  const moduleTransport = adapter ?? this._transport;
31188
+ this._tokenEngine = await this.buildTokenEngine();
31189
+ const tokenEngine = this._tokenEngine;
30833
31190
  this._payments.initialize({
30834
31191
  identity: this._identity,
30835
31192
  storage: this._storage,
@@ -30840,7 +31197,8 @@ var Sphere = class _Sphere {
30840
31197
  // Pass chain code for L1 HD derivation
30841
31198
  chainCode: this._masterKey?.chainCode || void 0,
30842
31199
  price: this._priceProvider ?? void 0,
30843
- disabledProviderIds: this._disabledProviders
31200
+ disabledProviderIds: this._disabledProviders,
31201
+ tokenEngine
30844
31202
  });
30845
31203
  this._communications.initialize({
30846
31204
  identity: this._identity,
@@ -30876,7 +31234,8 @@ var Sphere = class _Sphere {
30876
31234
  emitEvent,
30877
31235
  on: this.on.bind(this),
30878
31236
  storage: this._storage,
30879
- communications: this._communications
31237
+ communications: this._communications,
31238
+ tokenEngine
30880
31239
  });
30881
31240
  } else {
30882
31241
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -30938,6 +31297,7 @@ var Sphere = class _Sphere {
30938
31297
  market: this._market,
30939
31298
  transportAdapter: adapter,
30940
31299
  tokenStorageProviders: new Map(this._tokenStorageProviders),
31300
+ tokenEngine: this._tokenEngine,
30941
31301
  initialized: true
30942
31302
  });
30943
31303
  }
@@ -31288,37 +31648,22 @@ init_constants();
31288
31648
 
31289
31649
  // validation/token-validator.ts
31290
31650
  init_logger();
31651
+ function stateIdOf(token) {
31652
+ return sha2562(bytesToHex3(token.blob.token), "hex");
31653
+ }
31291
31654
  var TokenValidator = class {
31292
- aggregatorClient = null;
31293
- trustBase = null;
31294
- skipVerification;
31295
- // Cache for spent state verification
31655
+ engine;
31656
+ // Spent-status cache: SPENT is permanent (immutable), UNSPENT expires after a TTL.
31296
31657
  spentStateCache = /* @__PURE__ */ new Map();
31297
31658
  UNSPENT_CACHE_TTL_MS = 5 * 60 * 1e3;
31298
31659
  // 5 minutes
31299
- constructor(options = {}) {
31300
- this.aggregatorClient = options.aggregatorClient || null;
31301
- this.trustBase = options.trustBase || null;
31302
- this.skipVerification = options.skipVerification || false;
31660
+ constructor(engine) {
31661
+ this.engine = engine;
31303
31662
  }
31304
- /**
31305
- * Set the aggregator client
31306
- */
31307
- setAggregatorClient(client) {
31308
- this.aggregatorClient = client;
31309
- }
31310
- /**
31311
- * Set the trust base
31312
- */
31313
- setTrustBase(trustBase) {
31314
- this.trustBase = trustBase;
31315
- }
31316
- // =============================================================================
31663
+ // ===========================================================================
31317
31664
  // Public API
31318
- // =============================================================================
31319
- /**
31320
- * Validate all tokens (parallel with batch limit)
31321
- */
31665
+ // ===========================================================================
31666
+ /** Validate all tokens (parallel, with a batch limit). */
31322
31667
  async validateAllTokens(tokens, options) {
31323
31668
  const validTokens = [];
31324
31669
  const issues = [];
@@ -31327,293 +31672,88 @@ var TokenValidator = class {
31327
31672
  let completed = 0;
31328
31673
  for (let i = 0; i < tokens.length; i += batchSize) {
31329
31674
  const batch = tokens.slice(i, i + batchSize);
31330
- const batchResults = await Promise.allSettled(
31331
- batch.map(async (token) => {
31332
- try {
31333
- const result = await this.validateToken(token);
31334
- return { token, result };
31335
- } catch (err) {
31336
- return {
31337
- token,
31338
- result: {
31339
- isValid: false,
31340
- reason: err instanceof Error ? err.message : String(err)
31341
- }
31342
- };
31343
- }
31344
- })
31675
+ const batchResults = await Promise.all(
31676
+ batch.map(async (token) => ({ token, result: await this.validateToken(token) }))
31345
31677
  );
31346
- for (const settledResult of batchResults) {
31678
+ for (const { token, result } of batchResults) {
31347
31679
  completed++;
31348
- if (settledResult.status === "fulfilled") {
31349
- const { token, result } = settledResult.value;
31350
- if (result.isValid) {
31351
- validTokens.push(token);
31352
- } else {
31353
- issues.push({
31354
- tokenId: token.id,
31355
- reason: result.reason || "Unknown validation error",
31356
- recoverable: false
31357
- });
31358
- }
31680
+ if (result.isValid) {
31681
+ validTokens.push(token);
31359
31682
  } else {
31360
31683
  issues.push({
31361
- tokenId: batch[batchResults.indexOf(settledResult)]?.id || "unknown",
31362
- reason: String(settledResult.reason),
31684
+ tokenId: stateIdOf(token),
31685
+ reason: result.reason || "Unknown validation error",
31363
31686
  recoverable: false
31364
31687
  });
31365
31688
  }
31366
31689
  }
31367
- if (options?.onProgress) {
31368
- options.onProgress(completed, total);
31369
- }
31690
+ options?.onProgress?.(completed, total);
31370
31691
  }
31371
31692
  return { validTokens, issues };
31372
31693
  }
31373
- /**
31374
- * Validate a single token
31375
- */
31694
+ /** Validate a single token's structural integrity against the trust base. */
31376
31695
  async validateToken(token) {
31377
- if (!token.sdkData) {
31378
- return {
31379
- isValid: false,
31380
- reason: "Token has no SDK data"
31381
- };
31382
- }
31383
- let txfToken;
31384
- try {
31385
- txfToken = JSON.parse(token.sdkData);
31386
- } catch {
31387
- return {
31388
- isValid: false,
31389
- reason: "Failed to parse token SDK data as JSON"
31390
- };
31391
- }
31392
- if (!this.hasValidTxfStructure(txfToken)) {
31393
- return {
31394
- isValid: false,
31395
- reason: "Token data missing required TXF fields (genesis, state)"
31396
- };
31397
- }
31398
- const uncommitted = this.getUncommittedTransactions(txfToken);
31399
- if (uncommitted.length > 0) {
31400
- return {
31401
- isValid: false,
31402
- reason: `${uncommitted.length} uncommitted transaction(s)`
31403
- };
31404
- }
31405
- if (this.trustBase && !this.skipVerification) {
31406
- try {
31407
- const verificationResult = await this.verifyWithSdk(txfToken);
31408
- if (!verificationResult.success) {
31409
- return {
31410
- isValid: false,
31411
- reason: verificationResult.error || "SDK verification failed"
31412
- };
31413
- }
31414
- } catch (err) {
31415
- logger.warn("Validation", "SDK verification skipped:", err instanceof Error ? err.message : err);
31416
- }
31417
- }
31418
- return { isValid: true };
31696
+ const result = await this.engine.verify(token);
31697
+ return result.ok ? { isValid: true } : { isValid: false, reason: result.reason || "Verification failed" };
31419
31698
  }
31420
31699
  /**
31421
- * Check if a token state is spent on the aggregator
31700
+ * Whether a token's current state has been spent on the network. Cached:
31701
+ * SPENT permanently, UNSPENT for `UNSPENT_CACHE_TTL_MS`. Graceful — an engine
31702
+ * error is treated as unspent (and not cached).
31422
31703
  */
31423
- async isTokenStateSpent(tokenId, stateHash, publicKey) {
31424
- if (!this.aggregatorClient) {
31425
- return false;
31426
- }
31427
- const cacheKey = `${tokenId}:${stateHash}:${publicKey}`;
31428
- const cached = this.spentStateCache.get(cacheKey);
31429
- if (cached !== void 0) {
31430
- if (cached.isSpent) {
31431
- return true;
31432
- }
31433
- if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) {
31434
- return false;
31435
- }
31704
+ async isSpent(token) {
31705
+ const key = stateIdOf(token);
31706
+ const cached = this.spentStateCache.get(key);
31707
+ if (cached) {
31708
+ if (cached.isSpent) return true;
31709
+ if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) return false;
31436
31710
  }
31711
+ let spent;
31437
31712
  try {
31438
- const { RequestId } = await import("@unicitylabs/state-transition-sdk/lib/api/RequestId");
31439
- const { DataHash } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHash");
31440
- const pubKeyBytes = Buffer.from(publicKey, "hex");
31441
- const stateHashObj = DataHash.fromJSON(stateHash);
31442
- const requestId2 = await RequestId.create(pubKeyBytes, stateHashObj);
31443
- const response = await this.aggregatorClient.getInclusionProof(requestId2);
31444
- let isSpent = false;
31445
- if (response.inclusionProof) {
31446
- const proof = response.inclusionProof;
31447
- const pathResult = await proof.merkleTreePath.verify(
31448
- requestId2.toBitString().toBigInt()
31449
- );
31450
- if (pathResult.isPathValid && pathResult.isPathIncluded && proof.authenticator !== null) {
31451
- isSpent = true;
31452
- }
31453
- }
31454
- this.spentStateCache.set(cacheKey, {
31455
- isSpent,
31456
- timestamp: Date.now()
31457
- });
31458
- return isSpent;
31713
+ spent = await this.engine.isSpent(token);
31459
31714
  } catch (err) {
31460
- logger.warn("Validation", "Error checking token state:", err);
31715
+ logger.warn("Validation", "Error checking spent status:", err);
31461
31716
  return false;
31462
31717
  }
31718
+ this.spentStateCache.set(key, { isSpent: spent, timestamp: Date.now() });
31719
+ return spent;
31463
31720
  }
31464
- /**
31465
- * Check which tokens are spent using SDK Token object to calculate state hash.
31466
- *
31467
- * Follows the same approach as the Sphere webgui TokenValidationService:
31468
- * 1. Parse TXF using SDK's Token.fromJSON()
31469
- * 2. Calculate CURRENT state hash via sdkToken.state.calculateHash()
31470
- * 3. Create RequestId via RequestId.create(walletPubKey, calculatedHash)
31471
- *
31472
- * Uses wallet's own pubkey (not source state predicate key) because "spent" means
31473
- * the CURRENT OWNER committed this state. Using the source state key would falsely
31474
- * detect received tokens as "spent" (sender's commitment matches source state).
31475
- */
31476
- async checkSpentTokens(tokens, publicKey, options) {
31721
+ /** Check which of the given tokens are spent, returning them by per-state id. */
31722
+ async checkSpentTokens(tokens, options) {
31477
31723
  const spentTokens = [];
31478
31724
  const errors = [];
31479
- if (!this.aggregatorClient) {
31480
- errors.push("Aggregator client not available");
31481
- return { spentTokens, errors };
31482
- }
31483
31725
  const batchSize = options?.batchSize ?? 3;
31484
31726
  const total = tokens.length;
31485
31727
  let completed = 0;
31486
- const { Token: SdkToken5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token");
31487
- const { RequestId } = await import("@unicitylabs/state-transition-sdk/lib/api/RequestId");
31488
- const pubKeyBytes = Buffer.from(publicKey, "hex");
31489
31728
  for (let i = 0; i < tokens.length; i += batchSize) {
31490
31729
  const batch = tokens.slice(i, i + batchSize);
31491
31730
  const batchResults = await Promise.allSettled(
31492
- batch.map(async (token) => {
31493
- try {
31494
- const txf = tokenToTxf(token);
31495
- if (!txf) {
31496
- return { tokenId: token.id, localId: token.id, stateHash: "", spent: false, error: "Invalid TXF" };
31497
- }
31498
- const tokenId = txf.genesis?.data?.tokenId || token.id;
31499
- const sdkToken = await SdkToken5.fromJSON(txf);
31500
- const calculatedStateHash = await sdkToken.state.calculateHash();
31501
- const calculatedStateHashStr = calculatedStateHash.toJSON();
31502
- const cacheKey = `${tokenId}:${calculatedStateHashStr}:${publicKey}`;
31503
- const cached = this.spentStateCache.get(cacheKey);
31504
- if (cached !== void 0) {
31505
- if (cached.isSpent) {
31506
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: true };
31507
- }
31508
- if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) {
31509
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: false };
31510
- }
31511
- }
31512
- const { DataHash } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHash");
31513
- const stateHashObj = DataHash.fromJSON(calculatedStateHashStr);
31514
- const requestId2 = await RequestId.create(pubKeyBytes, stateHashObj);
31515
- const response = await this.aggregatorClient.getInclusionProof(requestId2);
31516
- let isSpent = false;
31517
- if (response.inclusionProof) {
31518
- const proof = response.inclusionProof;
31519
- const pathResult = await proof.merkleTreePath.verify(
31520
- requestId2.toBitString().toBigInt()
31521
- );
31522
- if (pathResult.isPathValid && pathResult.isPathIncluded && proof.authenticator !== null) {
31523
- isSpent = true;
31524
- }
31525
- }
31526
- this.spentStateCache.set(cacheKey, {
31527
- isSpent,
31528
- timestamp: Date.now()
31529
- });
31530
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: isSpent };
31531
- } catch (err) {
31532
- return {
31533
- tokenId: token.id,
31534
- localId: token.id,
31535
- stateHash: "",
31536
- spent: false,
31537
- error: err instanceof Error ? err.message : String(err)
31538
- };
31539
- }
31540
- })
31731
+ batch.map(async (token) => ({ stateId: stateIdOf(token), spent: await this.isSpent(token) }))
31541
31732
  );
31542
31733
  for (const result of batchResults) {
31543
31734
  completed++;
31544
31735
  if (result.status === "fulfilled") {
31545
- if (result.value.spent) {
31546
- spentTokens.push({
31547
- tokenId: result.value.tokenId,
31548
- localId: result.value.localId,
31549
- stateHash: result.value.stateHash
31550
- });
31551
- }
31552
- if (result.value.error) {
31553
- errors.push(`Token ${result.value.tokenId}: ${result.value.error}`);
31554
- }
31736
+ if (result.value.spent) spentTokens.push({ stateId: result.value.stateId });
31555
31737
  } else {
31556
31738
  errors.push(String(result.reason));
31557
31739
  }
31558
31740
  }
31559
- if (options?.onProgress) {
31560
- options.onProgress(completed, total);
31561
- }
31741
+ options?.onProgress?.(completed, total);
31562
31742
  }
31563
31743
  return { spentTokens, errors };
31564
31744
  }
31565
- /**
31566
- * Clear the spent state cache
31567
- */
31745
+ /** Clear the spent-status cache. */
31568
31746
  clearSpentStateCache() {
31569
31747
  this.spentStateCache.clear();
31570
31748
  }
31571
- // =============================================================================
31572
- // Private Helpers
31573
- // =============================================================================
31574
- hasValidTxfStructure(obj) {
31575
- if (!obj || typeof obj !== "object") return false;
31576
- const txf = obj;
31577
- return !!(txf.genesis && typeof txf.genesis === "object" && txf.state && typeof txf.state === "object");
31578
- }
31579
- getUncommittedTransactions(txfToken) {
31580
- const txf = txfToken;
31581
- const transactions = txf.transactions;
31582
- if (!transactions || !Array.isArray(transactions)) {
31583
- return [];
31584
- }
31585
- return transactions.filter((tx) => tx.inclusionProof === null);
31586
- }
31587
- async verifyWithSdk(txfToken) {
31588
- try {
31589
- const { Token: Token5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token");
31590
- const sdkToken = await Token5.fromJSON(txfToken);
31591
- if (!this.trustBase) {
31592
- return { success: true };
31593
- }
31594
- const result = await sdkToken.verify(this.trustBase);
31595
- if (!result.isSuccessful) {
31596
- return {
31597
- success: false,
31598
- error: String(result) || "Verification failed"
31599
- };
31600
- }
31601
- return { success: true };
31602
- } catch (err) {
31603
- return {
31604
- success: false,
31605
- error: err instanceof Error ? err.message : String(err)
31606
- };
31607
- }
31608
- }
31609
31749
  };
31610
- function createTokenValidator(options) {
31611
- return new TokenValidator(options);
31750
+ function createTokenValidator(engine) {
31751
+ return new TokenValidator(engine);
31612
31752
  }
31613
31753
 
31614
31754
  // index.ts
31755
+ var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
31615
31756
  var import_nostr_js_sdk6 = require("@unicitylabs/nostr-js-sdk");
31616
- var import_nostr_js_sdk7 = require("@unicitylabs/nostr-js-sdk");
31617
31757
 
31618
31758
  // price/CoinGeckoPriceProvider.ts
31619
31759
  init_logger();