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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1914,26 +1914,14 @@ var NostrTransportProvider = class _NostrTransportProvider {
1914
1914
  }
1915
1915
  /**
1916
1916
  * Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
1917
- * Computes PROXY address from nametag if available.
1918
1917
  */
1919
1918
  async bindingInfoToPeerInfo(binding, nametag) {
1920
- const nametagValue = nametag || binding.nametag;
1921
- let proxyAddress = binding.proxyAddress;
1922
- if (nametagValue && !proxyAddress) {
1923
- try {
1924
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
1925
- const proxyAddr = await ProxyAddress.fromNameTag(nametagValue);
1926
- proxyAddress = proxyAddr.toString();
1927
- } catch {
1928
- }
1929
- }
1930
1919
  return {
1931
- nametag: nametagValue,
1920
+ nametag: nametag || binding.nametag,
1932
1921
  transportPubkey: binding.transportPubkey,
1933
1922
  chainPubkey: binding.publicKey || "",
1934
1923
  l1Address: binding.l1Address || "",
1935
1924
  directAddress: binding.directAddress || "",
1936
- proxyAddress,
1937
1925
  timestamp: binding.timestamp
1938
1926
  };
1939
1927
  }
@@ -1959,7 +1947,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
1959
1947
  chainPubkey: content.public_key || "",
1960
1948
  l1Address: content.l1_address || "",
1961
1949
  directAddress: content.direct_address || "",
1962
- proxyAddress: content.proxy_address || void 0,
1963
1950
  timestamp: bindingEvent.created_at * 1e3
1964
1951
  };
1965
1952
  } catch {
@@ -2002,7 +1989,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2002
1989
  chainPubkey: content.public_key || "",
2003
1990
  l1Address: content.l1_address || "",
2004
1991
  directAddress: content.direct_address || "",
2005
- proxyAddress: content.proxy_address || void 0,
2006
1992
  timestamp: event.created_at * 1e3
2007
1993
  });
