@unicitylabs/sphere-sdk 0.17.0 → 0.17.1

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.
@@ -832,71 +832,15 @@ function createMachineStores(kv) {
832
832
  };
833
833
  }
834
834
 
835
- // token-engine/sdk.ts
836
- var import_StateTransitionClient = require("@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js");
837
- var import_AggregatorClient = require("@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js");
838
- var import_NetworkId = require("@unicitylabs/state-transition-sdk/lib/api/NetworkId.js");
839
- var import_CertificationData = require("@unicitylabs/state-transition-sdk/lib/api/CertificationData.js");
840
- var import_CertificationResponse = require("@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js");
841
- var import_StateId = require("@unicitylabs/state-transition-sdk/lib/api/StateId.js");
842
- var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js");
843
- var import_InclusionProofResponse = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js");
844
- var import_JsonRpcNetworkError = require("@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js");
845
- var import_RootTrustBase = require("@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js");
846
- var import_InclusionProofUtils = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
847
- var import_InclusionProofVerificationRule = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js");
848
- var import_Token = require("@unicitylabs/state-transition-sdk/lib/transaction/Token.js");
849
- var import_MintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js");
850
- var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js");
851
- var import_CertifiedMintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js");
852
- var import_CertifiedTransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js");
853
- var import_TokenId = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js");
854
- var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js");
855
- var import_TokenSalt = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js");
856
- var import_StateMask = require("@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js");
857
- var import_MintJustificationVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js");
858
- var import_TokenIssuanceVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js");
859
- var import_VerificationContext = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js");
860
- var import_UnicityCertificateVerifier = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js");
861
- var import_UnicitySealQuorumSignaturesVerificationRule = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js");
862
- var import_VerifiedSealCache = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js");
863
- var import_Secp256k1SignatureVerifier = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js");
864
- var import_WorkerTokenVerifier = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js");
865
- var import_EncodedPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js");
866
- var import_SignaturePredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js");
867
- var import_SignaturePredicateUnlockScript = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
868
- var import_BurnPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js");
869
- var import_PredicateVerifierService = require("@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js");
870
- var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js");
871
- var import_Signature = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js");
872
- var import_MintSigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js");
873
- var import_HashAlgorithm = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js");
874
- var import_DataHash = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js");
875
- var import_DataHasher = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js");
876
- var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js");
877
- var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
878
- var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
879
- var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
880
- var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
881
- var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
882
- var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
883
- var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
884
- var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
885
- var import_TokenSplit = require("@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js");
886
- var import_SplitTokenRequest = require("@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js");
887
- var import_SplitToken = require("@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js");
888
- var import_SplitAllocationProof = require("@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js");
889
- var import_SplitMintJustification = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js");
890
- var import_SplitMintJustificationVerifier = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js");
891
- var import_VerificationStatus = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js");
892
- var import_VerificationResult = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js");
893
- var import_HexConverter = require("@unicitylabs/state-transition-sdk/lib/util/HexConverter.js");
894
- var import_BigintConverter = require("@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js");
895
- var import_BitString = require("@unicitylabs/state-transition-sdk/lib/util/BitString.js");
896
-
897
- // token-engine/value-envelope.ts
898
- function isCoinlessEnvelope(envelope) {
899
- return envelope.startsWith("none_");
835
+ // modules/payments-v2/machine/payload-view.ts
836
+ function isWholeIntent(p) {
837
+ return p.kind === "whole" || p.kind === "coinless";
838
+ }
839
+ function splitOf(p) {
840
+ return p.kind === "coin" ? p.split : void 0;
841
+ }
842
+ function coinIdOf(p) {
843
+ return p.kind === "coin" ? p.coinId : void 0;
900
844
  }
901
845
 
902
846
  // token-engine/errors.ts
@@ -938,14 +882,6 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
938
882
  }
939
883
  };
940
884
 
941
- // modules/payments-v2/machine/payload-view.ts
942
- function splitOf(p) {
943
- return p.kind === "coin" ? p.split : void 0;
944
- }
945
- function coinIdOf(p) {
946
- return p.kind === "coin" ? p.coinId : void 0;
947
- }
948
-
949
885
  // modules/payments-v2/machine/TransferMachine.ts
