@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.
- package/dist/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +170 -141
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -2
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.js +170 -141
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/index.cjs +170 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +170 -141
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +243 -214
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +13 -11
- package/dist/modules/payments-v2/index.d.ts +13 -11
- package/dist/modules/payments-v2/index.js +243 -214
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -88,7 +88,7 @@ interface SendRequest {
|
|
|
88
88
|
coinId: string;
|
|
89
89
|
memo?: string;
|
|
90
90
|
}
|
|
91
|
-
interface
|
|
91
|
+
interface SendWholeTokenRequest {
|
|
92
92
|
recipient: string;
|
|
93
93
|
tokenId: string;
|
|
94
94
|
memo?: string;
|
|
@@ -186,7 +186,9 @@ interface PaymentsV2 {
|
|
|
186
186
|
limit?: number;
|
|
187
187
|
}): Promise<HistoryPage>;
|
|
188
188
|
send(req: SendRequest): Promise<TransferResult>;
|
|
189
|
-
|
|
189
|
+
sendWholeToken(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
190
|
+
/** NFT-scoped twin: refuses a valued source. Connect's `send_nft` routes here. */
|
|
191
|
+
sendCoinless(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
190
192
|
mint(coinId: string, amount: bigint): Promise<MintResult>;
|
|
191
193
|
receive(): Promise<{
|
|
192
194
|
transfers: IncomingTransfer[];
|
package/dist/index.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ interface SendRequest {
|
|
|
88
88
|
coinId: string;
|
|
89
89
|
memo?: string;
|
|
90
90
|
}
|
|
91
|
-
interface
|
|
91
|
+
interface SendWholeTokenRequest {
|
|
92
92
|
recipient: string;
|
|
93
93
|
tokenId: string;
|
|
94
94
|
memo?: string;
|
|
@@ -186,7 +186,9 @@ interface PaymentsV2 {
|
|
|
186
186
|
limit?: number;
|
|
187
187
|
}): Promise<HistoryPage>;
|
|
188
188
|
send(req: SendRequest): Promise<TransferResult>;
|
|
189
|
-
|
|
189
|
+
sendWholeToken(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
190
|
+
/** NFT-scoped twin: refuses a valued source. Connect's `send_nft` routes here. */
|
|
191
|
+
sendCoinless(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
190
192
|
mint(coinId: string, amount: bigint): Promise<MintResult>;
|
|
191
193
|
receive(): Promise<{
|
|
192
194
|
transfers: IncomingTransfer[];
|
package/dist/index.js
CHANGED
|
@@ -11821,6 +11821,9 @@ function createMachineStores(kv) {
|
|
|
11821
11821
|
}
|
|
11822
11822
|
|
|
11823
11823
|
// modules/payments-v2/machine/payload-view.ts
|
|
11824
|
+
function isWholeIntent(p) {
|
|
11825
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
11826
|
+
}
|
|
11824
11827
|
function splitOf(p) {
|
|
11825
11828
|
return p.kind === "coin" ? p.split : void 0;
|
|
11826
11829
|
}
|
|
@@ -12189,7 +12192,14 @@ var TransferMachine = class {
|
|
|
12189
12192
|
try {
|
|
12190
12193
|
const coinId = coinIdOf(payload);
|
|
12191
12194
|
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12192
|
-
await this.
|
|
12195
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
12196
|
+
await this.deps.recordHistory?.({
|
|
12197
|
+
transferId,
|
|
12198
|
+
payload,
|
|
12199
|
+
phase,
|
|
12200
|
+
committedAmount,
|
|
12201
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
12202
|
+
});
|
|
12193
12203
|
} catch {
|
|
12194
12204
|
}
|
|
12195
12205
|
try {
|
|
@@ -12203,6 +12213,18 @@ var TransferMachine = class {
|
|
|
12203
12213
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
12204
12214
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
12205
12215
|
*/
|
|
12216
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
12217
|
+
async settledAssets(engine, committed) {
|
|
12218
|
+
const total = /* @__PURE__ */ new Map();
|
|
12219
|
+
for (const outcome of committed) {
|
|
12220
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
12221
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
12222
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
12223
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
12227
|
+
}
|
|
12206
12228
|
async settledAmount(engine, coinId, committed) {
|
|
12207
12229
|
let total = 0n;
|
|
12208
12230
|
for (const outcome of committed) {
|
|
@@ -12342,7 +12364,7 @@ async function runOne(ctx, job, report) {
|
|
|
12342
12364
|
report.failed.push(job.transferId);
|
|
12343
12365
|
return;
|
|
12344
12366
|
}
|
|
12345
|
-
if (job.payload.
|
|
12367
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
12346
12368
|
logger.warn(
|
|
12347
12369
|
"PaymentsV2",
|
|
12348
12370
|
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
@@ -12390,7 +12412,7 @@ function validateCoinPayload(c) {
|
|
|
12390
12412
|
}
|
|
12391
12413
|
return { ...c, kind: "coin" };
|
|
12392
12414
|
}
|
|
12393
|
-
function
|
|
12415
|
+
function validateWholePayload(p) {
|
|
12394
12416
|
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12395
12417
|
throw new SphereError(
|
|
12396
12418
|
"token intent payload must name exactly one source token",
|
|
@@ -12400,7 +12422,7 @@ function validateCoinlessPayload(p) {
|
|
|
12400
12422
|
if (p.split !== void 0) {
|
|
12401
12423
|
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12402
12424
|
}
|
|
12403
|
-
return { ...p, kind: "
|
|
12425
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
12404
12426
|
}
|
|
12405
12427
|
function validatePayload(raw) {
|
|
12406
12428
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
@@ -12414,13 +12436,13 @@ function validatePayload(raw) {
|
|
|
12414
12436
|
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12415
12437
|
}
|
|
12416
12438
|
const kind = p.kind ?? "coin";
|
|
12417
|
-
if (kind !== "coin" && kind !== "coinless") {
|
|
12439
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
12418
12440
|
throw new SphereError(
|
|
12419
12441
|
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12420
12442
|
"VALIDATION_ERROR"
|
|
12421
12443
|
);
|
|
12422
12444
|
}
|
|
12423
|
-
return kind === "
|
|
12445
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
12424
12446
|
}
|
|
12425
12447
|
|
|
12426
12448
|
// modules/payments-v2/convergence.ts
|
|
@@ -12633,8 +12655,9 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12633
12655
|
};
|
|
12634
12656
|
}
|
|
12635
12657
|
function subject(payload) {
|
|
12636
|
-
|
|
12637
|
-
|
|
12658
|
+
const rawKind = payload?.kind;
|
|
12659
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
12660
|
+
const tokenId = payload?.direct?.[0];
|
|
12638
12661
|
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12639
12662
|
}
|
|
12640
12663
|
const coin = payload;
|
|
@@ -12707,10 +12730,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
|
12707
12730
|
function messageOf(err) {
|
|
12708
12731
|
return err instanceof Error ? err.message : String(err);
|
|
12709
12732
|
}
|
|
12710
|
-
function
|
|
12733
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
12711
12734
|
return {
|
|
12712
12735
|
v: 2,
|
|
12713
|
-
kind: "
|
|
12736
|
+
kind: "whole",
|
|
12714
12737
|
recipient: recipientPubkey,
|
|
12715
12738
|
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12716
12739
|
direct: [request.tokenId],
|
|
@@ -12779,8 +12802,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
|
12779
12802
|
deps.refreshView();
|
|
12780
12803
|
}
|
|
12781
12804
|
|
|
12782
|
-
// modules/payments-v2/
|
|
12783
|
-
|
|
12805
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
12806
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12807
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12808
|
+
return {
|
|
12809
|
+
id: tokenId,
|
|
12810
|
+
coinId: asset.coinId,
|
|
12811
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
12812
|
+
name: registry.getName(asset.coinId),
|
|
12813
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
12814
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12815
|
+
amount: asset.amount,
|
|
12816
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
12817
|
+
createdAt: snapshot.createdAt,
|
|
12818
|
+
updatedAt: snapshot.updatedAt,
|
|
12819
|
+
lazy: true,
|
|
12820
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12821
|
+
};
|
|
12822
|
+
}
|
|
12823
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
12824
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12825
|
+
for (const [tokenId, entry] of entries) {
|
|
12826
|
+
const moving = isTransferring(tokenId);
|
|
12827
|
+
for (const asset of entry.assets) {
|
|
12828
|
+
let group = groups.get(asset.coinId);
|
|
12829
|
+
if (!group) {
|
|
12830
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12831
|
+
groups.set(asset.coinId, group);
|
|
12832
|
+
}
|
|
12833
|
+
if (moving) {
|
|
12834
|
+
group.transferring += BigInt(asset.amount);
|
|
12835
|
+
group.transferringCount += 1;
|
|
12836
|
+
} else {
|
|
12837
|
+
group.total += BigInt(asset.amount);
|
|
12838
|
+
group.count += 1;
|
|
12839
|
+
}
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12843
|
+
}
|
|
12844
|
+
function toAsset(coinId, totals, registry) {
|
|
12845
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
12846
|
+
return {
|
|
12847
|
+
coinId,
|
|
12848
|
+
symbol: registry.getSymbol(coinId),
|
|
12849
|
+
name: registry.getName(coinId),
|
|
12850
|
+
decimals: registry.getDecimals(coinId),
|
|
12851
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12852
|
+
totalAmount: totals.total.toString(),
|
|
12853
|
+
tokenCount: totals.count,
|
|
12854
|
+
confirmedAmount: totals.total.toString(),
|
|
12855
|
+
unconfirmedAmount: "0",
|
|
12856
|
+
confirmedTokenCount: totals.count,
|
|
12857
|
+
unconfirmedTokenCount: 0,
|
|
12858
|
+
transferringTokenCount: totals.transferringCount,
|
|
12859
|
+
transferringAmount: totals.transferring.toString(),
|
|
12860
|
+
priceUsd: null,
|
|
12861
|
+
priceEur: null,
|
|
12862
|
+
change24h: null,
|
|
12863
|
+
fiatValueUsd: null,
|
|
12864
|
+
fiatValueEur: null
|
|
12865
|
+
};
|
|
12866
|
+
}
|
|
12867
|
+
function priceIdOf(registry, coinId) {
|
|
12868
|
+
return registry.getName(coinId).toLowerCase();
|
|
12869
|
+
}
|
|
12870
|
+
async function withPrices(raw, registry, price) {
|
|
12871
|
+
try {
|
|
12872
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
12873
|
+
const quotes = await price.getPrices(ids);
|
|
12874
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
12875
|
+
} catch {
|
|
12876
|
+
return raw;
|
|
12877
|
+
}
|
|
12878
|
+
}
|
|
12879
|
+
function priceOne(asset, quote) {
|
|
12880
|
+
if (!quote) return asset;
|
|
12881
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
12882
|
+
return {
|
|
12883
|
+
...asset,
|
|
12884
|
+
priceUsd: quote.priceUsd,
|
|
12885
|
+
priceEur: quote.priceEur ?? null,
|
|
12886
|
+
change24h: quote.change24h ?? null,
|
|
12887
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
12888
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
12889
|
+
};
|
|
12890
|
+
}
|
|
12891
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
12892
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
12893
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
12894
|
+
transferring: true,
|
|
12895
|
+
suspectedSpent: false
|
|
12896
|
+
});
|
|
12897
|
+
}
|
|
12898
|
+
|
|
12899
|
+
// modules/payments-v2/send-whole.ts
|
|
12900
|
+
async function materializeWholeSpend(deps, input) {
|
|
12784
12901
|
const { engine } = deps;
|
|
12785
12902
|
const tokenId = input.request.tokenId;
|
|
12786
12903
|
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
@@ -12788,14 +12905,20 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12788
12905
|
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12789
12906
|
}
|
|
12790
12907
|
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12791
|
-
if (
|
|
12908
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
12909
|
+
throw new SphereError(
|
|
12910
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
12911
|
+
"VALIDATION_ERROR"
|
|
12912
|
+
);
|
|
12913
|
+
}
|
|
12914
|
+
if (input.requireCoinless && token.value !== null) {
|
|
12792
12915
|
throw new SphereError(
|
|
12793
|
-
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use
|
|
12916
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
12794
12917
|
"VALIDATION_ERROR"
|
|
12795
12918
|
);
|
|
12796
12919
|
}
|
|
12797
12920
|
const keys = await engine.deliveryKeys(bytes);
|
|
12798
|
-
const payload =
|
|
12921
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
12799
12922
|
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12800
12923
|
});
|
|
12801
12924
|
const plan = {
|
|
@@ -12806,7 +12929,10 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12806
12929
|
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12807
12930
|
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12808
12931
|
};
|
|
12809
|
-
|
|
12932
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
12933
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
12934
|
+
);
|
|
12935
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
12810
12936
|
}
|
|
12811
12937
|
|
|
12812
12938
|
// modules/payments-v2/send-errors.ts
|
|
@@ -12953,100 +13079,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
12953
13079
|
return blobs;
|
|
12954
13080
|
}
|
|
12955
13081
|
|
|
12956
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
12957
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12958
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12959
|
-
return {
|
|
12960
|
-
id: tokenId,
|
|
12961
|
-
coinId: asset.coinId,
|
|
12962
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
12963
|
-
name: registry.getName(asset.coinId),
|
|
12964
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
12965
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
12966
|
-
amount: asset.amount,
|
|
12967
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
12968
|
-
createdAt: snapshot.createdAt,
|
|
12969
|
-
updatedAt: snapshot.updatedAt,
|
|
12970
|
-
lazy: true,
|
|
12971
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12972
|
-
};
|
|
12973
|
-
}
|
|
12974
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
12975
|
-
const groups = /* @__PURE__ */ new Map();
|
|
12976
|
-
for (const [tokenId, entry] of entries) {
|
|
12977
|
-
const moving = isTransferring(tokenId);
|
|
12978
|
-
for (const asset of entry.assets) {
|
|
12979
|
-
let group = groups.get(asset.coinId);
|
|
12980
|
-
if (!group) {
|
|
12981
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12982
|
-
groups.set(asset.coinId, group);
|
|
12983
|
-
}
|
|
12984
|
-
if (moving) {
|
|
12985
|
-
group.transferring += BigInt(asset.amount);
|
|
12986
|
-
group.transferringCount += 1;
|
|
12987
|
-
} else {
|
|
12988
|
-
group.total += BigInt(asset.amount);
|
|
12989
|
-
group.count += 1;
|
|
12990
|
-
}
|
|
12991
|
-
}
|
|
12992
|
-
}
|
|
12993
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12994
|
-
}
|
|
12995
|
-
function toAsset(coinId, totals, registry) {
|
|
12996
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
12997
|
-
return {
|
|
12998
|
-
coinId,
|
|
12999
|
-
symbol: registry.getSymbol(coinId),
|
|
13000
|
-
name: registry.getName(coinId),
|
|
13001
|
-
decimals: registry.getDecimals(coinId),
|
|
13002
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
13003
|
-
totalAmount: totals.total.toString(),
|
|
13004
|
-
tokenCount: totals.count,
|
|
13005
|
-
confirmedAmount: totals.total.toString(),
|
|
13006
|
-
unconfirmedAmount: "0",
|
|
13007
|
-
confirmedTokenCount: totals.count,
|
|
13008
|
-
unconfirmedTokenCount: 0,
|
|
13009
|
-
transferringTokenCount: totals.transferringCount,
|
|
13010
|
-
transferringAmount: totals.transferring.toString(),
|
|
13011
|
-
priceUsd: null,
|
|
13012
|
-
priceEur: null,
|
|
13013
|
-
change24h: null,
|
|
13014
|
-
fiatValueUsd: null,
|
|
13015
|
-
fiatValueEur: null
|
|
13016
|
-
};
|
|
13017
|
-
}
|
|
13018
|
-
function priceIdOf(registry, coinId) {
|
|
13019
|
-
return registry.getName(coinId).toLowerCase();
|
|
13020
|
-
}
|
|
13021
|
-
async function withPrices(raw, registry, price) {
|
|
13022
|
-
try {
|
|
13023
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13024
|
-
const quotes = await price.getPrices(ids);
|
|
13025
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13026
|
-
} catch {
|
|
13027
|
-
return raw;
|
|
13028
|
-
}
|
|
13029
|
-
}
|
|
13030
|
-
function priceOne(asset, quote) {
|
|
13031
|
-
if (!quote) return asset;
|
|
13032
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
13033
|
-
return {
|
|
13034
|
-
...asset,
|
|
13035
|
-
priceUsd: quote.priceUsd,
|
|
13036
|
-
priceEur: quote.priceEur ?? null,
|
|
13037
|
-
change24h: quote.change24h ?? null,
|
|
13038
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
13039
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
13040
|
-
};
|
|
13041
|
-
}
|
|
13042
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
13043
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13044
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13045
|
-
transferring: true,
|
|
13046
|
-
suspectedSpent: false
|
|
13047
|
-
});
|
|
13048
|
-
}
|
|
13049
|
-
|
|
13050
13082
|
// modules/payments-v2/history/History.ts
|
|
13051
13083
|
function wireAssets(assets) {
|
|
13052
13084
|
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
@@ -13302,10 +13334,10 @@ var InventoryView = class {
|
|
|
13302
13334
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13303
13335
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13304
13336
|
}
|
|
13305
|
-
/**
|
|
13306
|
-
|
|
13337
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
13338
|
+
spendableToken(tokenId) {
|
|
13307
13339
|
const entry = this.mirror.get(tokenId);
|
|
13308
|
-
return entry !== void 0 &&
|
|
13340
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
13309
13341
|
}
|
|
13310
13342
|
/**
|
|
13311
13343
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
@@ -13678,7 +13710,7 @@ async function screen(deps, engine, entry) {
|
|
|
13678
13710
|
tokenId: keys.tokenId,
|
|
13679
13711
|
stateHash: keys.stateHash,
|
|
13680
13712
|
assets: toAssetAmounts(token),
|
|
13681
|
-
...
|
|
13713
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13682
13714
|
}
|
|
13683
13715
|
};
|
|
13684
13716
|
}
|
|
@@ -13815,9 +13847,6 @@ function claimAck(entry) {
|
|
|
13815
13847
|
function rejectAck(entry, reason) {
|
|
13816
13848
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13817
13849
|
}
|
|
13818
|
-
function isCoinlessEnvelope2(envelope) {
|
|
13819
|
-
return envelope.startsWith("none_");
|
|
13820
|
-
}
|
|
13821
13850
|
function toAssetAmounts(token) {
|
|
13822
13851
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13823
13852
|
coinId: asset.coinId,
|
|
@@ -14392,7 +14421,7 @@ var SpendQueue = class {
|
|
|
14392
14421
|
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14393
14422
|
* become possible.
|
|
14394
14423
|
*/
|
|
14395
|
-
|
|
14424
|
+
planWhole(reservationId, tokenId) {
|
|
14396
14425
|
if (this.destroyed) {
|
|
14397
14426
|
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14398
14427
|
}
|
|
@@ -14400,11 +14429,8 @@ var SpendQueue = class {
|
|
|
14400
14429
|
if (unproven !== null) {
|
|
14401
14430
|
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14402
14431
|
}
|
|
14403
|
-
if (this.deps.
|
|
14404
|
-
throw new SphereError(
|
|
14405
|
-
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14406
|
-
"VALIDATION_ERROR"
|
|
14407
|
-
);
|
|
14432
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
14433
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
14408
14434
|
}
|
|
14409
14435
|
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14410
14436
|
if (holder !== void 0) {
|
|
@@ -14605,7 +14631,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14605
14631
|
const queue = new SpendQueue({
|
|
14606
14632
|
ledger,
|
|
14607
14633
|
getPool: (coinId) => view.pool(coinId),
|
|
14608
|
-
|
|
14634
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
14609
14635
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14610
14636
|
});
|
|
14611
14637
|
const historyStore = new History({
|
|
@@ -14686,12 +14712,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14686
14712
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
14687
14713
|
emit: deps.emit,
|
|
14688
14714
|
now: deps.now ?? Date.now,
|
|
14689
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14715
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
14690
14716
|
await historyStore.recordSent({
|
|
14691
14717
|
transferId,
|
|
14692
14718
|
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14693
14719
|
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14694
|
-
...payload
|
|
14720
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14695
14721
|
recipientPubkey: payload.recipient,
|
|
14696
14722
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14697
14723
|
});
|
|
@@ -14906,10 +14932,14 @@ var PaymentsFacade = class {
|
|
|
14906
14932
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14907
14933
|
}
|
|
14908
14934
|
send(request) {
|
|
14909
|
-
return this.track(this.
|
|
14935
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
14936
|
+
}
|
|
14937
|
+
sendWholeToken(request) {
|
|
14938
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
14910
14939
|
}
|
|
14940
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
14911
14941
|
sendCoinless(request) {
|
|
14912
|
-
return this.track(this.
|
|
14942
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
14913
14943
|
}
|
|
14914
14944
|
async receive() {
|
|
14915
14945
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -14957,13 +14987,6 @@ var PaymentsFacade = class {
|
|
|
14957
14987
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14958
14988
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14959
14989
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14960
|
-
sendOutcome(request) {
|
|
14961
|
-
return this.runJob({ kind: "coin", request }, request.amount);
|
|
14962
|
-
}
|
|
14963
|
-
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
14964
|
-
sendCoinlessOutcome(request) {
|
|
14965
|
-
return this.runJob({ kind: "coinless", request }, "0");
|
|
14966
|
-
}
|
|
14967
14990
|
async runJob(job, amount) {
|
|
14968
14991
|
const run = {
|
|
14969
14992
|
amount,
|
|
@@ -15008,7 +15031,7 @@ var PaymentsFacade = class {
|
|
|
15008
15031
|
const disposition = await this.runAttempt(ctx);
|
|
15009
15032
|
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
15010
15033
|
if (disposition.kind === "retry-full") {
|
|
15011
|
-
if (job.kind === "
|
|
15034
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
15012
15035
|
continue;
|
|
15013
15036
|
}
|
|
15014
15037
|
if (disposition.kind === "success") {
|
|
@@ -15112,13 +15135,19 @@ var PaymentsFacade = class {
|
|
|
15112
15135
|
async planAndMaterialize(recipientPubkey, job, run) {
|
|
15113
15136
|
const transferId = this.newId();
|
|
15114
15137
|
run.lastTransferId = transferId;
|
|
15115
|
-
if (job.kind === "
|
|
15116
|
-
const spend2 = this.queue.
|
|
15138
|
+
if (job.kind === "whole") {
|
|
15139
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
15117
15140
|
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15118
15141
|
try {
|
|
15119
|
-
return await
|
|
15120
|
-
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15121
|
-
{
|
|
15142
|
+
return await materializeWholeSpend(
|
|
15143
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
15144
|
+
{
|
|
15145
|
+
transferId,
|
|
15146
|
+
recipientPubkey,
|
|
15147
|
+
request: job.request,
|
|
15148
|
+
sourceIds: sourceIds2,
|
|
15149
|
+
requireCoinless: job.requireCoinless
|
|
15150
|
+
}
|
|
15122
15151
|
);
|
|
15123
15152
|
} catch (err) {
|
|
15124
15153
|
this.settleFailure(transferId, "", sourceIds2);
|