@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.
@@ -785,71 +785,15 @@ function createMachineStores(kv) {
785
785
  };
786
786
  }
787
787
 
788
- // token-engine/sdk.ts
789
- import { StateTransitionClient } from "@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js";
790
- import { AggregatorClient } from "@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js";
791
- import { NetworkId } from "@unicitylabs/state-transition-sdk/lib/api/NetworkId.js";
792
- import { CertificationData } from "@unicitylabs/state-transition-sdk/lib/api/CertificationData.js";
793
- import { CertificationResponse, CertificationStatus } from "@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js";
794
- import { StateId } from "@unicitylabs/state-transition-sdk/lib/api/StateId.js";
795
- import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js";
796
- import { InclusionProofResponse } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js";
797
- import { JsonRpcNetworkError } from "@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js";
798
- import { RootTrustBase } from "@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js";
799
- import { waitInclusionProof } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js";
800
- import { InclusionProofVerificationStatus } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js";
801
- import { Token } from "@unicitylabs/state-transition-sdk/lib/transaction/Token.js";
802
- import { MintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js";
803
- import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js";
804
- import { CertifiedMintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js";
805
- import { CertifiedTransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js";
806
- import { TokenId } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js";
807
- import { TokenType } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js";
808
- import { TokenSalt } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js";
809
- import { StateMask } from "@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js";
810
- import { MintJustificationVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js";
811
- import { TokenIssuanceVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js";
812
- import { VerificationContext } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js";
813
- import { UnicityCertificateVerifier } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js";
814
- import { UnicitySealQuorumSignaturesVerificationRule } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js";
815
- import { VerifiedSealCache } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js";
816
- import { Secp256k1SignatureVerifier } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js";
817
- import { WorkerTokenVerifier } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js";
818
- import { EncodedPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js";
819
- import { SignaturePredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js";
820
- import { SignaturePredicateUnlockScript } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
821
- import { BurnPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js";
822
- import { PredicateVerifierService } from "@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js";
823
- import { SigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js";
824
- import { Signature } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js";
825
- import { MintSigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js";
826
- import { HashAlgorithm } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js";
827
- import { DataHash } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js";
828
- import { DataHasher } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js";
829
- import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js";
830
- import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
831
- import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
832
- import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
833
- import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
834
- import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
835
- import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
836
- import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
837
- import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
838
- import { TokenSplit } from "@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js";
839
- import { SplitTokenRequest } from "@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js";
840
- import { SplitToken } from "@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js";
841
- import { SplitAllocationProof } from "@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js";
842
- import { SplitMintJustification } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js";
843
- import { SplitMintJustificationVerifier } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js";
844
- import { VerificationStatus } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js";
845
- import { VerificationResult } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js";
846
- import { HexConverter } from "@unicitylabs/state-transition-sdk/lib/util/HexConverter.js";
847
- import { BigintConverter } from "@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js";
848
- import { BitString } from "@unicitylabs/state-transition-sdk/lib/util/BitString.js";
849
-
850
- // token-engine/value-envelope.ts
851
- function isCoinlessEnvelope(envelope) {
852
- return envelope.startsWith("none_");
788
+ // modules/payments-v2/machine/payload-view.ts
789
+ function isWholeIntent(p) {
790
+ return p.kind === "whole" || p.kind === "coinless";
791
+ }
792
+ function splitOf(p) {
793
+ return p.kind === "coin" ? p.split : void 0;
794
+ }
795
+ function coinIdOf(p) {
796
+ return p.kind === "coin" ? p.coinId : void 0;
853
797
  }
854
798
 
855
799
  // token-engine/errors.ts
@@ -891,14 +835,6 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
891
835
  }
892
836
  };
893
837
 
894
- // modules/payments-v2/machine/payload-view.ts
895
- function splitOf(p) {
896
- return p.kind === "coin" ? p.split : void 0;
897
- }
898
- function coinIdOf(p) {
899
- return p.kind === "coin" ? p.coinId : void 0;
900
- }
901
-
902
838
  // modules/payments-v2/machine/TransferMachine.ts
903
839
  var CERTIFY_WIDTH = 20;
904
840
  var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
@@ -1260,7 +1196,14 @@ var TransferMachine = class {
1260
1196
  try {
1261
1197
  const coinId = coinIdOf(payload);
1262
1198
  const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
1263
- await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
1199
+ const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
1200
+ await this.deps.recordHistory?.({
1201
+ transferId,
1202
+ payload,
1203
+ phase,
1204
+ committedAmount,
1205
+ ...committedAssets !== void 0 ? { committedAssets } : {}
1206
+ });
1264
1207
  } catch {
1265
1208
  }
1266
1209
  try {
@@ -1274,6 +1217,18 @@ var TransferMachine = class {
1274
1217
  * a journal-rehydrated resume leg has no source token, but every committed
1275
1218
  * leg carries its recipient blob, so the outcome-derived rule is uniform).
1276
1219
  */
1220
+ /** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
1221
+ async settledAssets(engine, committed) {
1222
+ const total = /* @__PURE__ */ new Map();
1223
+ for (const outcome of committed) {
1224
+ if (outcome.recipientBlob === void 0) continue;
1225
+ const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
1226
+ for (const asset of engine.readValue(token)?.assets ?? []) {
1227
+ total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
1228
+ }
1229
+ }
1230
+ return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
1231
+ }
1277
1232
  async settledAmount(engine, coinId, committed) {
1278
1233
  let total = 0n;
1279
1234
  for (const outcome of committed) {
@@ -1413,7 +1368,7 @@ async function runOne(ctx, job, report) {
1413
1368
  report.failed.push(job.transferId);
1414
1369
  return;
1415
1370
  }
1416
- if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
1371
+ if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
1417
1372
  logger.warn(
1418
1373
  "PaymentsV2",
1419
1374
  `resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
@@ -1461,7 +1416,7 @@ function validateCoinPayload(c) {
1461
1416
  }
1462
1417
  return { ...c, kind: "coin" };
1463
1418
  }
1464
- function validateCoinlessPayload(p) {
1419
+ function validateWholePayload(p) {
1465
1420
  if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
1466
1421
  throw new SphereError(
1467
1422
  "token intent payload must name exactly one source token",
@@ -1471,7 +1426,7 @@ function validateCoinlessPayload(p) {
1471
1426
  if (p.split !== void 0) {
1472
1427
  throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
1473
1428
  }
1474
- return { ...p, kind: "coinless", direct: [p.direct[0]] };
1429
+ return { ...p, kind: "whole", direct: [p.direct[0]] };
1475
1430
  }
1476
1431
  function validatePayload(raw) {
1477
1432
  const p = raw !== null && typeof raw === "object" ? raw : null;
@@ -1485,13 +1440,13 @@ function validatePayload(raw) {
1485
1440
  throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
1486
1441
  }
1487
1442
  const kind = p.kind ?? "coin";
1488
- if (kind !== "coin" && kind !== "coinless") {
1443
+ if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
1489
1444
  throw new SphereError(
1490
1445
  `unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
1491
1446
  "VALIDATION_ERROR"
1492
1447
  );
1493
1448
  }
1494
- return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
1449
+ return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
1495
1450
  }
1496
1451
 
1497
1452
  // modules/payments-v2/convergence.ts
@@ -1704,8 +1659,9 @@ async function openRow(entry, journal, decryptPayload) {
1704
1659
  };
1705
1660
  }
1706
1661
  function subject(payload) {
1707
- if (payload?.kind === "coinless") {
1708
- const tokenId = payload.direct?.[0];
1662
+ const rawKind = payload?.kind;
1663
+ if (rawKind === "whole" || rawKind === "coinless") {
1664
+ const tokenId = payload?.direct?.[0];
1709
1665
  return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
1710
1666
  }
1711
1667
  const coin = payload;
@@ -1778,10 +1734,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
1778
1734
  function messageOf(err) {
1779
1735
  return err instanceof Error ? err.message : String(err);
1780
1736
  }
1781
- function buildCoinlessPayload(recipientPubkey, request, spentStates) {
1737
+ function buildWholePayload(recipientPubkey, request, spentStates) {
1782
1738
  return {
1783
1739
  v: 2,
1784
- kind: "coinless",
1740
+ kind: "whole",
1785
1741
  recipient: recipientPubkey,
1786
1742
  ...request.memo !== void 0 ? { memo: request.memo } : {},
1787
1743
  direct: [request.tokenId],
@@ -1850,8 +1806,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
1850
1806
  deps.refreshView();
1851
1807
  }
1852
1808
 
1853
- // modules/payments-v2/send-coinless.ts
1854
- async function materializeCoinlessSpend(deps, input) {
1809
+ // modules/payments-v2/inventory/presentation.ts
1810
+ function toToken(tokenId, snapshot, asset, registry, flags) {
1811
+ const iconUrl = registry.getIconUrl(asset.coinId);
1812
+ return {
1813
+ id: tokenId,
1814
+ coinId: asset.coinId,
1815
+ symbol: registry.getSymbol(asset.coinId),
1816
+ name: registry.getName(asset.coinId),
1817
+ decimals: registry.getDecimals(asset.coinId),
1818
+ ...iconUrl !== null ? { iconUrl } : {},
1819
+ amount: asset.amount,
1820
+ status: flags.transferring ? "transferring" : "confirmed",
1821
+ createdAt: snapshot.createdAt,
1822
+ updatedAt: snapshot.updatedAt,
1823
+ lazy: true,
1824
+ ...flags.suspectedSpent ? { suspectedSpent: true } : {}
1825
+ };
1826
+ }
1827
+ function aggregateAssets(entries, isTransferring, registry) {
1828
+ const groups = /* @__PURE__ */ new Map();
1829
+ for (const [tokenId, entry] of entries) {
1830
+ const moving = isTransferring(tokenId);
1831
+ for (const asset of entry.assets) {
1832
+ let group = groups.get(asset.coinId);
1833
+ if (!group) {
1834
+ group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
1835
+ groups.set(asset.coinId, group);
1836
+ }
1837
+ if (moving) {
1838
+ group.transferring += BigInt(asset.amount);
1839
+ group.transferringCount += 1;
1840
+ } else {
1841
+ group.total += BigInt(asset.amount);
1842
+ group.count += 1;
1843
+ }
1844
+ }
1845
+ }
1846
+ return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
1847
+ }
1848
+ function toAsset(coinId, totals, registry) {
1849
+ const iconUrl = registry.getIconUrl(coinId);
1850
+ return {
1851
+ coinId,
1852
+ symbol: registry.getSymbol(coinId),
1853
+ name: registry.getName(coinId),
1854
+ decimals: registry.getDecimals(coinId),
1855
+ ...iconUrl !== null ? { iconUrl } : {},
1856
+ totalAmount: totals.total.toString(),
1857
+ tokenCount: totals.count,
1858
+ confirmedAmount: totals.total.toString(),
1859
+ unconfirmedAmount: "0",
1860
+ confirmedTokenCount: totals.count,
1861
+ unconfirmedTokenCount: 0,
1862
+ transferringTokenCount: totals.transferringCount,
1863
+ transferringAmount: totals.transferring.toString(),
1864
+ priceUsd: null,
1865
+ priceEur: null,
1866
+ change24h: null,
1867
+ fiatValueUsd: null,
1868
+ fiatValueEur: null
1869
+ };
1870
+ }
1871
+ function priceIdOf(registry, coinId) {
1872
+ return registry.getName(coinId).toLowerCase();
1873
+ }
1874
+ async function withPrices(raw, registry, price) {
1875
+ try {
1876
+ const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
1877
+ const quotes = await price.getPrices(ids);
1878
+ return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
1879
+ } catch {
1880
+ return raw;
1881
+ }
1882
+ }
1883
+ function priceOne(asset, quote) {
1884
+ if (!quote) return asset;
1885
+ const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
1886
+ return {
1887
+ ...asset,
1888
+ priceUsd: quote.priceUsd,
1889
+ priceEur: quote.priceEur ?? null,
1890
+ change24h: quote.change24h ?? null,
1891
+ fiatValueUsd: human * quote.priceUsd,
1892
+ fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
1893
+ };
1894
+ }
1895
+ function transferringToken(tokenId, coinId, amount, registry, now) {
1896
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
1897
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
1898
+ transferring: true,
1899
+ suspectedSpent: false
1900
+ });
1901
+ }
1902
+
1903
+ // modules/payments-v2/send-whole.ts
1904
+ async function materializeWholeSpend(deps, input) {
1855
1905
  const { engine } = deps;
1856
1906
  const tokenId = input.request.tokenId;
1857
1907
  const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
@@ -1859,14 +1909,20 @@ async function materializeCoinlessSpend(deps, input) {
1859
1909
  throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
1860
1910
  }
1861
1911
  const token = await engine.decodeToken({ tokenId, token: bytes });
1862
- if (!isCoinlessEnvelope(token.valueEnvelope)) {
1912
+ if (token.valueEnvelope === "bare_collection") {
1913
+ throw new SphereError(
1914
+ `Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
1915
+ "VALIDATION_ERROR"
1916
+ );
1917
+ }
1918
+ if (input.requireCoinless && token.value !== null) {
1863
1919
  throw new SphereError(
1864
- `Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
1920
+ `Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
1865
1921
  "VALIDATION_ERROR"
1866
1922
  );
1867
1923
  }
1868
1924
  const keys = await engine.deliveryKeys(bytes);
1869
- const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
1925
+ const payload = buildWholePayload(input.recipientPubkey, input.request, {
1870
1926
  [tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
1871
1927
  });
1872
1928
  const plan = {
@@ -1877,7 +1933,10 @@ async function materializeCoinlessSpend(deps, input) {
1877
1933
  sources: /* @__PURE__ */ new Map([[tokenId, token]]),
1878
1934
  ...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
1879
1935
  };
1880
- return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
1936
+ const sourceTokens = (token.value?.assets ?? []).map(
1937
+ (asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
1938
+ );
1939
+ return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
1881
1940
  }
1882
1941
 
1883
1942
  // modules/payments-v2/send-errors.ts
@@ -2024,100 +2083,6 @@ async function takeSourceBlobs(deps, sourceIds) {
2024
2083
  return blobs;
2025
2084
  }
2026
2085
 
2027
- // modules/payments-v2/inventory/presentation.ts
2028
- function toToken(tokenId, snapshot, asset, registry, flags) {
2029
- const iconUrl = registry.getIconUrl(asset.coinId);
2030
- return {
2031
- id: tokenId,
2032
- coinId: asset.coinId,
2033
- symbol: registry.getSymbol(asset.coinId),
2034
- name: registry.getName(asset.coinId),
2035
- decimals: registry.getDecimals(asset.coinId),
2036
- ...iconUrl !== null ? { iconUrl } : {},
2037
- amount: asset.amount,
2038
- status: flags.transferring ? "transferring" : "confirmed",
2039
- createdAt: snapshot.createdAt,
2040
- updatedAt: snapshot.updatedAt,
2041
- lazy: true,
2042
- ...flags.suspectedSpent ? { suspectedSpent: true } : {}
2043
- };
2044
- }
2045
- function aggregateAssets(entries, isTransferring, registry) {
2046
- const groups = /* @__PURE__ */ new Map();
2047
- for (const [tokenId, entry] of entries) {
2048
- const moving = isTransferring(tokenId);
2049
- for (const asset of entry.assets) {
2050
- let group = groups.get(asset.coinId);
2051
- if (!group) {
2052
- group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
2053
- groups.set(asset.coinId, group);
2054
- }
2055
- if (moving) {
2056
- group.transferring += BigInt(asset.amount);
2057
- group.transferringCount += 1;
2058
- } else {
2059
- group.total += BigInt(asset.amount);
2060
- group.count += 1;
2061
- }
2062
- }
2063
- }
2064
- return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
2065
- }
2066
- function toAsset(coinId, totals, registry) {
2067
- const iconUrl = registry.getIconUrl(coinId);
2068
- return {
2069
- coinId,
2070
- symbol: registry.getSymbol(coinId),
2071
- name: registry.getName(coinId),
2072
- decimals: registry.getDecimals(coinId),
2073
- ...iconUrl !== null ? { iconUrl } : {},
2074
- totalAmount: totals.total.toString(),
2075
- tokenCount: totals.count,
2076
- confirmedAmount: totals.total.toString(),
2077
- unconfirmedAmount: "0",
2078
- confirmedTokenCount: totals.count,
2079
- unconfirmedTokenCount: 0,
2080
- transferringTokenCount: totals.transferringCount,
2081
- transferringAmount: totals.transferring.toString(),
2082
- priceUsd: null,
2083
- priceEur: null,
2084
- change24h: null,
2085
- fiatValueUsd: null,
2086
- fiatValueEur: null
2087
- };
2088
- }
2089
- function priceIdOf(registry, coinId) {
2090
- return registry.getName(coinId).toLowerCase();
2091
- }
2092
- async function withPrices(raw, registry, price) {
2093
- try {
2094
- const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
2095
- const quotes = await price.getPrices(ids);
2096
- return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
2097
- } catch {
2098
- return raw;
2099
- }
2100
- }
2101
- function priceOne(asset, quote) {
2102
- if (!quote) return asset;
2103
- const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
2104
- return {
2105
- ...asset,
2106
- priceUsd: quote.priceUsd,
2107
- priceEur: quote.priceEur ?? null,
2108
- change24h: quote.change24h ?? null,
2109
- fiatValueUsd: human * quote.priceUsd,
2110
- fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
2111
- };
2112
- }
2113
- function transferringToken(tokenId, coinId, amount, registry, now) {
2114
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
2115
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
2116
- transferring: true,
2117
- suspectedSpent: false
2118
- });
2119
- }
2120
-
2121
2086
  // node_modules/@noble/ciphers/utils.js
2122
2087
  function isBytes2(a) {
2123
2088
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
@@ -3223,10 +3188,10 @@ var InventoryView = class {
3223
3188
  if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
3224
3189
  return !this.suspected.has(stateKey(tokenId, entry.stateHash));
3225
3190
  }
3226
- /** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
3227
- spendableCoinless(tokenId) {
3191
+ /** Is this NAMED token spendable whole — coinless or valued (#777)? */
3192
+ spendableToken(tokenId) {
3228
3193
  const entry = this.mirror.get(tokenId);
3229
- return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
3194
+ return entry !== void 0 && this.isSpendable(tokenId, entry);
3230
3195
  }
3231
3196
  /**
3232
3197
  * Current mirrored state of an ACTIVE token, or undefined when it is unknown
@@ -3450,6 +3415,73 @@ var InventoryView = class {
3450
3415
  }
3451
3416
  };
3452
3417
 
3418
+ // token-engine/sdk.ts
3419
+ import { StateTransitionClient } from "@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js";
3420
+ import { AggregatorClient } from "@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js";
3421
+ import { NetworkId } from "@unicitylabs/state-transition-sdk/lib/api/NetworkId.js";
3422
+ import { CertificationData } from "@unicitylabs/state-transition-sdk/lib/api/CertificationData.js";
3423
+ import { CertificationResponse, CertificationStatus } from "@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js";
3424
+ import { StateId } from "@unicitylabs/state-transition-sdk/lib/api/StateId.js";
3425
+ import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js";
3426
+ import { InclusionProofResponse } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js";
3427
+ import { JsonRpcNetworkError } from "@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js";
3428
+ import { RootTrustBase } from "@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js";
3429
+ import { waitInclusionProof } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js";
3430
+ import { InclusionProofVerificationStatus } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js";
3431
+ import { Token } from "@unicitylabs/state-transition-sdk/lib/transaction/Token.js";
3432
+ import { MintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js";
3433
+ import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js";
3434
+ import { CertifiedMintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js";
3435
+ import { CertifiedTransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js";
3436
+ import { TokenId } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js";
3437
+ import { TokenType } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js";
3438
+ import { TokenSalt } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js";
3439
+ import { StateMask } from "@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js";
3440
+ import { MintJustificationVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js";
3441
+ import { TokenIssuanceVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js";
3442
+ import { VerificationContext } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js";
3443
+ import { UnicityCertificateVerifier } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js";
3444
+ import { UnicitySealQuorumSignaturesVerificationRule } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js";
3445
+ import { VerifiedSealCache } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js";
3446
+ import { Secp256k1SignatureVerifier } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js";
3447
+ import { WorkerTokenVerifier } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js";
3448
+ import { EncodedPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js";
3449
+ import { SignaturePredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js";
3450
+ import { SignaturePredicateUnlockScript } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
3451
+ import { BurnPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js";
3452
+ import { PredicateVerifierService } from "@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js";
3453
+ import { SigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js";
3454
+ import { Signature } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js";
3455
+ import { MintSigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js";
3456
+ import { HashAlgorithm } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js";
3457
+ import { DataHash } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js";
3458
+ import { DataHasher } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js";
3459
+ import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js";
3460
+ import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
3461
+ import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
3462
+ import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
3463
+ import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
3464
+ import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
3465
+ import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
3466
+ import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
3467
+ import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
3468
+ import { TokenSplit } from "@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js";
3469
+ import { SplitTokenRequest } from "@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js";
3470
+ import { SplitToken } from "@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js";
3471
+ import { SplitAllocationProof } from "@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js";
3472
+ import { SplitMintJustification } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js";
3473
+ import { SplitMintJustificationVerifier } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js";
3474
+ import { VerificationStatus } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js";
3475
+ import { VerificationResult } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js";
3476
+ import { HexConverter } from "@unicitylabs/state-transition-sdk/lib/util/HexConverter.js";
3477
+ import { BigintConverter } from "@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js";
3478
+ import { BitString } from "@unicitylabs/state-transition-sdk/lib/util/BitString.js";
3479
+
3480
+ // token-engine/value-envelope.ts
3481
+ function isCoinlessEnvelope(envelope) {
3482
+ return envelope.startsWith("none_");
3483
+ }
3484
+
3453
3485
  // modules/payments-v2/receive/Receive.ts
3454
3486
  var ACK_BATCH_SIZE = 200;
3455
3487
  var REFRESH_INTERVAL_MS = 2500;
@@ -3594,7 +3626,7 @@ async function screen(deps, engine, entry) {
3594
3626
  tokenId: keys.tokenId,
3595
3627
  stateHash: keys.stateHash,
3596
3628
  assets: toAssetAmounts(token),
3597
- ...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
3629
+ ...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
3598
3630
  }
3599
3631
  };
3600
3632
  }
@@ -3731,9 +3763,6 @@ function claimAck(entry) {
3731
3763
  function rejectAck(entry, reason) {
3732
3764
  return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
3733
3765
  }
3734
- function isCoinlessEnvelope2(envelope) {
3735
- return envelope.startsWith("none_");
3736
- }
3737
3766
  function toAssetAmounts(token) {
3738
3767
  return (token.value?.assets ?? []).map((asset) => ({
3739
3768
  coinId: asset.coinId,
@@ -4308,7 +4337,7 @@ var SpendQueue = class {
4308
4337
  * can release, so queueing would block until a timeout on a spend that cannot
4309
4338
  * become possible.
4310
4339
  */
4311
- planCoinless(reservationId, tokenId) {
4340
+ planWhole(reservationId, tokenId) {
4312
4341
  if (this.destroyed) {
4313
4342
  throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
4314
4343
  }
@@ -4316,11 +4345,8 @@ var SpendQueue = class {
4316
4345
  if (unproven !== null) {
4317
4346
  throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
4318
4347
  }
4319
- if (this.deps.spendableCoinless?.(tokenId) !== true) {
4320
- throw new SphereError(
4321
- `Token ${tokenId} is not a spendable coinless holding`,
4322
- "VALIDATION_ERROR"
4323
- );
4348
+ if (this.deps.spendableToken?.(tokenId) !== true) {
4349
+ throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
4324
4350
  }
4325
4351
  const holder = this.deps.ledger.holderOf(tokenId);
4326
4352
  if (holder !== void 0) {
@@ -4521,7 +4547,7 @@ function composeFacadeParts(deps, hooks) {
4521
4547
  const queue = new SpendQueue({
4522
4548
  ledger,
4523
4549
  getPool: (coinId) => view.pool(coinId),
4524
- spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
4550
+ spendableToken: (tokenId) => view.spendableToken(tokenId),
4525
4551
  ...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
4526
4552
  });
4527
4553
  const historyStore = new History({
@@ -4602,12 +4628,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
4602
4628
  ...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
4603
4629
  emit: deps.emit,
4604
4630
  now: deps.now ?? Date.now,
4605
- recordHistory: async ({ transferId, payload, committedAmount }) => {
4631
+ recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
4606
4632
  await historyStore.recordSent({
4607
4633
  transferId,
4608
4634
  // §5.9: the SETTLED amount, never payload.amount — the plan. A token
4609
4635
  // spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
4610
- ...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
4636
+ ...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
4611
4637
  recipientPubkey: payload.recipient,
4612
4638
  ...payload.memo !== void 0 ? { memo: payload.memo } : {}
4613
4639
  });
@@ -4822,10 +4848,14 @@ var PaymentsFacade = class {
4822
4848
  return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
4823
4849
  }
4824
4850
  send(request) {
4825
- return this.track(this.sendOutcome(request));
4851
+ return this.track(this.runJob({ kind: "coin", request }, request.amount));
4852
+ }
4853
+ sendWholeToken(request) {
4854
+ return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
4826
4855
  }
4856
+ /** NFT-scoped twin: refuses a valued source. */
4827
4857
  sendCoinless(request) {
4828
- return this.track(this.sendCoinlessOutcome(request));
4858
+ return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
4829
4859
  }
4830
4860
  async receive() {
4831
4861
  const transfers = await this.track(this.receiveLoop.drainOnce());
@@ -4873,13 +4903,6 @@ var PaymentsFacade = class {
4873
4903
  // ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
4874
4904
  /** The ONE place a send() outcome is shaped: success emits in finishSend, a
4875
4905
  * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
4876
- sendOutcome(request) {
4877
- return this.runJob({ kind: "coin", request }, request.amount);
4878
- }
4879
- /** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
4880
- sendCoinlessOutcome(request) {
4881
- return this.runJob({ kind: "coinless", request }, "0");
4882
- }
4883
4906
  async runJob(job, amount) {
4884
4907
  const run = {
4885
4908
  amount,
@@ -4924,7 +4947,7 @@ var PaymentsFacade = class {
4924
4947
  const disposition = await this.runAttempt(ctx);
4925
4948
  if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
4926
4949
  if (disposition.kind === "retry-full") {
4927
- if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
4950
+ if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
4928
4951
  continue;
4929
4952
  }
4930
4953
  if (disposition.kind === "success") {
@@ -5028,13 +5051,19 @@ var PaymentsFacade = class {
5028
5051
  async planAndMaterialize(recipientPubkey, job, run) {
5029
5052
  const transferId = this.newId();
5030
5053
  run.lastTransferId = transferId;
5031
- if (job.kind === "coinless") {
5032
- const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
5054
+ if (job.kind === "whole") {
5055
+ const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
5033
5056
  const sourceIds2 = this.markPlanned(transferId, "", spend2);
5034
5057
  try {
5035
- return await materializeCoinlessSpend(
5036
- { engine: this.engine(), storagePort: this.deps.storagePort },
5037
- { transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
5058
+ return await materializeWholeSpend(
5059
+ { engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
5060
+ {
5061
+ transferId,
5062
+ recipientPubkey,
5063
+ request: job.request,
5064
+ sourceIds: sourceIds2,
5065
+ requireCoinless: job.requireCoinless
5066
+ }
5038
5067
  );
5039
5068
  } catch (err) {
5040
5069
  this.settleFailure(transferId, "", sourceIds2);