@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.
@@ -1890,26 +1890,14 @@ var NostrTransportProvider = class _NostrTransportProvider {
1890
1890
  }
1891
1891
  /**
1892
1892
  * Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
1893
- * Computes PROXY address from nametag if available.
1894
1893
  */
1895
1894
  async bindingInfoToPeerInfo(binding, nametag) {
1896
- const nametagValue = nametag || binding.nametag;
1897
- let proxyAddress = binding.proxyAddress;
1898
- if (nametagValue && !proxyAddress) {
1899
- try {
1900
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
1901
- const proxyAddr = await ProxyAddress.fromNameTag(nametagValue);
1902
- proxyAddress = proxyAddr.toString();
1903
- } catch {
1904
- }
1905
- }
1906
1895
  return {
1907
- nametag: nametagValue,
1896
+ nametag: nametag || binding.nametag,
1908
1897
  transportPubkey: binding.transportPubkey,
1909
1898
  chainPubkey: binding.publicKey || "",
1910
1899
  l1Address: binding.l1Address || "",
1911
1900
  directAddress: binding.directAddress || "",
1912
- proxyAddress,
1913
1901
  timestamp: binding.timestamp
1914
1902
  };
1915
1903
  }
@@ -1935,7 +1923,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
1935
1923
  chainPubkey: content.public_key || "",
1936
1924
  l1Address: content.l1_address || "",
1937
1925
  directAddress: content.direct_address || "",
1938
- proxyAddress: content.proxy_address || void 0,
1939
1926
  timestamp: bindingEvent.created_at * 1e3
1940
1927
  };
1941
1928
  } catch {
@@ -1978,7 +1965,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
1978
1965
  chainPubkey: content.public_key || "",
1979
1966
  l1Address: content.l1_address || "",
1980
1967
  directAddress: content.direct_address || "",
1981
- proxyAddress: content.proxy_address || void 0,
1982
1968
  timestamp: event.created_at * 1e3
1983
1969
  });
1984
1970
  } catch {
@@ -2048,8 +2034,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2048
2034
  }
2049
2035
  const nostrPubkey = this.getNostrPubkey();