950
886
  var CERTIFY_WIDTH = 20;
951
887
  var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
@@ -1307,7 +1243,14 @@ var TransferMachine = class {
1307
1243
  try {
1308
1244
  const coinId = coinIdOf(payload);
1309
1245
  const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
1310
- await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
1246
+ const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
1247
+ await this.deps.recordHistory?.({
1248
+ transferId,
1249
+ payload,
1250
+ phase,
1251
+ committedAmount,
1252
+ ...committedAssets !== void 0 ? { committedAssets } : {}
1253
+ });
1311
1254
  } catch {
1312
1255
  }
1313
1256
  try {
@@ -1321,6 +1264,18 @@ var TransferMachine = class {
1321
1264
  * a journal-rehydrated resume leg has no source token, but every committed
1322
1265
  * leg carries its recipient blob, so the outcome-derived rule is uniform).
1323
1266
  */
1267
+ /** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
1268
+ async settledAssets(engine, committed) {
1269
+ const total = /* @__PURE__ */ new Map();
1270
+ for (const outcome of committed) {
1271
+ if (outcome.recipientBlob === void 0) continue;
1272
+ const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
1273
+ for (const asset of engine.readValue(token)?.assets ?? []) {
1274
+ total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
1275
+ }
1276
+ }
1277
+ return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
1278
+ }
1324
1279
  async settledAmount(engine, coinId, committed) {
1325
1280
  let total = 0n;
1326
1281
  for (const outcome of committed) {
@@ -1460,7 +1415,7 @@ async function runOne(ctx, job, report) {
1460
1415
  report.failed.push(job.transferId);
1461
1416
  return;
1462
1417
  }
1463
- if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
1418
+ if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
1464
1419
  logger.warn(
1465
1420
  "PaymentsV2",
1466
1421
  `resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
@@ -1508,7 +1463,7 @@ function validateCoinPayload(c) {
1508
1463
  }
1509
1464
  return { ...c, kind: "coin" };
1510
1465
  }
1511
- function validateCoinlessPayload(p) {
1466
+ function validateWholePayload(p) {
1512
1467
  if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
1513
1468
  throw new SphereError(
1514
1469
  "token intent payload must name exactly one source token",
@@ -1518,7 +1473,7 @@ function validateCoinlessPayload(p) {
1518
1473
  if (p.split !== void 0) {
1519
1474
  throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
1520
1475
  }
1521
- return { ...p, kind: "coinless", direct: [p.direct[0]] };
1476
+ return { ...p, kind: "whole", direct: [p.direct[0]] };
1522
1477
  }
1523
1478
  function validatePayload(raw) {
1524
1479
  const p = raw !== null && typeof raw === "object" ? raw : null;
@@ -1532,13 +1487,13 @@ function validatePayload(raw) {
1532
1487
  throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
1533
1488
  }
1534
1489
  const kind = p.kind ?? "coin";
1535
- if (kind !== "coin" && kind !== "coinless") {
1490
+ if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
1536
1491
  throw new SphereError(
1537
1492
  `unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
1538
1493
  "VALIDATION_ERROR"
1539
1494
  );
1540
1495
  }
1541
- return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
1496
+ return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
1542
1497
  }
1543
1498
 
1544
1499
  // modules/payments-v2/convergence.ts
@@ -1751,8 +1706,9 @@ async function openRow(entry, journal, decryptPayload) {
1751
1706
  };
1752
1707
  }