2008
1994
  } catch {
@@ -2072,8 +2058,6 @@ var NostrTransportProvider = class _NostrTransportProvider {
2072
2058
  }
2073
2059
  const nostrPubkey = this.getNostrPubkey();
2074
2060
  if (nametag) {
2075
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
2076
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
2077
2061
  try {
2078
2062
  const success2 = await this.nostrClient.publishNametagBinding(
2079
2063
  nametag,
@@ -2081,8 +2065,7 @@ var NostrTransportProvider = class _NostrTransportProvider {
2081
2065
  {
2082
2066
  publicKey: chainPubkey,
2083
2067
  l1Address,
2084
- directAddress,
2085
- proxyAddress: proxyAddr.toString()
2068
+ directAddress
2086
2069
  }
2087
2070
  );
2088
2071
  if (success2) {
@@ -6286,6 +6269,13 @@ function createL1PaymentsModule(config) {
6286
6269
  return new L1PaymentsModule(config);
6287
6270
  }
6288
6271
 
6272
+ // types/v2-transfer.ts
6273
+ function isV2TransferPayload(obj) {
6274
+ if (!obj || typeof obj !== "object") return false;
6275
+ const p = obj;
6276
+ return p.type === "V2_TRANSFER" && typeof p.tokenBlob === "string" && p.tokenBlob.length > 0;
6277
+ }
6278
+
6289
6279
  // modules/payments/TokenSplitExecutor.ts
6290
6280
  init_logger();
6291
6281
  init_errors();
@@ -6661,16 +6651,115 @@ init_logger();
6661
6651
  init_errors();
6662
6652
  import { Token as SdkToken } from "@unicitylabs/state-transition-sdk/lib/token/Token";
6663
6653
  import { CoinId as CoinId2 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
6654
+
6655
+ // token-engine/sdk.ts
6656
+ import { StateTransitionClient } from "state-transition-sdk-v2/lib/StateTransitionClient.js";
6657
+ import { AggregatorClient } from "state-transition-sdk-v2/lib/api/AggregatorClient.js";
6658
+ import { NetworkId } from "state-transition-sdk-v2/lib/api/NetworkId.js";
6659
+ import { CertificationData } from "state-transition-sdk-v2/lib/api/CertificationData.js";
6660
+ import { CertificationResponse, CertificationStatus } from "state-transition-sdk-v2/lib/api/CertificationResponse.js";
6661
+ import { StateId } from "state-transition-sdk-v2/lib/api/StateId.js";
6662
+ import { InclusionProof } from "state-transition-sdk-v2/lib/api/InclusionProof.js";
6663
+ import { InclusionProofResponse } from "state-transition-sdk-v2/lib/api/InclusionProofResponse.js";
6664
+ import { RootTrustBase } from "state-transition-sdk-v2/lib/api/bft/RootTrustBase.js";
6665
+ import { waitInclusionProof as waitInclusionProof2 } from "state-transition-sdk-v2/lib/util/InclusionProofUtils.js";
6666
+ import { Token as Token2 } from "state-transition-sdk-v2/lib/transaction/Token.js";
6667
+ import { MintTransaction } from "state-transition-sdk-v2/lib/transaction/MintTransaction.js";
6668
+ import { TransferTransaction } from "state-transition-sdk-v2/lib/transaction/TransferTransaction.js";
6669
+ import { CertifiedMintTransaction } from "state-transition-sdk-v2/lib/transaction/CertifiedMintTransaction.js";
6670
+ import { CertifiedTransferTransaction } from "state-transition-sdk-v2/lib/transaction/CertifiedTransferTransaction.js";
6671
+ import { TokenId as TokenId2 } from "state-transition-sdk-v2/lib/transaction/TokenId.js";
6672
+ import { TokenType } from "state-transition-sdk-v2/lib/transaction/TokenType.js";
6673
+ import { TokenSalt } from "state-transition-sdk-v2/lib/transaction/TokenSalt.js";
6674
+ import { MintJustificationVerifierService } from "state-transition-sdk-v2/lib/transaction/verification/MintJustificationVerifierService.js";
6675
+ import { EncodedPredicate } from "state-transition-sdk-v2/lib/predicate/EncodedPredicate.js";
6676
+ import { SignaturePredicate } from "state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicate.js";
6677
+ import { SignaturePredicateUnlockScript } from "state-transition-sdk-v2/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
6678
+ import { BurnPredicate } from "state-transition-sdk-v2/lib/predicate/builtin/BurnPredicate.js";
6679
+ import { PredicateVerifierService } from "state-transition-sdk-v2/lib/predicate/verification/PredicateVerifierService.js";
6680
+ import { SigningService } from "state-transition-sdk-v2/lib/crypto/secp256k1/SigningService.js";
6681
+ import { Signature } from "state-transition-sdk-v2/lib/crypto/secp256k1/Signature.js";
6682
+ import { MintSigningService } from "state-transition-sdk-v2/lib/crypto/MintSigningService.js";
6683
+ import { HashAlgorithm as HashAlgorithm2 } from "state-transition-sdk-v2/lib/crypto/hash/HashAlgorithm.js";
6684
+ import { DataHash } from "state-transition-sdk-v2/lib/crypto/hash/DataHash.js";
6685
+ import { DataHasher } from "state-transition-sdk-v2/lib/crypto/hash/DataHasher.js";
6686
+ import { DataHasherFactory } from "state-transition-sdk-v2/lib/crypto/hash/DataHasherFactory.js";
6687
+ import { CborSerializer } from "state-transition-sdk-v2/lib/serialization/cbor/CborSerializer.js";
6688
+ import { CborDeserializer } from "state-transition-sdk-v2/lib/serialization/cbor/CborDeserializer.js";
6689
+ import { CborError } from "state-transition-sdk-v2/lib/serialization/cbor/CborError.js";
6690
+ import { Asset } from "state-transition-sdk-v2/lib/payment/asset/Asset.js";
6691
+ import { AssetId } from "state-transition-sdk-v2/lib/payment/asset/AssetId.js";
6692
+ import { PaymentAssetCollection } from "state-transition-sdk-v2/lib/payment/asset/PaymentAssetCollection.js";
6693
+ import { TokenSplit } from "state-transition-sdk-v2/lib/payment/TokenSplit.js";
6694
+ import { SplitTokenRequest } from "state-transition-sdk-v2/lib/payment/SplitTokenRequest.js";
6695
+ import { SplitToken } from "state-transition-sdk-v2/lib/payment/SplitToken.js";
6696
+ import { SplitAssetProof } from "state-transition-sdk-v2/lib/payment/SplitAssetProof.js";
6697
+ import { SplitMintJustification } from "state-transition-sdk-v2/lib/payment/SplitMintJustification.js";
6698
+ import { SplitMintJustificationVerifier } from "state-transition-sdk-v2/lib/payment/SplitMintJustificationVerifier.js";
6699
+ import { VerificationStatus } from "state-transition-sdk-v2/lib/verification/VerificationStatus.js";
6700
+ import { VerificationResult } from "state-transition-sdk-v2/lib/verification/VerificationResult.js";
6701
+ import { HexConverter } from "state-transition-sdk-v2/lib/util/HexConverter.js";
6702
+ import { BigintConverter } from "state-transition-sdk-v2/lib/util/BigintConverter.js";
6703
+ import { BitString } from "state-transition-sdk-v2/lib/util/BitString.js";
6704
+
6705
+ // token-engine/token-blob.ts
6706
+ var TOKEN_BLOB_TAG = 39051n;
6707
+ var TOKEN_BLOB_VERSION = 1;
6708
+ function encodeTokenBlob(blob) {
6709
+ return CborSerializer.encodeTag(
6710
+ TOKEN_BLOB_TAG,
6711
+ CborSerializer.encodeArray(
6712
+ CborSerializer.encodeUnsignedInteger(BigInt(blob.v)),
6713
+ CborSerializer.encodeUnsignedInteger(BigInt(blob.network)),
6714
+ CborSerializer.encodeTextString(blob.tokenId),
6715
+ CborSerializer.encodeByteString(blob.token)
6716
+ )
6717
+ );
6718
+ }
6719
+ function decodeTokenBlob(bytes) {
6720
+ const tag = CborDeserializer.decodeTag(bytes);
6721
+ if (tag.tag !== TOKEN_BLOB_TAG) {
6722
+ throw new CborError(`Invalid TokenBlob tag: ${tag.tag}`);
6723
+ }
6724
+ const fields = CborDeserializer.decodeArray(tag.data, 4);
6725
+ const v = Number(CborDeserializer.decodeUnsignedInteger(fields[0]));
6726
+ if (v !== TOKEN_BLOB_VERSION) {
6727
+ throw new CborError(`Unsupported TokenBlob version: ${v}`);
6728
+ }
6729
+ return {
6730
+ v,
6731
+ network: Number(CborDeserializer.decodeUnsignedInteger(fields[1])),
6732
+ tokenId: CborDeserializer.decodeTextString(fields[2]),
6733
+ token: CborDeserializer.decodeByteString(fields[3])
6734
+ };
6735
+ }
6736
+
6737
+ // modules/payments/SpendQueue.ts
6664
6738
  var QUEUE_TIMEOUT_MS = 3e4;
6665
6739
  var QUEUE_MAX_SIZE = 100;
6666
6740
  var TAG2 = "SpendQueue";
6667
6741
  var SpendPlanner = class {
6742
+ /**
6743
+ * Token engine (path B). When injected, value reads go through the v2 engine
6744
+ * (sdkData = engine blob); otherwise the legacy v1 SdkToken path is used. The
6745
+ * engine is wired after construction via {@link setEngine} (it is bound to the
6746
+ * payments deps, which arrive after the planner is built).
6747
+ */
6748
+ engine;
6749
+ constructor(engine) {
6750
+ this.engine = engine;
6751
+ }
6752
+ /** Inject (or clear) the token engine. */
6753
+ setEngine(engine) {
6754
+ this.engine = engine;
6755
+ }
6668
6756
  /**
6669
6757
  * Async pre-computation: parse all tokens for a given coinId.
6670
6758
  * Called BEFORE the synchronous critical section.
6671
6759
  *
6672
- * Filters to confirmed tokens matching the coinId, parses each
6673
- * token's sdkData into an SdkToken, and extracts the bigint amount.
6760
+ * Filters to confirmed tokens matching the coinId, decodes each token's
6761
+ * sdkData and extracts the bigint amount — via the v2 engine when injected,
6762
+ * else the legacy v1 SdkToken path.
6674
6763
  */
6675
6764
  async buildParsedPool(tokens, coinId) {
6676
6765
  const pool = /* @__PURE__ */ new Map();
@@ -6679,20 +6768,28 @@ var SpendPlanner = class {
6679
6768
  if (t.status !== "confirmed") continue;
6680
6769
  if (!t.sdkData) continue;
6681
6770
  try {
6682
- const parsed = JSON.parse(t.sdkData);
6683
- const sdkToken = await SdkToken.fromJSON(parsed);
6684
- const realAmount = this.getTokenBalance(sdkToken, coinId);
6685
- if (realAmount <= 0n) {
6771
+ const { sdkToken, amount } = this.engine ? await this.parseViaEngine(t.sdkData, coinId) : await this.parseViaSdk(t.sdkData, coinId);
6772
+ if (amount <= 0n) {
6686
6773
  logger.warn(TAG2, `Token ${t.id} has 0 balance for coinId ${coinId}`);
6687
6774
  continue;
6688
6775
  }
6689
- pool.set(t.id, { token: t, sdkToken, amount: realAmount });
6776
+ pool.set(t.id, { token: t, sdkToken, amount });
6690
6777
  } catch (e) {
6691
6778
  logger.warn(TAG2, "Failed to parse token", t.id, e);
6692
6779
  }
6693
6780
  }
6694
6781
  return pool;
6695
6782
  }
6783
+ /** Engine path (v2): sdkData is the engine blob (hex of CBOR(TokenBlob)). */
6784
+ async parseViaEngine(sdkData, coinId) {
6785
+ const token = await this.engine.decodeToken(decodeTokenBlob(hexToBytes2(sdkData)));
6786
+ return { sdkToken: token, amount: this.engine.balanceOf(token, coinId) };
6787
+ }
6788
+ /** Legacy v1 path: sdkData is TXF JSON. */
6789
+ async parseViaSdk(sdkData, coinId) {
6790
+ const sdkToken = await SdkToken.fromJSON(JSON.parse(sdkData));
6791
+ return { sdkToken, amount: this.getTokenBalance(sdkToken, coinId) };
6792
+ }
6696
6793
  /**
6697
6794
  * SYNCHRONOUS critical section. NO await allowed anywhere in this method.
6698
6795
  *
@@ -7103,174 +7200,6 @@ var SpendQueue = class {
7103
7200
  }
7104
7201
  };
7105
7202
 
7106
- // modules/payments/NametagMinter.ts
7107
- init_logger();
7108
- import { Token as Token2 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
7109
- import { TokenId as TokenId2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
7110
- import { TokenType } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
7111
- import { TokenState as TokenState2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
7112
- import { MintTransactionData } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
7113
- import { MintCommitment } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
7114
- import { HashAlgorithm as HashAlgorithm2 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
7115
- import { UnmaskedPredicate as UnmaskedPredicate2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
7116
- import { waitInclusionProof as waitInclusionProof2 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
7117
- import { normalizeNametag } from "@unicitylabs/nostr-js-sdk";
7118
- var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
7119
- var NametagMinter = class {
7120
- client;
7121
- trustBase;
7122
- signingService;
7123
- skipVerification;
7124
- debug;
7125
- constructor(config) {
7126
- this.client = config.stateTransitionClient;
7127
- this.trustBase = config.trustBase;
7128
- this.signingService = config.signingService;
7129
- this.skipVerification = config.skipVerification ?? false;
7130
- this.debug = config.debug ?? false;
7131
- }
7132
- log(message, ...args) {
7133
- logger.debug("NametagMinter", message, ...args);
7134
- }
7135
- /**
7136
- * Check if a nametag is available (not already minted)
7137
- */
7138
- async isNametagAvailable(nametag) {
7139
- try {
7140
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
7141
- const cleanNametag = normalizeNametag(stripped);
7142
- const nametagTokenId = await TokenId2.fromNameTag(cleanNametag);
7143
- const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
7144
- return !isMinted;
7145
- } catch (error) {
7146
- this.log("Error checking nametag availability:", error);
7147
- return false;
7148
- }
7149
- }
7150
- /**
7151
- * Mint a nametag token on-chain
7152
- *
7153
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
7154
- * @param ownerAddress - The owner's direct address
7155
- * @returns MintNametagResult with token if successful
7156
- */
7157
- async mintNametag(nametag, ownerAddress) {
7158
- const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
7159
- const cleanNametag = normalizeNametag(stripped);
7160
- this.log(`Starting mint for nametag: ${cleanNametag}`);
7161
- try {
7162
- const nametagTokenId = await TokenId2.fromNameTag(cleanNametag);
7163
- const nametagTokenType = new TokenType(
7164
- Buffer.from(UNICITY_TOKEN_TYPE_HEX, "hex")
7165
- );
7166
- const nametagBytes = new TextEncoder().encode(cleanNametag);
7167
- const pubKey = this.signingService.publicKey;
7168
- const saltInput = new Uint8Array(pubKey.length + nametagBytes.length);
7169
- saltInput.set(pubKey, 0);
7170
- saltInput.set(nametagBytes, pubKey.length);
7171
- const saltBuffer = await crypto.subtle.digest("SHA-256", saltInput);
7172
- const salt = new Uint8Array(saltBuffer);
7173
- this.log("Generated deterministic salt");
7174
- const mintData = await MintTransactionData.createFromNametag(
7175
- cleanNametag,
7176
- nametagTokenType,
7177
- ownerAddress,
7178
- salt,
7179
- ownerAddress
7180
- );
7181
- this.log("Created MintTransactionData");
7182
- const commitment = await MintCommitment.create(mintData);
7183
- this.log("Created MintCommitment");
7184
- const MAX_RETRIES = 3;
7185
- let submitSuccess = false;
7186
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
7187
- try {
7188
- this.log(`Submitting commitment (attempt ${attempt}/${MAX_RETRIES})...`);
7189
- const response = await this.client.submitMintCommitment(commitment);
7190
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
7191
- this.log(`Commitment ${response.status === "REQUEST_ID_EXISTS" ? "already exists" : "submitted successfully"}`);
7192
- submitSuccess = true;
7193
- break;
7194
- } else {
7195
- this.log(`Commitment failed: ${response.status}`);
7196
- if (attempt === MAX_RETRIES) {
7197
- return {
7198
- success: false,
7199
- error: `Failed to submit commitment after ${MAX_RETRIES} attempts: ${response.status}`
7200
- };
7201
- }
7202
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7203
- }
7204
- } catch (error) {
7205
- this.log(`Attempt ${attempt} error:`, error);
7206
- if (attempt === MAX_RETRIES) {
7207
- return {
7208
- success: false,
7209
- error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`
7210
- };
7211
- }
7212
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
7213
- }
7214
- }
7215
- if (!submitSuccess) {
7216
- return {
7217
- success: false,
7218
- error: "Failed to submit commitment after retries"
7219
- };
7220
- }
7221
- this.log("Waiting for inclusion proof...");
7222
- const inclusionProof = await waitInclusionProof2(this.trustBase, this.client, commitment);
7223
- this.log("Received inclusion proof");
7224
- const genesisTransaction = commitment.toTransaction(inclusionProof);
7225
- const nametagPredicate = await UnmaskedPredicate2.create(
7226
- nametagTokenId,
7227
- nametagTokenType,
7228
- this.signingService,
7229
- HashAlgorithm2.SHA256,
7230
- salt
7231
- );
7232
- const tokenState = new TokenState2(nametagPredicate, null);
7233
- let token;
7234
- if (this.skipVerification) {
7235
- this.log("Creating token WITHOUT verification (dev mode)");
7236
- const tokenJson = {
7237
- version: "2.0",
7238
- state: tokenState.toJSON(),
7239
- genesis: genesisTransaction.toJSON(),
7240
- transactions: [],
7241
- nametags: []
7242
- };
7243
- token = await Token2.fromJSON(tokenJson);
7244
- } else {
7245
- token = await Token2.mint(
7246
- this.trustBase,
7247
- tokenState,
7248
- genesisTransaction
7249
- );
7250
- }
7251
- this.log(`Nametag minted successfully: ${cleanNametag}`);
7252
- const nametagData = {
7253
- name: cleanNametag,
7254
- token: token.toJSON(),
7255
- timestamp: Date.now(),
7256
- format: "txf",
7257
- version: "2.0"
7258
- };
7259
- return {
7260
- success: true,
7261
- token,
7262
- nametagData
7263
- };
7264
- } catch (error) {
7265
- this.log("Minting failed:", error);
7266
- return {
7267
- success: false,
7268
- error: error instanceof Error ? error.message : String(error)
7269
- };
7270
- }
7271
- }
7272
- };
7273
-
7274
7203
  // modules/payments/PaymentsModule.ts
7275
7204
  init_constants();
7276
7205
 
@@ -7887,6 +7816,15 @@ function txfToToken(tokenId, txf) {
7887
7816
  sdkData: JSON.stringify(txf)
7888
7817
  };
7889
7818
  }
7819
+ function isV2TokenBlob(sdkData) {
7820
+ return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
7821
+ }
7822
+ function v2TokenId(token) {
7823
+ return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
7824
+ }
7825
+ function isV2TokenEntry(entry) {
7826
+ return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
7827
+ }
7890
7828
  async function buildTxfStorageData(tokens, meta, options) {
7891
7829
  const storageData = {
7892
7830
  _meta: {
@@ -7917,6 +7855,8 @@ async function buildTxfStorageData(tokens, meta, options) {
7917
7855
  if (txf) {
7918
7856
  const actualTokenId = txf.genesis.data.tokenId;
7919
7857
  storageData[keyFromTokenId(actualTokenId)] = txf;
7858
+ } else if (isV2TokenBlob(token.sdkData)) {
7859
+ storageData[keyFromTokenId(v2TokenId(token))] = token;
7920
7860
  }
7921
7861
  }
7922
7862
  if (options?.archivedTokens && options.archivedTokens.size > 0) {
@@ -8014,6 +7954,8 @@ function parseTxfStorageData(data) {
8014
7954
  if (txfToken?.genesis?.data?.tokenId) {
8015
7955
  const token = txfToToken(tokenId, txfToken);
8016
7956
  result.tokens.push(token);
7957
+ } else if (isV2TokenEntry(storageData[key])) {
7958
+ result.tokens.push(storageData[key]);
8017
7959
  }
8018
7960
  } catch (err) {
8019
7961
  result.validationErrors.push(`Token ${tokenId}: ${err}`);
@@ -8289,12 +8231,12 @@ init_logger();
8289
8231
  init_errors();
8290
8232
  import { Token as Token3 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
8291
8233
  import { TokenId as TokenId3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
8292
- import { TokenState as TokenState3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8234
+ import { TokenState as TokenState2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8293
8235
  import { CoinId as CoinId3 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
8294
8236
  import { TokenCoinData as TokenCoinData2 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData";
8295
8237
  import { TokenSplitBuilder as TokenSplitBuilder2 } from "@unicitylabs/state-transition-sdk/lib/transaction/split/TokenSplitBuilder";
8296
8238
  import { HashAlgorithm as HashAlgorithm3 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
8297
- import { UnmaskedPredicate as UnmaskedPredicate3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8239
+ import { UnmaskedPredicate as UnmaskedPredicate2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8298
8240
  import { UnmaskedPredicateReference as UnmaskedPredicateReference2 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference";
8299
8241
  import { TransferCommitment as TransferCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
8300
8242
  import { waitInclusionProof as waitInclusionProof3 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
@@ -8416,14 +8358,14 @@ var InstantSplitExecutor = class {
8416
8358
  options?.message
8417
8359
  // on-chain message (invoice memo bytes, or null)
8418
8360
  );
8419
- const mintedPredicate = await UnmaskedPredicate3.create(
8361
+ const mintedPredicate = await UnmaskedPredicate2.create(
8420
8362
  recipientTokenId,
8421
8363
  tokenToSplit.type,
8422
8364
  this.signingService,
8423
8365
  HashAlgorithm3.SHA256,
8424
8366
  recipientSalt
8425
8367
  );
8426
- const mintedState = new TokenState3(mintedPredicate, null);
8368
+ const mintedState = new TokenState2(mintedPredicate, null);
8427
8369
  logger.debug("InstantSplit", "Step 5: Packaging V5 bundle...");
8428
8370
  const senderPubkey = toHex2(this.signingService.publicKey);
8429
8371
  let nametagTokenJson;
@@ -8538,14 +8480,14 @@ var InstantSplitExecutor = class {
8538
8480
  * It does NOT need the genesis transaction or mint proof.
8539
8481
  */
8540
8482
  async createTransferCommitmentFromMintData(mintData, recipientAddress, transferSalt, signingService, nametagTokens, message) {
8541
- const predicate = await UnmaskedPredicate3.create(
8483
+ const predicate = await UnmaskedPredicate2.create(
8542
8484
  mintData.tokenId,
8543
8485
  mintData.tokenType,
8544
8486
  signingService,
8545
8487
  HashAlgorithm3.SHA256,
8546
8488
  mintData.salt
8547
8489
  );
8548
- const state = new TokenState3(predicate, null);
8490
+ const state = new TokenState2(predicate, null);
8549
8491
  const minimalToken = {
8550
8492
  state,
8551
8493
  nametagTokens: nametagTokens || [],
@@ -8606,14 +8548,14 @@ var InstantSplitExecutor = class {
8606
8548
  message: `Mint proof received in ${proofDuration.toFixed(0)}ms`
8607
8549
  });
8608
8550
  const mintTransaction = senderMintCommitment.toTransaction(senderMintProof);
8609
- const predicate = await UnmaskedPredicate3.create(
8551
+ const predicate = await UnmaskedPredicate2.create(
8610
8552
  context.senderTokenId,
8611
8553
  context.tokenType,
8612
8554
  context.signingService,
8613
8555
  HashAlgorithm3.SHA256,
8614
8556
  context.senderSalt
8615
8557
  );
8616
- const state = new TokenState3(predicate, null);
8558
+ const state = new TokenState2(predicate, null);
8617
8559
  const changeToken = await Token3.mint(this.trustBase, state, mintTransaction);
8618
8560
  if (!this.devMode) {
8619
8561
  const verification = await changeToken.verify(this.trustBase);
@@ -8693,14 +8635,14 @@ var InstantSplitExecutor = class {
8693
8635
  init_logger();
8694
8636
  init_errors();
8695
8637
  import { Token as Token4 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
8696
- import { TokenState as TokenState4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8638
+ import { TokenState as TokenState3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8697
8639
  import { TokenType as TokenType2 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
8698
8640
  import { HashAlgorithm as HashAlgorithm4 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
8699
- import { UnmaskedPredicate as UnmaskedPredicate4 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8641
+ import { UnmaskedPredicate as UnmaskedPredicate3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8700
8642
  import { TransferCommitment as TransferCommitment3 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
8701
- import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8702
- import { MintCommitment as MintCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8703
- import { MintTransactionData as MintTransactionData2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8643
+ import { TransferTransaction as TransferTransaction2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8644
+ import { MintCommitment } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8645
+ import { MintTransactionData } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8704
8646
  import { waitInclusionProof as waitInclusionProof4 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
8705
8647
 
8706
8648
  // types/instant-split.ts
@@ -8793,11 +8735,11 @@ var InstantSplitProcessor = class {
8793
8735
  logger.warn("InstantSplit", "Sender pubkey mismatch (non-fatal)");
8794
8736
  }
8795
8737
  const burnTxJson = JSON.parse(bundle.burnTransaction);
8796
- const _burnTransaction = await TransferTransaction.fromJSON(burnTxJson);
8738
+ const _burnTransaction = await TransferTransaction2.fromJSON(burnTxJson);
8797
8739
  logger.debug("InstantSplit", "Burn transaction validated");
8798
8740
  const mintDataJson = JSON.parse(bundle.recipientMintData);
8799
- const mintData = await MintTransactionData2.fromJSON(mintDataJson);
8800
- const mintCommitment = await MintCommitment2.create(mintData);
8741
+ const mintData = await MintTransactionData.fromJSON(mintDataJson);
8742
+ const mintCommitment = await MintCommitment.create(mintData);
8801
8743
  logger.debug("InstantSplit", "Mint commitment recreated");
8802
8744
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
8803
8745
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -8829,14 +8771,14 @@ var InstantSplitProcessor = class {
8829
8771
  const transferTransaction = transferCommitment.toTransaction(transferProof);
8830
8772
  logger.debug("InstantSplit", "Transfer proof received");
8831
8773
  const transferSalt = fromHex3(bundle.transferSaltHex);
8832
- const finalRecipientPredicate = await UnmaskedPredicate4.create(
8774
+ const finalRecipientPredicate = await UnmaskedPredicate3.create(
8833
8775
  mintData.tokenId,
8834
8776
  tokenType,
8835
8777
  signingService,
8836
8778
  HashAlgorithm4.SHA256,
8837
8779
  transferSalt
8838
8780
  );
8839
- const finalRecipientState = new TokenState4(finalRecipientPredicate, null);
8781
+ const finalRecipientState = new TokenState3(finalRecipientPredicate, null);
8840
8782
  logger.debug("InstantSplit", "Final recipient state created");
8841
8783
  let nametagTokens = [];
8842
8784
  const recipientAddressStr = bundle.recipientAddressJson;
@@ -8943,8 +8885,8 @@ var InstantSplitProcessor = class {
8943
8885
  await this.waitInclusionProofWithDevBypass(burnCommitment, options?.proofTimeoutMs);
8944
8886
  logger.debug("InstantSplit", "V4: Burn proof received");
8945
8887
  const mintDataJson = JSON.parse(bundle.recipientMintData);
8946
- const mintData = await MintTransactionData2.fromJSON(mintDataJson);
8947
- const mintCommitment = await MintCommitment2.create(mintData);
8888
+ const mintData = await MintTransactionData.fromJSON(mintDataJson);
8889
+ const mintCommitment = await MintCommitment.create(mintData);
8948
8890
  const mintResponse = await this.client.submitMintCommitment(mintCommitment);
8949
8891
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
8950
8892
  throw new SphereError(`Mint submission failed: ${mintResponse.status}`, "TRANSFER_FAILED");
@@ -8957,14 +8899,14 @@ var InstantSplitProcessor = class {
8957
8899
  logger.debug("InstantSplit", "V4: Mint proof received");
8958
8900
  const tokenType = new TokenType2(fromHex3(bundle.tokenTypeHex));
8959
8901
  const recipientSalt = fromHex3(bundle.recipientSaltHex);
8960
- const recipientPredicate = await UnmaskedPredicate4.create(
8902
+ const recipientPredicate = await UnmaskedPredicate3.create(
8961
8903
  mintData.tokenId,
8962
8904
  tokenType,
8963
8905
  signingService,
8964
8906
  HashAlgorithm4.SHA256,
8965
8907
  recipientSalt
8966
8908
  );
8967
- const recipientState = new TokenState4(recipientPredicate, null);
8909
+ const recipientState = new TokenState3(recipientPredicate, null);
8968
8910
  const tokenJson = {
8969
8911
  version: "2.0",
8970
8912
  state: recipientState.toJSON(),
@@ -8987,14 +8929,14 @@ var InstantSplitProcessor = class {
8987
8929
  const transferTransaction = transferCommitment.toTransaction(transferProof);
8988
8930
  logger.debug("InstantSplit", "V4: Transfer proof received");
8989
8931
  const transferSalt = fromHex3(bundle.transferSaltHex);
8990
- const finalPredicate = await UnmaskedPredicate4.create(
8932
+ const finalPredicate = await UnmaskedPredicate3.create(
8991
8933
  mintData.tokenId,
8992
8934
  tokenType,
8993
8935
  signingService,
8994
8936
  HashAlgorithm4.SHA256,
8995
8937
  transferSalt
8996
8938
  );
8997
- const finalState = new TokenState4(finalPredicate, null);
8939
+ const finalState = new TokenState3(finalPredicate, null);
8998
8940
  const finalTokenJson = mintedToken.toJSON();
8999
8941
  finalTokenJson.state = finalState.toJSON();
9000
8942
  finalTokenJson.transactions = [transferTransaction.toJSON()];
@@ -9045,17 +8987,17 @@ var InstantSplitProcessor = class {
9045
8987
  import { Token as SdkToken2 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
9046
8988
  import { CoinId as CoinId4 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
9047
8989
  import { TransferCommitment as TransferCommitment4 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferCommitment";
9048
- import { TransferTransaction as TransferTransaction2 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
9049
- import { SigningService } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
8990
+ import { TransferTransaction as TransferTransaction3 } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction";
8991
+ import { SigningService as SigningService2 } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
9050
8992
  import { AddressScheme } from "@unicitylabs/state-transition-sdk/lib/address/AddressScheme";
9051
- import { UnmaskedPredicate as UnmaskedPredicate5 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
9052
- import { TokenState as TokenState5 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
8993
+ import { UnmaskedPredicate as UnmaskedPredicate4 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
8994
+ import { TokenState as TokenState4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
9053
8995
  import { HashAlgorithm as HashAlgorithm5 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
9054
8996
  import { TokenType as TokenType3 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
9055
- import { MintCommitment as MintCommitment3 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
9056
- import { MintTransactionData as MintTransactionData3 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
8997
+ import { MintCommitment as MintCommitment2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment";
8998
+ import { MintTransactionData as MintTransactionData2 } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData";
9057
8999
  import { waitInclusionProof as waitInclusionProof5 } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils";
9058
- import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof";
9000
+ import { InclusionProof as InclusionProof2 } from "@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof";
9059
9001
  function computeHistoryDedupKey(type, tokenId, transferId) {
9060
9002
  if (type === "SENT" && transferId) return `${type}_transfer_${transferId}`;
9061
9003
  if (tokenId) return `${type}_${tokenId}`;
@@ -9076,7 +9018,7 @@ function enrichWithRegistry(info) {
9076
9018
  }
9077
9019
  return info;
9078
9020
  }
9079
- async function parseTokenInfo(tokenData) {
9021
+ async function parseTokenInfo(tokenData, engine) {
9080
9022
  const defaultInfo = {
9081
9023
  coinId: "ALPHA",
9082
9024
  symbol: "ALPHA",
@@ -9084,6 +9026,25 @@ async function parseTokenInfo(tokenData) {
9084
9026
  decimals: 0,
9085
9027
  amount: "0"
9086
9028
  };
9029
+ if (engine && typeof tokenData === "string" && looksLikeTokenBlob(tokenData)) {
9030
+ try {
9031
+ const token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(tokenData)));
9032
+ const first = engine.readValue(token)?.assets[0];
9033
+ if (first) {
9034
+ return enrichWithRegistry({
9035
+ coinId: first.coinId,
9036
+ symbol: first.coinId.slice(0, 8),
9037
+ name: `Token ${first.coinId.slice(0, 8)}`,
9038
+ decimals: 0,
9039
+ amount: String(first.amount),
9040
+ tokenId: engine.tokenId(token)
9041
+ });
9042
+ }
9043
+ return { ...defaultInfo, tokenId: engine.tokenId(token) };
9044
+ } catch (error) {
9045
+ logger.warn("Payments", "Failed to parse token info via engine:", error);
9046
+ }
9047
+ }
9087
9048
  try {
9088
9049
  const data = typeof tokenData === "string" ? JSON.parse(tokenData) : tokenData;
9089
9050
  try {
@@ -9222,27 +9183,41 @@ async function parseTokenInfo(tokenData) {
9222
9183
  }
9223
9184
  var sdkDataCache = /* @__PURE__ */ new Map();
9224
9185
  var SDK_DATA_CACHE_MAX = 2e3;
9186
+ function looksLikeTokenBlob(sdkData) {
9187
+ return sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
9188
+ }
9189
+ function tryParseBlobKeys(sdkData) {
9190
+ try {
9191
+ const blob = decodeTokenBlob(hexToBytes2(sdkData));
9192
+ return { tokenId: blob.tokenId, stateHash: sha2562(bytesToHex3(blob.token), "hex") };
9193
+ } catch {
9194
+ return null;
9195
+ }
9196
+ }
9225
9197
  function parseSdkDataCached(sdkData) {
9226
9198
  const cached = sdkDataCache.get(sdkData);
9227
9199
  if (cached) return cached;
9228
- let tokenId = null;
9229
- let stateHash = "";
9230
- try {
9231
- const txf = JSON.parse(sdkData);
9232
- tokenId = txf.genesis?.data?.tokenId || null;
9233
- stateHash = getCurrentStateHash(txf) || "";
9234
- if (!stateHash) {
9235
- if (txf.state?.hash) {
9236
- stateHash = txf.state.hash;
9237
- } else if (txf.stateHash) {
9238
- stateHash = txf.stateHash;
9239
- } else if (txf.currentStateHash) {
9240
- stateHash = txf.currentStateHash;
9200
+ let entry = looksLikeTokenBlob(sdkData) ? tryParseBlobKeys(sdkData) : null;
9201
+ if (!entry) {
9202
+ let tokenId = null;
9203
+ let stateHash = "";
9204
+ try {
9205
+ const txf = JSON.parse(sdkData);
9206
+ tokenId = txf.genesis?.data?.tokenId || null;
9207
+ stateHash = getCurrentStateHash(txf) || "";
9208
+ if (!stateHash) {
9209
+ if (txf.state?.hash) {
9210
+ stateHash = txf.state.hash;
9211
+ } else if (txf.stateHash) {
9212
+ stateHash = txf.stateHash;
9213
+ } else if (txf.currentStateHash) {
9214
+ stateHash = txf.currentStateHash;
9215
+ }
9241
9216
  }
9217
+ } catch {
9242
9218
  }
9243
- } catch {
9219
+ entry = { tokenId, stateHash };
9244
9220
  }
9245
- const entry = { tokenId, stateHash };
9246
9221
  if (sdkDataCache.size >= SDK_DATA_CACHE_MAX) {
9247
9222
  sdkDataCache.clear();
9248
9223
  }
@@ -9519,6 +9494,7 @@ var PaymentsModule = class _PaymentsModule {
9519
9494
  );
9520
9495
  this.deps = deps;
9521
9496
  this.priceProvider = deps.price ?? null;
9497
+ this.spendPlanner.setEngine(deps.tokenEngine);
9522
9498
  if (this.l1) {
9523
9499
  this.l1.initialize({
9524
9500
  identity: deps.identity,
@@ -9743,7 +9719,59 @@ var PaymentsModule = class _PaymentsModule {
9743
9719
  request.invoiceRefundAddress,
9744
9720
  request.invoiceContact
9745
9721
  );
9746
- if (transferMode === "conservative") {
9722
+ if (this.deps?.tokenEngine && peerInfo?.chainPubkey) {
9723
+ const engine = this.deps.tokenEngine;
9724
+ const recipientChainPubkey = hexToBytes2(peerInfo.chainPubkey);
9725
+ const memoData = onChainMessage ?? void 0;
9726
+ const handToRecipient = async (finished) => {
9727
+ const tokenBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(finished)));
9728
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
9729
+ type: "V2_TRANSFER",
9730
+ version: "2.0",
9731
+ tokenBlob,
9732
+ memo: request.memo
9733
+ });
9734
+ };
9735
+ for (const tw of splitPlan.tokensToTransferDirectly) {
9736
+ const finished = await engine.transfer({
9737
+ token: tw.sdkToken,
9738
+ recipientPubkey: recipientChainPubkey,
9739
+ data: memoData
9740
+ });
9741
+ await handToRecipient(finished);
9742
+ result.tokenTransfers.push({ sourceTokenId: tw.uiToken.id, method: "direct" });
9743
+ await this.removeToken(tw.uiToken.id, result.id);
9744
+ }
9745
+ if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9746
+ const selfChainPubkey = hexToBytes2(this.deps.identity.chainPubkey);
9747
+ const { outputs } = await engine.split({
9748
+ token: splitPlan.tokenToSplit.sdkToken,
9749
+ outputs: [
9750
+ { recipientPubkey: recipientChainPubkey, coinId: request.coinId, amount: splitPlan.splitAmount, data: memoData },
9751
+ { recipientPubkey: selfChainPubkey, coinId: request.coinId, amount: splitPlan.remainderAmount }
9752
+ ]
9753
+ });
9754
+ await handToRecipient(outputs[0]);
9755
+ const changeToken = outputs[1];
9756
+ const changeBlob = bytesToHex3(encodeTokenBlob(engine.encodeToken(changeToken)));
9757
+ const changeInfo = await parseTokenInfo(changeBlob, engine);
9758
+ const registry = TokenRegistry.getInstance();
9759
+ await this.addToken({
9760
+ id: `v2_${engine.tokenId(changeToken)}`,
9761
+ coinId: changeInfo.coinId,
9762
+ symbol: registry.getSymbol(changeInfo.coinId) || changeInfo.symbol,
9763
+ name: registry.getName(changeInfo.coinId) || changeInfo.name,
9764
+ decimals: registry.getDecimals(changeInfo.coinId) ?? changeInfo.decimals,
9765
+ amount: changeInfo.amount,
9766
+ status: "confirmed",
9767
+ createdAt: Date.now(),
9768
+ updatedAt: Date.now(),
9769
+ sdkData: changeBlob
9770
+ });
9771
+ result.tokenTransfers.push({ sourceTokenId: splitPlan.tokenToSplit.uiToken.id, method: "split" });
9772
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, result.id);
9773
+ }
9774
+ } else if (transferMode === "conservative") {
9747
9775
  if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
9748
9776
  logger.debug("Payments", "Executing conservative split...");
9749
9777
  const splitExecutor = new TokenSplitExecutor({
@@ -10339,7 +10367,7 @@ var PaymentsModule = class _PaymentsModule {
10339
10367
  * because bundle-level dedup protects against replays, and split children share genesis IDs.
10340
10368
  */
10341
10369
  async saveCommitmentOnlyToken(sourceTokenInput, commitmentInput, senderPubkey, deferPersistence = false, skipGenesisDedup = false) {
10342
- const tokenInfo = await parseTokenInfo(sourceTokenInput);
10370
+ const tokenInfo = await parseTokenInfo(sourceTokenInput, this.deps?.tokenEngine);
10343
10371
  const sdkData = typeof sourceTokenInput === "string" ? sourceTokenInput : JSON.stringify(sourceTokenInput);
10344
10372
  const nostrTokenId = extractTokenIdFromSdkData(sdkData);
10345
10373
  const nostrStateHash = extractStateHashFromSdkData(sdkData);
@@ -11431,8 +11459,8 @@ var PaymentsModule = class _PaymentsModule {
11431
11459
  if (pending2.stage === "RECEIVED") {
11432
11460
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
11433
11461
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11434
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11435
- const mintCommitment = await MintCommitment3.create(mintData);
11462
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11463
+ const mintCommitment = await MintCommitment2.create(mintData);
11436
11464
  const mintResponse = await stClient.submitMintCommitment(mintCommitment);
11437
11465
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
11438
11466
  if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
@@ -11444,8 +11472,8 @@ var PaymentsModule = class _PaymentsModule {
11444
11472
  if (pending2.stage === "MINT_SUBMITTED") {
11445
11473
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
11446
11474
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11447
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11448
- const mintCommitment = await MintCommitment3.create(mintData);
11475
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11476
+ const mintCommitment = await MintCommitment2.create(mintData);
11449
11477
  const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
11450
11478
  if (!proof) {
11451
11479
  logger.debug("Payments", `[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
@@ -11542,10 +11570,10 @@ var PaymentsModule = class _PaymentsModule {
11542
11570
  */
11543
11571
  async finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase) {
11544
11572
  const mintDataJson = JSON.parse(bundle.recipientMintData);
11545
- const mintData = await MintTransactionData3.fromJSON(mintDataJson);
11546
- const mintCommitment = await MintCommitment3.create(mintData);
11573
+ const mintData = await MintTransactionData2.fromJSON(mintDataJson);
11574
+ const mintCommitment = await MintCommitment2.create(mintData);
11547
11575
  const mintProofJson = JSON.parse(pending2.mintProofJson);
11548
- const mintProof = InclusionProof.fromJSON(mintProofJson);
11576
+ const mintProof = InclusionProof2.fromJSON(mintProofJson);
11549
11577
  const mintTransaction = mintCommitment.toTransaction(mintProof);
11550
11578
  const tokenType = new TokenType3(fromHex4(bundle.tokenTypeHex));
11551
11579
  const senderMintedStateJson = JSON.parse(bundle.mintedTokenStateJson);
@@ -11562,14 +11590,14 @@ var PaymentsModule = class _PaymentsModule {
11562
11590
  const transferProof = await waitInclusionProof5(trustBase, stClient, transferCommitment);
11563
11591
  const transferTransaction = transferCommitment.toTransaction(transferProof);
11564
11592
  const transferSalt = fromHex4(bundle.transferSaltHex);
11565
- const recipientPredicate = await UnmaskedPredicate5.create(
11593
+ const recipientPredicate = await UnmaskedPredicate4.create(
11566
11594
  mintData.tokenId,
11567
11595
  tokenType,
11568
11596
  signingService,
11569
11597
  HashAlgorithm5.SHA256,
11570
11598
  transferSalt
11571
11599
  );
11572
- const recipientState = new TokenState5(recipientPredicate, null);
11600
+ const recipientState = new TokenState4(recipientPredicate, null);
11573
11601
  let nametagTokens = [];
11574
11602
  const recipientAddressStr = bundle.recipientAddressJson;
11575
11603
  if (recipientAddressStr.startsWith("PROXY://")) {
@@ -12306,68 +12334,6 @@ var PaymentsModule = class _PaymentsModule {
12306
12334
  }
12307
12335
  }
12308
12336
  }
12309
- /**
12310
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
12311
- * This creates the nametag token required for receiving tokens via PROXY addresses
12312
- *
12313
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
12314
- * @returns MintNametagResult with success status and token if successful
12315
- */
12316
- async mintNametag(nametag) {
12317
- this.ensureInitialized();
12318
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12319
- if (!stClient) {
12320
- return {
12321
- success: false,
12322
- error: "State transition client not available. Oracle provider must implement getStateTransitionClient()"
12323
- };
12324
- }
12325
- const trustBase = this.deps.oracle.getTrustBase?.();
12326
- if (!trustBase) {
12327
- return {
12328
- success: false,
12329
- error: "Trust base not available. Oracle provider must implement getTrustBase()"
12330
- };
12331
- }
12332
- try {
12333
- const signingService = await this.createSigningService();
12334
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12335
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12336
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12337
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12338
- const addressRef = await UnmaskedPredicateReference4.create(
12339
- tokenType,
12340
- signingService.algorithm,
12341
- signingService.publicKey,
12342
- HashAlgorithm5.SHA256
12343
- );
12344
- const ownerAddress = await addressRef.toAddress();
12345
- const minter = new NametagMinter({
12346
- stateTransitionClient: stClient,
12347
- trustBase,
12348
- signingService,
12349
- debug: this.moduleConfig.debug
12350
- });
12351
- const result = await minter.mintNametag(nametag, ownerAddress);
12352
- if (result.success && result.nametagData) {
12353
- await this.setNametag(result.nametagData);
12354
- logger.debug("Payments", `Unicity ID minted and saved: ${result.nametagData.name}`);
12355
- this.deps.emitEvent("nametag:registered", {
12356
- nametag: result.nametagData.name,
12357
- addressIndex: 0
12358
- // Primary address
12359
- });
12360
- }
12361
- return result;
12362
- } catch (error) {
12363
- const errorMsg = error instanceof Error ? error.message : String(error);
12364
- logger.debug("Payments", "mintNametag failed:", errorMsg);
12365
- return {
12366
- success: false,
12367
- error: errorMsg
12368
- };
12369
- }
12370
- }
12371
12337
  /**
12372
12338
  * Mint a fungible token directly to this wallet (genesis mint).
12373
12339
  *
@@ -12408,18 +12374,18 @@ var PaymentsModule = class _PaymentsModule {
12408
12374
  }
12409
12375
  try {
12410
12376
  const signingService = await this.createSigningService();
12411
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12377
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
12412
12378
  const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
12413
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12379
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12414
12380
  const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
12415
12381
  const tokenType = new TokenType3(tokenTypeBytes);
12416
12382
  const tokenIdBytes = new Uint8Array(32);
12417
12383
  crypto.getRandomValues(tokenIdBytes);
12418
- const tokenId = new TokenId5(tokenIdBytes);
12384
+ const tokenId = new TokenId4(tokenIdBytes);
12419
12385
  const coinIdBytes = fromHex4(coinIdHex);
12420
12386
  const coinId = new CoinId4(coinIdBytes);
12421
12387
  const coinData = TokenCoinData3.create([[coinId, amount]]);
12422
- const addressRef = await UnmaskedPredicateReference4.create(
12388
+ const addressRef = await UnmaskedPredicateReference3.create(
12423
12389
  tokenType,
12424
12390
  signingService.algorithm,
12425
12391
  signingService.publicKey,
@@ -12428,7 +12394,7 @@ var PaymentsModule = class _PaymentsModule {
12428
12394
  const ownerAddress = await addressRef.toAddress();
12429
12395
  const salt = new Uint8Array(32);
12430
12396
  crypto.getRandomValues(salt);
12431
- const mintData = await MintTransactionData3.create(
12397
+ const mintData = await MintTransactionData2.create(
12432
12398
  tokenId,
12433
12399
  tokenType,
12434
12400
  null,
@@ -12443,7 +12409,7 @@ var PaymentsModule = class _PaymentsModule {
12443
12409
  null
12444
12410
  // reason: null (genesis, no burn predecessor)
12445
12411
  );
12446
- const commitment = await MintCommitment3.create(mintData);
12412
+ const commitment = await MintCommitment2.create(mintData);
12447
12413
  const MAX_RETRIES = 3;
12448
12414
  let lastStatus;
12449
12415
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
@@ -12460,14 +12426,14 @@ var PaymentsModule = class _PaymentsModule {
12460
12426
  }
12461
12427
  const inclusionProof = await waitInclusionProof5(trustBase, stClient, commitment);
12462
12428
  const genesisTransaction = commitment.toTransaction(inclusionProof);
12463
- const predicate = await UnmaskedPredicate5.create(
12429
+ const predicate = await UnmaskedPredicate4.create(
12464
12430
  tokenId,
12465
12431
  tokenType,
12466
12432
  signingService,
12467
12433
  HashAlgorithm5.SHA256,
12468
12434
  salt
12469
12435
  );
12470
- const tokenState = new TokenState5(predicate, null);
12436
+ const tokenState = new TokenState4(predicate, null);
12471
12437
  const sdkToken = await SdkToken2.mint(trustBase, tokenState, genesisTransaction);
12472
12438
  const tokenIdHex = tokenId.toJSON();
12473
12439
  const symbol = this.getCoinSymbol(coinIdHex);
@@ -12494,29 +12460,6 @@ var PaymentsModule = class _PaymentsModule {
12494
12460
  return { success: false, error: `Local mint failed: ${msg}` };
12495
12461
  }
12496
12462
  }
12497
- /**
12498
- * Check if a nametag is available for minting
12499
- * @param nametag - The nametag to check (e.g., "alice" or "@alice")
12500
- */
12501
- async isNametagAvailable(nametag) {
12502
- this.ensureInitialized();
12503
- const stClient = this.deps.oracle.getStateTransitionClient?.();
12504
- const trustBase = this.deps.oracle.getTrustBase?.();
12505
- if (!stClient || !trustBase) {
12506
- return false;
12507
- }
12508
- try {
12509
- const signingService = await this.createSigningService();
12510
- const minter = new NametagMinter({
12511
- stateTransitionClient: stClient,
12512
- trustBase,
12513
- signingService
12514
- });
12515
- return await minter.isNametagAvailable(nametag);
12516
- } catch {
12517
- return false;
12518
- }
12519
- }
12520
12463
  // ===========================================================================
12521
12464
  // Public API - Sync & Validate
12522
12465
  // ===========================================================================
@@ -12832,7 +12775,7 @@ var PaymentsModule = class _PaymentsModule {
12832
12775
  const privateKeyBytes = new Uint8Array(
12833
12776
  privateKeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
12834
12777
  );
12835
- return SigningService.createFromSecret(privateKeyBytes);
12778
+ return SigningService2.createFromSecret(privateKeyBytes);
12836
12779
  }
12837
12780
  /**
12838
12781
  * Get the wallet's signing public key (used for token ownership predicates).
@@ -12847,14 +12790,14 @@ var PaymentsModule = class _PaymentsModule {
12847
12790
  * Create DirectAddress from a public key using UnmaskedPredicateReference
12848
12791
  */
12849
12792
  async createDirectAddressFromPubkey(pubkeyHex) {
12850
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12851
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12852
- const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12853
- const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
12793
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
12794
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
12795
+ const UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
12796
+ const tokenType = new TokenType4(Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex"));
12854
12797
  const pubkeyBytes = new Uint8Array(
12855
12798
  pubkeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
12856
12799
  );
12857
- const addressRef = await UnmaskedPredicateReference4.create(
12800
+ const addressRef = await UnmaskedPredicateReference3.create(
12858
12801
  tokenType,
12859
12802
  "secp256k1",
12860
12803
  pubkeyBytes,
@@ -12866,10 +12809,9 @@ var PaymentsModule = class _PaymentsModule {
12866
12809
  * Resolve recipient to IAddress for L3 transfers.
12867
12810
  * Uses pre-resolved PeerInfo when available to avoid redundant network queries.
12868
12811
  */
12869
- async resolveRecipientAddress(recipient, addressMode = "auto", peerInfo) {
12812
+ async resolveRecipientAddress(recipient, _addressMode = "auto", peerInfo) {
12870
12813
  const { AddressFactory } = await import("@unicitylabs/state-transition-sdk/lib/address/AddressFactory");
12871
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
12872
- if (recipient.startsWith("PROXY:") || recipient.startsWith("DIRECT:")) {
12814
+ if (recipient.startsWith("DIRECT:")) {
12873
12815
  return AddressFactory.createAddress(recipient);
12874
12816
  }
12875
12817
  if (recipient.length === 66 && /^[0-9a-fA-F]+$/.test(recipient)) {
@@ -12879,28 +12821,19 @@ var PaymentsModule = class _PaymentsModule {
12879
12821
  const info = peerInfo ?? await this.deps?.transport.resolve?.(recipient) ?? null;
12880
12822
  if (!info) {
12881
12823
  throw new SphereError(
12882
- `Recipient "${recipient}" not found. Use @nametag, a valid PROXY:/DIRECT: address, or a 33-byte hex pubkey.`,
12824
+ `Recipient "${recipient}" not found. Use @nametag, a DIRECT: address, or a 33-byte hex pubkey.`,
12883
12825
  "INVALID_RECIPIENT"
12884
12826
  );
12885
12827
  }
12886
12828
  const nametag = recipient.startsWith("@") ? recipient.slice(1) : info.nametag || recipient;
12887
- if (addressMode === "proxy") {
12888
- logger.debug("Payments", `Using PROXY address for "${nametag}" (forced)`);
12889
- return ProxyAddress.fromNameTag(nametag);
12890
- }
12891
- if (addressMode === "direct") {
12892
- if (!info.directAddress) {
12893
- throw new SphereError(`"${nametag}" has no DirectAddress stored. It may be a legacy registration.`, "INVALID_RECIPIENT");
12894
- }
12895
- logger.debug("Payments", `Using DirectAddress for "${nametag}" (forced): ${info.directAddress.slice(0, 30)}...`);
12896
- return AddressFactory.createAddress(info.directAddress);
12897
- }
12898
- if (info.directAddress) {
12899
- logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
12900
- return AddressFactory.createAddress(info.directAddress);
12829
+ if (!info.directAddress) {
12830
+ throw new SphereError(
12831
+ `"${nametag}" has no DirectAddress \u2014 the recipient must publish a key-based identity binding.`,
12832
+ "INVALID_RECIPIENT"
12833
+ );
12901
12834
  }
12902
- logger.debug("Payments", `Using PROXY address for legacy nametag "${nametag}"`);
12903
- return ProxyAddress.fromNameTag(nametag);
12835
+ logger.debug("Payments", `Using DirectAddress for "${nametag}": ${info.directAddress.slice(0, 30)}...`);
12836
+ return AddressFactory.createAddress(info.directAddress);
12904
12837
  }
12905
12838
  /**
12906
12839
  * Handle NOSTR-FIRST commitment-only transfer (recipient side)
@@ -12955,14 +12888,14 @@ var PaymentsModule = class _PaymentsModule {
12955
12888
  const addressScheme = recipientAddress.scheme;
12956
12889
  const signingService = await this.createSigningService();
12957
12890
  const transferSalt = transferTx.data.salt;
12958
- const recipientPredicate = await UnmaskedPredicate5.create(
12891
+ const recipientPredicate = await UnmaskedPredicate4.create(
12959
12892
  sourceToken.id,
12960
12893
  sourceToken.type,
12961
12894
  signingService,
12962
12895
  HashAlgorithm5.SHA256,
12963
12896
  transferSalt
12964
12897
  );
12965
- const recipientState = new TokenState5(recipientPredicate, null);
12898
+ const recipientState = new TokenState4(recipientPredicate, null);
12966
12899
  let nametagTokens = [];
12967
12900
  if (addressScheme === AddressScheme.PROXY) {
12968
12901
  const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
@@ -13059,6 +12992,67 @@ var PaymentsModule = class _PaymentsModule {
13059
12992
  }
13060
12993
  }
13061
12994
  }
12995
+ /**
12996
+ * v2 engine transfer (sender-driven): the sender handed us a FINISHED token.
12997
+ * Decode the blob, dedup by the genesis-stable token id, store it as a
12998
+ * confirmed token, and emit/record the receipt. No commitment / inclusion-proof
12999
+ * / finalization round-trip (contrast the v1 sourceToken+transferTx path).
13000
+ */
13001
+ async handleV2Transfer(payload, senderPubkey) {
13002
+ this.ensureInitialized();
13003
+ if (!this.loaded && this.loadedPromise) {
13004
+ await this.loadedPromise;
13005
+ }
13006
+ const engine = this.deps.tokenEngine;
13007
+ if (!engine) return;
13008
+ let token;
13009
+ try {
13010
+ token = await engine.decodeToken(decodeTokenBlob(hexToBytes2(payload.tokenBlob)));
13011
+ } catch (err) {
13012
+ logger.error("Payments", "V2 transfer: failed to decode token blob:", err);
13013
+ return;
13014
+ }
13015
+ const id = `v2_${engine.tokenId(token)}`;
13016
+ if (this.tokens.has(id)) {
13017
+ logger.debug("Payments", `V2 transfer ${id.slice(0, 16)}... already present, skipping`);
13018
+ return;
13019
+ }
13020
+ const info = await parseTokenInfo(payload.tokenBlob, engine);
13021
+ const registry = TokenRegistry.getInstance();
13022
+ const uiToken = {
13023
+ id,
13024
+ coinId: info.coinId,
13025
+ symbol: registry.getSymbol(info.coinId) || info.symbol,
13026
+ name: registry.getName(info.coinId) || info.name,
13027
+ decimals: registry.getDecimals(info.coinId) ?? info.decimals,
13028
+ amount: info.amount,
13029
+ status: "confirmed",
13030
+ createdAt: Date.now(),
13031
+ updatedAt: Date.now(),
13032
+ sdkData: payload.tokenBlob
13033
+ };
13034
+ await this.addToken(uiToken);
13035
+ const senderInfo = await this.resolveSenderInfo(senderPubkey);
13036
+ this.deps.emitEvent("transfer:incoming", {
13037
+ id,
13038
+ senderPubkey,
13039
+ senderNametag: senderInfo.senderNametag,
13040
+ tokens: [uiToken],
13041
+ memo: payload.memo,
13042
+ receivedAt: Date.now()
13043
+ });
13044
+ await this.addToHistory({
13045
+ type: "RECEIVED",
13046
+ amount: info.amount,
13047
+ coinId: info.coinId,
13048
+ symbol: uiToken.symbol,
13049
+ timestamp: Date.now(),
13050
+ senderPubkey,
13051
+ ...senderInfo,
13052
+ memo: payload.memo,
13053
+ tokenId: id
13054
+ });
13055
+ }
13062
13056
  async handleIncomingTransfer(transfer) {
13063
13057
  if (!this.loaded && this.loadedPromise) {
13064
13058
  await this.loadedPromise;
@@ -13066,6 +13060,10 @@ var PaymentsModule = class _PaymentsModule {
13066
13060
  try {
13067
13061
  const payload = transfer.payload;
13068
13062
  logger.debug("Payments", "handleIncomingTransfer: keys=", Object.keys(payload).join(","));
13063
+ if (this.deps.tokenEngine && isV2TransferPayload(transfer.payload)) {
13064
+ await this.handleV2Transfer(transfer.payload, transfer.senderTransportPubkey);
13065
+ return;
13066
+ }
13069
13067
  let combinedBundle = null;
13070
13068
  if (isCombinedTransferBundleV6(payload)) {
13071
13069
  combinedBundle = payload;
@@ -13147,7 +13145,7 @@ var PaymentsModule = class _PaymentsModule {
13147
13145
  const hasTransactionData = transferTxInput.transactionData !== void 0;
13148
13146
  const hasAuthenticator = transferTxInput.authenticator !== void 0;
13149
13147
  if (hasData && hasInclusionProof) {
13150
- transferTx = await TransferTransaction2.fromJSON(transferTxInput);
13148
+ transferTx = await TransferTransaction3.fromJSON(transferTxInput);
13151
13149
  } else if (hasTransactionData && hasAuthenticator) {
13152
13150
  const commitment = await TransferCommitment4.fromJSON(transferTxInput);
13153
13151
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -13168,7 +13166,7 @@ var PaymentsModule = class _PaymentsModule {
13168
13166
  transferTx = commitment.toTransaction(inclusionProof);
13169
13167
  } else {
13170
13168
  try {
13171
- transferTx = await TransferTransaction2.fromJSON(transferTxInput);
13169
+ transferTx = await TransferTransaction3.fromJSON(transferTxInput);
13172
13170
  } catch {
13173
13171
  const commitment = await TransferCommitment4.fromJSON(transferTxInput);
13174
13172
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -13210,7 +13208,7 @@ var PaymentsModule = class _PaymentsModule {
13210
13208
  logger.warn("Payments", "Received invalid token");
13211
13209
  return;
13212
13210
  }
13213
- const tokenInfo = await parseTokenInfo(tokenData);
13211
+ const tokenInfo = await parseTokenInfo(tokenData, this.deps?.tokenEngine);
13214
13212
  const token = {
13215
13213
  id: tokenInfo.tokenId ?? crypto.randomUUID(),
13216
13214
  coinId: tokenInfo.coinId,
@@ -13516,24 +13514,6 @@ function createPaymentsModule(config) {
13516
13514
  return new PaymentsModule(config);
13517
13515
  }
13518
13516
 
13519
- // modules/payments/TokenSplitCalculator.ts
13520
- init_logger();
13521
- import { Token as SdkToken3 } from "@unicitylabs/state-transition-sdk/lib/token/Token";
13522
- import { CoinId as CoinId5 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
13523
-
13524
- // modules/payments/BackgroundCommitmentService.ts
13525
- init_logger();
13526
- init_errors();
13527
-
13528
- // modules/payments/TokenRecoveryService.ts
13529
- init_logger();
13530
- import { TokenId as TokenId4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenId";
13531
- import { TokenState as TokenState6 } from "@unicitylabs/state-transition-sdk/lib/token/TokenState";
13532
- import { TokenType as TokenType4 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
13533
- import { CoinId as CoinId6 } from "@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId";
13534
- import { HashAlgorithm as HashAlgorithm6 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
13535
- import { UnmaskedPredicate as UnmaskedPredicate6 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate";
13536
-
13537
13517
  // modules/communications/CommunicationsModule.ts
13538
13518
  init_logger();
13539
13519
  init_errors();
@@ -17175,7 +17155,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17175
17155
  const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : void 0;
17176
17156
  return abytes(bytes, sizer);
17177
17157
  }
17178
- class Signature {
17158
+ class Signature2 {
17179
17159
  r;
17180
17160
  s;
17181
17161
  recovery;
@@ -17195,7 +17175,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17195
17175
  let recid;
17196
17176
  if (format === "der") {
17197
17177
  const { r: r2, s: s2 } = DER.toSig(abytes(bytes));
17198
- return new Signature(r2, s2);
17178
+ return new Signature2(r2, s2);
17199
17179
  }
17200
17180
  if (format === "recovered") {
17201
17181
  recid = bytes[0];
@@ -17205,7 +17185,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17205
17185
  const L = lengths.signature / 2;
17206
17186
  const r = bytes.subarray(0, L);
17207
17187
  const s = bytes.subarray(L, L * 2);
17208
- return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);
17188
+ return new Signature2(Fn.fromBytes(r), Fn.fromBytes(s), recid);
17209
17189
  }
17210
17190
  static fromHex(hex, format) {
17211
17191
  return this.fromBytes(hexToBytes(hex), format);
@@ -17217,7 +17197,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17217
17197
  return recovery;
17218
17198
  }
17219
17199
  addRecoveryBit(recovery) {
17220
- return new Signature(this.r, this.s, recovery);
17200
+ return new Signature2(this.r, this.s, recovery);
17221
17201
  }
17222
17202
  recoverPublicKey(messageHash) {
17223
17203
  const { r, s } = this;
@@ -17309,7 +17289,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17309
17289
  normS = Fn.neg(s);
17310
17290
  recovery ^= 1;
17311
17291
  }
17312
- return new Signature(r, normS, hasLargeCofactor ? void 0 : recovery);
17292
+ return new Signature2(r, normS, hasLargeCofactor ? void 0 : recovery);
17313
17293
  }
17314
17294
  return { seed, k2sig };
17315
17295
  }
@@ -17324,12 +17304,12 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17324
17304
  publicKey = abytes(publicKey, void 0, "publicKey");
17325
17305
  message = validateMsgAndHash(message, prehash);
17326
17306
  if (!isBytes(signature)) {
17327
- const end = signature instanceof Signature ? ", use sig.toBytes()" : "";
17307
+ const end = signature instanceof Signature2 ? ", use sig.toBytes()" : "";
17328
17308
  throw new Error("verify expects Uint8Array signature" + end);
17329
17309
  }
17330
17310
  validateSigLength(signature, format);
17331
17311
  try {
17332
- const sig = Signature.fromBytes(signature, format);
17312
+ const sig = Signature2.fromBytes(signature, format);
17333
17313
  const P = Point.fromBytes(publicKey);
17334
17314
  if (lowS && sig.hasHighS())
17335
17315
  return false;
@@ -17350,7 +17330,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17350
17330
  function recoverPublicKey(signature, message, opts = {}) {
17351
17331
  const { prehash } = validateSigOpts(opts, defaultSigOpts);
17352
17332
  message = validateMsgAndHash(message, prehash);
17353
- return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17333
+ return Signature2.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
17354
17334
  }
17355
17335
  return Object.freeze({
17356
17336
  keygen,
@@ -17362,7 +17342,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
17362
17342
  sign,
17363
17343
  verify,
17364
17344
  recoverPublicKey,
17365
- Signature,
17345
+ Signature: Signature2,
17366
17346
  hash
17367
17347
  });
17368
17348
  }
@@ -18634,7 +18614,7 @@ function freezeCoinAsset(coinAsset, state, latestSender) {
18634
18614
  }
18635
18615
 
18636
18616
  // modules/accounting/AccountingModule.ts
18637
- import { Token as SdkToken4 } from "@unicitylabs/state-transition-sdk/lib/token/Token.js";
18617
+ import { Token as SdkToken3 } from "@unicitylabs/state-transition-sdk/lib/token/Token.js";
18638
18618
  var LOG_TAG2 = "Accounting";
18639
18619
  var INV_LEDGER_PREFIX = "inv_ledger:";
18640
18620
  var AccountingModule = class _AccountingModule {
@@ -19287,129 +19267,146 @@ var AccountingModule = class _AccountingModule {
19287
19267
  );
19288
19268
  }
19289
19269
  try {
19290
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19291
- const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19292
- const { MintTransactionData: MintTransactionData4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19293
- const { MintCommitment: MintCommitment4 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19294
- const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19295
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19296
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19297
- const { UnmaskedPredicate: UnmaskedPredicate7 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19298
- const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19299
- const { TokenState: TokenState7 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19300
- const { Token: SdkToken5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19301
- const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19302
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(invoiceBytesEncoded).digest();
19303
- const invoiceTokenId = new TokenId5(hash.imprint);
19304
- const invoiceId = invoiceTokenId.toJSON();
19305
- if (this.invoiceTermsCache.has(invoiceId)) {
19306
- throw new SphereError(
19307
- `Invoice already exists locally: ${invoiceId}`,
19308
- "INVOICE_ALREADY_EXISTS"
19270
+ let invoiceId;
19271
+ let sdkData;
19272
+ const engine = deps.tokenEngine;
19273
+ if (engine) {
19274
+ const invoiceToken = await engine.mintDataToken({
19275
+ recipientPubkey: hexToBytes(deps.identity.chainPubkey),
19276
+ data: invoiceBytesEncoded,
19277
+ tokenType: hexToBytes(INVOICE_TOKEN_TYPE_HEX),
19278
+ salt
19279
+ });
19280
+ invoiceId = engine.tokenId(invoiceToken);
19281
+ if (this.invoiceTermsCache.has(invoiceId)) {
19282
+ throw new SphereError(`Invoice already exists locally: ${invoiceId}`, "INVOICE_ALREADY_EXISTS");
19283
+ }
19284
+ sdkData = bytesToHex(encodeTokenBlob(engine.encodeToken(invoiceToken)));
19285
+ } else {
19286
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19287
+ const { TokenType: TokenType4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType.js");
19288
+ const { MintTransactionData: MintTransactionData3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData.js");
19289
+ const { MintCommitment: MintCommitment3 } = await import("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment.js");
19290
+ const { SigningService: SigningService3 } = await import("@unicitylabs/state-transition-sdk/lib/sign/SigningService.js");
19291
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19292
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19293
+ const { UnmaskedPredicate: UnmaskedPredicate5 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate.js");
19294
+ const { UnmaskedPredicateReference: UnmaskedPredicateReference3 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference.js");
19295
+ const { TokenState: TokenState5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenState.js");
19296
+ const { Token: SdkToken4 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token.js");
19297
+ const { waitInclusionProof: waitInclusionProof6 } = await import("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
19298
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(invoiceBytesEncoded).digest();
19299
+ const invoiceTokenId = new TokenId4(hash.imprint);
19300
+ invoiceId = invoiceTokenId.toJSON();
19301
+ if (this.invoiceTermsCache.has(invoiceId)) {
19302
+ throw new SphereError(
19303
+ `Invoice already exists locally: ${invoiceId}`,
19304
+ "INVOICE_ALREADY_EXISTS"
19305
+ );
19306
+ }
19307
+ const invoiceTokenType = new TokenType4(
19308
+ Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19309
19309
  );
19310
- }
19311
- const invoiceTokenType = new TokenType6(
19312
- Buffer.from(INVOICE_TOKEN_TYPE_HEX, "hex")
19313
- );
19314
- const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19315
- const addressRef = await UnmaskedPredicateReference4.create(
19316
- invoiceTokenType,
19317
- signingService.algorithm,
19318
- signingService.publicKey,
19319
- HashAlgorithm8.SHA256
19320
- );
19321
- const ownerAddress = await addressRef.toAddress();
19322
- const mintData = await MintTransactionData4.create(
19323
- invoiceTokenId,
19324
- invoiceTokenType,
19325
- invoiceBytesEncoded,
19326
- // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19327
- null,
19328
- // coinData: null (non-fungible invoice token)
19329
- ownerAddress,
19330
- salt,
19331
- null,
19332
- // recipientDataHash: null
19333
- null
19334
- // reason: null
19335
- );
19336
- if (this.config.debug) {
19337
- logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19338
- }
19339
- const commitment = await MintCommitment4.create(mintData);
19340
- if (this.config.debug) {
19341
- logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19342
- }
19343
- const MAX_RETRIES = 3;
19344
- let submitSuccess = false;
19345
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19346
- try {
19347
- if (this.config.debug) {
19348
- logger.debug(
19349
- LOG_TAG2,
19350
- `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19351
- );
19352
- }
19353
- const response = await stClient.submitMintCommitment(commitment);
19354
- if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19310
+ const signingService = await SigningService3.createFromSecret(signingKeyBytes);
19311
+ const addressRef = await UnmaskedPredicateReference3.create(
19312
+ invoiceTokenType,
19313
+ signingService.algorithm,
19314
+ signingService.publicKey,
19315
+ HashAlgorithm6.SHA256
19316
+ );
19317
+ const ownerAddress = await addressRef.toAddress();
19318
+ const mintData = await MintTransactionData3.create(
19319
+ invoiceTokenId,
19320
+ invoiceTokenType,
19321
+ invoiceBytesEncoded,
19322
+ // tokenData: serialized InvoiceTerms (UTF-8 JSON)
19323
+ null,
19324
+ // coinData: null (non-fungible invoice token)
19325
+ ownerAddress,
19326
+ salt,
19327
+ null,
19328
+ // recipientDataHash: null
19329
+ null
19330
+ // reason: null
19331
+ );
19332
+ if (this.config.debug) {
19333
+ logger.debug(LOG_TAG2, `Created MintTransactionData for invoice ${invoiceId}`);
19334
+ }
19335
+ const commitment = await MintCommitment3.create(mintData);
19336
+ if (this.config.debug) {
19337
+ logger.debug(LOG_TAG2, "Created MintCommitment for invoice");
19338
+ }
19339
+ const MAX_RETRIES = 3;
19340
+ let submitSuccess = false;
19341
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
19342
+ try {
19355
19343
  if (this.config.debug) {
19356
19344
  logger.debug(
19357
19345
  LOG_TAG2,
19358
- response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19346
+ `Submitting invoice commitment (attempt ${attempt}/${MAX_RETRIES})...`
19359
19347
  );
19360
19348
  }
19361
- submitSuccess = true;
19362
- break;
19363
- } else {
19364
- logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19349
+ const response = await stClient.submitMintCommitment(commitment);
19350
+ if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") {
19351
+ if (this.config.debug) {
19352
+ logger.debug(
19353
+ LOG_TAG2,
19354
+ response.status === "REQUEST_ID_EXISTS" ? "Invoice commitment already exists (idempotent re-mint)" : "Invoice commitment submitted successfully"
19355
+ );
19356
+ }
19357
+ submitSuccess = true;
19358
+ break;
19359
+ } else {
19360
+ logger.warn(LOG_TAG2, `Invoice commitment submission failed: ${response.status}`);
19361
+ if (attempt === MAX_RETRIES) {
19362
+ throw new SphereError(
19363
+ `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19364
+ "INVOICE_MINT_FAILED"
19365
+ );
19366
+ }
19367
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
19368
+ }
19369
+ } catch (retryErr) {
19370
+ 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;
19371
+ logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19365
19372
  if (attempt === MAX_RETRIES) {
19366
19373
  throw new SphereError(
19367
- `Failed to mint invoice token: commitment rejected after ${MAX_RETRIES} attempts: ${response.status}`,
19368
- "INVOICE_MINT_FAILED"
19374
+ `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19375
+ "INVOICE_MINT_FAILED",
19376
+ retryErr
19369
19377
  );
19370
19378
  }
19371
19379
  await new Promise((r) => setTimeout(r, 1e3 * attempt));
19372
19380
  }
19373
- } catch (retryErr) {
19374
- 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;
19375
- logger.warn(LOG_TAG2, `Invoice commitment attempt ${attempt} error:`, retryErr);
19376
- if (attempt === MAX_RETRIES) {
19377
- throw new SphereError(
19378
- `Failed to mint invoice token: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
19379
- "INVOICE_MINT_FAILED",
19380
- retryErr
19381
- );
19382
- }
19383
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
19384
19381
  }
19385
- }
19386
- if (!submitSuccess) {
19387
- throw new SphereError(
19388
- "Failed to mint invoice token: commitment submission failed after retries",
19389
- "INVOICE_MINT_FAILED"
19382
+ if (!submitSuccess) {
19383
+ throw new SphereError(
19384
+ "Failed to mint invoice token: commitment submission failed after retries",
19385
+ "INVOICE_MINT_FAILED"
19386
+ );
19387
+ }
19388
+ if (this.config.debug) {
19389
+ logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19390
+ }
19391
+ const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19392
+ if (this.config.debug) {
19393
+ logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19394
+ }
19395
+ const genesisTransaction = commitment.toTransaction(inclusionProof);
19396
+ const invoicePredicate = await UnmaskedPredicate5.create(
19397
+ invoiceTokenId,
19398
+ invoiceTokenType,
19399
+ signingService,
19400
+ HashAlgorithm6.SHA256,
19401
+ salt
19390
19402
  );
19403
+ const tokenState = new TokenState5(invoicePredicate, null);
19404
+ const sdkToken = await SdkToken4.mint(trustBase, tokenState, genesisTransaction);
19405
+ if (this.config.debug) {
19406
+ logger.debug(LOG_TAG2, "Invoice token minted successfully");
19407
+ }
19408
+ sdkData = JSON.stringify(sdkToken.toJSON());
19391
19409
  }
19392
- if (this.config.debug) {
19393
- logger.debug(LOG_TAG2, "Waiting for invoice inclusion proof...");
19394
- }
19395
- const inclusionProof = await waitInclusionProof6(trustBase, stClient, commitment);
19396
- if (this.config.debug) {
19397
- logger.debug(LOG_TAG2, "Invoice inclusion proof received");
19398
- }
19399
- const genesisTransaction = commitment.toTransaction(inclusionProof);
19400
- const invoicePredicate = await UnmaskedPredicate7.create(
19401
- invoiceTokenId,
19402
- invoiceTokenType,
19403
- signingService,
19404
- HashAlgorithm8.SHA256,
19405
- salt
19406
- );
19407
- const tokenState = new TokenState7(invoicePredicate, null);
19408
- const sdkToken = await SdkToken5.mint(trustBase, tokenState, genesisTransaction);
19409
- if (this.config.debug) {
19410
- logger.debug(LOG_TAG2, "Invoice token minted successfully");
19411
- }
19412
- const sdkTokenJson = sdkToken.toJSON();
19413
19410
  const uiToken = {
19414
19411
  id: invoiceId,
19415
19412
  coinId: INVOICE_TOKEN_TYPE_HEX,
@@ -19420,7 +19417,7 @@ var AccountingModule = class _AccountingModule {
19420
19417
  status: "confirmed",
19421
19418
  createdAt: terms.createdAt,
19422
19419
  updatedAt: terms.createdAt,
19423
- sdkData: JSON.stringify(sdkTokenJson)
19420
+ sdkData
19424
19421
  };
19425
19422
  await deps.payments.addToken(uiToken);
19426
19423
  this.invoiceTermsCache.set(invoiceId, this._normalizeInvoiceTerms(terms));
@@ -19431,17 +19428,7 @@ var AccountingModule = class _AccountingModule {
19431
19428
  const allTokens = deps.payments.getTokens();
19432
19429
  let anyScanDirty = false;
19433
19430
  for (const token of allTokens) {
19434
- if (!token.sdkData) continue;
19435
- let txf;
19436
- try {
19437
- txf = JSON.parse(token.sdkData);
19438
- } catch {
19439
- continue;
19440
- }
19441
- const txCount = txf.transactions?.length ?? 0;
19442
- if (txCount === 0) continue;
19443
- this._processTokenTransactions(token.id, txf, 0);
19444
- anyScanDirty = true;
19431
+ if (await this._scanTokenForAttribution(token, 0)) anyScanDirty = true;
19445
19432
  }
19446
19433
  const archivedTokensForScan = deps.payments.getArchivedTokens();
19447
19434
  for (const [archivedId, txf] of archivedTokensForScan) {
@@ -19457,7 +19444,7 @@ var AccountingModule = class _AccountingModule {
19457
19444
  if (this.config.debug) {
19458
19445
  logger.debug(LOG_TAG2, `Invoice created and stored: ${invoiceId}`);
19459
19446
  }
19460
- const txfToken = sdkTokenJson;
19447
+ const txfToken = engine ? sdkData : JSON.parse(sdkData);
19461
19448
  return {
19462
19449
  success: true,
19463
19450
  invoiceId,
@@ -19498,36 +19485,60 @@ var AccountingModule = class _AccountingModule {
19498
19485
  this.ensureNotDestroyed();
19499
19486
  this.ensureInitialized();
19500
19487
  const deps = this.deps;
19501
- const tokenType = token.genesis?.data?.tokenType;
19502
- if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19503
- throw new SphereError(
19504
- `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19505
- "INVOICE_WRONG_TOKEN_TYPE"
19506
- );
19507
- }
19508
- const tokenData = token.genesis?.data?.tokenData;
19509
- if (!tokenData || typeof tokenData !== "string") {
19510
- throw new SphereError(
19511
- "Invoice import failed: missing or invalid tokenData field.",
19512
- "INVOICE_INVALID_DATA"
19513
- );
19514
- }
19515
- let jsonString = tokenData;
19516
- if (!/^\s*[\[{"]/.test(tokenData)) {
19488
+ let terms;
19489
+ let tokenId;
19490
+ let sdkDataForStore;
19491
+ const engine = deps.tokenEngine;
19492
+ const isV2 = !!engine && typeof token === "string";
19493
+ if (isV2) {
19494
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token)));
19495
+ const verifyResult = await engine.verify(sphereToken);
19496
+ if (!verifyResult.ok) {
19497
+ throw new SphereError("Invoice import failed: inclusion proof is invalid.", "INVOICE_INVALID_PROOF");
19498
+ }
19499
+ tokenId = engine.tokenId(sphereToken);
19500
+ const data = engine.readTokenData(sphereToken);
19501
+ if (!data) {
19502
+ throw new SphereError("Invoice import failed: missing or invalid tokenData field.", "INVOICE_INVALID_DATA");
19503
+ }
19517
19504
  try {
19518
- const bytes = hexToBytes(tokenData);
19519
- jsonString = new TextDecoder().decode(bytes);
19505
+ terms = JSON.parse(new TextDecoder().decode(data));
19520
19506
  } catch {
19507
+ throw new SphereError("Invoice import failed: tokenData is not valid JSON.", "INVOICE_INVALID_DATA");
19521
19508
  }
19522
- }
19523
- let terms;
19524
- try {
19525
- terms = JSON.parse(jsonString);
19526
- } catch {
19527
- throw new SphereError(
19528
- "Invoice import failed: tokenData is not valid JSON.",
19529
- "INVOICE_INVALID_DATA"
19530
- );
19509
+ sdkDataForStore = token;
19510
+ } else {
19511
+ const tokenType = token.genesis?.data?.tokenType;
19512
+ if (tokenType !== INVOICE_TOKEN_TYPE_HEX) {
19513
+ throw new SphereError(
19514
+ `Invoice import failed: token type "${tokenType}" is not the expected invoice type.`,
19515
+ "INVOICE_WRONG_TOKEN_TYPE"
19516
+ );
19517
+ }
19518
+ const tokenData = token.genesis?.data?.tokenData;
19519
+ if (!tokenData || typeof tokenData !== "string") {
19520
+ throw new SphereError(
19521
+ "Invoice import failed: missing or invalid tokenData field.",
19522
+ "INVOICE_INVALID_DATA"
19523
+ );
19524
+ }
19525
+ let jsonString = tokenData;
19526
+ if (!/^\s*[[{"]/.test(tokenData)) {
19527
+ try {
19528
+ const bytes = hexToBytes(tokenData);
19529
+ jsonString = new TextDecoder().decode(bytes);
19530
+ } catch {
19531
+ }
19532
+ }
19533
+ try {
19534
+ terms = JSON.parse(jsonString);
19535
+ } catch {
19536
+ throw new SphereError(
19537
+ "Invoice import failed: tokenData is not valid JSON.",
19538
+ "INVOICE_INVALID_DATA"
19539
+ );
19540
+ }
19541
+ sdkDataForStore = JSON.stringify(token);
19531
19542
  }
19532
19543
  if (!terms || typeof terms !== "object") {
19533
19544
  throw new SphereError(
@@ -19614,7 +19625,7 @@ var AccountingModule = class _AccountingModule {
19614
19625
  }
19615
19626
  }
19616
19627
  }
19617
- const tokenId = token.genesis?.data?.tokenId;
19628
+ if (!isV2) tokenId = token.genesis?.data?.tokenId;
19618
19629
  if (!tokenId || typeof tokenId !== "string") {
19619
19630
  throw new SphereError(
19620
19631
  "Invoice import failed: missing tokenId in genesis data.",
@@ -19627,13 +19638,13 @@ var AccountingModule = class _AccountingModule {
19627
19638
  "INVOICE_ALREADY_EXISTS"
19628
19639
  );
19629
19640
  }
19630
- {
19631
- const { DataHasher } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19632
- const { HashAlgorithm: HashAlgorithm8 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19633
- const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19641
+ if (!isV2) {
19642
+ const { DataHasher: DataHasher2 } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHasher.js");
19643
+ const { HashAlgorithm: HashAlgorithm6 } = await import("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm.js");
19644
+ const { TokenId: TokenId4 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId.js");
19634
19645
  const reSerializedBytes = new TextEncoder().encode(canonicalSerialize(terms));
19635
- const hash = await new DataHasher(HashAlgorithm8.SHA256).update(reSerializedBytes).digest();
19636
- const reTokenId = new TokenId5(hash.imprint).toJSON();
19646
+ const hash = await new DataHasher2(HashAlgorithm6.SHA256).update(reSerializedBytes).digest();
19647
+ const reTokenId = new TokenId4(hash.imprint).toJSON();
19637
19648
  if (reTokenId !== tokenId) {
19638
19649
  throw new SphereError(
19639
19650
  "Invoice import failed: parsed terms do not match on-chain token ID (canonical hash mismatch).",
@@ -19641,35 +19652,37 @@ var AccountingModule = class _AccountingModule {
19641
19652
  );
19642
19653
  }
19643
19654
  }
19644
- if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19645
- throw new SphereError(
19646
- "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19647
- "INVOICE_INVALID_PROOF"
19648
- );
19649
- }
19650
- try {
19651
- const sdkToken = await SdkToken4.fromJSON(token);
19652
- const verifyResult = await sdkToken.verify(deps.trustBase);
19653
- const verifyOk = verifyResult.isSuccessful === true;
19654
- if (!verifyOk) {
19655
+ if (!isV2) {
19656
+ if (!deps.trustBase || deps.trustBase instanceof Uint8Array && deps.trustBase.length === 0) {
19655
19657
  throw new SphereError(
19656
- "Invoice import failed: inclusion proof is invalid.",
19658
+ "Trust base unavailable \u2014 cannot verify invoice proof. Ensure oracle supports getTrustBase().",
19657
19659
  "INVOICE_INVALID_PROOF"
19658
19660
  );
19659
19661
  }
19660
- const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19661
- if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19662
+ try {
19663
+ const sdkToken = await SdkToken3.fromJSON(token);
19664
+ const verifyResult = await sdkToken.verify(deps.trustBase);
19665
+ const verifyOk = verifyResult.isSuccessful === true;
19666
+ if (!verifyOk) {
19667
+ throw new SphereError(
19668
+ "Invoice import failed: inclusion proof is invalid.",
19669
+ "INVOICE_INVALID_PROOF"
19670
+ );
19671
+ }
19672
+ const canonicalTokenId = sdkToken.id?.toJSON?.() ?? null;
19673
+ if (!canonicalTokenId || canonicalTokenId !== tokenId) {
19674
+ throw new SphereError(
19675
+ `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19676
+ "INVOICE_INVALID_DATA"
19677
+ );
19678
+ }
19679
+ } catch (err) {
19680
+ if (err instanceof SphereError) throw err;
19662
19681
  throw new SphereError(
19663
- `Invoice import failed: tokenId mismatch or unverifiable \u2014 JSON claims ${tokenId}, cryptographic identity is ${canonicalTokenId ?? "unknown"}`,
19664
- "INVOICE_INVALID_DATA"
19682
+ `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19683
+ "INVOICE_INVALID_PROOF"
19665
19684
  );
19666
19685
  }
19667
- } catch (err) {
19668
- if (err instanceof SphereError) throw err;
19669
- throw new SphereError(
19670
- `Invoice import failed: proof verification error \u2014 ${err instanceof Error ? err.message : String(err)}`,
19671
- "INVOICE_INVALID_PROOF"
19672
- );
19673
19686
  }
19674
19687
  try {
19675
19688
  const uiToken = {
@@ -19682,7 +19695,7 @@ var AccountingModule = class _AccountingModule {
19682
19695
  status: "confirmed",
19683
19696
  createdAt: terms.createdAt,
19684
19697
  updatedAt: terms.createdAt,
19685
- sdkData: JSON.stringify(token)
19698
+ sdkData: sdkDataForStore
19686
19699
  };
19687
19700
  await deps.payments.addToken(uiToken);
19688
19701
  } catch (err) {
@@ -19737,17 +19750,8 @@ var AccountingModule = class _AccountingModule {
19737
19750
  const allTokens = deps.payments.getTokens();
19738
19751
  let anyDirty = false;
19739
19752
  for (const existingToken of allTokens) {
19740
- if (!existingToken.sdkData) continue;
19741
- let txf;
19742
- try {
19743
- txf = JSON.parse(existingToken.sdkData);
19744
- } catch {
19745
- continue;
19746
- }
19747
- const transactions = txf.transactions ?? [];
19748
19753
  const startIndex = this.tokenScanState.get(existingToken.id) ?? 0;
19749
- if (transactions.length > startIndex) {
19750
- this._processTokenTransactions(existingToken.id, txf, startIndex);
19754
+ if (await this._scanTokenForAttribution(existingToken, startIndex)) {
19751
19755
  anyDirty = true;
19752
19756
  }
19753
19757
  }
@@ -21637,17 +21641,8 @@ var AccountingModule = class _AccountingModule {
21637
21641
  const allTokens = deps.payments.getTokens();
21638
21642
  let anyDirty = false;
21639
21643
  for (const token of allTokens) {
21640
- if (!token.sdkData) continue;
21641
- let txf;
21642
- try {
21643
- txf = JSON.parse(token.sdkData);
21644
- } catch {
21645
- continue;
21646
- }
21647
- const transactions = txf.transactions ?? [];
21648
21644
  const startIndex = this.tokenScanState.get(token.id) ?? 0;
21649
- if (transactions.length > startIndex) {
21650
- this._processTokenTransactions(token.id, txf, startIndex);
21645
+ if (await this._scanTokenForAttribution(token, startIndex)) {
21651
21646
  anyDirty = true;
21652
21647
  }
21653
21648
  }
@@ -21702,6 +21697,53 @@ var AccountingModule = class _AccountingModule {
21702
21697
  * @param txf - Parsed TxfToken.
21703
21698
  * @param startIndex - First unprocessed transaction index.
21704
21699
  */
21700
+ /**
21701
+ * Attribute one payment token's invoice memo(s) to the ledger.
21702
+ *
21703
+ * v2 (engine blob): the token carries a single on-chain memo. We decode it and
21704
+ * shim the token into a v1-shaped `txf` (coinData ← engine.readValue, the memo
21705
+ * ← engine.readMemo) so the battle-hardened `_processTokenTransactions` runs
21706
+ * UNCHANGED — same dedup, direction, provisional/synthetic/orphan handling.
21707
+ * v1 (TXF JSON): parse and scan transactions directly.
21708
+ *
21709
+ * `startIndex` is the per-token watermark (0 for a full retroactive scan).
21710
+ * Returns true when the token was scanned. Async because engine.decodeToken is.
21711
+ */
21712
+ async _scanTokenForAttribution(token, startIndex) {
21713
+ if (!token.sdkData) return false;
21714
+ const engine = this.deps?.tokenEngine;
21715
+ const isBlob = token.sdkData.length >= 2 && token.sdkData.length % 2 === 0 && token.sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(token.sdkData);
21716
+ if (engine && isBlob) {
21717
+ let syntheticTxf;
21718
+ try {
21719
+ const sphereToken = await engine.decodeToken(decodeTokenBlob(hexToBytes(token.sdkData)));
21720
+ const memo = engine.readMemo(sphereToken);
21721
+ if (!memo) return false;
21722
+ const coinData = (engine.readValue(sphereToken)?.assets ?? []).map(
21723
+ (a) => [a.coinId, a.amount.toString()]
21724
+ );
21725
+ syntheticTxf = {
21726
+ genesis: { data: { coinData } },
21727
+ transactions: [{ data: { message: bytesToHex(memo) }, inclusionProof: {} }]
21728
+ };
21729
+ } catch {
21730
+ return false;
21731
+ }
21732
+ this._processTokenTransactions(token.id, syntheticTxf, startIndex);
21733
+ return true;
21734
+ }
21735
+ let txf;
21736
+ try {
21737
+ txf = JSON.parse(token.sdkData);
21738
+ } catch {
21739
+ return false;
21740
+ }
21741
+ if ((txf.transactions?.length ?? 0) > startIndex) {
21742
+ this._processTokenTransactions(token.id, txf, startIndex);
21743
+ return true;
21744
+ }
21745
+ return false;
21746
+ }
21705
21747
  _processTokenTransactions(tokenId, txf, startIndex) {
21706
21748
  const transactions = txf.transactions ?? [];
21707
21749
  let lastSuccessIdx = startIndex;
@@ -21981,17 +22023,7 @@ var AccountingModule = class _AccountingModule {
21981
22023
  async _handleIncomingTransfer(transfer) {
21982
22024
  if (this.destroyed) return;
21983
22025
  for (const token of transfer.tokens) {
21984
- if (!token.sdkData) continue;
21985
- let txf;
21986
- try {
21987
- txf = JSON.parse(token.sdkData);
21988
- } catch {
21989
- continue;
21990
- }
21991
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
21992
- if ((txf.transactions?.length ?? 0) > startIndex) {
21993
- this._processTokenTransactions(token.id, txf, startIndex);
21994
- }
22026
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
21995
22027
  }
21996
22028
  if (this.destroyed) return;
21997
22029
  await this._flushDirtyLedgerEntries();
@@ -22129,16 +22161,7 @@ var AccountingModule = class _AccountingModule {
22129
22161
  if (this.destroyed) return;
22130
22162
  for (const token of result.tokens) {
22131
22163
  if (!token.sdkData) continue;
22132
- let txf;
22133
- try {
22134
- txf = JSON.parse(token.sdkData);
22135
- } catch {
22136
- continue;
22137
- }
22138
- const startIndex = this.tokenScanState.get(token.id) ?? 0;
22139
- if ((txf.transactions?.length ?? 0) > startIndex) {
22140
- this._processTokenTransactions(token.id, txf, startIndex);
22141
- }
22164
+ await this._scanTokenForAttribution(token, this.tokenScanState.get(token.id) ?? 0);
22142
22165
  const relatedInvoices = this.tokenInvoiceMap.get(token.id);
22143
22166
  if (relatedInvoices) {
22144
22167
  for (const invoiceId of relatedInvoices) {
@@ -22209,15 +22232,8 @@ var AccountingModule = class _AccountingModule {
22209
22232
  const tokens = this.deps.payments.getTokens();
22210
22233
  const token = tokens.find((t) => t.id === tokenId);
22211
22234
  if (!token?.sdkData) return;
22212
- let txf;
22213
- try {
22214
- txf = JSON.parse(token.sdkData);
22215
- } catch {
22216
- return;
22217
- }
22218
22235
  const startIndex = this.tokenScanState.get(tokenId) ?? 0;
22219
- if ((txf.transactions?.length ?? 0) > startIndex) {
22220
- this._processTokenTransactions(tokenId, txf, startIndex);
22236
+ if (await this._scanTokenForAttribution(token, startIndex)) {
22221
22237
  if (this.destroyed) return;
22222
22238
  await this._flushDirtyLedgerEntries();
22223
22239
  }
@@ -27650,29 +27666,421 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
27650
27666
  };
27651
27667
  }
27652
27668
 
27669
+ // token-engine/identity.ts
27670
+ var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27671
+ var SIGNING_ALGORITHM = "secp256k1";
27672
+ var EMBEDDED_PREDICATE_UNMASKED = 0;
27673
+ var HASH_ALGORITHM_SHA256 = 0n;
27674
+ var SHA256_IMPRINT_PREFIX = new Uint8Array([0, 0]);
27675
+ function hexToBytes4(hex) {
27676
+ const bytes = new Uint8Array(hex.length / 2);
27677
+ for (let i = 0; i < bytes.length; i++) {
27678
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
27679
+ }
27680
+ return bytes;
27681
+ }
27682
+ function sha2565(data) {
27683
+ return new DataHasher(HashAlgorithm2.SHA256).update(data).digest().then((h) => h.data);
27684
+ }
27685
+ async function deriveDirectAddress(publicKey) {
27686
+ const tokenTypeCbor = CborSerializer.encodeByteString(hexToBytes4(UNICITY_TOKEN_TYPE_HEX));
27687
+ const reference = CborSerializer.encodeArray(
27688
+ CborSerializer.encodeByteString(new Uint8Array([EMBEDDED_PREDICATE_UNMASKED])),
27689
+ CborSerializer.encodeByteString(tokenTypeCbor),
27690
+ CborSerializer.encodeTextString(SIGNING_ALGORITHM),
27691
+ CborSerializer.encodeUnsignedInteger(HASH_ALGORITHM_SHA256),
27692
+ CborSerializer.encodeByteString(publicKey)
27693
+ );
27694
+ const refHash = await sha2565(reference);
27695
+ const imprint = new Uint8Array([...SHA256_IMPRINT_PREFIX, ...refHash]);
27696
+ const checksum = (await sha2565(imprint)).slice(0, 4);
27697
+ return `DIRECT://${HexConverter.encode(imprint)}${HexConverter.encode(checksum)}`;
27698
+ }
27699
+
27700
+ // token-engine/factory.ts
27701
+ init_errors();
27702
+
27703
+ // token-engine/SpherePaymentData.ts
27704
+ init_errors();
27705
+ var COIN_ID_PATTERN = /^([0-9a-f]{2})+$/;
27706
+ function assertAsset(coinId, amount) {
27707
+ if (!COIN_ID_PATTERN.test(coinId)) {
27708
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27709
+ }
27710
+ if (amount < 0n) {
27711
+ throw new SphereError(`Asset amount must be non-negative: ${amount.toString()}`, "VALIDATION_ERROR");
27712
+ }
27713
+ }
27714
+ function sphereAssetToSdk(coinId, amount) {
27715
+ assertAsset(coinId, amount);
27716
+ return new Asset(new AssetId(HexConverter.decode(coinId)), amount);
27717
+ }
27718
+ var SpherePaymentData = class _SpherePaymentData {
27719
+ constructor(assets, _memo = null) {
27720
+ this.assets = assets;
27721
+ this._memo = _memo;
27722
+ }
27723
+ /** Sphere-private CBOR tag (verified free in the v2 SDK tag space). */
27724
+ static CBOR_TAG = 39050n;
27725
+ /** Envelope version; bump when the structure changes. */
27726
+ static VERSION = 1n;
27727
+ /** Opaque, app-defined memo carried alongside the value (e.g. invoice attribution). */
27728
+ get memo() {
27729
+ return this._memo ? new Uint8Array(this._memo) : null;
27730
+ }
27731
+ /** Wrap an existing SDK asset collection (+ optional opaque memo). */
27732
+ static create(assets, memo = null) {
27733
+ return new _SpherePaymentData(assets, memo);
27734
+ }
27735
+ /** Build from a sphere-domain value (hex coin id → bigint amount) + optional opaque memo. */
27736
+ static fromValue(value, memo = null) {
27737
+ const assets = value.assets.map((a) => sphereAssetToSdk(a.coinId, a.amount));
27738
+ return new _SpherePaymentData(PaymentAssetCollection.create(...assets), memo);
27739
+ }
27740
+ /** Decode from the CBOR envelope produced by {@link encode}. */
27741
+ static fromCBOR(bytes) {
27742
+ const tag = CborDeserializer.decodeTag(bytes);
27743
+ if (tag.tag !== _SpherePaymentData.CBOR_TAG) {
27744
+ throw new CborError(`Invalid SpherePaymentData tag: ${tag.tag}`);
27745
+ }
27746
+ const fields = CborDeserializer.decodeArray(tag.data, 3);
27747
+ const version = CborDeserializer.decodeUnsignedInteger(fields[0]);
27748
+ if (version !== _SpherePaymentData.VERSION) {
27749
+ throw new CborError(`Unsupported SpherePaymentData version: ${version}`);
27750
+ }
27751
+ const memo = CborDeserializer.decodeNullable(fields[2], CborDeserializer.decodeByteString);
27752
+ return new _SpherePaymentData(PaymentAssetCollection.fromCBOR(fields[1]), memo);
27753
+ }
27754
+ /** Deterministic, versioned, tagged CBOR: `tag(39050)[ version, assets, memo? ]`. */
27755
+ encode() {
27756
+ return Promise.resolve(
27757
+ CborSerializer.encodeTag(
27758
+ _SpherePaymentData.CBOR_TAG,
27759
+ CborSerializer.encodeArray(
27760
+ CborSerializer.encodeUnsignedInteger(_SpherePaymentData.VERSION),
27761
+ this.assets.toCBOR(),
27762
+ CborSerializer.encodeNullable(this._memo, CborSerializer.encodeByteString)
27763
+ )
27764
+ )
27765
+ );
27766
+ }
27767
+ /** Project to a sphere-domain value (hex coin id + bigint amount), preserving order. */
27768
+ toValue() {
27769
+ return {
27770
+ assets: this.assets.toArray().map((a) => ({
27771
+ coinId: HexConverter.encode(a.id.bytes),
27772
+ amount: a.value
27773
+ }))
27774
+ };
27775
+ }
27776
+ /** Balance of a single coin within this payload (0n when absent). */
27777
+ balanceOf(coinId) {
27778
+ if (!COIN_ID_PATTERN.test(coinId)) {
27779
+ throw new SphereError(`Invalid coin id (expected even-length lowercase hex): "${coinId}"`, "VALIDATION_ERROR");
27780
+ }
27781
+ const asset = this.assets.get(new AssetId(HexConverter.decode(coinId)));
27782
+ return asset ? asset.value : 0n;
27783
+ }
27784
+ };
27785
+ function decodeSpherePaymentData(bytes) {
27786
+ return Promise.resolve(SpherePaymentData.fromCBOR(bytes));
27787
+ }
27788
+
27789
+ // token-engine/SphereTokenEngine.ts
27790
+ init_errors();
27791
+ var SphereTokenEngine = class {
27792
+ constructor(deps) {
27793
+ this.deps = deps;
27794
+ }
27795
+ // ── identity ────────────────────────────────────────────────────────────────
27796
+ getIdentity() {
27797
+ return { chainPubkey: new Uint8Array(this.deps.signingService.publicKey) };
27798
+ }
27799
+ /** Legacy DIRECT:// address (Path A). Async: the derivation hashes via the SDK. */
27800
+ deriveIdentityAddress(pubkey) {
27801
+ return deriveDirectAddress(pubkey ?? this.deps.signingService.publicKey);
27802
+ }
27803
+ // ── value (read) ─────────────────────────────────────────────────────────────
27804
+ readValue(token) {
27805
+ return token.value;
27806
+ }
27807
+ balanceOf(token, coinId) {
27808
+ let sum = 0n;
27809
+ for (const asset of token.value?.assets ?? []) {
27810
+ if (asset.coinId === coinId) sum += asset.amount;
27811
+ }
27812
+ return sum;
27813
+ }
27814
+ tokenId(token) {
27815
+ return token.blob.tokenId;
27816
+ }
27817
+ readMemo(token) {
27818
+ const sdkToken = token.sdkToken;
27819
+ if (sdkToken.transactions.length > 0) {
27820
+ return sdkToken.latestTransaction.data;
27821
+ }
27822
+ const data = sdkToken.genesis.data;
27823
+ if (data && this.isSpherePaymentData(data)) {
27824
+ return SpherePaymentData.fromCBOR(data).memo;
27825
+ }
27826
+ return null;
27827
+ }
27828
+ readTokenData(token) {
27829
+ const data = token.sdkToken.genesis.data;
27830
+ return data ? new Uint8Array(data) : null;
27831
+ }
27832
+ // ── lifecycle ────────────────────────────────────────────────────────────────
27833
+ async mint(params, options) {
27834
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27835
+ const data = params.value ? await SpherePaymentData.fromValue(params.value).encode() : null;
27836
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, data);
27837
+ const certificationData = await CertificationData.fromMintTransaction(mintTx);
27838
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27839
+ if (response.status !== CertificationStatus.SUCCESS) {
27840
+ throw new SphereError(`Mint certification failed: ${response.status}`, "AGGREGATOR_ERROR");
27841
+ }
27842
+ const proof = await waitInclusionProof2(
27843
+ this.deps.client,
27844
+ this.deps.trustBase,
27845
+ this.deps.predicateVerifier,
27846
+ mintTx,
27847
+ options?.signal
27848
+ );
27849
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27850
+ const token = await Token2.mint(
27851
+ this.deps.trustBase,
27852
+ this.deps.predicateVerifier,
27853
+ this.deps.mintJustificationVerifier,
27854
+ certified
27855
+ );
27856
+ return this.wrapToken(token);
27857
+ }
27858
+ async mintDataToken(params, options) {
27859
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27860
+ const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
27861
+ const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
27862
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, params.data, tokenType, salt);
27863
+ const certificationData = await CertificationData.fromMintTransaction(mintTx);
27864
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27865
+ if (response.status !== CertificationStatus.SUCCESS) {
27866
+ throw new SphereError(`Data-token mint failed: ${response.status}`, "AGGREGATOR_ERROR");
27867
+ }
27868
+ const proof = await waitInclusionProof2(
27869
+ this.deps.client,
27870
+ this.deps.trustBase,
27871
+ this.deps.predicateVerifier,
27872
+ mintTx,
27873
+ options?.signal
27874
+ );
27875
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27876
+ const token = await Token2.mint(
27877
+ this.deps.trustBase,
27878
+ this.deps.predicateVerifier,
27879
+ this.deps.mintJustificationVerifier,
27880
+ certified
27881
+ );
27882
+ return this.wrapToken(token);
27883
+ }
27884
+ async transfer(params, options) {
27885
+ this.assertOwned(params.token);
27886
+ const recipient = SignaturePredicate.create(params.recipientPubkey);
27887
+ const stateMask = crypto.getRandomValues(new Uint8Array(32));
27888
+ const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, params.data ?? null);
27889
+ const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
27890
+ const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
27891
+ const response = await this.deps.client.submitCertificationRequest(certificationData);
27892
+ if (response.status !== CertificationStatus.SUCCESS) {
27893
+ throw new SphereError(`Transfer certification failed: ${response.status}`, "TRANSFER_FAILED");
27894
+ }
27895
+ const proof = await waitInclusionProof2(
27896
+ this.deps.client,
27897
+ this.deps.trustBase,
27898
+ this.deps.predicateVerifier,
27899
+ transferTx,
27900
+ options?.signal
27901
+ );
27902
+ const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27903
+ const transferred = await params.token.sdkToken.transfer(this.deps.trustBase, this.deps.predicateVerifier, certified);
27904
+ return this.wrapToken(transferred);
27905
+ }
27906
+ async split(params, options) {
27907
+ this.assertOwned(params.token);
27908
+ if (params.outputs.length === 0) {
27909
+ throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
27910
+ }
27911
+ const requests = params.outputs.map(
27912
+ (o) => SplitTokenRequest.create(
27913
+ SignaturePredicate.create(o.recipientPubkey),
27914
+ PaymentAssetCollection.create(sphereAssetToSdk(o.coinId, o.amount))
27915
+ )
27916
+ );
27917
+ const split = await TokenSplit.split(params.token.sdkToken, decodeSpherePaymentData, requests);
27918
+ const burnUnlock = await SignaturePredicateUnlockScript.create(split.burn.transaction, this.deps.signingService);
27919
+ const burnCert = await CertificationData.fromTransaction(split.burn.transaction, burnUnlock);
27920
+ const burnResponse = await this.deps.client.submitCertificationRequest(burnCert);
27921
+ if (burnResponse.status !== CertificationStatus.SUCCESS) {
27922
+ throw new SphereError(`Split burn failed: ${burnResponse.status}`, "TRANSFER_FAILED");
27923
+ }
27924
+ const burnProof = await waitInclusionProof2(
27925
+ this.deps.client,
27926
+ this.deps.trustBase,
27927
+ this.deps.predicateVerifier,
27928
+ split.burn.transaction,
27929
+ options?.signal
27930
+ );
27931
+ const burnCertified = await split.burn.transaction.toCertifiedTransaction(
27932
+ this.deps.trustBase,
27933
+ this.deps.predicateVerifier,
27934
+ burnProof
27935
+ );
27936
+ const burntToken = await params.token.sdkToken.transfer(
27937
+ this.deps.trustBase,
27938
+ this.deps.predicateVerifier,
27939
+ burnCertified
27940
+ );
27941
+ const outputs = [];
27942
+ for (let i = 0; i < split.tokens.length; i++) {
27943
+ const splitToken = split.tokens[i];
27944
+ const data = await SpherePaymentData.create(splitToken.assets, params.outputs[i].data ?? null).encode();
27945
+ const justification = SplitMintJustification.create(burntToken, splitToken.proofs).toCBOR();
27946
+ const mintTx = await MintTransaction.create(
27947
+ splitToken.networkId,
27948
+ splitToken.recipient,
27949
+ data,
27950
+ splitToken.tokenType,
27951
+ splitToken.salt,
27952
+ justification
27953
+ );
27954
+ const certData = await CertificationData.fromMintTransaction(mintTx);
27955
+ const response = await this.deps.client.submitCertificationRequest(certData);
27956
+ if (response.status !== CertificationStatus.SUCCESS) {
27957
+ throw new SphereError(`Split mint failed: ${response.status}`, "AGGREGATOR_ERROR");
27958
+ }
27959
+ const proof = await waitInclusionProof2(
27960
+ this.deps.client,
27961
+ this.deps.trustBase,
27962
+ this.deps.predicateVerifier,
27963
+ mintTx,
27964
+ options?.signal
27965
+ );
27966
+ const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, proof);
27967
+ const token = await Token2.mint(
27968
+ this.deps.trustBase,
27969
+ this.deps.predicateVerifier,
27970
+ this.deps.mintJustificationVerifier,
27971
+ certified
27972
+ );
27973
+ outputs.push(this.wrapToken(token));
27974
+ }
27975
+ return { outputs };
27976
+ }
27977
+ // ── verification ─────────────────────────────────────────────────────────────
27978
+ async verify(token, _options) {
27979
+ const result = await token.sdkToken.verify(
27980
+ this.deps.trustBase,
27981
+ this.deps.predicateVerifier,
27982
+ this.deps.mintJustificationVerifier
27983
+ );
27984
+ return result.status === VerificationStatus.OK ? { ok: true } : { ok: false, reason: String(result.status) };
27985
+ }
27986
+ async isSpent(token, _options) {
27987
+ const probe = await TransferTransaction.create(
27988
+ token.sdkToken,
27989
+ SignaturePredicate.create(this.deps.signingService.publicKey),
27990
+ new Uint8Array(32)
27991
+ );
27992
+ const stateId = await StateId.fromTransaction(probe);
27993
+ const response = await this.deps.client.getInclusionProof(stateId);
27994
+ return response.inclusionProof.inclusionCertificate !== null;
27995
+ }
27996
+ // ── serialization ────────────────────────────────────────────────────────────
27997
+ encodeToken(token) {
27998
+ return token.blob;
27999
+ }
28000
+ async decodeToken(blob) {
28001
+ const sdkToken = await Token2.fromCBOR(blob.token);
28002
+ if (sdkToken.genesis.networkId.id !== this.deps.networkId.id) {
28003
+ throw new SphereError(
28004
+ `Token network mismatch: token is on network ${sdkToken.genesis.networkId.id}, engine on ${this.deps.networkId.id}`,
28005
+ "VALIDATION_ERROR"
28006
+ );
28007
+ }
28008
+ return this.wrapToken(sdkToken);
28009
+ }
28010
+ // ── internals ────────────────────────────────────────────────────────────────
28011
+ /** Fail fast if this engine's key does not own the token's current state. */
28012
+ assertOwned(token) {
28013
+ const owner = token.sdkToken.latestTransaction.recipient;
28014
+ const mine = EncodedPredicate.fromPredicate(SignaturePredicate.create(this.deps.signingService.publicKey));
28015
+ if (!EncodedPredicate.equals(owner, mine)) {
28016
+ throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
28017
+ }
28018
+ }
28019
+ /** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
28020
+ wrapToken(sdkToken) {
28021
+ const data = sdkToken.genesis.data;
28022
+ let value = null;
28023
+ if (data && this.isSpherePaymentData(data)) {
28024
+ try {
28025
+ value = SpherePaymentData.fromCBOR(data).toValue();
28026
+ } catch (err) {
28027
+ throw new SphereError(
28028
+ `Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
28029
+ "VALIDATION_ERROR"
28030
+ );
28031
+ }
28032
+ }
28033
+ const blob = {
28034
+ v: TOKEN_BLOB_VERSION,
28035
+ network: sdkToken.genesis.networkId.id,
28036
+ tokenId: HexConverter.encode(sdkToken.id.bytes),
28037
+ token: sdkToken.toCBOR()
28038
+ };
28039
+ return { sdkToken, blob, value };
28040
+ }
28041
+ /** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
28042
+ isSpherePaymentData(data) {
28043
+ try {
28044
+ return CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
28045
+ } catch {
28046
+ return false;
28047
+ }
28048
+ }
28049
+ };
28050
+
28051
+ // token-engine/factory.ts
28052
+ async function createSphereTokenEngine(config) {
28053
+ if (config.trustBaseJson == null) {
28054
+ throw new SphereError("Engine config requires a trust base (trustBaseJson)", "INVALID_CONFIG");
28055
+ }
28056
+ const trustBase = RootTrustBase.fromJSON(config.trustBaseJson);
28057
+ const predicateVerifier = PredicateVerifierService.create();
28058
+ const mintJustificationVerifier = new MintJustificationVerifierService();
28059
+ mintJustificationVerifier.register(
28060
+ new SplitMintJustificationVerifier(trustBase, predicateVerifier, decodeSpherePaymentData)
28061
+ );
28062
+ const deps = {
28063
+ client: new StateTransitionClient(new AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
28064
+ trustBase,
28065
+ predicateVerifier,
28066
+ mintJustificationVerifier,
28067
+ signingService: new SigningService(config.privateKey),
28068
+ // The trust base is the single source of truth for the network id (it carries
28069
+ // NetworkId.fromId, so any id works — e.g. testnet2 = 4 — with no enum entry).
28070
+ networkId: trustBase.networkId
28071
+ };
28072
+ return new SphereTokenEngine(deps);
28073
+ }
28074
+
27653
28075
  // core/Sphere.ts
27654
- import { SigningService as SigningService2 } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
27655
- import { TokenType as TokenType5 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
27656
- import { HashAlgorithm as HashAlgorithm7 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
27657
- import { UnmaskedPredicateReference as UnmaskedPredicateReference3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference";
27658
- import { normalizeNametag as normalizeNametag2, isPhoneNumber } from "@unicitylabs/nostr-js-sdk";
28076
+ import { normalizeNametag, isPhoneNumber } from "@unicitylabs/nostr-js-sdk";
27659
28077
  function isValidNametag2(nametag) {
27660
28078
  if (isPhoneNumber(nametag)) return true;
27661
28079
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
27662
28080
  }
27663
- var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27664
28081
  async function deriveL3PredicateAddress(privateKey) {
27665
- const secret = Buffer.from(privateKey, "hex");
27666
- const signingService = await SigningService2.createFromSecret(secret);
27667
- const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
27668
- const tokenType = new TokenType5(tokenTypeBytes);
27669
- const predicateRef = UnmaskedPredicateReference3.create(
27670
- tokenType,
27671
- signingService.algorithm,
27672
- signingService.publicKey,
27673
- HashAlgorithm7.SHA256
27674
- );
27675
- return (await (await predicateRef).toAddress()).toString();
28082
+ const prehashedPublicKey = getPublicKey(sha2562(privateKey, "hex"));
28083
+ return deriveDirectAddress(hexToBytes2(prehashedPublicKey));
27676
28084
  }
27677
28085
  var Sphere = class _Sphere {
27678
28086
  // Singleton
@@ -27694,14 +28102,14 @@ var Sphere = class _Sphere {
27694
28102
  _addressIdToIndex = /* @__PURE__ */ new Map();
27695
28103
  /** Nametag cache: addressId -> (nametagIndex -> nametag). Separate from tracked addresses. */
27696
28104
  _addressNametags = /* @__PURE__ */ new Map();
27697
- /** Cached PROXY address (computed once when nametag is set) */
27698
- _cachedProxyAddress = void 0;
27699
28105
  // Providers
27700
28106
  _storage;
27701
28107
  _tokenStorageProviders = /* @__PURE__ */ new Map();
27702
28108
  _transport;
27703
28109
  _oracle;
27704
28110
  _priceProvider;
28111
+ /** v2 token engine (built per active address from the oracle); injected into modules. */
28112
+ _tokenEngine;
27705
28113
  // Modules (single-instance — backward compat, delegates to active address)
27706
28114
  _payments;
27707
28115
  _communications;
@@ -28042,20 +28450,6 @@ var Sphere = class _Sphere {
28042
28450
  await sphere.syncIdentityWithTransport();
28043
28451
  sphere._initialized = true;
28044
28452
  _Sphere.instance = sphere;
28045
- if (sphere._identity?.nametag && !sphere._payments.hasNametag()) {
28046
- progress?.({ step: "registering_nametag", message: "Restoring nametag token..." });
28047
- logger.debug("Sphere", `Unicity ID @${sphere._identity.nametag} has no token, attempting to mint...`);
28048
- try {
28049
- const result = await sphere.mintNametag(sphere._identity.nametag);
28050
- if (result.success) {
28051
- logger.debug("Sphere", `Nametag token minted successfully on load`);
28052
- } else {
28053
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
28054
- }
28055
- } catch (err) {
28056
- logger.warn("Sphere", `Nametag token mint failed:`, err);
28057
- }
28058
- }
28059
28453
  if (options.discoverAddresses !== false && sphere._transport.discoverAddresses && sphere._masterKey) {
28060
28454
  progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
28061
28455
  try {
@@ -29107,13 +29501,13 @@ var Sphere = class _Sphere {
29107
29501
  oracle: this._oracle,
29108
29502
  emitEvent: this.emitEvent.bind(this),
29109
29503
  chainCode: this._masterKey?.chainCode || void 0,
29110
- price: this._priceProvider ?? void 0
29504
+ price: this._priceProvider ?? void 0,
29505
+ tokenEngine: moduleSet.tokenEngine
29111
29506
  });
29112
29507
  }
29113
29508
  }
29114
29509
  this._identity = newIdentity;
29115
29510
  this._currentAddressIndex = index;
29116
- await this._updateCachedProxyAddress();
29117
29511
  const activeModules = this._addressModules.get(index);
29118
29512
  this._payments = activeModules.payments;
29119
29513
  this._communications = activeModules.communications;
@@ -29151,35 +29545,10 @@ var Sphere = class _Sphere {
29151
29545
  }
29152
29546
  if (newNametag) {
29153
29547
  await this.persistAddressNametags();
29154
- if (!this._payments.hasNametag()) {
29155
- logger.debug("Sphere", `Minting nametag token for @${newNametag}...`);
29156
- try {
29157
- const result = await this.mintNametag(newNametag);
29158
- if (result.success) {
29159
- logger.debug("Sphere", `Nametag token minted successfully`);
29160
- } else {
29161
- logger.warn("Sphere", `Could not mint nametag token: ${result.error}`);
29162
- }
29163
- } catch (err) {
29164
- logger.warn("Sphere", `Nametag token mint failed:`, err);
29165
- }
29166
- }
29167
29548
  this.emitEvent("nametag:registered", {
29168
29549
  nametag: newNametag,
29169
29550
  addressIndex: index
29170
29551
  });
29171
- } else if (this._identity?.nametag && !this._payments.hasNametag()) {
29172
- logger.debug("Sphere", `Unicity ID @${this._identity.nametag} has no token after switch, minting...`);
29173
- try {
29174
- const result = await this.mintNametag(this._identity.nametag);
29175
- if (result.success) {
29176
- logger.debug("Sphere", `Nametag token minted successfully after switch`);
29177
- } else {
29178
- logger.warn("Sphere", `Could not mint nametag token after switch: ${result.error}`);
29179
- }
29180
- } catch (err) {
29181
- logger.warn("Sphere", `Nametag token mint failed after switch:`, err);
29182
- }
29183
29552
  }
29184
29553
  }
29185
29554
  /**
@@ -29209,6 +29578,7 @@ var Sphere = class _Sphere {
29209
29578
  const communications = createCommunicationsModule(this._communicationsConfig);
29210
29579
  const groupChat = this._groupChatConfig ? createGroupChatModule(this._groupChatConfig) : null;
29211
29580
  const market = this._marketConfig ? createMarketModule(this._marketConfig) : null;
29581
+ const tokenEngine = await this.buildTokenEngine(identity);
29212
29582
  payments.initialize({
29213
29583
  identity,
29214
29584
  storage: this._storage,
@@ -29217,7 +29587,8 @@ var Sphere = class _Sphere {
29217
29587
  oracle: this._oracle,
29218
29588
  emitEvent,
29219
29589
  chainCode: this._masterKey?.chainCode || void 0,
29220
- price: this._priceProvider ?? void 0
29590
+ price: this._priceProvider ?? void 0,
29591
+ tokenEngine
29221
29592
  });
29222
29593
  communications.initialize({
29223
29594
  identity,
@@ -29253,7 +29624,8 @@ var Sphere = class _Sphere {
29253
29624
  emitEvent,
29254
29625
  on: this.on.bind(this),
29255
29626
  storage: this._storage,
29256
- communications
29627
+ communications,
29628
+ tokenEngine
29257
29629
  });
29258
29630
  } else {
29259
29631
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -29313,6 +29685,7 @@ var Sphere = class _Sphere {
29313
29685
  market,
29314
29686
  transportAdapter: adapter,
29315
29687
  tokenStorageProviders: new Map(tokenStorageProviders),
29688
+ tokenEngine,
29316
29689
  initialized: true
29317
29690
  };
29318
29691
  this._addressModules.set(index, moduleSet);
@@ -29868,15 +30241,6 @@ var Sphere = class _Sphere {
29868
30241
  hasNametag() {
29869
30242
  return !!this._identity?.nametag;
29870
30243
  }
29871
- /**
29872
- * Get the PROXY address for the current nametag
29873
- * PROXY addresses are derived from the nametag hash and require
29874
- * the nametag token to claim funds sent to them
29875
- * @returns PROXY address string or undefined if no nametag
29876
- */
29877
- getProxyAddress() {
29878
- return this._cachedProxyAddress;
29879
- }
29880
30244
  /**
29881
30245
  * Resolve any identifier to full peer information.
29882
30246
  * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, or transport pubkey.
@@ -29911,17 +30275,6 @@ var Sphere = class _Sphere {
29911
30275
  throw new SphereError(`Cannot resolve address: ${address.slice(0, 30)}`, "INVALID_RECIPIENT");
29912
30276
  }
29913
30277
  }
29914
- /** Compute and cache the PROXY address from the current nametag */
29915
- async _updateCachedProxyAddress() {
29916
- const nametag = this._identity?.nametag;
29917
- if (!nametag) {
29918
- this._cachedProxyAddress = void 0;
29919
- return;
29920
- }
29921
- const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
29922
- const proxyAddr = await ProxyAddress.fromNameTag(nametag);
29923
- this._cachedProxyAddress = proxyAddr.toString();
29924
- }
29925
30278
  /**
29926
30279
  * Register a nametag for the current active address
29927
30280
  * Each address can have its own independent nametag
@@ -29949,17 +30302,6 @@ var Sphere = class _Sphere {
29949
30302
  if (this._identity?.nametag) {
29950
30303
  throw new SphereError(`Unicity ID already registered for address ${this._currentAddressIndex}: @${this._identity.nametag}`, "ALREADY_INITIALIZED");
29951
30304
  }
29952
- if (!this._payments.hasNametag()) {
29953
- logger.debug("Sphere", `Minting nametag token for @${cleanNametag}...`);
29954
- const result = await this.mintNametag(cleanNametag);
29955
- if (!result.success) {
29956
- throw new SphereError(
29957
- `Failed to mint nametag token: ${result.error}`,
29958
- "AGGREGATOR_ERROR"
29959
- );
29960
- }
29961
- logger.debug("Sphere", `Nametag token minted successfully`);
29962
- }
29963
30305
  if (this._transport.publishIdentityBinding) {
29964
30306
  const success = await this._transport.publishIdentityBinding(
29965
30307
  this._identity.chainPubkey,
@@ -29972,7 +30314,6 @@ var Sphere = class _Sphere {
29972
30314
  }
29973
30315
  }
29974
30316
  this._identity.nametag = cleanNametag;
29975
- await this._updateCachedProxyAddress();
29976
30317
  const currentAddressId = this._trackedAddresses.get(this._currentAddressIndex)?.addressId;
29977
30318
  if (currentAddressId) {
29978
30319
  let nametags = this._addressNametags.get(currentAddressId);
@@ -30005,35 +30346,19 @@ var Sphere = class _Sphere {
30005
30346
  await this._storage.saveTrackedAddresses(entries);
30006
30347
  }
30007
30348
  /**
30008
- * Mint a nametag token on-chain (like Sphere wallet and lottery)
30009
- * This creates the nametag token required for receiving tokens via PROXY addresses (@nametag)
30349
+ * Check whether a nametag is available to register.
30010
30350
  *
30011
- * @param nametag - The nametag to mint (e.g., "alice" or "@alice")
30012
- * @returns MintNametagResult with success status and token if successful
30351
+ * D5: nametags are Nostr bindings (name chainPubkey), not on-chain tokens. Availability is
30352
+ * first-seen-wins a name is available iff no binding resolves for it.
30013
30353
  *
30014
- * @example
30015
- * ```typescript
30016
- * // Mint nametag token for receiving via @alice
30017
- * const result = await sphere.mintNametag('alice');
30018
- * if (result.success) {
30019
- * console.log('Nametag minted:', result.nametagData?.name);
30020
- * } else {
30021
- * console.error('Mint failed:', result.error);
30022
- * }
30023
- * ```
30024
- */
30025
- async mintNametag(nametag) {
30026
- this.ensureReady();
30027
- return this._payments.mintNametag(nametag);
30028
- }
30029
- /**
30030
- * Check if a nametag is available for minting
30031
30354
  * @param nametag - The nametag to check (e.g., "alice" or "@alice")
30032
- * @returns true if available, false if taken or error
30355
+ * @returns true if available, false if already taken
30033
30356
  */
30034
30357
  async isNametagAvailable(nametag) {
30035
30358
  this.ensureReady();
30036
- return this._payments.isNametagAvailable(nametag);
30359
+ if (!this._transport.resolveNametag) return true;
30360
+ const bound = await this._transport.resolveNametag(this.cleanNametag(nametag));
30361
+ return bound == null;
30037
30362
  }
30038
30363
  /**
30039
30364
  * Load tracked addresses from storage.
@@ -30208,7 +30533,6 @@ var Sphere = class _Sphere {
30208
30533
  }
30209
30534
  if (recoveredNametag && !this._identity?.nametag) {
30210
30535
  this._identity.nametag = recoveredNametag;
30211
- await this._updateCachedProxyAddress();
30212
30536
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
30213
30537
  let nametags = this._addressNametags.get(entry.addressId);
30214
30538
  if (!nametags) {
@@ -30297,7 +30621,6 @@ var Sphere = class _Sphere {
30297
30621
  try {
30298
30622
  if (this._identity) {
30299
30623
  this._identity.nametag = recoveredNametag;
30300
- await this._updateCachedProxyAddress();
30301
30624
  }
30302
30625
  const entry = await this.ensureAddressTracked(this._currentAddressIndex);
30303
30626
  let nametags = this._addressNametags.get(entry.addressId);
@@ -30317,7 +30640,7 @@ var Sphere = class _Sphere {
30317
30640
  */
30318
30641
  cleanNametag(raw) {
30319
30642
  const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
30320
- return normalizeNametag2(stripped);
30643
+ return normalizeNametag(stripped);
30321
30644
  }
30322
30645
  // ===========================================================================
30323
30646
  // Public Methods - Lifecycle
@@ -30497,7 +30820,6 @@ var Sphere = class _Sphere {
30497
30820
  } else if (this._identity && nametag) {
30498
30821
  this._identity.nametag = nametag;
30499
30822
  }
30500
- await this._updateCachedProxyAddress();
30501
30823
  }
30502
30824
  async initializeIdentityFromMnemonic(mnemonic, derivationPath) {
30503
30825
  const basePath = derivationPath ?? DEFAULT_BASE_PATH;
@@ -30648,10 +30970,45 @@ var Sphere = class _Sphere {
30648
30970
  this._providerEventCleanups = [];
30649
30971
  this._lastProviderConnected.clear();
30650
30972
  }
30973
+ /**
30974
+ * Construct the v2 token engine for a given address identity (defaults to the
30975
+ * active one) from the oracle's gateway URL + trust base and that address's
30976
+ * signing key. The engine is per-address — each address signs with its own key.
30977
+ * The trust base is the single source of truth for the network id (so any id
30978
+ * works — e.g. testnet2 = 4 — with no enum entry). Returns undefined (modules
30979
+ * keep their legacy path) when the oracle can't supply a trust base / url, or
30980
+ * construction fails — a misconfigured oracle never breaks initialization.
30981
+ */
30982
+ async buildTokenEngine(identity) {
30983
+ const oracle = this._oracle;
30984
+ const privateKey = (identity ?? this._identity)?.privateKey;
30985
+ const trustBaseJson = oracle.getTrustBaseJson?.() ?? null;
30986
+ const aggregatorUrl = oracle.getAggregatorUrl?.();
30987
+ if (!trustBaseJson || !aggregatorUrl || !privateKey) {
30988
+ logger.warn("Sphere", "v2 token engine not constructed (oracle has no trust base / url, or no identity) \u2014 legacy path");
30989
+ return void 0;
30990
+ }
30991
+ try {
30992
+ return await createSphereTokenEngine({
30993
+ aggregatorUrl,
30994
+ apiKey: oracle.getApiKey?.(),
30995
+ privateKey: hexToBytes2(privateKey),
30996
+ trustBaseJson
30997
+ });
30998
+ } catch (err) {
30999
+ logger.warn(
31000
+ "Sphere",
31001
+ `Failed to construct v2 token engine \u2014 modules use the legacy path: ${err instanceof Error ? err.message : String(err)}`
31002
+ );
31003
+ return void 0;
31004
+ }
31005
+ }
30651
31006
  async initializeModules() {
30652
31007
  const emitEvent = this.emitEvent.bind(this);
30653
31008
  const adapter = await this.ensureTransportMux(this._currentAddressIndex, this._identity);
30654
31009
  const moduleTransport = adapter ?? this._transport;
31010
+ this._tokenEngine = await this.buildTokenEngine();
31011
+ const tokenEngine = this._tokenEngine;
30655
31012
  this._payments.initialize({
30656
31013
  identity: this._identity,
30657
31014
  storage: this._storage,
@@ -30662,7 +31019,8 @@ var Sphere = class _Sphere {
30662
31019
  // Pass chain code for L1 HD derivation
30663
31020
  chainCode: this._masterKey?.chainCode || void 0,
30664
31021
  price: this._priceProvider ?? void 0,
30665
- disabledProviderIds: this._disabledProviders
31022
+ disabledProviderIds: this._disabledProviders,
31023
+ tokenEngine
30666
31024
  });
30667
31025
  this._communications.initialize({
30668
31026
  identity: this._identity,
@@ -30698,7 +31056,8 @@ var Sphere = class _Sphere {
30698
31056
  emitEvent,
30699
31057
  on: this.on.bind(this),
30700
31058
  storage: this._storage,
30701
- communications: this._communications
31059
+ communications: this._communications,
31060
+ tokenEngine
30702
31061
  });
30703
31062
  } else {
30704
31063
  logger.warn("Sphere", "Accounting module enabled but no token storage available \u2014 disabling");
@@ -30760,6 +31119,7 @@ var Sphere = class _Sphere {
30760
31119
  market: this._market,
30761
31120
  transportAdapter: adapter,
30762
31121
  tokenStorageProviders: new Map(this._tokenStorageProviders),
31122
+ tokenEngine: this._tokenEngine,
30763
31123
  initialized: true
30764
31124
  });
30765
31125
  }
@@ -31110,37 +31470,22 @@ init_constants();
31110
31470
 
31111
31471
  // validation/token-validator.ts
31112
31472
  init_logger();
31473
+ function stateIdOf(token) {
31474
+ return sha2562(bytesToHex3(token.blob.token), "hex");
31475
+ }
31113
31476
  var TokenValidator = class {
31114
- aggregatorClient = null;
31115
- trustBase = null;
31116
- skipVerification;
31117
- // Cache for spent state verification
31477
+ engine;
31478
+ // Spent-status cache: SPENT is permanent (immutable), UNSPENT expires after a TTL.
31118
31479
  spentStateCache = /* @__PURE__ */ new Map();
31119
31480
  UNSPENT_CACHE_TTL_MS = 5 * 60 * 1e3;
31120
31481
  // 5 minutes
31121
- constructor(options = {}) {
31122
- this.aggregatorClient = options.aggregatorClient || null;
31123
- this.trustBase = options.trustBase || null;
31124
- this.skipVerification = options.skipVerification || false;
31482
+ constructor(engine) {
31483
+ this.engine = engine;
31125
31484
  }
31126
- /**
31127
- * Set the aggregator client
31128
- */
31129
- setAggregatorClient(client) {
31130
- this.aggregatorClient = client;
31131
- }
31132
- /**
31133
- * Set the trust base
31134
- */
31135
- setTrustBase(trustBase) {
31136
- this.trustBase = trustBase;
31137
- }
31138
- // =============================================================================
31485
+ // ===========================================================================
31139
31486
  // Public API
31140
- // =============================================================================
31141
- /**
31142
- * Validate all tokens (parallel with batch limit)
31143
- */
31487
+ // ===========================================================================
31488
+ /** Validate all tokens (parallel, with a batch limit). */
31144
31489
  async validateAllTokens(tokens, options) {
31145
31490
  const validTokens = [];
31146
31491
  const issues = [];
@@ -31149,293 +31494,88 @@ var TokenValidator = class {
31149
31494
  let completed = 0;
31150
31495
  for (let i = 0; i < tokens.length; i += batchSize) {
31151
31496
  const batch = tokens.slice(i, i + batchSize);
31152
- const batchResults = await Promise.allSettled(
31153
- batch.map(async (token) => {
31154
- try {
31155
- const result = await this.validateToken(token);
31156
- return { token, result };
31157
- } catch (err) {
31158
- return {
31159
- token,
31160
- result: {
31161
- isValid: false,
31162
- reason: err instanceof Error ? err.message : String(err)
31163
- }
31164
- };
31165
- }
31166
- })
31497
+ const batchResults = await Promise.all(
31498
+ batch.map(async (token) => ({ token, result: await this.validateToken(token) }))
31167
31499
  );
31168
- for (const settledResult of batchResults) {
31500
+ for (const { token, result } of batchResults) {
31169
31501
  completed++;
31170
- if (settledResult.status === "fulfilled") {
31171
- const { token, result } = settledResult.value;
31172
- if (result.isValid) {
31173
- validTokens.push(token);
31174
- } else {
31175
- issues.push({
31176
- tokenId: token.id,
31177
- reason: result.reason || "Unknown validation error",
31178
- recoverable: false
31179
- });
31180
- }
31502
+ if (result.isValid) {
31503
+ validTokens.push(token);
31181
31504
  } else {
31182
31505
  issues.push({
31183
- tokenId: batch[batchResults.indexOf(settledResult)]?.id || "unknown",
31184
- reason: String(settledResult.reason),
31506
+ tokenId: stateIdOf(token),
31507
+ reason: result.reason || "Unknown validation error",
31185
31508
  recoverable: false
31186
31509
  });
31187
31510
  }
31188
31511
  }
31189
- if (options?.onProgress) {
31190
- options.onProgress(completed, total);
31191
- }
31512
+ options?.onProgress?.(completed, total);
31192
31513
  }
31193
31514
  return { validTokens, issues };
31194
31515
  }
31195
- /**
31196
- * Validate a single token
31197
- */
31516
+ /** Validate a single token's structural integrity against the trust base. */
31198
31517
  async validateToken(token) {
31199
- if (!token.sdkData) {
31200
- return {
31201
- isValid: false,
31202
- reason: "Token has no SDK data"
31203
- };
31204
- }
31205
- let txfToken;
31206
- try {
31207
- txfToken = JSON.parse(token.sdkData);
31208
- } catch {
31209
- return {
31210
- isValid: false,
31211
- reason: "Failed to parse token SDK data as JSON"
31212
- };
31213
- }
31214
- if (!this.hasValidTxfStructure(txfToken)) {
31215
- return {
31216
- isValid: false,
31217
- reason: "Token data missing required TXF fields (genesis, state)"
31218
- };
31219
- }
31220
- const uncommitted = this.getUncommittedTransactions(txfToken);
31221
- if (uncommitted.length > 0) {
31222
- return {
31223
- isValid: false,
31224
- reason: `${uncommitted.length} uncommitted transaction(s)`
31225
- };
31226
- }
31227
- if (this.trustBase && !this.skipVerification) {
31228
- try {
31229
- const verificationResult = await this.verifyWithSdk(txfToken);
31230
- if (!verificationResult.success) {
31231
- return {
31232
- isValid: false,
31233
- reason: verificationResult.error || "SDK verification failed"
31234
- };
31235
- }
31236
- } catch (err) {
31237
- logger.warn("Validation", "SDK verification skipped:", err instanceof Error ? err.message : err);
31238
- }
31239
- }
31240
- return { isValid: true };
31518
+ const result = await this.engine.verify(token);
31519
+ return result.ok ? { isValid: true } : { isValid: false, reason: result.reason || "Verification failed" };
31241
31520
  }
31242
31521
  /**
31243
- * Check if a token state is spent on the aggregator
31522
+ * Whether a token's current state has been spent on the network. Cached:
31523
+ * SPENT permanently, UNSPENT for `UNSPENT_CACHE_TTL_MS`. Graceful — an engine
31524
+ * error is treated as unspent (and not cached).
31244
31525
  */
31245
- async isTokenStateSpent(tokenId, stateHash, publicKey) {
31246
- if (!this.aggregatorClient) {
31247
- return false;
31248
- }
31249
- const cacheKey = `${tokenId}:${stateHash}:${publicKey}`;
31250
- const cached = this.spentStateCache.get(cacheKey);
31251
- if (cached !== void 0) {
31252
- if (cached.isSpent) {
31253
- return true;
31254
- }
31255
- if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) {
31256
- return false;
31257
- }
31526
+ async isSpent(token) {
31527
+ const key = stateIdOf(token);
31528
+ const cached = this.spentStateCache.get(key);
31529
+ if (cached) {
31530
+ if (cached.isSpent) return true;
31531
+ if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) return false;
31258
31532
  }
31533
+ let spent;
31259
31534
  try {
31260
- const { RequestId } = await import("@unicitylabs/state-transition-sdk/lib/api/RequestId");
31261
- const { DataHash } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHash");
31262
- const pubKeyBytes = Buffer.from(publicKey, "hex");
31263
- const stateHashObj = DataHash.fromJSON(stateHash);
31264
- const requestId2 = await RequestId.create(pubKeyBytes, stateHashObj);
31265
- const response = await this.aggregatorClient.getInclusionProof(requestId2);
31266
- let isSpent = false;
31267
- if (response.inclusionProof) {
31268
- const proof = response.inclusionProof;
31269
- const pathResult = await proof.merkleTreePath.verify(
31270
- requestId2.toBitString().toBigInt()
31271
- );
31272
- if (pathResult.isPathValid && pathResult.isPathIncluded && proof.authenticator !== null) {
31273
- isSpent = true;
31274
- }
31275
- }
31276
- this.spentStateCache.set(cacheKey, {
31277
- isSpent,
31278
- timestamp: Date.now()
31279
- });
31280
- return isSpent;
31535
+ spent = await this.engine.isSpent(token);
31281
31536
  } catch (err) {
31282
- logger.warn("Validation", "Error checking token state:", err);
31537
+ logger.warn("Validation", "Error checking spent status:", err);
31283
31538
  return false;
31284
31539
  }
31540
+ this.spentStateCache.set(key, { isSpent: spent, timestamp: Date.now() });
31541
+ return spent;
31285
31542
  }
31286
- /**
31287
- * Check which tokens are spent using SDK Token object to calculate state hash.
31288
- *
31289
- * Follows the same approach as the Sphere webgui TokenValidationService:
31290
- * 1. Parse TXF using SDK's Token.fromJSON()
31291
- * 2. Calculate CURRENT state hash via sdkToken.state.calculateHash()
31292
- * 3. Create RequestId via RequestId.create(walletPubKey, calculatedHash)
31293
- *
31294
- * Uses wallet's own pubkey (not source state predicate key) because "spent" means
31295
- * the CURRENT OWNER committed this state. Using the source state key would falsely
31296
- * detect received tokens as "spent" (sender's commitment matches source state).
31297
- */
31298
- async checkSpentTokens(tokens, publicKey, options) {
31543
+ /** Check which of the given tokens are spent, returning them by per-state id. */
31544
+ async checkSpentTokens(tokens, options) {
31299
31545
  const spentTokens = [];
31300
31546
  const errors = [];
31301
- if (!this.aggregatorClient) {
31302
- errors.push("Aggregator client not available");
31303
- return { spentTokens, errors };
31304
- }
31305
31547
  const batchSize = options?.batchSize ?? 3;
31306
31548
  const total = tokens.length;
31307
31549
  let completed = 0;
31308
- const { Token: SdkToken5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token");
31309
- const { RequestId } = await import("@unicitylabs/state-transition-sdk/lib/api/RequestId");
31310
- const pubKeyBytes = Buffer.from(publicKey, "hex");
31311
31550
  for (let i = 0; i < tokens.length; i += batchSize) {
31312
31551
  const batch = tokens.slice(i, i + batchSize);
31313
31552
  const batchResults = await Promise.allSettled(
31314
- batch.map(async (token) => {
31315
- try {
31316
- const txf = tokenToTxf(token);
31317
- if (!txf) {
31318
- return { tokenId: token.id, localId: token.id, stateHash: "", spent: false, error: "Invalid TXF" };
31319
- }
31320
- const tokenId = txf.genesis?.data?.tokenId || token.id;
31321
- const sdkToken = await SdkToken5.fromJSON(txf);
31322
- const calculatedStateHash = await sdkToken.state.calculateHash();
31323
- const calculatedStateHashStr = calculatedStateHash.toJSON();
31324
- const cacheKey = `${tokenId}:${calculatedStateHashStr}:${publicKey}`;
31325
- const cached = this.spentStateCache.get(cacheKey);
31326
- if (cached !== void 0) {
31327
- if (cached.isSpent) {
31328
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: true };
31329
- }
31330
- if (Date.now() - cached.timestamp < this.UNSPENT_CACHE_TTL_MS) {
31331
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: false };
31332
- }
31333
- }
31334
- const { DataHash } = await import("@unicitylabs/state-transition-sdk/lib/hash/DataHash");
31335
- const stateHashObj = DataHash.fromJSON(calculatedStateHashStr);
31336
- const requestId2 = await RequestId.create(pubKeyBytes, stateHashObj);
31337
- const response = await this.aggregatorClient.getInclusionProof(requestId2);
31338
- let isSpent = false;
31339
- if (response.inclusionProof) {
31340
- const proof = response.inclusionProof;
31341
- const pathResult = await proof.merkleTreePath.verify(
31342
- requestId2.toBitString().toBigInt()
31343
- );
31344
- if (pathResult.isPathValid && pathResult.isPathIncluded && proof.authenticator !== null) {
31345
- isSpent = true;
31346
- }
31347
- }
31348
- this.spentStateCache.set(cacheKey, {
31349
- isSpent,
31350
- timestamp: Date.now()
31351
- });
31352
- return { tokenId, localId: token.id, stateHash: calculatedStateHashStr, spent: isSpent };
31353
- } catch (err) {
31354
- return {
31355
- tokenId: token.id,
31356
- localId: token.id,
31357
- stateHash: "",
31358
- spent: false,
31359
- error: err instanceof Error ? err.message : String(err)
31360
- };
31361
- }
31362
- })
31553
+ batch.map(async (token) => ({ stateId: stateIdOf(token), spent: await this.isSpent(token) }))
31363
31554
  );
31364
31555
  for (const result of batchResults) {
31365
31556
  completed++;
31366
31557
  if (result.status === "fulfilled") {
31367
- if (result.value.spent) {
31368
- spentTokens.push({
31369
- tokenId: result.value.tokenId,
31370
- localId: result.value.localId,
31371
- stateHash: result.value.stateHash
31372
- });
31373
- }
31374
- if (result.value.error) {
31375
- errors.push(`Token ${result.value.tokenId}: ${result.value.error}`);
31376
- }
31558
+ if (result.value.spent) spentTokens.push({ stateId: result.value.stateId });
31377
31559
  } else {
31378
31560
  errors.push(String(result.reason));
31379
31561
  }
31380
31562
  }
31381
- if (options?.onProgress) {
31382
- options.onProgress(completed, total);
31383
- }
31563
+ options?.onProgress?.(completed, total);
31384
31564
  }
31385
31565
  return { spentTokens, errors };
31386
31566
  }
31387
- /**
31388
- * Clear the spent state cache
31389
- */
31567
+ /** Clear the spent-status cache. */
31390
31568
  clearSpentStateCache() {
31391
31569
  this.spentStateCache.clear();
31392
31570
  }
31393
- // =============================================================================
31394
- // Private Helpers
31395
- // =============================================================================
31396
- hasValidTxfStructure(obj) {
31397
- if (!obj || typeof obj !== "object") return false;
31398
- const txf = obj;
31399
- return !!(txf.genesis && typeof txf.genesis === "object" && txf.state && typeof txf.state === "object");
31400
- }
31401
- getUncommittedTransactions(txfToken) {
31402
- const txf = txfToken;
31403
- const transactions = txf.transactions;
31404
- if (!transactions || !Array.isArray(transactions)) {
31405
- return [];
31406
- }
31407
- return transactions.filter((tx) => tx.inclusionProof === null);
31408
- }
31409
- async verifyWithSdk(txfToken) {
31410
- try {
31411
- const { Token: Token5 } = await import("@unicitylabs/state-transition-sdk/lib/token/Token");
31412
- const sdkToken = await Token5.fromJSON(txfToken);
31413
- if (!this.trustBase) {
31414
- return { success: true };
31415
- }
31416
- const result = await sdkToken.verify(this.trustBase);
31417
- if (!result.isSuccessful) {
31418
- return {
31419
- success: false,
31420
- error: String(result) || "Verification failed"
31421
- };
31422
- }
31423
- return { success: true };
31424
- } catch (err) {
31425
- return {
31426
- success: false,
31427
- error: err instanceof Error ? err.message : String(err)
31428
- };
31429
- }
31430
- }
31431
31571
  };
31432
- function createTokenValidator(options) {
31433
- return new TokenValidator(options);
31572
+ function createTokenValidator(engine) {
31573
+ return new TokenValidator(engine);
31434
31574
  }
31435
31575
 
31436
31576
  // index.ts
31437
31577
  import {
31438
- normalizeNametag as normalizeNametag3,
31578
+ normalizeNametag as normalizeNametag2,
31439
31579
  isPhoneNumber as isPhoneNumber2,
31440
31580
  hashNametag,
31441
31581
  hashAddressForTag,
@@ -31841,7 +31981,7 @@ export {
31841
31981
  mnemonicToSeedSync2 as mnemonicToSeedSync,
31842
31982
  normalizeAddress,
31843
31983
  normalizeCoinId,
31844
- normalizeNametag3 as normalizeNametag,
31984
+ normalizeNametag2 as normalizeNametag,
31845
31985
  normalizeSdkTokenToStorage,
31846
31986
  objectToTxf,
31847
31987
  parseAddress,