2050
2036
  if (nametag) {
2051
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
2052
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
2053
2037
  try {
2054
2038
  const success2 = await this.nostrClient.publishNametagBinding(
2055
2039
  nametag,
@@ -2057,8 +2041,7 @@ var NostrTransportProvider = class _NostrTransportProvider {
2057
2041
  {
2058
2042
  publicKey: chainPubkey,
2059
2043
  l1Address,
2060
- directAddress,
2061
- proxyAddress: proxyAddr.toString()
2044
+ directAddress
2062
2045
  }
2063
2046
  );
2064
2047
  if (success2) {
@@ -6108,6 +6091,13 @@ var L1PaymentsModule = class {
6108
6091
  }
6109
6092
  };
6110
6093
 
6094
+ // types/v2-transfer.ts
6095
+ function isV2TransferPayload(obj) {
6096
+ if (!obj || typeof obj !== "object") return false;
6097
+ const p = obj;
6098
+ return p.type === "V2_TRANSFER" && typeof p.tokenBlob === "string" && p.tokenBlob.length > 0;
6099
+ }
6100
+
6111
6101
  // modules/payments/TokenSplitExecutor.ts
6112
6102
  init_logger();
6113
6103
  init_errors();
@@ -6483,16 +6473,115 @@ init_logger();
6483
6473
  init_errors();
6484
6474
  import { Token as SdkToken } from "@unicitylabs/state-transition-sdk/lib/token/Token";
6485
6475
  import { CoinId as CoinId2 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
6476
+
6477
+ // token-engine/sdk.ts
6478
+ import { StateTransitionClient } from "state-transition-sdk-v2/lib/StateTransitionClient.js";
6479
+ import { AggregatorClient } from "state-transition-sdk-v2/lib/api/AggregatorClient.js";
6480
+ import { NetworkId } from "state-transition-sdk-v2/lib/api/NetworkId.js";
6481
+ import { CertificationData } from "state-transition-sdk-v2/lib/api/CertificationData.js";
6482
+ import { CertificationResponse, CertificationStatus } from "state-transition-sdk-v2/lib/api/CertificationResponse.js";
6483
+ import { StateId } from "state-transition-sdk-v2/lib/api/StateId.js";
6484
+ import { InclusionProof } from "state-transition-sdk-v2/lib/api/InclusionProof.js";
6485
+ import { InclusionProofResponse } from "state-transition-sdk-v2/lib/api/InclusionProofResponse.js";
6486
+ import { RootTrustBase } from "state-transition-sdk-v2/lib/api/bft/RootTrustBase.js";
6487
+ import { waitInclusionProof as waitInclusionProof2 } from "state-transition-sdk-v2/lib/util/InclusionProofUtils.js";
6488
+ import { Token as Token2 } from "state-transition-sdk-v2/lib/transaction/Token.js";
6489
+ import { MintTransaction } from "state-transition-sdk-v2/lib/transaction/MintTransaction.js";
6490
+ import { TransferTransaction } from "state-transition-sdk-v2/lib/transaction/TransferTransaction.js";
6491
+ import { CertifiedMintTransaction } from "state-transition-sdk-v2/lib/transaction/CertifiedMintTransaction.js";
6492
+ import { CertifiedTransferTransaction } from "state-transition-sdk-v2/lib/transaction/CertifiedTransferTransaction.js";
6493
+ import { TokenId as TokenId2 } from "state-transition-sdk-v2/lib/transaction/TokenId.js";
6494
+ import { TokenType } from "state-transition-sdk-v2/lib/transaction/TokenType.js";
6495
+ import { TokenSalt } from "state-transition-sdk-v2/lib/transaction/TokenSalt.js";
6496
+ import { MintJustificationVerifierService } from "state-transition-sdk-v2/lib/transaction/verification/MintJustificationVerifierService.js";
6497
+ import { EncodedPredicate } from "state-transition-sdk-v2/lib/predicate/EncodedPredicate.js";
6498
+ import { SignaturePredicate } from "state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicate.js";
6499
+ import { SignaturePredicateUnlockScript } from "state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
6500
+ import { BurnPredicate } from "state-transition-sdk-v2/lib/predicate/builtin/BurnPredicate.js";
6501
+ import { PredicateVerifierService } from "state-transition-sdk-v2/lib/predicate/verification/PredicateVerifierService.js";
6502
+ import { SigningService } from "state-transition-sdk-v2/lib/crypto/secp256k1/SigningService.js";
6503
+ import { Signature } from "state-transition-sdk-v2/lib/crypto/secp256k1/Signature.js";
6504
+ import { MintSigningService } from "state-transition-sdk-v2/lib/crypto/MintSigningService.js";
6505
+ import { HashAlgorithm as HashAlgorithm2 } from "state-transition-sdk-v2/lib/crypto/hash/HashAlgorithm.js";
6506
+ import { DataHash } from "state-transition-sdk-v2/lib/crypto/hash/DataHash.js";
6507
+ import { DataHasher } from "state-transition-sdk-v2/lib/crypto/hash/DataHasher.js";
6508
+ import { DataHasherFactory } from "state-transition-sdk-v2/lib/crypto/hash/DataHasherFactory.js";
6509
+ import { CborSerializer } from "state-transition-sdk-v2/lib/serialization/cbor/CborSerializer.js";
6510
+ import { CborDeserializer } from "state-transition-sdk-v2/lib/serialization/cbor/CborDeserializer.js";
6511
+ import { CborError } from "state-transition-sdk-v2/lib/serialization/cbor/CborError.js";
6512
+ import { Asset } from "state-transition-sdk-v2/lib/payment/asset/Asset.js";
6513
+ import { AssetId } from "state-transition-sdk-v2/lib/payment/asset/AssetId.js";
6514
+ import { PaymentAssetCollection } from "state-transition-sdk-v2/lib/payment/asset/PaymentAssetCollection.js";
6515
+ import { TokenSplit } from "state-transition-sdk-v2/lib/payment/TokenSplit.js";
6516
+ import { SplitTokenRequest } from "state-transition-sdk-v2/lib/payment/SplitTokenRequest.js";
6517
+ import { SplitToken } from "state-transition-sdk-v2/lib/payment/SplitToken.js";
6518
+ import { SplitAssetProof } from "state-transition-sdk-v2/lib/payment/SplitAssetProof.js";
6519
+ import { SplitMintJustification } from "state-transition-sdk-v2/lib/payment/SplitMintJustification.js";
6520
+ import { SplitMintJustificationVerifier } from "state-transition-sdk-v2/lib/payment/SplitMintJustificationVerifier.js";
6521
+ import { VerificationStatus } from "state-transition-sdk-v2/lib/verification/VerificationStatus.js";
6522
+ import { VerificationResult } from "state-transition-sdk-v2/lib/verification/VerificationResult.js";
6523
+ import { HexConverter } from "state-transition-sdk-v2/lib/util/HexConverter.js";
6524
+ import { BigintConverter } from "state-transition-sdk-v2/lib/util/BigintConverter.js";
6525
+ import { BitString } from "state-transition-sdk-v2/lib/util/BitString.js";
6526
+
6527
+ // token-engine/token-blob.ts
6528
+ var TOKEN_BLOB_TAG = 39051n;
6529
+ var TOKEN_BLOB_VERSION = 1;
6530
+ function encodeTokenBlob(blob) {
6531
+ return CborSerializer.encodeTag(
6532
+ TOKEN_BLOB_TAG,
6533
+ CborSerializer.encodeArray(
6534
+ CborSerializer.encodeUnsignedInteger(BigInt(blob.v)),
6535
+ CborSerializer.encodeUnsignedInteger(BigInt(blob.network)),
6536
+ CborSerializer.encodeTextString(blob.tokenId),
6537
+ CborSerializer.encodeByteString(blob.token)
6538
+ )
6539
+ );
6540
+ }
6541
+ function decodeTokenBlob(bytes) {
6542
+ const tag = CborDeserializer.decodeTag(bytes);
6543
+ if (tag.tag !== TOKEN_BLOB_TAG) {
6544
+ throw new CborError(`Invalid TokenBlob tag: ${tag.tag}`);
6545
+ }
6546
+ const fields = CborDeserializer.decodeArray(tag.data, 4);
6547
+ const v = Number(CborDeserializer.decodeUnsignedInteger(fields[0]));
6548
+ if (v !== TOKEN_BLOB_VERSION) {
6549
+ throw new CborError(`Unsupported TokenBlob version: ${v}`);
6550
+ }
6551
+ return {
6552
+ v,
6553
+ network: Number(CborDeserializer.decodeUnsignedInteger(fields[1])),
6554
+ tokenId: CborDeserializer.decodeTextString(fields[2]),
6555
+ token: CborDeserializer.decodeByteString(fields[3])
6556
+ };
6557
+ }
6558
+
6559
+ // modules/payments/SpendQueue.ts
6486
6560
  var QUEUE_TIMEOUT_MS = 3e4;
6487
6561
  var QUEUE_MAX_SIZE = 100;
6488
6562
  var TAG2 = "SpendQueue";
6489
6563
  var SpendPlanner = class {
6564
+ /**
6565
+ * Token engine (path B). When injected, value reads go through the v2 engine
6566
+ * (sdkData = engine blob); otherwise the legacy v1 SdkToken path is used. The
6567
+ * engine is wired after construction via {@link setEngine} (it is bound to the
6568
+ * payments deps, which arrive after the planner is built).
6569
+ */
6570
+ engine;
6571
+ constructor(engine) {
6572
+ this.engine = engine;
6573
+ }
6574
+ /** Inject (or clear) the token engine. */
6575
+ setEngine(engine) {
6576
+ this.engine = engine;
6577
+ }
6490
6578
  /**
6491
6579
  * Async pre-computation: parse all tokens for a given coinId.
6492
6580
  * Called BEFORE the synchronous critical section.
6493
6581
  *
6494
- * Filters to confirmed tokens matching the coinId, parses each
6495
- * token's sdkData into an SdkToken, and extracts the bigint amount.
6582
+ * Filters to confirmed tokens matching the coinId, decodes each token's
6583
+ * sdkData and extracts the bigint amount — via the v2 engine when injected,
6584
+ * else the legacy v1 SdkToken path.
6496
6585
  */
6497
6586
  async buildParsedPool(tokens, coinId) {
6498
6587
  const pool = /* @__PURE__ */ new Map();
@@ -6501,20 +6590,28 @@ var SpendPlanner = class {
6501
6590
  if (t.status !== "confirmed") continue;
6502
6591
  if (!t.sdkData) continue;
6503
6592
  try {
6504
- const parsed = JSON.parse(t.sdkData);
6505
- const sdkToken = await SdkToken.fromJSON(parsed);
6506
- const realAmount = this.getTokenBalance(sdkToken, coinId);
6507
- if (realAmount <= 0n) {
6593
+ const { sdkToken, amount } = this.engine ? await this.parseViaEngine(t.sdkData, coinId) : await this.parseViaSdk(t.sdkData, coinId);
6594
+ if (amount <= 0n) {
6508
6595
  logger.warn(TAG2, `Token ${t.id} has 0 balance for coinId ${coinId}`);
6509
6596
  continue;
6510
6597
  }
6511
- pool.set(t.id, { token: t, sdkToken, amount: realAmount });
6598
+ pool.set(t.id, { token: t, sdkToken, amount });
6512
6599
  } catch (e) {
6513
6600
  logger.warn(TAG2, "Failed to parse token", t.id, e);
6514
6601
  }
6515
6602
  }
6516
6603
  return pool;
6517
6604
  }
6605
+ /** Engine path (v2): sdkData is the engine blob (hex of CBOR(TokenBlob)). */
6606
+ async parseViaEngine(sdkData, coinId) {
6607
+ const token = await this.engine.decodeToken(decodeTokenBlob(hexToBytes2(sdkData)));
6608
+ return { sdkToken: token, amount: this.engine.balanceOf(token, coinId) };
6609
+ }
6610
+ /** Legacy v1 path: sdkData is TXF JSON. */
6611
+ async parseViaSdk(sdkData, coinId) {
6612
+ const sdkToken = await SdkToken.fromJSON(JSON.parse(sdkData));
6613
+ return { sdkToken, amount: this.getTokenBalance(sdkToken, coinId) };
6614
+ }
6518
6615
  /**
6519
6616
  * SYNCHRONOUS critical section. NO await allowed anywhere in this method.
6520
6617
  *
@@ -6925,174 +7022,6 @@ var SpendQueue = class {
6925
7022
  }
6926
7023
  };
6927
7024
 
6928
- // modules/payments/NametagMinter.ts
6929
- init_logger();
6930
- import { Token as Token2 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
6931
- import { TokenId as TokenId2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
6932
- import { TokenType } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
6933
- import { TokenState as TokenState2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
6934
- import { MintTransactionData } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
6935
- import { MintCommitment } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
6936
- import { HashAlgorithm as HashAlgorithm2 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
6937
- import { UnmaskedPredicate as UnmaskedPredicate2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
6938
- import { waitInclusionProof as waitInclusionProof2 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
6939
- import { normalizeNametag } from "@unicitylabs/nostr-js-sdk";
6940
- var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
6941
- var NametagMinter = class {
6942
- client;
6943
- trustBase;
6944
- signingService;
6945
- skipVerification;
6946
- debug;
6947
- constructor(config) {
6948
- this.client = config.stateTransitionClient;
6949
- this.trustBase = config.trustBase;
6950
- this.signingService = config.signingService;
6951
- this.skipVerification = config.skipVerification ?? false;
6952
- this.debug = config.debug ?? false;
6953
- }
6954
- log(message, ...args) {
6955
- logger.debug("NametagMinter", message, ...args);
6956
- }
6957
- /**
6958
- * Check if a nametag is available (not already minted)
6959
- */
6960
- async isNametagAvailable(nametag) {
6961
- try {
6962
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
6963
- const cleanNametag = normalizeNametag(stripped);
6964
- const nametagTokenId = await TokenId2.fromNameTag(cleanNametag);
6965
- const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
6966
- return !isMinted;
6967
- } catch (error) {
6968
- this.log("Error checking nametag availability:", error);
6969
- return false;
6970
- }
6971
- }
6972
- /**
6973
- * Mint a nametag token on-chain
6974
- *
6975
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
6976
- * @param ownerAddress - The owner's direct address
6977
- * @returns MintNametagResult with token if successful
6978
- */
6979
- async mintNametag(nametag, ownerAddress) {
6980
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
6981
- const cleanNametag = normalizeNametag(stripped);
6982
- this.log(`Starting mint for nametag: ${cleanNametag}`);
6983
- try {
6984
- const nametagTokenId = await TokenId2.fromNameTag(cleanNametag);
6985
- const nametagTokenType = new TokenType(
6986
- Buffer.from(UNICITY_TOKEN_TYPE_HEX, "hex")
6987
- );
6988
- const nametagBytes = new TextEncoder().encode(cleanNametag);
6989
- const pubKey = this.signingService.publicKey;
6990
- const saltInput = new Uint8Array(pubKey.length + nametagBytes.length);
6991
- saltInput.set(pubKey, 0);
6992
- saltInput.set(nametagBytes, pubKey.length);
6993
- const saltBuffer = await crypto.subtle.digest("SHA-256", saltInput);
6994
- const salt = new Uint8Array(saltBuffer);
6995
- this.log("Generated deterministic salt");
6996
- const mintData = await MintTransactionData.createFromNametag(
6997
- cleanNametag,
6998
- nametagTokenType,
6999
- ownerAddress,
7000
- salt,
7001
- ownerAddress
7002
- );
7003
- this.log("Created MintTransactionData");
7004
- const commitment = await MintCommitment.create(mintData);
7005
- this.log("Created MintCommitment");
7006
- const MAX_RETRIES = 3;
7007
- let submitSuccess = false;
7008
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
7009
- try {
7010
- this.log(`Submitting commitment (attempt ${attempt}/${MAX_RETRIES})...`);
7011
- const response = await this.client.submitMintCommitment(commitment);
7012
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
7013
- this.log(`Commitment ${response.status === "REQUEST_ID_EXISTS" ? "already exists" : "submitted successfully"}`);
7014
- submitSuccess = true;
7015
- break;
7016
- } else {
7017
- this.log(`Commitment failed: ${response.status}`);
7018
- if (attempt === MAX_RETRIES) {
7019
- return {
7020
- success: false,
7021
- error: `Failed to submit commitment after ${MAX_RETRIES} attempts: ${response.status}`
7022
- };
7023
- }
7024
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7025
- }
7026
- } catch (error) {
7027
- this.log(`Attempt ${attempt} error:`, error);
7028
- if (attempt === MAX_RETRIES) {
7029
- return {
7030
- success: false,
7031
- error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`
7032
- };
7033
- }
7034
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7035
- }
7036
- }
7037
- if (!submitSuccess) {
7038
- return {
7039
- success: false,
7040
- error: "Failed to submit commitment after retries"
7041
- };
7042
- }
7043
- this.log("Waiting for inclusion proof...");
7044
- const inclusionProof = await waitInclusionProof2(this.trustBase, this.client, commitment);
7045
- this.log("Received inclusion proof");
7046
- const genesisTransaction = commitment.toTransaction(inclusionProof);
7047
- const nametagPredicate = await UnmaskedPredicate2.create(
7048
- nametagTokenId,
7049
- nametagTokenType,
7050
- this.signingService,
7051
- HashAlgorithm2.SHA256,
7052
- salt
7053
- );
7054
- const tokenState = new TokenState2(nametagPredicate, null);
7055
- let token;
7056
- if (this.skipVerification) {
7057
- this.log("Creating token WITHOUT verification (dev mode)");
7058
- const tokenJson = {
7059
- version: "2.0",
7060
- state: tokenState.toJSON(),
7061
- genesis: genesisTransaction.toJSON(),
7062
- transactions: [],
7063
- nametags: []
7064
- };
7065
- token = await Token2.fromJSON(tokenJson);
7066
- } else {
7067
- token = await Token2.mint(
7068
- this.trustBase,
7069
- tokenState,
7070
- genesisTransaction
7071
- );
7072
- }
7073
- this.log(`Nametag minted successfully: ${cleanNametag}`);
7074
- const nametagData = {
7075
- name: cleanNametag,
7076
- token: token.toJSON(),
7077
- timestamp: Date.now(),
7078
- format: "txf",
7079
- version: "2.0"
7080
- };
7081
- return {
7082
- success: true,
7083
- token,
7084
- nametagData
7085
- };
7086
- } catch (error) {
7087
- this.log("Minting failed:", error);
7088
- return {
7089
- success: false,
7090
- error: error instanceof Error ? error.message : String(error)
7091
- };
7092
- }
7093
- }
7094
- };
7095
-
7096
7025
  // modules/payments/PaymentsModule.ts
7097
7026
  init_constants();
7098
7027
 
@@ -7675,6 +7604,15 @@ function txfToToken(tokenId, txf) {
7675
7604
  sdkData: JSON.stringify(txf)
7676
7605
  };
7677
7606
  }
7607
+ function isV2TokenBlob(sdkData) {
7608
+ return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
7609
+ }
7610
+ function v2TokenId(token) {
7611
+ return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
7612
+ }
7613
+ function isV2TokenEntry(entry) {
7614
+ return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
7615
+ }
7678
7616
  async function buildTxfStorageData(tokens, meta, options) {
7679
7617
  const storageData = {
7680
7618
  _meta: {
@@ -7705,6 +7643,8 @@ async function buildTxfStorageData(tokens, meta, options) {
7705
7643
  if (txf) {
7706
7644
  const actualTokenId = txf.genesis.data.tokenId;
7707
7645
  storageData[keyFromTokenId(actualTokenId)] = txf;
7646
+ } else if (isV2TokenBlob(token.sdkData)) {
7647
+ storageData[keyFromTokenId(v2TokenId(token))] = token;
7708
7648
  }
7709
7649
  }
7710
7650
  if (options?.archivedTokens && options.archivedTokens.size > 0) {
@@ -7802,6 +7742,8 @@ function parseTxfStorageData(data) {
7802
7742
  if (txfToken?.genesis?.data?.tokenId) {
7803
7743
  const token = txfToToken(tokenId, txfToken);
7804
7744
  result.tokens.push(token);
7745
+ } else if (isV2TokenEntry(storageData[key])) {
7746
+ result.tokens.push(storageData[key]);
7805
7747
  }
7806
7748
  } catch (err) {
7807
7749
  result.validationErrors.push(`Token ${tokenId}: ${err}`);
@@ -8026,12 +7968,12 @@ init_logger();
8026
7968
  init_errors();
8027
7969
  import { Token as Token3 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
8028
7970
  import { TokenId as TokenId3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
8029
- import { TokenState as TokenState3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
7971
+ import { TokenState as TokenState2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8030
7972
  import { CoinId as CoinId3 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
8031
7973
  import { TokenCoinData as TokenCoinData2 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData";
8032
7974
  import { TokenSplitBuilder as TokenSplitBuilder2 } from "@unicitylabs/state-transition-sdk/lib/transaction/split/TokenSplitBuilder";
8033
7975
  import { HashAlgorithm as HashAlgorithm3 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
8034
- import { UnmaskedPredicate as UnmaskedPredicate3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
7976
+ import { UnmaskedPredicate as UnmaskedPredicate2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8035
7977
  import { UnmaskedPredicateReference as UnmaskedPredicateReference2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference";
8036
7978
  import { TransferCommitment as TransferCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
8037
7979
  import { waitInclusionProof as waitInclusionProof3 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
@@ -8153,14 +8095,14 @@ var InstantSplitExecutor = class {
8153
8095
  options?.message
8154
8096
  // on-chain message (invoice memo bytes, or null)
8155
8097
  );
8156
- const mintedPredicate = await UnmaskedPredicate3.create(
8098
+ const mintedPredicate = await UnmaskedPredicate2.create(
8157
8099
  recipientTokenId,
8158
8100
  tokenToSplit.type,
8159
8101
  this.signingService,
8160
8102
  HashAlgorithm3.SHA256,
8161
8103
  recipientSalt
8162
8104
  );
8163
- const mintedState = new TokenState3(mintedPredicate, null);
8105
+ const mintedState = new TokenState2(mintedPredicate, null);
8164
8106
  logger.debug("InstantSplit", "Step 5: Packaging V5 bundle...");
8165
8107
  const senderPubkey = toHex2(this.signingService.publicKey);
8166
8108
  let nametagTokenJson;
@@ -8275,14 +8217,14 @@ var InstantSplitExecutor = class {
8275
8217
  * It does NOT need the genesis transaction or mint proof.
8276
8218
  */
8277
8219
  async createTransferCommitmentFromMintData(mintData, recipientAddress, transferSalt, signingService, nametagTokens, message) {
8278
- const predicate = await UnmaskedPredicate3.create(
8220
+ const predicate = await UnmaskedPredicate2.create(
8279
8221
  mintData.tokenId,
8280
8222
  mintData.tokenType,
8281
8223
  signingService,
8282
8224
  HashAlgorithm3.SHA256,
8283
8225
  mintData.salt
8284
8226
  );
8285
- const state = new TokenState3(predicate, null);
8227
+ const state = new TokenState2(predicate, null);
8286
8228
  const minimalToken = {
8287
8229
  state,
8288
8230
  nametagTokens: nametagTokens || [],
@@ -8343,14 +8285,14 @@ var InstantSplitExecutor = class {
8343
8285
  message: `Mint proof received in ${proofDuration.toFixed(0)}ms`
8344
8286
  });
8345
8287
  const mintTransaction = senderMintCommitment.toTransaction(senderMintProof);
8346
- const predicate = await UnmaskedPredicate3.create(
8288
+ const predicate = await UnmaskedPredicate2.create(
8347
8289
  context.senderTokenId,
8348
8290
  context.tokenType,
8349
8291
  context.signingService,
8350
8292
  HashAlgorithm3.SHA256,
8351
8293
  context.senderSalt
8352
8294
  );
8353
- const state = new TokenState3(predicate, null);
8295
+ const state = new TokenState2(predicate, null);
8354
8296
  const changeToken = await Token3.mint(this.trustBase, state, mintTransaction);
8355
8297
  if (!this.devMode) {
8356
8298
  const verification = await changeToken.verify(this.trustBase);
@@ -8430,14 +8372,14 @@ var InstantSplitExecutor = class {
8430
8372
  init_logger();
8431
8373
  init_errors();
8432
8374
  import { Token as Token4 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
8433
- import { TokenState as TokenState4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8375
+ import { TokenState as TokenState3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8434
8376
  import { TokenType as TokenType2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
8435
8377
  import { HashAlgorithm as HashAlgorithm4 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
8436
- import { UnmaskedPredicate as UnmaskedPredicate4 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8378
+ import { UnmaskedPredicate as UnmaskedPredicate3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8437
8379
  import { TransferCommitment as TransferCommitment3 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
8438
- import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8439
- import { MintCommitment as MintCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8440
- import { MintTransactionData as MintTransactionData2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8380
+ import { TransferTransaction as TransferTransaction2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8381
+ import { MintCommitment } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8382
+ import { MintTransactionData } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8441
8383
  import { waitInclusionProof as waitInclusionProof4 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
8442
8384
 
8443
8385
  // types/instant-split.ts
@@ -8530,11 +8472,11 @@ var InstantSplitProcessor = class {
8530
8472
  logger.warn("InstantSplit", "Sender pubkey mismatch (non-fatal)");
8531
8473
  }
8532
8474
  const burnTxJson = JSON.parse(bundle.burnTransaction);
8533
- const _burnTransaction = await TransferTransaction.fromJSON(burnTxJson);
8475
+ const _burnTransaction = await TransferTransaction2.fromJSON(burnTxJson);
8534
8476
  logger.debug("InstantSplit", "Burn transaction validated");
8535
8477
  const mintDataJson = JSON.parse(bundle.recipientMintData);
8536
- const mintData = await MintTransactionData2.fromJSON(mintDataJson);
8537
- const mintCommitment = await MintCommitment2.create(mintData);
8478
+ const mintData = await MintTransactionData.fromJSON(mintDataJson);
8479
+ const mintCommitment = await MintCommitment.create(mintData);
8538
8480
  logger.debug("InstantSplit", "Mint commitment recreated");
8539
8481
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
8540
8482
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -8566,14 +8508,14 @@ var InstantSplitProcessor = class {
8566
8508
  const transferTransaction = transferCommitment.toTransaction(transferProof);
8567
8509
  logger.debug("InstantSplit", "Transfer proof received");
8568
8510
  const transferSalt = fromHex3(bundle.transferSaltHex);
8569
- const finalRecipientPredicate = await UnmaskedPredicate4.create(
8511
+ const finalRecipientPredicate = await UnmaskedPredicate3.create(
8570
8512
  mintData.tokenId,
8571
8513
  tokenType,
8572
8514
  signingService,
8573
8515
  HashAlgorithm4.SHA256,
8574
8516
  transferSalt
8575
8517
  );
8576
- const finalRecipientState = new TokenState4(finalRecipientPredicate, null);
8518
+ const finalRecipientState = new TokenState3(finalRecipientPredicate, null);
8577
8519
  logger.debug("InstantSplit", "Final recipient state created");
8578
8520
  let nametagTokens = [];
8579
8521
  const recipientAddressStr = bundle.recipientAddressJson;
@@ -8680,8 +8622,8 @@ var InstantSplitProcessor = class {
8680
8622
  await this.waitInclusionProofWithDevBypass(burnCommitment, options?.proofTimeoutMs);
8681
8623
  logger.debug("InstantSplit", "V4: Burn proof received");
8682
8624
  const mintDataJson = JSON.parse(bundle.recipientMintData);
8683
- const mintData = await MintTransactionData2.fromJSON(mintDataJson);
8684
- const mintCommitment = await MintCommitment2.create(mintData);
8625
+ const mintData = await MintTransactionData.fromJSON(mintDataJson);
8626
+ const mintCommitment = await MintCommitment.create(mintData);
8685
8627
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
8686
8628
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
8687
8629
  throw new SphereError(`Mint submission failed: ${mintResponse.status}`, "TRANSFER_FAILED");
@@ -8694,14 +8636,14 @@ var InstantSplitProcessor = class {
8694
8636
  logger.debug("InstantSplit", "V4: Mint proof received");
8695
8637
  const tokenType = new TokenType2(fromHex3(bundle.tokenTypeHex));
8696
8638
  const recipientSalt = fromHex3(bundle.recipientSaltHex);
8697
- const recipientPredicate = await UnmaskedPredicate4.create(
8639
+ const recipientPredicate = await UnmaskedPredicate3.create(
8698
8640
  mintData.tokenId,
8699
8641
  tokenType,
8700
8642
  signingService,
8701
8643
  HashAlgorithm4.SHA256,
8702
8644
  recipientSalt
8703
8645
  );
8704
- const recipientState = new TokenState4(recipientPredicate, null);
8646
+ const recipientState = new TokenState3(recipientPredicate, null);
8705
8647
  const tokenJson = {
8706
8648
  version: "2.0",
8707
8649
  state: recipientState.toJSON(),
@@ -8724,14 +8666,14 @@ var InstantSplitProcessor = class {
8724
8666
  const transferTransaction = transferCommitment.toTransaction(transferProof);
8725
8667
  logger.debug("InstantSplit", "V4: Transfer proof received");
8726
8668
  const transferSalt = fromHex3(bundle.transferSaltHex);
8727
- const finalPredicate = await UnmaskedPredicate4.create(
8669
+ const finalPredicate = await UnmaskedPredicate3.create(
8728
8670
  mintData.tokenId,
8729
8671
  tokenType,
8730
8672
  signingService,
8731
8673
  HashAlgorithm4.SHA256,
8732
8674
  transferSalt
8733
8675
  );
8734
- const finalState = new TokenState4(finalPredicate, null);
8676
+ const finalState = new TokenState3(finalPredicate, null);
8735
8677
  const finalTokenJson = mintedToken.toJSON();
8736
8678
  finalTokenJson.state = finalState.toJSON();
8737
8679
  finalTokenJson.transactions = [transferTransaction.toJSON()];
@@ -8782,17 +8724,17 @@ var InstantSplitProcessor = class {
8782
8724
  import { Token as SdkToken2 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
8783
8725
  import { CoinId as CoinId4 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
8784
8726
  import { TransferCommitment as TransferCommitment4 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
8785
- import { TransferTransaction as TransferTransaction2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8786
- import { SigningService } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
8727
+ import { TransferTransaction as TransferTransaction3 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8728
+ import { SigningService as SigningService2 } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
8787
8729
  import { AddressScheme } from "@unicitylabs/state-transition-sdk/lib/address/AddressScheme";
8788
- import { UnmaskedPredicate as UnmaskedPredicate5 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8789
- import { TokenState as TokenState5 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8730
+ import { UnmaskedPredicate as UnmaskedPredicate4 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8731
+ import { TokenState as TokenState4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8790
8732
  import { HashAlgorithm as HashAlgorithm5 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
8791
8733
  import { TokenType as TokenType3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
8792
- import { MintCommitment as MintCommitment3 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8793
- import { MintTransactionData as MintTransactionData3 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8734
+ import { MintCommitment as MintCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8735
+ import { MintTransactionData as MintTransactionData2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8794
8736
  import { waitInclusionProof as waitInclusionProof5 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
8795
- import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof";
8737
+ import { InclusionProof as InclusionProof2 } from "@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof";
8796
8738
  function computeHistoryDedupKey(type, tokenId, transferId) {
8797
8739
  if (type === "SENT" && transferId) return `${type}_transfer_${transferId}`;
8798
8740
  if (tokenId) return `${type}_${tokenId}`;
@@ -8813,7 +8755,7 @@ function enrichWithRegistry(info) {
8813
8755
  }
8814
8756
  return info;
8815
8757
  }
8816
- async function parseTokenInfo(tokenData) {
8758
+ async function parseTokenInfo(tokenData, engine) {
8817
8759
  const defaultInfo = {
8818
8760
  coinId: "ALPHA",
8819
8761
  symbol: "ALPHA",
@@ -8821,6 +8763,25 @@ async function parseTokenInfo(tokenData) {
8821
8763
  decimals: 0,
8822
8764
  amount: "0"
8823
8765
  };
8766
+ if (engine && typeof tokenData === "string" && looksLikeTokenBlob(tokenData)) {
8767
+ try {
8768
+ const token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(tokenData)));
8769
+ const first = engine.readValue(token)?.assets[0];
8770
+ if (first) {
8771
+ return enrichWithRegistry({
8772
+ coinId: first.coinId,
8773
+ symbol: first.coinId.slice(0, 8),
8774
+ name: `Token ${first.coinId.slice(0, 8)}`,
8775
+ decimals: 0,
8776
+ amount: String(first.amount),
8777
+ tokenId: engine.tokenId(token)
8778
+ });
8779
+ }
8780
+ return { ...defaultInfo, tokenId: engine.tokenId(token) };
8781
+ } catch (error) {
8782
+ logger.warn("Payments", "Failed to parse token info via engine:", error);
8783
+ }
8784
+ }
8824
8785
  try {
8825
8786
  const data = typeof tokenData === "string" ? JSON.parse(tokenData) : tokenData;
8826
8787
  try {
@@ -8959,27 +8920,41 @@ async function parseTokenInfo(tokenData) {
8959
8920
  }
8960
8921
  var sdkDataCache = /* @__PURE__ */ new Map();
8961
8922
  var SDK_DATA_CACHE_MAX = 2e3;
8923
+ function looksLikeTokenBlob(sdkData) {
8924
+ return sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
8925
+ }
8926
+ function tryParseBlobKeys(sdkData) {
8927
+ try {
8928
+ const blob = decodeTokenBlob(hexToBytes2(sdkData));
8929
+ return { tokenId: blob.tokenId, stateHash: sha2562(bytesToHex3(blob.token), "hex") };
8930
+ } catch {
8931
+ return null;
8932
+ }
8933
+ }
8962
8934
  function parseSdkDataCached(sdkData) {
8963
8935
  const cached = sdkDataCache.get(sdkData);
8964
8936
  if (cached) return cached;
8965
- let tokenId = null;
8966
- let stateHash = "";
8967
- try {
8968
- const txf = JSON.parse(sdkData);
8969
- tokenId = txf.genesis?.data?.tokenId || null;
8970
- stateHash = getCurrentStateHash(txf) || "";
8971
- if (!stateHash) {
8972
- if (txf.state?.hash) {
8973
- stateHash = txf.state.hash;
8974
- } else if (txf.stateHash) {
8975
- stateHash = txf.stateHash;
8976
- } else if (txf.currentStateHash) {
8977
- stateHash = txf.currentStateHash;
8937
+ let entry = looksLikeTokenBlob(sdkData) ? tryParseBlobKeys(sdkData) : null;
8938
+ if (!entry) {
8939
+ let tokenId = null;
8940
+ let stateHash = "";
8941
+ try {
8942
+ const txf = JSON.parse(sdkData);
8943
+ tokenId = txf.genesis?.data?.tokenId || null;
8944
+ stateHash = getCurrentStateHash(txf) || "";
8945
+ if (!stateHash) {
8946
+ if (txf.state?.hash) {
8947
+ stateHash = txf.state.hash;
8948
+ } else if (txf.stateHash) {
8949
+ stateHash = txf.stateHash;
8950
+ } else if (txf.currentStateHash) {
8951
+ stateHash = txf.currentStateHash;
8952
+ }
8978
8953
  }
8954
+ } catch {
8979
8955
  }
8980
- } catch {
8956
+ entry = { tokenId, stateHash };
8981
8957
  }
8982
- const entry = { tokenId, stateHash };
8983
8958
  if (sdkDataCache.size >= SDK_DATA_CACHE_MAX) {
8984
8959
  sdkDataCache.clear();
8985
8960
  }
@@ -9256,6 +9231,7 @@ var PaymentsModule = class _PaymentsModule {
9256
9231
  );
9257
9232
  this.deps = deps;
9258
9233
  this.priceProvider = deps.price ?? null;
9234
+ this.spendPlanner.setEngine(deps.tokenEngine);
9259
9235
  if (this.l1) {
9260
9236
  this.l1.initialize({
9261
9237
  identity: deps.identity,
@@ -9480,7 +9456,59 @@ var PaymentsModule = class _PaymentsModule {
9480
9456
  request.invoiceRefundAddress,
9481
9457
  request.invoiceContact
9482
9458
  );
9483
- if (transferMode === "conservative") {
9459
+ if (this.deps?.tokenEngine && peerInfo?.chainPubkey) {
9460
+ const engine = this.deps.tokenEngine;
9461
+ const recipientChainPubkey = hexToBytes2(peerInfo.chainPubkey);
9462
+ const memoData = onChainMessage ?? void 0;
9463
+ const handToRecipient = async (finished) => {
9464
+ const tokenBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(finished)));
9465
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
9466
+ type: "V2_TRANSFER",
9467
+ version: "2.0",
9468
+ tokenBlob,
9469
+ memo: request.memo
9470
+ });
9471
+ };
9472
+ for (const tw of splitPlan.tokensToTransferDirectly) {
9473
+ const finished = await engine.transfer({
9474
+ token: tw.sdkToken,
9475
+ recipientPubkey: recipientChainPubkey,
9476
+ data: memoData
9477
+ });
9478
+ await handToRecipient(finished);
9479
+ result.tokenTransfers.push({ sourceTokenId: tw.uiToken.id, method: "direct" });
9480
+ await this.removeToken(tw.uiToken.id, result.id);
9481
+ }
9482
+ if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9483
+ const selfChainPubkey = hexToBytes2(this.deps.identity.chainPubkey);
9484
+ const { outputs } = await engine.split({
9485
+ token: splitPlan.tokenToSplit.sdkToken,
9486
+ outputs: [
9487
+ { recipientPubkey: recipientChainPubkey, coinId: request.coinId, amount: splitPlan.splitAmount, data: memoData },
9488
+ { recipientPubkey: selfChainPubkey, coinId: request.coinId, amount: splitPlan.remainderAmount }
9489
+ ]
9490
+ });
9491
+ await handToRecipient(outputs[0]);
9492
+ const changeToken = outputs[1];
9493
+ const changeBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(changeToken)));
9494
+ const changeInfo = await parseTokenInfo(changeBlob, engine);
9495
+ const registry = TokenRegistry.getInstance();
9496
+ await this.addToken({
9497
+ id: `v2_${engine.tokenId(changeToken)}`,
9498
+ coinId: changeInfo.coinId,
9499
+ symbol: registry.getSymbol(changeInfo.coinId) || changeInfo.symbol,
9500
+ name: registry.getName(changeInfo.coinId) || changeInfo.name,
9501
+ decimals: registry.getDecimals(changeInfo.coinId) ?? changeInfo.decimals,
9502
+ amount: changeInfo.amount,
9503
+ status: "confirmed",
9504
+ createdAt: Date.now(),
9505
+ updatedAt: Date.now(),
9506
+ sdkData: changeBlob
9507
+ });
9508
+ result.tokenTransfers.push({ sourceTokenId: splitPlan.tokenToSplit.uiToken.id, method: "split" });
9509
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, result.id);
9510
+ }
9511
+ } else if (transferMode === "conservative") {
9484
9512
  if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9485
9513
  logger.debug("Payments", "Executing conservative split...");
9486
9514
  const splitExecutor = new TokenSplitExecutor({
@@ -10076,7 +10104,7 @@ var PaymentsModule = class _PaymentsModule {
10076
10104
  * because bundle-level dedup protects against replays, and split children share genesis IDs.
10077
10105
  */
10078
10106
  async saveCommitmentOnlyToken(sourceTokenInput, commitmentInput, senderPubkey, deferPersistence = false, skipGenesisDedup = false) {
10079
- const tokenInfo = await parseTokenInfo(sourceTokenInput);
10107
+ const tokenInfo = await parseTokenInfo(sourceTokenInput, this.deps?.tokenEngine);
10080
10108
  const sdkData = typeof sourceTokenInput === "string" ? sourceTokenInput : JSON.stringify(sourceTokenInput);
10081
10109
  const nostrTokenId = extractTokenIdFromSdkData(sdkData);
10082
10110
  const nostrStateHash = extractStateHashFromSdkData(sdkData);
@@ -11168,8 +11196,8 @@ var PaymentsModule = class _PaymentsModule {
11168
11196
  if (pending2.stage === "RECEIVED") {
11169
11197
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
11170
11198
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11171
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11172
- const mintCommitment = await MintCommitment3.create(mintData);
11199
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11200
+ const mintCommitment = await MintCommitment2.create(mintData);
11173
11201
  const mintResponse = await stClient.submitMintCommitment(mintCommitment);
11174
11202
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
11175
11203
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -11181,8 +11209,8 @@ var PaymentsModule = class _PaymentsModule {
11181
11209
  if (pending2.stage === "MINT_SUBMITTED") {
11182
11210
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
11183
11211
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11184
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11185
- const mintCommitment = await MintCommitment3.create(mintData);
11212
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11213
+ const mintCommitment = await MintCommitment2.create(mintData);
11186
11214
  const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
11187
11215
  if (!proof) {
11188
11216
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
@@ -11279,10 +11307,10 @@ var PaymentsModule = class _PaymentsModule {
11279
11307
  */
11280
11308
  async finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase) {
11281
11309
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11282
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11283
- const mintCommitment = await MintCommitment3.create(mintData);
11310
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11311
+ const mintCommitment = await MintCommitment2.create(mintData);
11284
11312
  const mintProofJson = JSON.parse(pending2.mintProofJson);
11285
- const mintProof = InclusionProof.fromJSON(mintProofJson);
11313
+ const mintProof = InclusionProof2.fromJSON(mintProofJson);
11286
11314
  const mintTransaction = mintCommitment.toTransaction(mintProof);
11287
11315
  const tokenType = new TokenType3(fromHex4(bundle.tokenTypeHex));
11288
11316
  const senderMintedStateJson = JSON.parse(bundle.mintedTokenStateJson);
@@ -11299,14 +11327,14 @@ var PaymentsModule = class _PaymentsModule {
11299
11327
  const transferProof = await waitInclusionProof5(trustBase, stClient, transferCommitment);
11300
11328
  const transferTransaction = transferCommitment.toTransaction(transferProof);
11301
11329
  const transferSalt = fromHex4(bundle.transferSaltHex);
11302
- const recipientPredicate = await UnmaskedPredicate5.create(
11330
+ const recipientPredicate = await UnmaskedPredicate4.create(
11303
11331
  mintData.tokenId,
11304
11332
  tokenType,
11305
11333
  signingService,
11306
11334
  HashAlgorithm5.SHA256,
11307
11335
  transferSalt
11308
11336
  );
11309
- const recipientState = new TokenState5(recipientPredicate, null);
11337
+ const recipientState = new TokenState4(recipientPredicate, null);
11310
11338
  let nametagTokens = [];
11311
11339
  const recipientAddressStr = bundle.recipientAddressJson;
11312
11340
  if (recipientAddressStr.startsWith("PROXY://")) {
@@ -12043,68 +12071,6 @@ var PaymentsModule = class _PaymentsModule {
12043
12071
  }
12044
12072
  }
12045
12073
  }
12046
- /**
12047
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
12048
- * This creates the nametag token required for receiving tokens via PROXY addresses
12049
- *
12050
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
12051
- * @returns MintNametagResult with success status and token if successful
12052
- */
12053
- async mintNametag(nametag) {
12054
- this.ensureInitialized();
12055
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12056
- if (!stClient) {
12057
- return {
12058
- success: false,
12059
- error: "State transition client not available. Oracle provider must implement getStateTransitionClient()"
12060
- };
12061
- }
12062
- const trustBase = this.deps.oracle.getTrustBase?.();
12063
- if (!trustBase) {
12064
- return {
12065
- success: false,
12066
- error: "Trust base not available. Oracle provider must implement getTrustBase()"
12067
- };
12068
- }
12069
- try {
12070
- const signingService = await this.createSigningService();
12071
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12072
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12073
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12074
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12075
- const addressRef = await UnmaskedPredicateReference4.create(
12076
- tokenType,
12077
- signingService.algorithm,
12078
- signingService.publicKey,
12079
- HashAlgorithm5.SHA256
12080
- );
12081
- const ownerAddress = await addressRef.toAddress();
12082
- const minter = new NametagMinter({
12083
- stateTransitionClient: stClient,
12084
- trustBase,
12085
- signingService,
12086
- debug: this.moduleConfig.debug
12087
- });
12088
- const result = await minter.mintNametag(nametag, ownerAddress);
12089
- if (result.success && result.nametagData) {
12090
- await this.setNametag(result.nametagData);
12091
- logger.debug("Payments", `Unicity ID minted and saved: ${result.nametagData.name}`);
12092
- this.deps.emitEvent("nametag:registered", {
12093
- nametag: result.nametagData.name,
12094
- addressIndex: 0
12095
- // Primary address
12096
- });
12097
- }
12098
- return result;
12099
- } catch (error) {
12100
- const errorMsg = error instanceof Error ? error.message : String(error);
12101
- logger.debug("Payments", "mintNametag failed:", errorMsg);
12102
- return {
12103
- success: false,
12104
- error: errorMsg
12105
- };
12106
- }
12107
- }
12108
12074
  /**
12109
12075
  * Mint a fungible token directly to this wallet (genesis mint).
12110
12076
  *
@@ -12145,18 +12111,18 @@ var PaymentsModule = class _PaymentsModule {
12145
12111
  }
12146
12112
  try {
12147
12113
  const signingService = await this.createSigningService();
12148
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12114
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12149
12115
  const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
12150
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12116
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12151
12117
  const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
12152
12118
  const tokenType = new TokenType3(tokenTypeBytes);
12153
12119
  const tokenIdBytes = new Uint8Array(32);
12154
12120
  crypto.getRandomValues(tokenIdBytes);
12155
- const tokenId = new TokenId5(tokenIdBytes);
12121
+ const tokenId = new TokenId4(tokenIdBytes);
12156
12122
  const coinIdBytes = fromHex4(coinIdHex);
12157
12123
  const coinId = new CoinId4(coinIdBytes);
12158
12124
  const coinData = TokenCoinData3.create([[coinId, amount]]);
12159
- const addressRef = await UnmaskedPredicateReference4.create(
12125
+ const addressRef = await UnmaskedPredicateReference3.create(
12160
12126
  tokenType,
12161
12127
  signingService.algorithm,
12162
12128
  signingService.publicKey,
@@ -12165,7 +12131,7 @@ var PaymentsModule = class _PaymentsModule {
12165
12131
  const ownerAddress = await addressRef.toAddress();
12166
12132
  const salt = new Uint8Array(32);
12167
12133
  crypto.getRandomValues(salt);
12168
- const mintData = await MintTransactionData3.create(
12134
+ const mintData = await MintTransactionData2.create(
12169
12135
  tokenId,
12170
12136
  tokenType,
12171
12137
  null,
@@ -12180,7 +12146,7 @@ var PaymentsModule = class _PaymentsModule {
12180
12146
  null
12181
12147
  // reason: null (genesis, no burn predecessor)
12182
12148
  );
12183
- const commitment = await MintCommitment3.create(mintData);
12149
+ const commitment = await MintCommitment2.create(mintData);
12184
12150
  const MAX_RETRIES = 3;
12185
12151
  let lastStatus;
12186
12152
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
@@ -12197,14 +12163,14 @@ var PaymentsModule = class _PaymentsModule {
12197
12163
  }
12198
12164
  const inclusionProof = await waitInclusionProof5(trustBase, stClient, commitment);
12199
12165
  const genesisTransaction = commitment.toTransaction(inclusionProof);
12200
- const predicate = await UnmaskedPredicate5.create(
12166
+ const predicate = await UnmaskedPredicate4.create(
12201
12167
  tokenId,
12202
12168
  tokenType,
12203
12169
  signingService,
12204
12170
  HashAlgorithm5.SHA256,
12205
12171
  salt
12206
12172
  );
12207
- const tokenState = new TokenState5(predicate, null);
12173
+ const tokenState = new TokenState4(predicate, null);
12208
12174
  const sdkToken = await SdkToken2.mint(trustBase, tokenState, genesisTransaction);
12209
12175
  const tokenIdHex = tokenId.toJSON();
12210
12176
  const symbol = this.getCoinSymbol(coinIdHex);
@@ -12231,29 +12197,6 @@ var PaymentsModule = class _PaymentsModule {
12231
12197
  return { success: false, error: `Local mint failed: ${msg}` };
12232
12198
  }
12233
12199
  }
12234
- /**
12235
- * Check if a nametag is available for minting
12236
- * @param nametag - The nametag to check (e.g., "alice" or "@alice")
12237
- */
12238
- async isNametagAvailable(nametag) {
12239
- this.ensureInitialized();
12240
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12241
- const trustBase = this.deps.oracle.getTrustBase?.();
12242
- if (!stClient || !trustBase) {
12243
- return false;
12244
- }
12245
- try {
12246
- const signingService = await this.createSigningService();
12247
- const minter = new NametagMinter({
12248
- stateTransitionClient: stClient,
12249
- trustBase,
12250
- signingService
12251
- });
12252
- return await minter.isNametagAvailable(nametag);
12253
- } catch {
12254
- return false;
12255
- }
12256
- }
12257
12200
  // ===========================================================================
12258
12201
  // Public API - Sync & Validate
12259
12202
  // ===========================================================================
@@ -12569,7 +12512,7 @@ var PaymentsModule = class _PaymentsModule {
12569
12512
  const privateKeyBytes = new Uint8Array(
12570
12513
  privateKeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
12571
12514
  );
12572
- return SigningService.createFromSecret(privateKeyBytes);
12515
+ return SigningService2.createFromSecret(privateKeyBytes);
12573
12516
  }
12574
12517
  /**
12575
12518
  * Get the wallet's signing public key (used for token ownership predicates).
@@ -12584,14 +12527,14 @@ var PaymentsModule = class _PaymentsModule {
12584
12527
  * Create DirectAddress from a public key using UnmaskedPredicateReference
12585
12528
  */
12586
12529
  async createDirectAddressFromPubkey(pubkeyHex) {
12587
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12588
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12589
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12590
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12530
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12531
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12532
+ const UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12533
+ const tokenType = new TokenType4(Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex"));
12591
12534
  const pubkeyBytes = new Uint8Array(
12592
12535
  pubkeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
12593
12536
  );
12594
- const addressRef = await UnmaskedPredicateReference4.create(
12537
+ const addressRef = await UnmaskedPredicateReference3.create(
12595
12538
  tokenType,
12596
12539
  "secp256k1",
12597
12540
  pubkeyBytes,
@@ -12603,10 +12546,9 @@ var PaymentsModule = class _PaymentsModule {
12603
12546
  * Resolve recipient to IAddress for L3 transfers.
12604
12547
  * Uses pre-resolved PeerInfo when available to avoid redundant network queries.
12605
12548
  */
12606
- async resolveRecipientAddress(recipient, addressMode = "auto", peerInfo) {
12549
+ async resolveRecipientAddress(recipient, _addressMode = "auto", peerInfo) {
12607
12550
  const { AddressFactory } = await import("@unicitylabs/state-transition-sdk/lib/address/AddressFactory");
12608
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
12609
- if (recipient.startsWith("PROXY:") || recipient.startsWith("DIRECT:")) {
12551
+ if (recipient.startsWith("DIRECT:")) {
12610
12552
  return AddressFactory.createAddress(recipient);
12611
12553
  }
12612
12554
  if (recipient.length === 66 && /^[0-9a-fA-F]+$/.test(recipient)) {
@@ -12616,28 +12558,19 @@ var PaymentsModule = class _PaymentsModule {
12616
12558
  const info = peerInfo ?? await this.deps?.transport.resolve?.(recipient) ?? null;
12617
12559
  if (!info) {
12618
12560
  throw new SphereError(
12619
- `Recipient "${recipient}" not found. Use @nametag, a valid PROXY:/DIRECT: address, or a 33-byte hex pubkey.`,
12561
+ `Recipient "${recipient}" not found. Use @nametag, a DIRECT: address, or a 33-byte hex pubkey.`,
12620
12562
  "INVALID_RECIPIENT"
12621
12563
  );
12622
12564
  }
12623
12565
  const nametag = recipient.startsWith("@") ? recipient.slice(1) : info.nametag || recipient;
12624
- if (addressMode === "proxy") {
12625
- logger.debug("Payments", `Using PROXY address for "${nametag}" (forced)`);
12626
- return ProxyAddress.fromNameTag(nametag);
12627
- }
12628
- if (addressMode === "direct") {
12629
- if (!info.directAddress) {
12630
- throw new SphereError(`"${nametag}" has no DirectAddress stored. It may be a legacy registration.`, "INVALID_RECIPIENT");
12631
- }
12632
- logger.debug("Payments", `Using DirectAddress for "${nametag}" (forced): ${info.directAddress.slice(0, 30)}...`);
12633
- return AddressFactory.createAddress(info.directAddress);
12634
- }
12635
- if (info.directAddress) {
12636
- logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
12637
- return AddressFactory.createAddress(info.directAddress);
12566
+ if (!info.directAddress) {
12567
+ throw new SphereError(
12568
+ `"${nametag}" has no DirectAddress \u2014 the recipient must publish a key-based identity binding.`,
12569
+ "INVALID_RECIPIENT"
12570
+ );
12638
12571
  }
12639
- logger.debug("Payments", `Using PROXY address for legacy nametag "${nametag}"`);
12640
- return ProxyAddress.fromNameTag(nametag);
12572
+ logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
12573
+ return AddressFactory.createAddress(info.directAddress);
12641
12574
  }
12642
12575
  /**
12643
12576
  * Handle NOSTR-FIRST commitment-only transfer (recipient side)
@@ -12692,14 +12625,14 @@ var PaymentsModule = class _PaymentsModule {
12692
12625
  const addressScheme = recipientAddress.scheme;
12693
12626
  const signingService = await this.createSigningService();
12694
12627
  const transferSalt = transferTx.data.salt;
12695
- const recipientPredicate = await UnmaskedPredicate5.create(
12628
+ const recipientPredicate = await UnmaskedPredicate4.create(
12696
12629
  sourceToken.id,
12697
12630
  sourceToken.type,
12698
12631
  signingService,
12699
12632
  HashAlgorithm5.SHA256,
12700
12633
  transferSalt
12701
12634
  );
12702
- const recipientState = new TokenState5(recipientPredicate, null);
12635
+ const recipientState = new TokenState4(recipientPredicate, null);
12703
12636
  let nametagTokens = [];
12704
12637
  if (addressScheme === AddressScheme.PROXY) {
12705
12638
  const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
@@ -12796,6 +12729,67 @@ var PaymentsModule = class _PaymentsModule {
12796
12729
  }
12797
12730
  }
12798
12731
  }
12732
+ /**
12733
+ * v2 engine transfer (sender-driven): the sender handed us a FINISHED token.
12734
+ * Decode the blob, dedup by the genesis-stable token id, store it as a
12735
+ * confirmed token, and emit/record the receipt. No commitment / inclusion-proof
12736
+ * / finalization round-trip (contrast the v1 sourceToken+transferTx path).
12737
+ */
12738
+ async handleV2Transfer(payload, senderPubkey) {
12739
+ this.ensureInitialized();
12740
+ if (!this.loaded && this.loadedPromise) {
12741
+ await this.loadedPromise;
12742
+ }
12743
+ const engine = this.deps.tokenEngine;
12744
+ if (!engine) return;
12745
+ let token;
12746
+ try {
12747
+ token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(payload.tokenBlob)));
12748
+ } catch (err) {
12749
+ logger.error("Payments", "V2 transfer: failed to decode token blob:", err);
12750
+ return;
12751
+ }
12752
+ const id = `v2_${engine.tokenId(token)}`;
12753
+ if (this.tokens.has(id)) {
12754
+ logger.debug("Payments", `V2 transfer ${id.slice(0, 16)}... already present, skipping`);
12755
+ return;
12756
+ }
12757
+ const info = await parseTokenInfo(payload.tokenBlob, engine);
12758
+ const registry = TokenRegistry.getInstance();
12759
+ const uiToken = {
12760
+ id,
12761
+ coinId: info.coinId,
12762
+ symbol: registry.getSymbol(info.coinId) || info.symbol,
12763
+ name: registry.getName(info.coinId) || info.name,
12764
+ decimals: registry.getDecimals(info.coinId) ?? info.decimals,
12765
+ amount: info.amount,
12766
+ status: "confirmed",
12767
+ createdAt: Date.now(),
12768
+ updatedAt: Date.now(),
12769
+ sdkData: payload.tokenBlob
12770
+ };
12771
+ await this.addToken(uiToken);
12772
+ const senderInfo = await this.resolveSenderInfo(senderPubkey);
12773
+ this.deps.emitEvent("transfer:incoming", {
12774
+ id,
12775
+ senderPubkey,
12776
+ senderNametag: senderInfo.senderNametag,
12777
+ tokens: [uiToken],
12778
+ memo: payload.memo,
12779
+ receivedAt: Date.now()
12780
+ });
12781
+ await this.addToHistory({
12782
+ type: "RECEIVED",
12783
+ amount: info.amount,
12784
+ coinId: info.coinId,
12785
+ symbol: uiToken.symbol,
12786
+ timestamp: Date.now(),
12787
+ senderPubkey,
12788
+ ...senderInfo,
12789
+ memo: payload.memo,
12790
+ tokenId: id
12791
+ });
12792
+ }
12799
12793
  async handleIncomingTransfer(transfer) {
12800
12794
  if (!this.loaded && this.loadedPromise) {
12801
12795
  await this.loadedPromise;
@@ -12803,6 +12797,10 @@ var PaymentsModule = class _PaymentsModule {
12803
12797
  try {
12804
12798
  const payload = transfer.payload;
12805
12799
  logger.debug("Payments", "handleIncomingTransfer: keys=", Object.keys(payload).join(","));
12800
+ if (this.deps.tokenEngine && isV2TransferPayload(transfer.payload)) {
12801
+ await this.handleV2Transfer(transfer.payload, transfer.senderTransportPubkey);
12802
+ return;
12803
+ }
12806
12804
  let combinedBundle = null;
12807
12805
  if (isCombinedTransferBundleV6(payload)) {
12808
12806
  combinedBundle = payload;
@@ -12884,7 +12882,7 @@ var PaymentsModule = class _PaymentsModule {
12884
12882
  const hasTransactionData = transferTxInput.transactionData !== void 0;
12885
12883
  const hasAuthenticator = transferTxInput.authenticator !== void 0;
12886
12884
  if (hasData && hasInclusionProof) {
12887
- transferTx = await TransferTransaction2.fromJSON(transferTxInput);
12885
+ transferTx = await TransferTransaction3.fromJSON(transferTxInput);
12888
12886
  } else if (hasTransactionData && hasAuthenticator) {
12889
12887
  const commitment = await TransferCommitment4.fromJSON(transferTxInput);
12890
12888
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -12905,7 +12903,7 @@ var PaymentsModule = class _PaymentsModule {
12905
12903
  transferTx = commitment.toTransaction(inclusionProof);
12906
12904
  } else {
12907
12905
  try {
12908
- transferTx = await TransferTransaction2.fromJSON(transferTxInput);
12906
+ transferTx = await TransferTransaction3.fromJSON(transferTxInput);
12909
12907
  } catch {
12910
12908
  const commitment = await TransferCommitment4.fromJSON(transferTxInput);
12911
12909
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -12947,7 +12945,7 @@ var PaymentsModule = class _PaymentsModule {
12947
12945
  logger.warn("Payments", "Received invalid token");
12948
12946
  return;
12949
12947
  }
12950
- const tokenInfo = await parseTokenInfo(tokenData);
12948
+ const tokenInfo = await parseTokenInfo(tokenData, this.deps?.tokenEngine);
12951
12949
  const token = {
12952
12950
  id: tokenInfo.tokenId ?? crypto.randomUUID(),
12953
12951
  coinId: tokenInfo.coinId,
@@ -13253,24 +13251,6 @@ function createPaymentsModule(config) {
13253
13251
  return new PaymentsModule(config);
13254
13252
  }
13255
13253
 
13256
- // modules/payments/TokenSplitCalculator.ts
13257
- init_logger();
13258
- import { Token as SdkToken3 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
13259
- import { CoinId as CoinId5 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
13260
-
13261
- // modules/payments/BackgroundCommitmentService.ts
13262
- init_logger();
13263
- init_errors();
13264
-
13265
- // modules/payments/TokenRecoveryService.ts
13266
- init_logger();
13267
- import { TokenId as TokenId4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
13268
- import { TokenState as TokenState6 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
13269
- import { TokenType as TokenType4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
13270
- import { CoinId as CoinId6 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
13271
- import { HashAlgorithm as HashAlgorithm6 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
13272
- import { UnmaskedPredicate as UnmaskedPredicate6 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
13273
-
13274
13254
  // modules/communications/CommunicationsModule.ts
13275
13255
  init_logger();
13276
13256
  init_errors();
@@ -16912,7 +16892,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
16912
16892
  const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : void 0;
16913
16893
  return abytes(bytes, sizer);
16914
16894
  }
16915
- class Signature {
16895
+ class Signature2 {
16916
16896
  r;
16917
16897
  s;
16918
16898
  recovery;
@@ -16932,7 +16912,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
16932
16912
  let recid;
16933
16913
  if (format === "der") {
16934
16914
  const { r: r2, s: s2 } = DER.toSig(abytes(bytes));
16935
- return new Signature(r2, s2);
16915
+ return new Signature2(r2, s2);
16936
16916
  }
16937
16917
  if (format === "recovered") {
16938
16918
  recid = bytes[0];
@@ -16942,7 +16922,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
16942
16922
  const L = lengths.signature / 2;
16943
16923
  const r = bytes.subarray(0, L);
16944
16924
  const s = bytes.subarray(L, L * 2);
16945
- return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);
16925
+ return new Signature2(Fn.fromBytes(r), Fn.fromBytes(s), recid);
16946
16926
  }
16947
16927
  static fromHex(hex, format) {
16948
16928
  return this.fromBytes(hexToBytes(hex), format);
@@ -16954,7 +16934,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
16954
16934
  return recovery;
16955
16935
  }
16956
16936
  addRecoveryBit(recovery) {
16957
- return new Signature(this.r, this.s, recovery);
16937
+ return new Signature2(this.r, this.s, recovery);
16958
16938
  }
16959
16939
  recoverPublicKey(messageHash) {
16960
16940
  const { r, s } = this;
@@ -17046,7 +17026,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17046
17026
  normS = Fn.neg(s);
17047
17027
  recovery ^= 1;
17048
17028
  }
17049
- return new Signature(r, normS, hasLargeCofactor ? void 0 : recovery);
17029
+ return new Signature2(r, normS, hasLargeCofactor ? void 0 : recovery);
17050
17030
  }
17051
17031
  return { seed, k2sig };
17052
17032
  }
@@ -17061,12 +17041,12 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17061
17041
  publicKey = abytes(publicKey, void 0, "publicKey");
17062
17042
  message = validateMsgAndHash(message, prehash);
17063
17043
  if (!isBytes(signature)) {
17064
- const end = signature instanceof Signature ? ", use sig.toBytes()" : "";
17044
+ const end = signature instanceof Signature2 ? ", use sig.toBytes()" : "";
17065
17045
  throw new Error("verify expects Uint8Array signature" + end);
17066
17046
  }
17067
17047
  validateSigLength(signature, format);
17068
17048
  try {
17069
- const sig = Signature.fromBytes(signature, format);
17049
+ const sig = Signature2.fromBytes(signature, format);
17070
17050
  const P = Point.fromBytes(publicKey);
17071
17051
  if (lowS && sig.hasHighS())
17072
17052
  return false;
@@ -17087,7 +17067,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17087
17067
  function recoverPublicKey(signature, message, opts = {}) {
17088
17068
  const { prehash } = validateSigOpts(opts, defaultSigOpts);
17089
17069
  message = validateMsgAndHash(message, prehash);
17090
- return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17070
+ return Signature2.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17091
17071
  }
17092
17072
  return Object.freeze({
17093
17073
  keygen,
@@ -17099,7 +17079,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17099
17079
  sign,
17100
17080
  verify,
17101
17081
  recoverPublicKey,
17102
- Signature,
17082
+ Signature: Signature2,
17103
17083
  hash
17104
17084
  });
17105
17085
  }
@@ -18371,7 +18351,7 @@ function freezeCoinAsset(coinAsset, state, latestSender) {
18371
18351
  }
18372
18352
 
18373
18353
  // modules/accounting/AccountingModule.ts
18374
- import { Token as SdkToken4 } from "@unicitylabs/state-transition-sdk/lib/token/Token.js";
18354
+ import { Token as SdkToken3 } from "@unicitylabs/state-transition-sdk/lib/token/Token.js";
18375
18355
  var LOG_TAG2 = "Accounting";
18376
18356
  var INV_LEDGER_PREFIX = "inv_ledger:";
18377
18357
  var AccountingModule = class _AccountingModule {
@@ -19024,129 +19004,146 @@ var AccountingModule = class _AccountingModule {
19024
19004
  );
19025
19005
  }
19026
19006
  try {
19027
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19028
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19029
- const { MintTransactionData: MintTransactionData4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19030
- const { MintCommitment: MintCommitment4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19031
- const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19032
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19033
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19034
- const { UnmaskedPredicate: UnmaskedPredicate7 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19035
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19036
- const { TokenState: TokenState7 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19037
- const { Token: SdkToken5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19038
- const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19039
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(invoiceBytesEncoded).digest();
19040
- const invoiceTokenId = new TokenId5(hash.imprint);
19041
- const invoiceId = invoiceTokenId.toJSON();
19042
- if (this.invoiceTermsCache.has(invoiceId)) {
19043
- throw new SphereError(
19044
- `Invoice already exists locally: ${invoiceId}`,
19045
- "INVOICE_ALREADY_EXISTS"
19046
- );
19047
- }
19048
- const invoiceTokenType = new TokenType6(
19049
- Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19050
- );
19051
- const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19052
- const addressRef = await UnmaskedPredicateReference4.create(
19053
- invoiceTokenType,
19054
- signingService.algorithm,
19055
- signingService.publicKey,
19056
- HashAlgorithm8.SHA256
19057
- );
19058
- const ownerAddress = await addressRef.toAddress();
19059
- const mintData = await MintTransactionData4.create(
19060
- invoiceTokenId,
19061
- invoiceTokenType,
19062
- invoiceBytesEncoded,
19063
- // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19064
- null,
19065
- // coinData: null (non-fungible invoice token)
19066
- ownerAddress,
19067
- salt,
19068
- null,
19069
- // recipientDataHash: null
19070
- null
19071
- // reason: null
19072
- );
19073
- if (this.config.debug) {
19074
- logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19075
- }
19076
- const commitment = await MintCommitment4.create(mintData);
19077
- if (this.config.debug) {
19078
- logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19079
- }
19080
- const MAX_RETRIES = 3;
19081
- let submitSuccess = false;
19082
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19083
- try {
19084
- if (this.config.debug) {
19085
- logger.debug(
19086
- LOG_TAG2,
19087
- `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19088
- );
19089
- }
19090
- const response = await stClient.submitMintCommitment(commitment);
19091
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19007
+ let invoiceId;
19008
+ let sdkData;
19009
+ const engine = deps.tokenEngine;
19010
+ if (engine) {
19011
+ const invoiceToken = await engine.mintDataToken({
19012
+ recipientPubkey: hexToBytes(deps.identity.chainPubkey),
19013
+ data: invoiceBytesEncoded,
19014
+ tokenType: hexToBytes(INVOICE_TOKEN_TYPE_HEX),
19015
+ salt
19016
+ });
19017
+ invoiceId = engine.tokenId(invoiceToken);
19018
+ if (this.invoiceTermsCache.has(invoiceId)) {
19019
+ throw new SphereError(`Invoice already exists locally: ${invoiceId}`, "INVOICE_ALREADY_EXISTS");
19020
+ }
19021
+ sdkData = bytesToHex(encodeTokenBlob(engine.encodeToken(invoiceToken)));
19022
+ } else {
19023
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19024
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19025
+ const { MintTransactionData: MintTransactionData3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19026
+ const { MintCommitment: MintCommitment3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19027
+ const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19028
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19029
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19030
+ const { UnmaskedPredicate: UnmaskedPredicate5 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19031
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19032
+ const { TokenState: TokenState5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19033
+ const { Token: SdkToken4 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19034
+ const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19035
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(invoiceBytesEncoded).digest();
19036
+ const invoiceTokenId = new TokenId4(hash.imprint);
19037
+ invoiceId = invoiceTokenId.toJSON();
19038
+ if (this.invoiceTermsCache.has(invoiceId)) {
19039
+ throw new SphereError(
19040
+ `Invoice already exists locally: ${invoiceId}`,
19041
+ "INVOICE_ALREADY_EXISTS"
19042
+ );
19043
+ }
19044
+ const invoiceTokenType = new TokenType4(
19045
+ Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19046
+ );
19047
+ const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19048
+ const addressRef = await UnmaskedPredicateReference3.create(
19049
+ invoiceTokenType,
19050
+ signingService.algorithm,
19051
+ signingService.publicKey,
19052
+ HashAlgorithm6.SHA256
19053
+ );
19054
+ const ownerAddress = await addressRef.toAddress();
19055
+ const mintData = await MintTransactionData3.create(
19056
+ invoiceTokenId,
19057
+ invoiceTokenType,
19058
+ invoiceBytesEncoded,
19059
+ // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19060
+ null,
19061
+ // coinData: null (non-fungible invoice token)
19062
+ ownerAddress,
19063
+ salt,
19064
+ null,
19065
+ // recipientDataHash: null
19066
+ null
19067
+ // reason: null
19068
+ );
19069
+ if (this.config.debug) {
19070
+ logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19071
+ }
19072
+ const commitment = await MintCommitment3.create(mintData);
19073
+ if (this.config.debug) {
19074
+ logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19075
+ }
19076
+ const MAX_RETRIES = 3;
19077
+ let submitSuccess = false;
19078
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19079
+ try {
19092
19080
  if (this.config.debug) {
19093
19081
  logger.debug(
19094
19082
  LOG_TAG2,
19095
- response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19083
+ `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19096
19084
  );
19097
19085
  }
19098
- submitSuccess = true;
19099
- break;
19100
- } else {
19101
- logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19086
+ const response = await stClient.submitMintCommitment(commitment);
19087
+ if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19088
+ if (this.config.debug) {
19089
+ logger.debug(
19090
+ LOG_TAG2,
19091
+ response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19092
+ );
19093
+ }
19094
+ submitSuccess = true;
19095
+ break;
19096
+ } else {
19097
+ logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19098
+ if (attempt === MAX_RETRIES) {
19099
+ throw new SphereError(
19100
+ `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19101
+ "INVOICE_MINT_FAILED"
19102
+ );
19103
+ }
19104
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
19105
+ }
19106
+ } catch (retryErr) {
19107
+ 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;
19108
+ logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19102
19109
  if (attempt === MAX_RETRIES) {
19103
19110
  throw new SphereError(
19104
- `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19105
- "INVOICE_MINT_FAILED"
19111
+ `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19112
+ "INVOICE_MINT_FAILED",
19113
+ retryErr
19106
19114
  );
19107
19115
  }
19108
19116
  await new Promise((r) => setTimeout(r, 1e3 * attempt));
19109
19117
  }
19110
- } catch (retryErr) {
19111
- 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;
19112
- logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19113
- if (attempt === MAX_RETRIES) {
19114
- throw new SphereError(
19115
- `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19116
- "INVOICE_MINT_FAILED",
19117
- retryErr
19118
- );
19119
- }
19120
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
19121
19118
  }
19122
- }
19123
- if (!submitSuccess) {
19124
- throw new SphereError(
19125
- "Failed to mint invoice token: commitment submission failed after retries",
19126
- "INVOICE_MINT_FAILED"
19119
+ if (!submitSuccess) {
19120
+ throw new SphereError(
19121
+ "Failed to mint invoice token: commitment submission failed after retries",
19122
+ "INVOICE_MINT_FAILED"
19123
+ );
19124
+ }
19125
+ if (this.config.debug) {
19126
+ logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19127
+ }
19128
+ const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19129
+ if (this.config.debug) {
19130
+ logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19131
+ }
19132
+ const genesisTransaction = commitment.toTransaction(inclusionProof);
19133
+ const invoicePredicate = await UnmaskedPredicate5.create(
19134
+ invoiceTokenId,
19135
+ invoiceTokenType,
19136
+ signingService,
19137
+ HashAlgorithm6.SHA256,
19138
+ salt
19127
19139
  );
19140
+ const tokenState = new TokenState5(invoicePredicate, null);
19141
+ const sdkToken = await SdkToken4.mint(trustBase, tokenState, genesisTransaction);
19142
+ if (this.config.debug) {
19143
+ logger.debug(LOG_TAG2, "Invoice token minted successfully");
19144
+ }
19145
+ sdkData = JSON.stringify(sdkToken.toJSON());
19128
19146
  }
19129
- if (this.config.debug) {
19130
- logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19131
- }
19132
- const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19133
- if (this.config.debug) {
19134
- logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19135
- }
19136
- const genesisTransaction = commitment.toTransaction(inclusionProof);
19137
- const invoicePredicate = await UnmaskedPredicate7.create(
19138
- invoiceTokenId,
19139
- invoiceTokenType,
19140
- signingService,
19141
- HashAlgorithm8.SHA256,
19142
- salt
19143
- );
19144
- const tokenState = new TokenState7(invoicePredicate, null);
19145
- const sdkToken = await SdkToken5.mint(trustBase, tokenState, genesisTransaction);
19146
- if (this.config.debug) {
19147
- logger.debug(LOG_TAG2, "Invoice token minted successfully");
19148
- }
19149
- const sdkTokenJson = sdkToken.toJSON();
19150
19147
  const uiToken = {
19151
19148
  id: invoiceId,
19152
19149
  coinId: INVOICE_TOKEN_TYPE_HEX,
@@ -19157,7 +19154,7 @@ var AccountingModule = class _AccountingModule {
19157
19154
  status: "confirmed",
19158
19155
  createdAt: terms.createdAt,
19159
19156
  updatedAt: terms.createdAt,
19160
- sdkData: JSON.stringify(sdkTokenJson)
19157
+ sdkData
19161
19158
  };
19162
19159
  await deps.payments.addToken(uiToken);
19163
19160
  this.invoiceTermsCache.set(invoiceId, this._normalizeInvoiceTerms(terms));
@@ -19168,17 +19165,7 @@ var AccountingModule = class _AccountingModule {
19168
19165
  const allTokens = deps.payments.getTokens();
19169
19166
  let anyScanDirty = false;
19170
19167
  for (const token of allTokens) {
19171
- if (!token.sdkData) continue;
19172
- let txf;
19173
- try {
19174
- txf = JSON.parse(token.sdkData);
19175
- } catch {
19176
- continue;
19177
- }
19178
- const txCount = txf.transactions?.length ?? 0;
19179
- if (txCount === 0) continue;
19180
- this._processTokenTransactions(token.id, txf, 0);
19181
- anyScanDirty = true;
19168
+ if (await this._scanTokenForAttribution(token, 0)) anyScanDirty = true;
19182
19169
  }
19183
19170
  const archivedTokensForScan = deps.payments.getArchivedTokens();
19184
19171
  for (const [archivedId, txf] of archivedTokensForScan) {
@@ -19194,7 +19181,7 @@ var AccountingModule = class _AccountingModule {
19194
19181
  if (this.config.debug) {
19195
19182
  logger.debug(LOG_TAG2, `Invoice created and stored: ${invoiceId}`);
19196
19183
  }
19197
- const txfToken = sdkTokenJson;
19184
+ const txfToken = engine ? sdkData : JSON.parse(sdkData);
19198
19185
  return {
19199
19186
  success: true,
19200
19187
  invoiceId,
@@ -19235,36 +19222,60 @@ var AccountingModule = class _AccountingModule {
19235
19222
  this.ensureNotDestroyed();
19236
19223
  this.ensureInitialized();
19237
19224
  const deps = this.deps;
19238
- const tokenType = token.genesis?.data?.tokenType;
19239
- if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19240
- throw new SphereError(
19241
- `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19242
- "INVOICE_WRONG_TOKEN_TYPE"
19243
- );
19244
- }
19245
- const tokenData = token.genesis?.data?.tokenData;
19246
- if (!tokenData || typeof tokenData !== "string") {
19247
- throw new SphereError(
19248
- "Invoice import failed: missing or invalid tokenData field.",
19249
- "INVOICE_INVALID_DATA"
19250
- );
19251
- }
19252
- let jsonString = tokenData;
19253
- if (!/^\s*[\[{"]/.test(tokenData)) {
19225
+ let terms;
19226
+ let tokenId;
19227
+ let sdkDataForStore;
19228
+ const engine = deps.tokenEngine;
19229
+ const isV2 = !!engine && typeof token === "string";
19230
+ if (isV2) {
19231
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token)));
19232
+ const verifyResult = await engine.verify(sphereToken);
19233
+ if (!verifyResult.ok) {
19234
+ throw new SphereError("Invoice import failed: inclusion proof is invalid.", "INVOICE_INVALID_PROOF");
19235
+ }
19236
+ tokenId = engine.tokenId(sphereToken);
19237
+ const data = engine.readTokenData(sphereToken);
19238
+ if (!data) {
19239
+ throw new SphereError("Invoice import failed: missing or invalid tokenData field.", "INVOICE_INVALID_DATA");
19240
+ }
19254
19241
  try {
19255
- const bytes = hexToBytes(tokenData);
19256
- jsonString = new TextDecoder().decode(bytes);
19242
+ terms = JSON.parse(new TextDecoder().decode(data));
19257
19243
  } catch {
19244
+ throw new SphereError("Invoice import failed: tokenData is not valid JSON.", "INVOICE_INVALID_DATA");
19258
19245
  }
19259
- }
19260
- let terms;
19261
- try {
19262
- terms = JSON.parse(jsonString);
19263
- } catch {
19264
- throw new SphereError(
19265
- "Invoice import failed: tokenData is not valid JSON.",
19266
- "INVOICE_INVALID_DATA"
19267
- );
19246
+ sdkDataForStore = token;
19247
+ } else {
19248
+ const tokenType = token.genesis?.data?.tokenType;
19249
+ if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19250
+ throw new SphereError(
19251
+ `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19252
+ "INVOICE_WRONG_TOKEN_TYPE"
19253
+ );
19254
+ }
19255
+ const tokenData = token.genesis?.data?.tokenData;
19256
+ if (!tokenData || typeof tokenData !== "string") {
19257
+ throw new SphereError(
19258
+ "Invoice import failed: missing or invalid tokenData field.",
19259
+ "INVOICE_INVALID_DATA"
19260
+ );
19261
+ }
19262
+ let jsonString = tokenData;
19263
+ if (!/^\s*[[{"]/.test(tokenData)) {
19264
+ try {
19265
+ const bytes = hexToBytes(tokenData);
19266
+ jsonString = new TextDecoder().decode(bytes);
19267
+ } catch {
19268
+ }
19269
+ }
19270
+ try {
19271
+ terms = JSON.parse(jsonString);
19272
+ } catch {
19273
+ throw new SphereError(
19274
+ "Invoice import failed: tokenData is not valid JSON.",
19275
+ "INVOICE_INVALID_DATA"
19276
+ );
19277
+ }
19278
+ sdkDataForStore = JSON.stringify(token);
19268
19279
  }
19269
19280
  if (!terms || typeof terms !== "object") {
19270
19281
  throw new SphereError(
@@ -19351,7 +19362,7 @@ var AccountingModule = class _AccountingModule {
19351
19362
  }
19352
19363
  }
19353
19364
  }
19354
- const tokenId = token.genesis?.data?.tokenId;
19365
+ if (!isV2) tokenId = token.genesis?.data?.tokenId;
19355
19366
  if (!tokenId || typeof tokenId !== "string") {
19356
19367
  throw new SphereError(
19357
19368
  "Invoice import failed: missing tokenId in genesis data.",
@@ -19364,13 +19375,13 @@ var AccountingModule = class _AccountingModule {
19364
19375
  "INVOICE_ALREADY_EXISTS"
19365
19376
  );
19366
19377
  }
19367
- {
19368
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19369
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19370
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19378
+ if (!isV2) {
19379
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19380
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19381
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19371
19382
  const reSerializedBytes = new TextEncoder().encode(canonicalSerialize(terms));
19372
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(reSerializedBytes).digest();
19373
- const reTokenId = new TokenId5(hash.imprint).toJSON();
19383
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(reSerializedBytes).digest();
19384
+ const reTokenId = new TokenId4(hash.imprint).toJSON();
19374
19385
  if (reTokenId !== tokenId) {
19375
19386
  throw new SphereError(
19376
19387
  "Invoice import failed: parsed terms do not match on-chain token ID (canonical hash mismatch).",
@@ -19378,35 +19389,37 @@ var AccountingModule = class _AccountingModule {
19378
19389
  );
19379
19390
  }
19380
19391
  }
19381
- if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19382
- throw new SphereError(
19383
- "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19384
- "INVOICE_INVALID_PROOF"
19385
- );
19386
- }
19387
- try {
19388
- const sdkToken = await SdkToken4.fromJSON(token);
19389
- const verifyResult = await sdkToken.verify(deps.trustBase);
19390
- const verifyOk = verifyResult.isSuccessful === true;
19391
- if (!verifyOk) {
19392
+ if (!isV2) {
19393
+ if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19392
19394
  throw new SphereError(
19393
- "Invoice import failed: inclusion proof is invalid.",
19395
+ "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19394
19396
  "INVOICE_INVALID_PROOF"
19395
19397
  );
19396
19398
  }
19397
- const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19398
- if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19399
+ try {
19400
+ const sdkToken = await SdkToken3.fromJSON(token);
19401
+ const verifyResult = await sdkToken.verify(deps.trustBase);
19402
+ const verifyOk = verifyResult.isSuccessful === true;
19403
+ if (!verifyOk) {
19404
+ throw new SphereError(
19405
+ "Invoice import failed: inclusion proof is invalid.",
19406
+ "INVOICE_INVALID_PROOF"
19407
+ );
19408
+ }
19409
+ const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19410
+ if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19411
+ throw new SphereError(
19412
+ `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19413
+ "INVOICE_INVALID_DATA"
19414
+ );
19415
+ }
19416
+ } catch (err) {
19417
+ if (err instanceof SphereError) throw err;
19399
19418
  throw new SphereError(
19400
- `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19401
- "INVOICE_INVALID_DATA"
19419
+ `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19420
+ "INVOICE_INVALID_PROOF"
19402
19421
  );
19403
19422
  }
19404
- } catch (err) {
19405
- if (err instanceof SphereError) throw err;
19406
- throw new SphereError(
19407
- `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19408
- "INVOICE_INVALID_PROOF"
19409
- );
19410
19423
  }
19411
19424
  try {
19412
19425
  const uiToken = {
@@ -19419,7 +19432,7 @@ var AccountingModule = class _AccountingModule {
19419
19432
  status: "confirmed",
19420
19433
  createdAt: terms.createdAt,
19421
19434
  updatedAt: terms.createdAt,
19422
- sdkData: JSON.stringify(token)
19435
+ sdkData: sdkDataForStore
19423
19436
  };
19424
19437
  await deps.payments.addToken(uiToken);
19425
19438
  } catch (err) {
@@ -19474,17 +19487,8 @@ var AccountingModule = class _AccountingModule {
19474
19487
  const allTokens = deps.payments.getTokens();
19475
19488
  let anyDirty = false;
19476
19489
  for (const existingToken of allTokens) {
19477
- if (!existingToken.sdkData) continue;
19478
- let txf;
19479
- try {
19480
- txf = JSON.parse(existingToken.sdkData);
19481
- } catch {
19482
- continue;
19483
- }
19484
- const transactions = txf.transactions ?? [];
19485
19490
  const startIndex = this.tokenScanState.get(existingToken.id) ?? 0;
19486
- if (transactions.length > startIndex) {
19487
- this._processTokenTransactions(existingToken.id, txf, startIndex);
19491
+ if (await this._scanTokenForAttribution(existingToken, startIndex)) {
19488
19492
  anyDirty = true;
19489
19493
  }
19490
19494
  }
@@ -21374,17 +21378,8 @@ var AccountingModule = class _AccountingModule {
21374
21378
  const allTokens = deps.payments.getTokens();
21375
21379
  let anyDirty = false;
21376
21380
  for (const token of allTokens) {
21377
- if (!token.sdkData) continue;
21378
- let txf;
21379
- try {
21380
- txf = JSON.parse(token.sdkData);
21381
- } catch {
21382
- continue;
21383
- }
21384
- const transactions = txf.transactions ?? [];
21385
21381
  const startIndex = this.tokenScanState.get(token.id) ?? 0;
21386
- if (transactions.length > startIndex) {
21387
- this._processTokenTransactions(token.id, txf, startIndex);
21382
+ if (await this._scanTokenForAttribution(token, startIndex)) {
21388
21383
  anyDirty = true;
21389
21384
  }
21390
21385
  }
@@ -21439,6 +21434,53 @@ var AccountingModule = class _AccountingModule {
21439
21434
  * @param txf - Parsed TxfToken.
21440
21435
  * @param startIndex - First unprocessed transaction index.
21441
21436
  */
21437
+ /**
21438
+ * Attribute one payment token's invoice memo(s) to the ledger.
21439
+ *
21440
+ * v2 (engine blob): the token carries a single on-chain memo. We decode it and
21441
+ * shim the token into a v1-shaped `txf` (coinData ← engine.readValue, the memo
21442
+ * ← engine.readMemo) so the battle-hardened `_processTokenTransactions` runs
21443
+ * UNCHANGED — same dedup, direction, provisional/synthetic/orphan handling.
21444
+ * v1 (TXF JSON): parse and scan transactions directly.
21445
+ *
21446
+ * `startIndex` is the per-token watermark (0 for a full retroactive scan).
21447
+ * Returns true when the token was scanned. Async because engine.decodeToken is.
21448
+ */
21449
+ async _scanTokenForAttribution(token, startIndex) {
21450
+ if (!token.sdkData) return false;
21451
+ const engine = this.deps?.tokenEngine;
21452
+ const isBlob = token.sdkData.length >= 2 && token.sdkData.length % 2 === 0 && token.sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(token.sdkData);
21453
+ if (engine && isBlob) {
21454
+ let syntheticTxf;
21455
+ try {
21456
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token.sdkData)));
21457
+ const memo = engine.readMemo(sphereToken);
21458
+ if (!memo) return false;
21459
+ const coinData = (engine.readValue(sphereToken)?.assets ?? []).map(
21460
+ (a) => [a.coinId, a.amount.toString()]
21461
+ );
21462
+ syntheticTxf = {
21463
+ genesis: { data: { coinData } },
21464
+ transactions: [{ data: { message: bytesToHex(memo) }, inclusionProof: {} }]
21465
+ };
21466
+ } catch {
21467
+ return false;
21468
+ }
21469
+ this._processTokenTransactions(token.id, syntheticTxf, startIndex);
21470
+ return true;
21471
+ }
21472
+ let txf;
21473
+ try {
21474
+ txf = JSON.parse(token.sdkData);
21475
+ } catch {
21476
+ return false;
21477
+ }
21478
+ if ((txf.transactions?.length ?? 0) > startIndex) {
21479
+ this._processTokenTransactions(token.id, txf, startIndex);
21480
+ return true;
21481
+ }
21482
+ return false;
21483
+ }
21442
21484
  _processTokenTransactions(tokenId, txf, startIndex) {
21443
21485
  const transactions = txf.transactions ?? [];
21444
21486
  let lastSuccessIdx = startIndex;
@@ -21718,17 +21760,7 @@ var AccountingModule = class _AccountingModule {
21718
21760
  async _handleIncomingTransfer(transfer) {
21719
21761
  if (this.destroyed) return;
21720
21762
  for (const token of transfer.tokens) {
21721
- if (!token.sdkData) continue;
21722
- let txf;
21723
- try {
21724
- txf = JSON.parse(token.sdkData);
21725
- } catch {
21726
- continue;
21727
- }
21728
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
21729
- if ((txf.transactions?.length ?? 0) > startIndex) {
21730
- this._processTokenTransactions(token.id, txf, startIndex);
21731
- }
21763
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
21732
21764
  }
21733
21765
  if (this.destroyed) return;
21734
21766
  await this._flushDirtyLedgerEntries();
@@ -21866,16 +21898,7 @@ var AccountingModule = class _AccountingModule {
21866
21898
  if (this.destroyed) return;
21867
21899
  for (const token of result.tokens) {
21868
21900
  if (!token.sdkData) continue;
21869
- let txf;
21870
- try {
21871
- txf = JSON.parse(token.sdkData);
21872
- } catch {
21873
- continue;
21874
- }
21875
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
21876
- if ((txf.transactions?.length ?? 0) > startIndex) {
21877
- this._processTokenTransactions(token.id, txf, startIndex);
21878
- }
21901
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
21879
21902
  const relatedInvoices = this.tokenInvoiceMap.get(token.id);
21880
21903
  if (relatedInvoices) {
21881
21904
  for (const invoiceId of relatedInvoices) {
@@ -21946,15 +21969,8 @@ var AccountingModule = class _AccountingModule {
21946
21969
  const tokens = this.deps.payments.getTokens();
21947
21970
  const token = tokens.find((t) => t.id === tokenId);
21948
21971
  if (!token?.sdkData) return;
21949
- let txf;
21950
- try {
21951
- txf = JSON.parse(token.sdkData);
21952
- } catch {
21953
- return;
21954
- }
21955
21972
  const startIndex = this.tokenScanState.get(tokenId) ?? 0;
21956
- if ((txf.transactions?.length ?? 0) > startIndex) {
21957
- this._processTokenTransactions(tokenId, txf, startIndex);
21973
+ if (await this._scanTokenForAttribution(token, startIndex)) {
21958
21974
  if (this.destroyed) return;
21959
21975
  await this._flushDirtyLedgerEntries();
21960
21976
  }
@@ -27392,29 +27408,421 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
27392
27408
  };
27393
27409
  }
27394
27410
 
27411
+ // token-engine/identity.ts
27412
+ var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27413
+ var SIGNING_ALGORITHM = "secp256k1";
27414
+ var EMBEDDED_PREDICATE_UNMASKED = 0;
27415
+ var HASH_ALGORITHM_SHA256 = 0n;
27416
+ var SHA256_IMPRINT_PREFIX = new Uint8Array([0, 0]);
27417
+ function hexToBytes4(hex) {
27418
+ const bytes = new Uint8Array(hex.length / 2);
27419
+ for (let i = 0; i < bytes.length; i++) {
27420
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
27421
+ }
27422
+ return bytes;
27423
+ }
27424
+ function sha2565(data) {
27425
+ return new DataHasher(HashAlgorithm2.SHA256).update(data).digest().then((h) => h.data);
27426
+ }
27427
+ async function deriveDirectAddress(publicKey) {
27428
+ const tokenTypeCbor = CborSerializer.encodeByteString(hexToBytes4(UNICITY_TOKEN_TYPE_HEX));
27429
+ const reference = CborSerializer.encodeArray(
27430
+ CborSerializer.encodeByteString(new Uint8Array([EMBEDDED_PREDICATE_UNMASKED])),
27431
+ CborSerializer.encodeByteString(tokenTypeCbor),
27432
+ CborSerializer.encodeTextString(SIGNING_ALGORITHM),
27433
+ CborSerializer.encodeUnsignedInteger(HASH_ALGORITHM_SHA256),
27434
+ CborSerializer.encodeByteString(publicKey)
27435
+ );
27436
+ const refHash = await sha2565(reference);
27437
+ const imprint = new Uint8Array([...SHA256_IMPRINT_PREFIX, ...refHash]);
27438
+ const checksum = (await sha2565(imprint)).slice(0, 4);
27439
+ return `DIRECT://${HexConverter.encode(imprint)}${HexConverter.encode(checksum)}`;
27440
+ }
27441
+
27442
+ // token-engine/factory.ts
27443
+ init_errors();
27444
+
27445
+ // token-engine/SpherePaymentData.ts
27446
+ init_errors();
27447
+ var COIN_ID_PATTERN = /^([0-9a-f]{2})+$/;
27448
+ function assertAsset(coinId, amount) {
27449
+ if (!COIN_ID_PATTERN.test(coinId)) {
27450
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27451
+ }
27452
+ if (amount < 0n) {
27453
+ throw new SphereError(`Asset amount must be non-negative: ${amount.toString()}`, "VALIDATION_ERROR");
27454
+ }
27455
+ }
27456
+ function sphereAssetToSdk(coinId, amount) {
27457
+ assertAsset(coinId, amount);
27458
+ return new Asset(new AssetId(HexConverter.decode(coinId)), amount);
27459
+ }
27460
+ var SpherePaymentData = class _SpherePaymentData {
27461
+ constructor(assets, _memo = null) {
27462
+ this.assets = assets;
27463
+ this._memo = _memo;
27464
+ }
27465
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
27466
+ static CBOR_TAG = 39050n;
27467
+ /** Envelope version; bump when the structure changes. */
27468
+ static VERSION = 1n;
27469
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
27470
+ get memo() {
27471
+ return this._memo ? new Uint8Array(this._memo) : null;
27472
+ }
27473
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
27474
+ static create(assets, memo = null) {
27475
+ return new _SpherePaymentData(assets, memo);
27476
+ }
27477
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
27478
+ static fromValue(value, memo = null) {
27479
+ const assets = value.assets.map((a) => sphereAssetToSdk(a.coinId, a.amount));
27480
+ return new _SpherePaymentData(PaymentAssetCollection.create(...assets), memo);
27481
+ }
27482
+ /** Decode from the CBOR envelope produced by {@link encode}. */
27483
+ static fromCBOR(bytes) {
27484
+ const tag = CborDeserializer.decodeTag(bytes);
27485
+ if (tag.tag !== _SpherePaymentData.CBOR_TAG) {
27486
+ throw new CborError(`Invalid SpherePaymentData tag: ${tag.tag}`);
27487
+ }
27488
+ const fields = CborDeserializer.decodeArray(tag.data, 3);
27489
+ const version = CborDeserializer.decodeUnsignedInteger(fields[0]);
27490
+ if (version !== _SpherePaymentData.VERSION) {
27491
+ throw new CborError(`Unsupported SpherePaymentData version: ${version}`);
27492
+ }
27493
+ const memo = CborDeserializer.decodeNullable(fields[2], CborDeserializer.decodeByteString);
27494
+ return new _SpherePaymentData(PaymentAssetCollection.fromCBOR(fields[1]), memo);
27495
+ }
27496
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
27497
+ encode() {
27498
+ return Promise.resolve(
27499
+ CborSerializer.encodeTag(
27500
+ _SpherePaymentData.CBOR_TAG,
27501
+ CborSerializer.encodeArray(
27502
+ CborSerializer.encodeUnsignedInteger(_SpherePaymentData.VERSION),
27503
+ this.assets.toCBOR(),
27504
+ CborSerializer.encodeNullable(this._memo, CborSerializer.encodeByteString)
27505
+ )
27506
+ )
27507
+ );
27508
+ }
27509
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
27510
+ toValue() {
27511
+ return {
27512
+ assets: this.assets.toArray().map((a) => ({
27513
+ coinId: HexConverter.encode(a.id.bytes),
27514
+ amount: a.value
27515
+ }))
27516
+ };
27517
+ }
27518
+ /** Balance of a single coin within this payload (0n when absent). */
27519
+ balanceOf(coinId) {
27520
+ if (!COIN_ID_PATTERN.test(coinId)) {
27521
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27522
+ }
27523
+ const asset = this.assets.get(new AssetId(HexConverter.decode(coinId)));
27524
+ return asset ? asset.value : 0n;
27525
+ }
27526
+ };
27527
+ function decodeSpherePaymentData(bytes) {
27528
+ return Promise.resolve(SpherePaymentData.fromCBOR(bytes));
27529
+ }
27530
+
27531
+ // token-engine/SphereTokenEngine.ts
27532
+ init_errors();
27533
+ var SphereTokenEngine = class {
27534
+ constructor(deps) {
27535
+ this.deps = deps;
27536
+ }
27537
+ // ── identity ────────────────────────────────────────────────────────────────
27538
+ getIdentity() {
27539
+ return { chainPubkey: new Uint8Array(this.deps.signingService.publicKey) };
27540
+ }
27541
+ /** Legacy DIRECT:// address (Path A). Async: the derivation hashes via the SDK. */
27542
+ deriveIdentityAddress(pubkey) {
27543
+ return deriveDirectAddress(pubkey ?? this.deps.signingService.publicKey);
27544
+ }
27545
+ // ── value (read) ─────────────────────────────────────────────────────────────
27546
+ readValue(token) {
27547
+ return token.value;
27548
+ }
27549
+ balanceOf(token, coinId) {
27550
+ let sum = 0n;
27551
+ for (const asset of token.value?.assets ?? []) {
27552
+ if (asset.coinId === coinId) sum += asset.amount;
27553
+ }
27554
+ return sum;
27555
+ }
27556
+ tokenId(token) {
27557
+ return token.blob.tokenId;
27558
+ }
27559
+ readMemo(token) {
27560
+ const sdkToken = token.sdkToken;
27561
+ if (sdkToken.transactions.length > 0) {
27562
+ return sdkToken.latestTransaction.data;
27563
+ }
27564
+ const data = sdkToken.genesis.data;
27565
+ if (data && this.isSpherePaymentData(data)) {
27566
+ return SpherePaymentData.fromCBOR(data).memo;
27567
+ }
27568
+ return null;
27569
+ }
27570
+ readTokenData(token) {
27571
+ const data = token.sdkToken.genesis.data;
27572
+ return data ? new Uint8Array(data) : null;
27573
+ }
27574
+ // ── lifecycle ────────────────────────────────────────────────────────────────
27575
+ async mint(params, options) {
27576
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27577
+ const data = params.value ? await SpherePaymentData.fromValue(params.value).encode() : null;
27578
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, data);
27579
+ const certificationData = await CertificationData.fromMintTransaction(mintTx);
27580
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27581
+ if (response.status !== CertificationStatus.SUCCESS) {
27582
+ throw new SphereError(`Mint certification failed: ${response.status}`, "AGGREGATOR_ERROR");
27583
+ }
27584
+ const proof = await waitInclusionProof2(
27585
+ this.deps.client,
27586
+ this.deps.trustBase,
27587
+ this.deps.predicateVerifier,
27588
+ mintTx,
27589
+ options?.signal
27590
+ );
27591
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27592
+ const token = await Token2.mint(
27593
+ this.deps.trustBase,
27594
+ this.deps.predicateVerifier,
27595
+ this.deps.mintJustificationVerifier,
27596
+ certified
27597
+ );
27598
+ return this.wrapToken(token);
27599
+ }
27600
+ async mintDataToken(params, options) {
27601
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27602
+ const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
27603
+ const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
27604
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, params.data, tokenType, salt);
27605
+ const certificationData = await CertificationData.fromMintTransaction(mintTx);
27606
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27607
+ if (response.status !== CertificationStatus.SUCCESS) {
27608
+ throw new SphereError(`Data-token mint failed: ${response.status}`, "AGGREGATOR_ERROR");
27609
+ }
27610
+ const proof = await waitInclusionProof2(
27611
+ this.deps.client,
27612
+ this.deps.trustBase,
27613
+ this.deps.predicateVerifier,
27614
+ mintTx,
27615
+ options?.signal
27616
+ );
27617
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27618
+ const token = await Token2.mint(
27619
+ this.deps.trustBase,
27620
+ this.deps.predicateVerifier,
27621
+ this.deps.mintJustificationVerifier,
27622
+ certified
27623
+ );
27624
+ return this.wrapToken(token);
27625
+ }
27626
+ async transfer(params, options) {
27627
+ this.assertOwned(params.token);
27628
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27629
+ const stateMask = crypto.getRandomValues(new Uint8Array(32));
27630
+ const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, params.data ?? null);
27631
+ const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
27632
+ const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
27633
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27634
+ if (response.status !== CertificationStatus.SUCCESS) {
27635
+ throw new SphereError(`Transfer certification failed: ${response.status}`, "TRANSFER_FAILED");
27636
+ }
27637
+ const proof = await waitInclusionProof2(
27638
+ this.deps.client,
27639
+ this.deps.trustBase,
27640
+ this.deps.predicateVerifier,
27641
+ transferTx,
27642
+ options?.signal
27643
+ );
27644
+ const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27645
+ const transferred = await params.token.sdkToken.transfer(this.deps.trustBase, this.deps.predicateVerifier, certified);
27646
+ return this.wrapToken(transferred);
27647
+ }
27648
+ async split(params, options) {
27649
+ this.assertOwned(params.token);
27650
+ if (params.outputs.length === 0) {
27651
+ throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
27652
+ }
27653
+ const requests = params.outputs.map(
27654
+ (o) => SplitTokenRequest.create(
27655
+ SignaturePredicate.create(o.recipientPubkey),
27656
+ PaymentAssetCollection.create(sphereAssetToSdk(o.coinId, o.amount))
27657
+ )
27658
+ );
27659
+ const split = await TokenSplit.split(params.token.sdkToken, decodeSpherePaymentData, requests);
27660
+ const burnUnlock = await SignaturePredicateUnlockScript.create(split.burn.transaction, this.deps.signingService);
27661
+ const burnCert = await CertificationData.fromTransaction(split.burn.transaction, burnUnlock);
27662
+ const burnResponse = await this.deps.client.submitCertificationRequest(burnCert);
27663
+ if (burnResponse.status !== CertificationStatus.SUCCESS) {
27664
+ throw new SphereError(`Split burn failed: ${burnResponse.status}`, "TRANSFER_FAILED");
27665
+ }
27666
+ const burnProof = await waitInclusionProof2(
27667
+ this.deps.client,
27668
+ this.deps.trustBase,
27669
+ this.deps.predicateVerifier,
27670
+ split.burn.transaction,
27671
+ options?.signal
27672
+ );
27673
+ const burnCertified = await split.burn.transaction.toCertifiedTransaction(
27674
+ this.deps.trustBase,
27675
+ this.deps.predicateVerifier,
27676
+ burnProof
27677
+ );
27678
+ const burntToken = await params.token.sdkToken.transfer(
27679
+ this.deps.trustBase,
27680
+ this.deps.predicateVerifier,
27681
+ burnCertified
27682
+ );
27683
+ const outputs = [];
27684
+ for (let i = 0; i < split.tokens.length; i++) {
27685
+ const splitToken = split.tokens[i];
27686
+ const data = await SpherePaymentData.create(splitToken.assets, params.outputs[i].data ?? null).encode();
27687
+ const justification = SplitMintJustification.create(burntToken, splitToken.proofs).toCBOR();
27688
+ const mintTx = await MintTransaction.create(
27689
+ splitToken.networkId,
27690
+ splitToken.recipient,
27691
+ data,
27692
+ splitToken.tokenType,
27693
+ splitToken.salt,
27694
+ justification
27695
+ );
27696
+ const certData = await CertificationData.fromMintTransaction(mintTx);
27697
+ const response = await this.deps.client.submitCertificationRequest(certData);
27698
+ if (response.status !== CertificationStatus.SUCCESS) {
27699
+ throw new SphereError(`Split mint failed: ${response.status}`, "AGGREGATOR_ERROR");
27700
+ }
27701
+ const proof = await waitInclusionProof2(
27702
+ this.deps.client,
27703
+ this.deps.trustBase,
27704
+ this.deps.predicateVerifier,
27705
+ mintTx,
27706
+ options?.signal
27707
+ );
27708
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27709
+ const token = await Token2.mint(
27710
+ this.deps.trustBase,
27711
+ this.deps.predicateVerifier,
27712
+ this.deps.mintJustificationVerifier,
27713
+ certified
27714
+ );
27715
+ outputs.push(this.wrapToken(token));
27716
+ }
27717
+ return { outputs };
27718
+ }
27719
+ // ── verification ─────────────────────────────────────────────────────────────
27720
+ async verify(token, _options) {
27721
+ const result = await token.sdkToken.verify(
27722
+ this.deps.trustBase,
27723
+ this.deps.predicateVerifier,
27724
+ this.deps.mintJustificationVerifier
27725
+ );
27726
+ return result.status === VerificationStatus.OK ? { ok: true } : { ok: false, reason: String(result.status) };
27727
+ }
27728
+ async isSpent(token, _options) {
27729
+ const probe = await TransferTransaction.create(
27730
+ token.sdkToken,
27731
+ SignaturePredicate.create(this.deps.signingService.publicKey),
27732
+ new Uint8Array(32)
27733
+ );
27734
+ const stateId = await StateId.fromTransaction(probe);
27735
+ const response = await this.deps.client.getInclusionProof(stateId);
27736
+ return response.inclusionProof.inclusionCertificate !== null;
27737
+ }
27738
+ // ── serialization ────────────────────────────────────────────────────────────
27739
+ encodeToken(token) {
27740
+ return token.blob;
27741
+ }
27742
+ async decodeToken(blob) {
27743
+ const sdkToken = await Token2.fromCBOR(blob.token);
27744
+ if (sdkToken.genesis.networkId.id !== this.deps.networkId.id) {
27745
+ throw new SphereError(
27746
+ `Token network mismatch: token is on network ${sdkToken.genesis.networkId.id}, engine on ${this.deps.networkId.id}`,
27747
+ "VALIDATION_ERROR"
27748
+ );
27749
+ }
27750
+ return this.wrapToken(sdkToken);
27751
+ }
27752
+ // ── internals ────────────────────────────────────────────────────────────────
27753
+ /** Fail fast if this engine's key does not own the token's current state. */
27754
+ assertOwned(token) {
27755
+ const owner = token.sdkToken.latestTransaction.recipient;
27756
+ const mine = EncodedPredicate.fromPredicate(SignaturePredicate.create(this.deps.signingService.publicKey));
27757
+ if (!EncodedPredicate.equals(owner, mine)) {
27758
+ throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
27759
+ }
27760
+ }
27761
+ /** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
27762
+ wrapToken(sdkToken) {
27763
+ const data = sdkToken.genesis.data;
27764
+ let value = null;
27765
+ if (data && this.isSpherePaymentData(data)) {
27766
+ try {
27767
+ value = SpherePaymentData.fromCBOR(data).toValue();
27768
+ } catch (err) {
27769
+ throw new SphereError(
27770
+ `Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
27771
+ "VALIDATION_ERROR"
27772
+ );
27773
+ }
27774
+ }
27775
+ const blob = {
27776
+ v: TOKEN_BLOB_VERSION,
27777
+ network: sdkToken.genesis.networkId.id,
27778
+ tokenId: HexConverter.encode(sdkToken.id.bytes),
27779
+ token: sdkToken.toCBOR()
27780
+ };
27781
+ return { sdkToken, blob, value };
27782
+ }
27783
+ /** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
27784
+ isSpherePaymentData(data) {
27785
+ try {
27786
+ return CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
27787
+ } catch {
27788
+ return false;
27789
+ }
27790
+ }
27791
+ };
27792
+
27793
+ // token-engine/factory.ts
27794
+ async function createSphereTokenEngine(config) {
27795
+ if (config.trustBaseJson == null) {
27796
+ throw new SphereError("Engine config requires a trust base (trustBaseJson)", "INVALID_CONFIG");
27797
+ }
27798
+ const trustBase = RootTrustBase.fromJSON(config.trustBaseJson);
27799
+ const predicateVerifier = PredicateVerifierService.create();
27800
+ const mintJustificationVerifier = new MintJustificationVerifierService();
27801
+ mintJustificationVerifier.register(
27802
+ new SplitMintJustificationVerifier(trustBase, predicateVerifier, decodeSpherePaymentData)
27803
+ );
27804
+ const deps = {
27805
+ client: new StateTransitionClient(new AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
27806
+ trustBase,
27807
+ predicateVerifier,
27808
+ mintJustificationVerifier,
27809
+ signingService: new SigningService(config.privateKey),
27810
+ // The trust base is the single source of truth for the network id (it carries
27811
+ // NetworkId.fromId, so any id works — e.g. testnet2 = 4 — with no enum entry).
27812
+ networkId: trustBase.networkId
27813
+ };
27814
+ return new SphereTokenEngine(deps);
27815
+ }
27816
+
27395
27817
  // core/Sphere.ts
27396
- import { SigningService as SigningService2 } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
27397
- import { TokenType as TokenType5 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
27398
- import { HashAlgorithm as HashAlgorithm7 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
27399
- import { UnmaskedPredicateReference as UnmaskedPredicateReference3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference";
27400
- import { normalizeNametag as normalizeNametag2, isPhoneNumber } from "@unicitylabs/nostr-js-sdk";
27818
+ import { normalizeNametag, isPhoneNumber } from "@unicitylabs/nostr-js-sdk";
27401
27819
  function isValidNametag2(nametag) {
27402
27820
  if (isPhoneNumber(nametag)) return true;
27403
27821
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
27404
27822
  }
27405
- var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27406
27823
  async function deriveL3PredicateAddress(privateKey) {
27407
- const secret = Buffer.from(privateKey, "hex");
27408
- const signingService = await SigningService2.createFromSecret(secret);
27409
- const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
27410
- const tokenType = new TokenType5(tokenTypeBytes);
27411
- const predicateRef = UnmaskedPredicateReference3.create(
27412
- tokenType,
27413
- signingService.algorithm,
27414
- signingService.publicKey,
27415
- HashAlgorithm7.SHA256
27416
- );
27417
- return (await (await predicateRef).toAddress()).toString();
27824
+ const prehashedPublicKey = getPublicKey(sha2562(privateKey, "hex"));
27825
+ return deriveDirectAddress(hexToBytes2(prehashedPublicKey));
27418
27826
  }
27419
27827
  var Sphere = class _Sphere {
27420
27828
  // Singleton
@@ -27436,14 +27844,14 @@ var Sphere = class _Sphere {
27436
27844
  _addressIdToIndex = /* @__PURE__ */ new Map();
27437
27845
  /** Nametag cache: addressId -> (nametagIndex -> nametag). Separate from tracked addresses. */
27438
27846
  _addressNametags = /* @__PURE__ */ new Map();
27439
- /** Cached PROXY address (computed once when nametag is set) */
27440
- _cachedProxyAddress = void 0;
27441
27847
  // Providers
27442
27848
  _storage;
27443
27849
  _tokenStorageProviders = /* @__PURE__ */ new Map();
27444
27850
  _transport;
27445
27851
  _oracle;
27446
27852
  _priceProvider;
27853
+ /** v2 token engine (built per active address from the oracle); injected into modules. */
27854
+ _tokenEngine;
27447
27855
  // Modules (single-instance — backward compat, delegates to active address)
27448
27856
  _payments;
27449
27857
  _communications;
@@ -27784,20 +28192,6 @@ var Sphere = class _Sphere {
27784
28192
  await sphere.syncIdentityWithTransport();
27785
28193
  sphere._initialized = true;
27786
28194
  _Sphere.instance = sphere;
27787
- if (sphere._identity?.nametag && !sphere._payments.hasNametag()) {
27788
- progress?.({ step: "registering_nametag", message: "Restoring nametag token..." });
27789
- logger.debug("Sphere", `Unicity ID @${sphere._identity.nametag} has no token, attempting to mint...`);
27790
- try {
27791
- const result = await sphere.mintNametag(sphere._identity.nametag);
27792
- if (result.success) {
27793
- logger.debug("Sphere", `Nametag token minted successfully on load`);
27794
- } else {
27795
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
27796
- }
27797
- } catch (err) {
27798
- logger.warn("Sphere", `Nametag token mint failed:`, err);
27799
- }
27800
- }
27801
28195
  if (options.discoverAddresses !== false && sphere._transport.discoverAddresses && sphere._masterKey) {
27802
28196
  progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
27803
28197
  try {
@@ -28849,13 +29243,13 @@ var Sphere = class _Sphere {
28849
29243
  oracle: this._oracle,
28850
29244
  emitEvent: this.emitEvent.bind(this),
28851
29245
  chainCode: this._masterKey?.chainCode || void 0,
28852
- price: this._priceProvider ?? void 0
29246
+ price: this._priceProvider ?? void 0,
29247
+ tokenEngine: moduleSet.tokenEngine
28853
29248
  });
28854
29249
  }
28855
29250
  }
28856
29251
  this._identity = newIdentity;
28857
29252
  this._currentAddressIndex = index;
28858
- await this._updateCachedProxyAddress();
28859
29253
  const activeModules = this._addressModules.get(index);
28860
29254
  this._payments = activeModules.payments;
28861
29255
  this._communications = activeModules.communications;
@@ -28893,35 +29287,10 @@ var Sphere = class _Sphere {
28893
29287
  }
28894
29288
  if (newNametag) {
28895
29289
  await this.persistAddressNametags();
28896
- if (!this._payments.hasNametag()) {
28897
- logger.debug("Sphere", `Minting nametag token for @${newNametag}...`);
28898
- try {
28899
- const result = await this.mintNametag(newNametag);
28900
- if (result.success) {
28901
- logger.debug("Sphere", `Nametag token minted successfully`);
28902
- } else {
28903
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
28904
- }
28905
- } catch (err) {
28906
- logger.warn("Sphere", `Nametag token mint failed:`, err);
28907
- }
28908
- }
28909
29290
  this.emitEvent("nametag:registered", {
28910
29291
  nametag: newNametag,
28911
29292
  addressIndex: index
28912
29293
  });
28913
- } else if (this._identity?.nametag && !this._payments.hasNametag()) {
28914
- logger.debug("Sphere", `Unicity ID @${this._identity.nametag} has no token after switch, minting...`);
28915
- try {
28916
- const result = await this.mintNametag(this._identity.nametag);
28917
- if (result.success) {
28918
- logger.debug("Sphere", `Nametag token minted successfully after switch`);
28919
- } else {
28920
- logger.warn("Sphere", `Could not mint nametag token after switch: ${result.error}`);
28921
- }
28922
- } catch (err) {
28923
- logger.warn("Sphere", `Nametag token mint failed after switch:`, err);
28924
- }
28925
29294
  }
28926
29295
  }
28927
29296
  /**
@@ -28951,6 +29320,7 @@ var Sphere = class _Sphere {
28951
29320
  const communications = createCommunicationsModule(this._communicationsConfig);
28952
29321
  const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
28953
29322
  const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
29323
+ const tokenEngine = await this.buildTokenEngine(identity);
28954
29324
  payments.initialize({
28955
29325
  identity,
28956
29326
  storage: this._storage,
@@ -28959,7 +29329,8 @@ var Sphere = class _Sphere {
28959
29329
  oracle: this._oracle,
28960
29330
  emitEvent,
28961
29331
  chainCode: this._masterKey?.chainCode || void 0,
28962
- price: this._priceProvider ?? void 0
29332
+ price: this._priceProvider ?? void 0,
29333
+ tokenEngine
28963
29334
  });
28964
29335
  communications.initialize({
28965
29336
  identity,
@@ -28995,7 +29366,8 @@ var Sphere = class _Sphere {
28995
29366
  emitEvent,
28996
29367
  on: this.on.bind(this),
28997
29368
  storage: this._storage,
28998
- communications
29369
+ communications,
29370
+ tokenEngine
28999
29371
  });
29000
29372
  } else {
29001
29373
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -29055,6 +29427,7 @@ var Sphere = class _Sphere {
29055
29427
  market,
29056
29428
  transportAdapter: adapter,
29057
29429
  tokenStorageProviders: new Map(tokenStorageProviders),
29430
+ tokenEngine,
29058
29431
  initialized: true
29059
29432
  };
29060
29433
  this._addressModules.set(index, moduleSet);
@@ -29610,15 +29983,6 @@ var Sphere = class _Sphere {
29610
29983
  hasNametag() {
29611
29984
  return !!this._identity?.nametag;
29612
29985
  }
29613
- /**
29614
- * Get the PROXY address for the current nametag
29615
- * PROXY addresses are derived from the nametag hash and require
29616
- * the nametag token to claim funds sent to them
29617
- * @returns PROXY address string or undefined if no nametag
29618
- */
29619
- getProxyAddress() {
29620
- return this._cachedProxyAddress;
29621
- }
29622
29986
  /**
29623
29987
  * Resolve any identifier to full peer information.
29624
29988
  * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, or transport pubkey.
@@ -29653,17 +30017,6 @@ var Sphere = class _Sphere {
29653
30017
  throw new SphereError(`Cannot resolve address: ${address.slice(0, 30)}`, "INVALID_RECIPIENT");
29654
30018
  }
29655
30019
  }
29656
- /** Compute and cache the PROXY address from the current nametag */
29657
- async _updateCachedProxyAddress() {
29658
- const nametag = this._identity?.nametag;
29659
- if (!nametag) {
29660
- this._cachedProxyAddress = void 0;
29661
- return;
29662
- }
29663
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
29664
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
29665
- this._cachedProxyAddress = proxyAddr.toString();
29666
- }
29667
30020
  /**
29668
30021
  * Register a nametag for the current active address
29669
30022
  * Each address can have its own independent nametag
@@ -29691,17 +30044,6 @@ var Sphere = class _Sphere {
29691
30044
  if (this._identity?.nametag) {
29692
30045
  throw new SphereError(`Unicity ID already registered for address ${this._currentAddressIndex}: @${this._identity.nametag}`, "ALREADY_INITIALIZED");
29693
30046
  }
29694
- if (!this._payments.hasNametag()) {
29695
- logger.debug("Sphere", `Minting nametag token for @${cleanNametag}...`);
29696
- const result = await this.mintNametag(cleanNametag);
29697
- if (!result.success) {
29698
- throw new SphereError(
29699
- `Failed to mint nametag token: ${result.error}`,
29700
- "AGGREGATOR_ERROR"
29701
- );
29702
- }
29703
- logger.debug("Sphere", `Nametag token minted successfully`);
29704
- }
29705
30047
  if (this._transport.publishIdentityBinding) {
29706
30048
  const success = await this._transport.publishIdentityBinding(
29707
30049
  this._identity.chainPubkey,
@@ -29714,7 +30056,6 @@ var Sphere = class _Sphere {
29714
30056
  }
29715
30057
  }
29716
30058
  this._identity.nametag = cleanNametag;
29717
- await this._updateCachedProxyAddress();
29718
30059
  const currentAddressId = this._trackedAddresses.get(this._currentAddressIndex)?.addressId;
29719
30060
  if (currentAddressId) {
29720
30061
  let nametags = this._addressNametags.get(currentAddressId);
@@ -29747,35 +30088,19 @@ var Sphere = class _Sphere {
29747
30088
  await this._storage.saveTrackedAddresses(entries);
29748
30089
  }
29749
30090
  /**
29750
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
29751
- * This creates the nametag token required for receiving tokens via PROXY addresses (@nametag)
30091
+ * Check whether a nametag is available to register.
29752
30092
  *
29753
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
29754
- * @returns MintNametagResult with success status and token if successful
30093
+ * D5: nametags are Nostr bindings (name chainPubkey), not on-chain tokens. Availability is
30094
+ * first-seen-wins a name is available iff no binding resolves for it.
29755
30095
  *
29756
- * @example
29757
- * ```typescript
29758
- * // Mint nametag token for receiving via @alice
29759
- * const result = await sphere.mintNametag('alice');
29760
- * if (result.success) {
29761
- * console.log('Nametag minted:', result.nametagData?.name);
29762
- * } else {
29763
- * console.error('Mint failed:', result.error);
29764
- * }
29765
- * ```
29766
- */
29767
- async mintNametag(nametag) {
29768
- this.ensureReady();
29769
- return this._payments.mintNametag(nametag);
29770
- }
29771
- /**
29772
- * Check if a nametag is available for minting
29773
30096
  * @param nametag - The nametag to check (e.g., "alice" or "@alice")
29774
- * @returns true if available, false if taken or error
30097
+ * @returns true if available, false if already taken
29775
30098
  */
29776
30099
  async isNametagAvailable(nametag) {
29777
30100
  this.ensureReady();
29778
- return this._payments.isNametagAvailable(nametag);
30101
+ if (!this._transport.resolveNametag) return true;
30102
+ const bound = await this._transport.resolveNametag(this.cleanNametag(nametag));
30103
+ return bound == null;
29779
30104
  }
29780
30105
  /**
29781
30106
  * Load tracked addresses from storage.
@@ -29950,7 +30275,6 @@ var Sphere = class _Sphere {
29950
30275
  }
29951
30276
  if (recoveredNametag && !this._identity?.nametag) {
29952
30277
  this._identity.nametag = recoveredNametag;
29953
- await this._updateCachedProxyAddress();
29954
30278
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
29955
30279
  let nametags = this._addressNametags.get(entry.addressId);
29956
30280
  if (!nametags) {
@@ -30039,7 +30363,6 @@ var Sphere = class _Sphere {
30039
30363
  try {
30040
30364
  if (this._identity) {
30041
30365
  this._identity.nametag = recoveredNametag;
30042
- await this._updateCachedProxyAddress();
30043
30366
  }
30044
30367
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
30045
30368
  let nametags = this._addressNametags.get(entry.addressId);
@@ -30059,7 +30382,7 @@ var Sphere = class _Sphere {
30059
30382
  */
30060
30383
  cleanNametag(raw) {
30061
30384
  const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
30062
- return normalizeNametag2(stripped);
30385
+ return normalizeNametag(stripped);
30063
30386
  }
30064
30387
  // ===========================================================================
30065
30388
  // Public Methods - Lifecycle
@@ -30239,7 +30562,6 @@ var Sphere = class _Sphere {
30239
30562
  } else if (this._identity && nametag) {
30240
30563
  this._identity.nametag = nametag;
30241
30564
  }
30242
- await this._updateCachedProxyAddress();
30243
30565
  }
30244
30566
  async initializeIdentityFromMnemonic(mnemonic, derivationPath) {
30245
30567
  const basePath = derivationPath ?? DEFAULT_BASE_PATH;
@@ -30390,10 +30712,45 @@ var Sphere = class _Sphere {
30390
30712
  this._providerEventCleanups = [];
30391
30713
  this._lastProviderConnected.clear();
30392
30714
  }
30715
+ /**
30716
+ * Construct the v2 token engine for a given address identity (defaults to the
30717
+ * active one) from the oracle's gateway URL + trust base and that address's
30718
+ * signing key. The engine is per-address — each address signs with its own key.
30719
+ * The trust base is the single source of truth for the network id (so any id
30720
+ * works — e.g. testnet2 = 4 — with no enum entry). Returns undefined (modules
30721
+ * keep their legacy path) when the oracle can't supply a trust base / url, or
30722
+ * construction fails — a misconfigured oracle never breaks initialization.
30723
+ */
30724
+ async buildTokenEngine(identity) {
30725
+ const oracle = this._oracle;
30726
+ const privateKey = (identity ?? this._identity)?.privateKey;
30727
+ const trustBaseJson = oracle.getTrustBaseJson?.() ?? null;
30728
+ const aggregatorUrl = oracle.getAggregatorUrl?.();
30729
+ if (!trustBaseJson || !aggregatorUrl || !privateKey) {
30730
+ logger.warn("Sphere", "v2 token engine not constructed (oracle has no trust base / url, or no identity) \u2014 legacy path");
30731
+ return void 0;
30732
+ }
30733
+ try {
30734
+ return await createSphereTokenEngine({
30735
+ aggregatorUrl,
30736
+ apiKey: oracle.getApiKey?.(),
30737
+ privateKey: hexToBytes2(privateKey),
30738
+ trustBaseJson
30739
+ });
30740
+ } catch (err) {
30741
+ logger.warn(
30742
+ "Sphere",
30743
+ `Failed to construct v2 token engine \u2014 modules use the legacy path: ${err instanceof Error ? err.message : String(err)}`
30744
+ );
30745
+ return void 0;
30746
+ }
30747
+ }
30393
30748
  async initializeModules() {
30394
30749
  const emitEvent = this.emitEvent.bind(this);
30395
30750
  const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
30396
30751
  const moduleTransport = adapter ?? this._transport;
30752
+ this._tokenEngine = await this.buildTokenEngine();
30753
+ const tokenEngine = this._tokenEngine;
30397
30754
  this._payments.initialize({
30398
30755
  identity: this._identity,
30399
30756
  storage: this._storage,
@@ -30404,7 +30761,8 @@ var Sphere = class _Sphere {
30404
30761
  // Pass chain code for L1 HD derivation
30405
30762
  chainCode: this._masterKey?.chainCode || void 0,
30406
30763
  price: this._priceProvider ?? void 0,
30407
- disabledProviderIds: this._disabledProviders
30764
+ disabledProviderIds: this._disabledProviders,
30765
+ tokenEngine
30408
30766
  });
30409
30767
  this._communications.initialize({
30410
30768
  identity: this._identity,
@@ -30440,7 +30798,8 @@ var Sphere = class _Sphere {
30440
30798
  emitEvent,
30441
30799
  on: this.on.bind(this),
30442
30800
  storage: this._storage,
30443
- communications: this._communications
30801
+ communications: this._communications,
30802
+ tokenEngine
30444
30803
  });
30445
30804
  } else {
30446
30805
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -30502,6 +30861,7 @@ var Sphere = class _Sphere {
30502
30861
  market: this._market,
30503
30862
  transportAdapter: adapter,
30504
30863
  tokenStorageProviders: new Map(this._tokenStorageProviders),
30864
+ tokenEngine: this._tokenEngine,
30505
30865
  initialized: true
30506
30866
  });
30507
30867
  }