1753
1708
  function subject(payload) {
1754
- if (payload?.kind === "coinless") {
1755
- const tokenId = payload.direct?.[0];
1709
+ const rawKind = payload?.kind;
1710
+ if (rawKind === "whole" || rawKind === "coinless") {
1711
+ const tokenId = payload?.direct?.[0];
1756
1712
  return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
1757
1713
  }
1758
1714
  const coin = payload;
@@ -1825,10 +1781,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
1825
1781
  function messageOf(err) {
1826
1782
  return err instanceof Error ? err.message : String(err);
1827
1783
  }
1828
- function buildCoinlessPayload(recipientPubkey, request, spentStates) {
1784
+ function buildWholePayload(recipientPubkey, request, spentStates) {
1829
1785
  return {
1830
1786
  v: 2,
1831
- kind: "coinless",
1787
+ kind: "whole",
1832
1788
  recipient: recipientPubkey,
1833
1789
  ...request.memo !== void 0 ? { memo: request.memo } : {},
1834
1790
  direct: [request.tokenId],
@@ -1897,8 +1853,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
1897
1853
  deps.refreshView();
1898
1854
  }
1899
1855
 
1900
- // modules/payments-v2/send-coinless.ts
1901
- async function materializeCoinlessSpend(deps, input) {
1856
+ // modules/payments-v2/inventory/presentation.ts
1857
+ function toToken(tokenId, snapshot, asset, registry, flags) {
1858
+ const iconUrl = registry.getIconUrl(asset.coinId);
1859
+ return {
1860
+ id: tokenId,
1861
+ coinId: asset.coinId,
1862
+ symbol: registry.getSymbol(asset.coinId),
1863
+ name: registry.getName(asset.coinId),
1864
+ decimals: registry.getDecimals(asset.coinId),
1865
+ ...iconUrl !== null ? { iconUrl } : {},
1866
+ amount: asset.amount,
1867
+ status: flags.transferring ? "transferring" : "confirmed",
1868
+ createdAt: snapshot.createdAt,
1869
+ updatedAt: snapshot.updatedAt,
1870
+ lazy: true,
1871
+ ...flags.suspectedSpent ? { suspectedSpent: true } : {}
1872
+ };
1873
+ }
1874
+ function aggregateAssets(entries, isTransferring, registry) {
1875
+ const groups = /* @__PURE__ */ new Map();
1876
+ for (const [tokenId, entry] of entries) {
1877
+ const moving = isTransferring(tokenId);
1878
+ for (const asset of entry.assets) {
1879
+ let group = groups.get(asset.coinId);
1880
+ if (!group) {
1881
+ group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
1882
+ groups.set(asset.coinId, group);
1883
+ }
1884
+ if (moving) {
1885
+ group.transferring += BigInt(asset.amount);
1886
+ group.transferringCount += 1;
1887
+ } else {
1888
+ group.total += BigInt(asset.amount);
1889
+ group.count += 1;
1890
+ }
1891
+ }
1892
+ }
1893
+ return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
1894
+ }
1895
+ function toAsset(coinId, totals, registry) {
1896
+ const iconUrl = registry.getIconUrl(coinId);
1897
+ return {
1898
+ coinId,
1899
+ symbol: registry.getSymbol(coinId),
1900
+ name: registry.getName(coinId),
1901
+ decimals: registry.getDecimals(coinId),
1902
+ ...iconUrl !== null ? { iconUrl } : {},
1903
+ totalAmount: totals.total.toString(),
1904
+ tokenCount: totals.count,
1905
+ confirmedAmount: totals.total.toString(),
1906
+ unconfirmedAmount: "0",
1907
+ confirmedTokenCount: totals.count,
1908
+ unconfirmedTokenCount: 0,
1909
+ transferringTokenCount: totals.transferringCount,
1910
+ transferringAmount: totals.transferring.toString(),
1911
+ priceUsd: null,
1912
+ priceEur: null,
1913
+ change24h: null,
1914
+ fiatValueUsd: null,
1915
+ fiatValueEur: null
1916
+ };
1917
+ }
1918
+ function priceIdOf(registry, coinId) {
1919
+ return registry.getName(coinId).toLowerCase();
1920
+ }
1921
+ async function withPrices(raw, registry, price) {
1922
+ try {
1923
+ const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
1924
+ const quotes = await price.getPrices(ids);
1925
+ return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
1926
+ } catch {
1927
+ return raw;
1928
+ }
1929
+ }
1930
+ function priceOne(asset, quote) {
1931
+ if (!quote) return asset;
1932
+ const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
1933
+ return {
1934
+ ...asset,
1935
+ priceUsd: quote.priceUsd,
1936
+ priceEur: quote.priceEur ?? null,
1937
+ change24h: quote.change24h ?? null,
1938
+ fiatValueUsd: human * quote.priceUsd,
1939
+ fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
1940
+ };
1941
+ }
1942
+ function transferringToken(tokenId, coinId, amount, registry, now) {
1943
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
1944
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
1945
+ transferring: true,
1946
+ suspectedSpent: false
1947
+ });
1948
+ }
1949
+
1950
+ // modules/payments-v2/send-whole.ts
1951
+ async function materializeWholeSpend(deps, input) {
1902
1952
  const { engine } = deps;
1903
1953
  const tokenId = input.request.tokenId;
1904
1954
  const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
@@ -1906,14 +1956,20 @@ async function materializeCoinlessSpend(deps, input) {
1906
1956
  throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
1907
1957
  }
1908
1958
  const token = await engine.decodeToken({ tokenId, token: bytes });
1909
- if (!isCoinlessEnvelope(token.valueEnvelope)) {
1959
+ if (token.valueEnvelope === "bare_collection") {
1960
+ throw new SphereError(
1961
+ `Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
1962
+ "VALIDATION_ERROR"
1963
+ );
1964
+ }
1965
+ if (input.requireCoinless && token.value !== null) {
1910
1966
  throw new SphereError(
1911
- `Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
1967
+ `Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
1912
1968
  "VALIDATION_ERROR"
1913
1969
  );
1914
1970
  }
1915
1971
  const keys = await engine.deliveryKeys(bytes);
1916
- const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
1972
+ const payload = buildWholePayload(input.recipientPubkey, input.request, {
1917
1973
  [tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
1918
1974
  });
1919
1975
  const plan = {
@@ -1924,7 +1980,10 @@ async function materializeCoinlessSpend(deps, input) {
1924
1980
  sources: /* @__PURE__ */ new Map([[tokenId, token]]),
1925
1981
  ...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
1926
1982
  };
1927
- return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
1983
+ const sourceTokens = (token.value?.assets ?? []).map(
1984
+ (asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
1985
+ );
1986
+ return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
1928
1987
  }
1929
1988
 
1930
1989
  // modules/payments-v2/send-errors.ts
@@ -2071,100 +2130,6 @@ async function takeSourceBlobs(deps, sourceIds) {
2071
2130
  return blobs;
2072
2131
  }
2073
2132
 
2074
- // modules/payments-v2/inventory/presentation.ts
2075
- function toToken(tokenId, snapshot, asset, registry, flags) {
2076
- const iconUrl = registry.getIconUrl(asset.coinId);
2077
- return {
2078
- id: tokenId,
2079
- coinId: asset.coinId,
2080
- symbol: registry.getSymbol(asset.coinId),
2081
- name: registry.getName(asset.coinId),
2082
- decimals: registry.getDecimals(asset.coinId),
2083
- ...iconUrl !== null ? { iconUrl } : {},
2084
- amount: asset.amount,
2085
- status: flags.transferring ? "transferring" : "confirmed",
2086
- createdAt: snapshot.createdAt,
2087
- updatedAt: snapshot.updatedAt,
2088
- lazy: true,
2089
- ...flags.suspectedSpent ? { suspectedSpent: true } : {}
2090
- };
2091
- }
2092
- function aggregateAssets(entries, isTransferring, registry) {
2093
- const groups = /* @__PURE__ */ new Map();
2094
- for (const [tokenId, entry] of entries) {
2095
- const moving = isTransferring(tokenId);
2096
- for (const asset of entry.assets) {
2097
- let group = groups.get(asset.coinId);
2098
- if (!group) {
2099
- group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
2100
- groups.set(asset.coinId, group);
2101
- }
2102
- if (moving) {
2103
- group.transferring += BigInt(asset.amount);
2104
- group.transferringCount += 1;
2105
- } else {
2106
- group.total += BigInt(asset.amount);
2107
- group.count += 1;
2108
- }
2109
- }
2110
- }
2111
- return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
2112
- }
2113
- function toAsset(coinId, totals, registry) {
2114
- const iconUrl = registry.getIconUrl(coinId);
2115
- return {
2116
- coinId,
2117
- symbol: registry.getSymbol(coinId),
2118
- name: registry.getName(coinId),
2119
- decimals: registry.getDecimals(coinId),
2120
- ...iconUrl !== null ? { iconUrl } : {},
2121
- totalAmount: totals.total.toString(),
2122
- tokenCount: totals.count,
2123
- confirmedAmount: totals.total.toString(),
2124
- unconfirmedAmount: "0",
2125
- confirmedTokenCount: totals.count,
2126
- unconfirmedTokenCount: 0,
2127
- transferringTokenCount: totals.transferringCount,
2128
- transferringAmount: totals.transferring.toString(),
2129
- priceUsd: null,
2130
- priceEur: null,
2131
- change24h: null,
2132
- fiatValueUsd: null,
2133
- fiatValueEur: null
2134
- };
2135
- }
2136
- function priceIdOf(registry, coinId) {
2137
- return registry.getName(coinId).toLowerCase();
2138
- }
2139
- async function withPrices(raw, registry, price) {
2140
- try {
2141
- const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
2142
- const quotes = await price.getPrices(ids);
2143
- return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
2144
- } catch {
2145
- return raw;
2146
- }
2147
- }
2148
- function priceOne(asset, quote) {
2149
- if (!quote) return asset;
2150
- const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
2151
- return {
2152
- ...asset,
2153
- priceUsd: quote.priceUsd,
2154
- priceEur: quote.priceEur ?? null,
2155
- change24h: quote.change24h ?? null,
2156
- fiatValueUsd: human * quote.priceUsd,
2157
- fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
2158
- };
2159
- }
2160
- function transferringToken(tokenId, coinId, amount, registry, now) {
2161
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
2162
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
2163
- transferring: true,
2164
- suspectedSpent: false
2165
- });
2166
- }
2167
-
2168
2133
  // node_modules/@noble/ciphers/utils.js
2169
2134
  function isBytes2(a) {
2170
2135
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
@@ -3270,10 +3235,10 @@ var InventoryView = class {
3270
3235
  if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
3271
3236
  return !this.suspected.has(stateKey(tokenId, entry.stateHash));
3272
3237
  }
3273
- /** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
3274
- spendableCoinless(tokenId) {
3238
+ /** Is this NAMED token spendable whole — coinless or valued (#777)? */
3239
+ spendableToken(tokenId) {
3275
3240
  const entry = this.mirror.get(tokenId);
3276
- return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
3241
+ return entry !== void 0 && this.isSpendable(tokenId, entry);
3277
3242
  }
3278
3243
  /**
3279
3244
  * Current mirrored state of an ACTIVE token, or undefined when it is unknown
@@ -3497,6 +3462,73 @@ var InventoryView = class {
3497
3462
  }
3498
3463
  };
3499
3464
 
3465
+ // token-engine/sdk.ts
3466
+ var import_StateTransitionClient = require("@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js");
3467
+ var import_AggregatorClient = require("@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js");
3468
+ var import_NetworkId = require("@unicitylabs/state-transition-sdk/lib/api/NetworkId.js");
3469
+ var import_CertificationData = require("@unicitylabs/state-transition-sdk/lib/api/CertificationData.js");
3470
+ var import_CertificationResponse = require("@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js");
3471
+ var import_StateId = require("@unicitylabs/state-transition-sdk/lib/api/StateId.js");
3472
+ var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js");
3473
+ var import_InclusionProofResponse = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js");
3474
+ var import_JsonRpcNetworkError = require("@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js");
3475
+ var import_RootTrustBase = require("@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js");
3476
+ var import_InclusionProofUtils = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
3477
+ var import_InclusionProofVerificationRule = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js");
3478
+ var import_Token = require("@unicitylabs/state-transition-sdk/lib/transaction/Token.js");
3479
+ var import_MintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js");
3480
+ var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js");
3481
+ var import_CertifiedMintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js");
3482
+ var import_CertifiedTransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js");
3483
+ var import_TokenId = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js");
3484
+ var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js");
3485
+ var import_TokenSalt = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js");
3486
+ var import_StateMask = require("@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js");
3487
+ var import_MintJustificationVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js");
3488
+ var import_TokenIssuanceVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js");
3489
+ var import_VerificationContext = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js");
3490
+ var import_UnicityCertificateVerifier = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js");
3491
+ var import_UnicitySealQuorumSignaturesVerificationRule = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js");
3492
+ var import_VerifiedSealCache = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js");
3493
+ var import_Secp256k1SignatureVerifier = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js");
3494
+ var import_WorkerTokenVerifier = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js");
3495
+ var import_EncodedPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js");
3496
+ var import_SignaturePredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js");
3497
+ var import_SignaturePredicateUnlockScript = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
3498
+ var import_BurnPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js");
3499
+ var import_PredicateVerifierService = require("@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js");
3500
+ var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js");
3501
+ var import_Signature = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js");
3502
+ var import_MintSigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js");
3503
+ var import_HashAlgorithm = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js");
3504
+ var import_DataHash = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js");
3505
+ var import_DataHasher = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js");
3506
+ var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js");
3507
+ var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
3508
+ var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
3509
+ var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
3510
+ var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
3511
+ var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
3512
+ var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
3513
+ var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
3514
+ var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
3515
+ var import_TokenSplit = require("@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js");
3516
+ var import_SplitTokenRequest = require("@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js");
3517
+ var import_SplitToken = require("@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js");
3518
+ var import_SplitAllocationProof = require("@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js");
3519
+ var import_SplitMintJustification = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js");
3520
+ var import_SplitMintJustificationVerifier = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js");
3521
+ var import_VerificationStatus = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js");
3522
+ var import_VerificationResult = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js");
3523
+ var import_HexConverter = require("@unicitylabs/state-transition-sdk/lib/util/HexConverter.js");
3524
+ var import_BigintConverter = require("@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js");
3525
+ var import_BitString = require("@unicitylabs/state-transition-sdk/lib/util/BitString.js");
3526
+
3527
+ // token-engine/value-envelope.ts
3528
+ function isCoinlessEnvelope(envelope) {
3529
+ return envelope.startsWith("none_");
3530
+ }
3531
+
3500
3532
  // modules/payments-v2/receive/Receive.ts
3501
3533
  var ACK_BATCH_SIZE = 200;
3502
3534
  var REFRESH_INTERVAL_MS = 2500;
@@ -3641,7 +3673,7 @@ async function screen(deps, engine, entry) {
3641
3673
  tokenId: keys.tokenId,
3642
3674
  stateHash: keys.stateHash,
3643
3675
  assets: toAssetAmounts(token),
3644
- ...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
3676
+ ...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
3645
3677
  }
3646
3678
  };
3647
3679
  }
@@ -3778,9 +3810,6 @@ function claimAck(entry) {
3778
3810
  function rejectAck(entry, reason) {
3779
3811
  return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
3780
3812
  }
3781
- function isCoinlessEnvelope2(envelope) {
3782
- return envelope.startsWith("none_");
3783
- }
3784
3813
  function toAssetAmounts(token) {
3785
3814
  return (token.value?.assets ?? []).map((asset) => ({
3786
3815
  coinId: asset.coinId,
@@ -4355,7 +4384,7 @@ var SpendQueue = class {
4355
4384
  * can release, so queueing would block until a timeout on a spend that cannot
4356
4385
  * become possible.
4357
4386
  */
4358
- planCoinless(reservationId, tokenId) {
4387
+ planWhole(reservationId, tokenId) {
4359
4388
  if (this.destroyed) {
4360
4389
  throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
4361
4390
  }
@@ -4363,11 +4392,8 @@ var SpendQueue = class {
4363
4392
  if (unproven !== null) {
4364
4393
  throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
4365
4394
  }
4366
- if (this.deps.spendableCoinless?.(tokenId) !== true) {
4367
- throw new SphereError(
4368
- `Token ${tokenId} is not a spendable coinless holding`,
4369
- "VALIDATION_ERROR"
4370
- );
4395
+ if (this.deps.spendableToken?.(tokenId) !== true) {
4396
+ throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
4371
4397
  }
4372
4398
  const holder = this.deps.ledger.holderOf(tokenId);
4373
4399
  if (holder !== void 0) {
@@ -4568,7 +4594,7 @@ function composeFacadeParts(deps, hooks) {
4568
4594
  const queue = new SpendQueue({
4569
4595
  ledger,
4570
4596
  getPool: (coinId) => view.pool(coinId),
4571
- spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
4597
+ spendableToken: (tokenId) => view.spendableToken(tokenId),
4572
4598
  ...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
4573
4599
  });
4574
4600
  const historyStore = new History({
@@ -4649,12 +4675,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
4649
4675
  ...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
4650
4676
  emit: deps.emit,
4651
4677
  now: deps.now ?? Date.now,
4652
- recordHistory: async ({ transferId, payload, committedAmount }) => {
4678
+ recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
4653
4679
  await historyStore.recordSent({
4654
4680
  transferId,
4655
4681
  // §5.9: the SETTLED amount, never payload.amount — the plan. A token
4656
4682
  // spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
4657
- ...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
4683
+ ...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
4658
4684
  recipientPubkey: payload.recipient,
4659
4685
  ...payload.memo !== void 0 ? { memo: payload.memo } : {}
4660
4686
  });
@@ -4869,10 +4895,14 @@ var PaymentsFacade = class {
4869
4895
  return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
4870
4896
  }
4871
4897
  send(request) {
4872
- return this.track(this.sendOutcome(request));
4898
+ return this.track(this.runJob({ kind: "coin", request }, request.amount));
4899
+ }
4900
+ sendWholeToken(request) {
4901
+ return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
4873
4902
  }
4903
+ /** NFT-scoped twin: refuses a valued source. */
4874
4904
  sendCoinless(request) {
4875
- return this.track(this.sendCoinlessOutcome(request));
4905
+ return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
4876
4906
  }
4877
4907
  async receive() {
4878
4908
  const transfers = await this.track(this.receiveLoop.drainOnce());
@@ -4920,13 +4950,6 @@ var PaymentsFacade = class {
4920
4950
  // ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
4921
4951
  /** The ONE place a send() outcome is shaped: success emits in finishSend, a
4922
4952
  * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
4923
- sendOutcome(request) {
4924
- return this.runJob({ kind: "coin", request }, request.amount);
4925
- }
4926
- /** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
4927
- sendCoinlessOutcome(request) {
4928
- return this.runJob({ kind: "coinless", request }, "0");
4929
- }
4930
4953
  async runJob(job, amount) {
4931
4954
  const run = {
4932
4955
  amount,
@@ -4971,7 +4994,7 @@ var PaymentsFacade = class {
4971
4994
  const disposition = await this.runAttempt(ctx);
4972
4995
  if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
4973
4996
  if (disposition.kind === "retry-full") {
4974
- if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
4997
+ if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
4975
4998
  continue;
4976
4999
  }
4977
5000
  if (disposition.kind === "success") {
@@ -5075,13 +5098,19 @@ var PaymentsFacade = class {
5075
5098
  async planAndMaterialize(recipientPubkey, job, run) {
5076
5099
  const transferId = this.newId();
5077
5100
  run.lastTransferId = transferId;
5078
- if (job.kind === "coinless") {
5079
- const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
5101
+ if (job.kind === "whole") {
5102
+ const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
5080
5103
  const sourceIds2 = this.markPlanned(transferId, "", spend2);
5081
5104
  try {
5082
- return await materializeCoinlessSpend(
5083
- { engine: this.engine(), storagePort: this.deps.storagePort },
5084
- { transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
5105
+ return await materializeWholeSpend(
5106
+ { engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
5107
+ {
5108
+ transferId,
5109
+ recipientPubkey,
5110
+ request: job.request,
5111
+ sourceIds: sourceIds2,
5112
+ requireCoinless: job.requireCoinless
5113
+ }
5085
5114
  );
5086
5115
  } catch (err) {
5087
5116
  this.settleFailure(transferId, "", sourceIds2);