@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/core/index.d.cts
CHANGED
|
@@ -89,7 +89,7 @@ interface SendRequest {
|
|
|
89
89
|
coinId: string;
|
|
90
90
|
memo?: string;
|
|
91
91
|
}
|
|
92
|
-
interface
|
|
92
|
+
interface SendWholeTokenRequest {
|
|
93
93
|
recipient: string;
|
|
94
94
|
tokenId: string;
|
|
95
95
|
memo?: string;
|
|
@@ -187,7 +187,9 @@ interface PaymentsV2 {
|
|
|
187
187
|
limit?: number;
|
|
188
188
|
}): Promise<HistoryPage>;
|
|
189
189
|
send(req: SendRequest): Promise<TransferResult>;
|
|
190
|
-
|
|
190
|
+
sendWholeToken(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
191
|
+
/** NFT-scoped twin: refuses a valued source. Connect's `send_nft` routes here. */
|
|
192
|
+
sendCoinless(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
191
193
|
mint(coinId: string, amount: bigint): Promise<MintResult>;
|
|
192
194
|
receive(): Promise<{
|
|
193
195
|
transfers: IncomingTransfer[];
|
package/dist/core/index.d.ts
CHANGED
|
@@ -89,7 +89,7 @@ interface SendRequest {
|
|
|
89
89
|
coinId: string;
|
|
90
90
|
memo?: string;
|
|
91
91
|
}
|
|
92
|
-
interface
|
|
92
|
+
interface SendWholeTokenRequest {
|
|
93
93
|
recipient: string;
|
|
94
94
|
tokenId: string;
|
|
95
95
|
memo?: string;
|
|
@@ -187,7 +187,9 @@ interface PaymentsV2 {
|
|
|
187
187
|
limit?: number;
|
|
188
188
|
}): Promise<HistoryPage>;
|
|
189
189
|
send(req: SendRequest): Promise<TransferResult>;
|
|
190
|
-
|
|
190
|
+
sendWholeToken(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
191
|
+
/** NFT-scoped twin: refuses a valued source. Connect's `send_nft` routes here. */
|
|
192
|
+
sendCoinless(req: SendWholeTokenRequest): Promise<TransferResult>;
|
|
191
193
|
mint(coinId: string, amount: bigint): Promise<MintResult>;
|
|
192
194
|
receive(): Promise<{
|
|
193
195
|
transfers: IncomingTransfer[];
|
package/dist/core/index.js
CHANGED
|
@@ -11803,6 +11803,9 @@ function createMachineStores(kv) {
|
|
|
11803
11803
|
}
|
|
11804
11804
|
|
|
11805
11805
|
// modules/payments-v2/machine/payload-view.ts
|
|
11806
|
+
function isWholeIntent(p) {
|
|
11807
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
11808
|
+
}
|
|
11806
11809
|
function splitOf(p) {
|
|
11807
11810
|
return p.kind === "coin" ? p.split : void 0;
|
|
11808
11811
|
}
|
|
@@ -12171,7 +12174,14 @@ var TransferMachine = class {
|
|
|
12171
12174
|
try {
|
|
12172
12175
|
const coinId = coinIdOf(payload);
|
|
12173
12176
|
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12174
|
-
await this.
|
|
12177
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
12178
|
+
await this.deps.recordHistory?.({
|
|
12179
|
+
transferId,
|
|
12180
|
+
payload,
|
|
12181
|
+
phase,
|
|
12182
|
+
committedAmount,
|
|
12183
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
12184
|
+
});
|
|
12175
12185
|
} catch {
|
|
12176
12186
|
}
|
|
12177
12187
|
try {
|
|
@@ -12185,6 +12195,18 @@ var TransferMachine = class {
|
|
|
12185
12195
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
12186
12196
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
12187
12197
|
*/
|
|
12198
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
12199
|
+
async settledAssets(engine, committed) {
|
|
12200
|
+
const total = /* @__PURE__ */ new Map();
|
|
12201
|
+
for (const outcome of committed) {
|
|
12202
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
12203
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
12204
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
12205
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
12209
|
+
}
|
|
12188
12210
|
async settledAmount(engine, coinId, committed) {
|
|
12189
12211
|
let total = 0n;
|
|
12190
12212
|
for (const outcome of committed) {
|
|
@@ -12324,7 +12346,7 @@ async function runOne(ctx, job, report) {
|
|
|
12324
12346
|
report.failed.push(job.transferId);
|
|
12325
12347
|
return;
|
|
12326
12348
|
}
|
|
12327
|
-
if (job.payload.
|
|
12349
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
12328
12350
|
logger.warn(
|
|
12329
12351
|
"PaymentsV2",
|
|
12330
12352
|
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
@@ -12372,7 +12394,7 @@ function validateCoinPayload(c) {
|
|
|
12372
12394
|
}
|
|
12373
12395
|
return { ...c, kind: "coin" };
|
|
12374
12396
|
}
|
|
12375
|
-
function
|
|
12397
|
+
function validateWholePayload(p) {
|
|
12376
12398
|
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12377
12399
|
throw new SphereError(
|
|
12378
12400
|
"token intent payload must name exactly one source token",
|
|
@@ -12382,7 +12404,7 @@ function validateCoinlessPayload(p) {
|
|
|
12382
12404
|
if (p.split !== void 0) {
|
|
12383
12405
|
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12384
12406
|
}
|
|
12385
|
-
return { ...p, kind: "
|
|
12407
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
12386
12408
|
}
|
|
12387
12409
|
function validatePayload(raw) {
|
|
12388
12410
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
@@ -12396,13 +12418,13 @@ function validatePayload(raw) {
|
|
|
12396
12418
|
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12397
12419
|
}
|
|
12398
12420
|
const kind = p.kind ?? "coin";
|
|
12399
|
-
if (kind !== "coin" && kind !== "coinless") {
|
|
12421
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
12400
12422
|
throw new SphereError(
|
|
12401
12423
|
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12402
12424
|
"VALIDATION_ERROR"
|
|
12403
12425
|
);
|
|
12404
12426
|
}
|
|
12405
|
-
return kind === "
|
|
12427
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
12406
12428
|
}
|
|
12407
12429
|
|
|
12408
12430
|
// modules/payments-v2/convergence.ts
|
|
@@ -12615,8 +12637,9 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12615
12637
|
};
|
|
12616
12638
|
}
|
|
12617
12639
|
function subject(payload) {
|
|
12618
|
-
|
|
12619
|
-
|
|
12640
|
+
const rawKind = payload?.kind;
|
|
12641
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
12642
|
+
const tokenId = payload?.direct?.[0];
|
|
12620
12643
|
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12621
12644
|
}
|
|
12622
12645
|
const coin = payload;
|
|
@@ -12689,10 +12712,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
|
12689
12712
|
function messageOf(err) {
|
|
12690
12713
|
return err instanceof Error ? err.message : String(err);
|
|
12691
12714
|
}
|
|
12692
|
-
function
|
|
12715
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
12693
12716
|
return {
|
|
12694
12717
|
v: 2,
|
|
12695
|
-
kind: "
|
|
12718
|
+
kind: "whole",
|
|
12696
12719
|
recipient: recipientPubkey,
|
|
12697
12720
|
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12698
12721
|
direct: [request.tokenId],
|
|
@@ -12761,8 +12784,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
|
12761
12784
|
deps.refreshView();
|
|
12762
12785
|
}
|
|
12763
12786
|
|
|
12764
|
-
// modules/payments-v2/
|
|
12765
|
-
|
|
12787
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
12788
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12789
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12790
|
+
return {
|
|
12791
|
+
id: tokenId,
|
|
12792
|
+
coinId: asset.coinId,
|
|
12793
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
12794
|
+
name: registry.getName(asset.coinId),
|
|
12795
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
12796
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12797
|
+
amount: asset.amount,
|
|
12798
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
12799
|
+
createdAt: snapshot.createdAt,
|
|
12800
|
+
updatedAt: snapshot.updatedAt,
|
|
12801
|
+
lazy: true,
|
|
12802
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12803
|
+
};
|
|
12804
|
+
}
|
|
12805
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
12806
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12807
|
+
for (const [tokenId, entry] of entries) {
|
|
12808
|
+
const moving = isTransferring(tokenId);
|
|
12809
|
+
for (const asset of entry.assets) {
|
|
12810
|
+
let group = groups.get(asset.coinId);
|
|
12811
|
+
if (!group) {
|
|
12812
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12813
|
+
groups.set(asset.coinId, group);
|
|
12814
|
+
}
|
|
12815
|
+
if (moving) {
|
|
12816
|
+
group.transferring += BigInt(asset.amount);
|
|
12817
|
+
group.transferringCount += 1;
|
|
12818
|
+
} else {
|
|
12819
|
+
group.total += BigInt(asset.amount);
|
|
12820
|
+
group.count += 1;
|
|
12821
|
+
}
|
|
12822
|
+
}
|
|
12823
|
+
}
|
|
12824
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12825
|
+
}
|
|
12826
|
+
function toAsset(coinId, totals, registry) {
|
|
12827
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
12828
|
+
return {
|
|
12829
|
+
coinId,
|
|
12830
|
+
symbol: registry.getSymbol(coinId),
|
|
12831
|
+
name: registry.getName(coinId),
|
|
12832
|
+
decimals: registry.getDecimals(coinId),
|
|
12833
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12834
|
+
totalAmount: totals.total.toString(),
|
|
12835
|
+
tokenCount: totals.count,
|
|
12836
|
+
confirmedAmount: totals.total.toString(),
|
|
12837
|
+
unconfirmedAmount: "0",
|
|
12838
|
+
confirmedTokenCount: totals.count,
|
|
12839
|
+
unconfirmedTokenCount: 0,
|
|
12840
|
+
transferringTokenCount: totals.transferringCount,
|
|
12841
|
+
transferringAmount: totals.transferring.toString(),
|
|
12842
|
+
priceUsd: null,
|
|
12843
|
+
priceEur: null,
|
|
12844
|
+
change24h: null,
|
|
12845
|
+
fiatValueUsd: null,
|
|
12846
|
+
fiatValueEur: null
|
|
12847
|
+
};
|
|
12848
|
+
}
|
|
12849
|
+
function priceIdOf(registry, coinId) {
|
|
12850
|
+
return registry.getName(coinId).toLowerCase();
|
|
12851
|
+
}
|
|
12852
|
+
async function withPrices(raw, registry, price) {
|
|
12853
|
+
try {
|
|
12854
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
12855
|
+
const quotes = await price.getPrices(ids);
|
|
12856
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
12857
|
+
} catch {
|
|
12858
|
+
return raw;
|
|
12859
|
+
}
|
|
12860
|
+
}
|
|
12861
|
+
function priceOne(asset, quote) {
|
|
12862
|
+
if (!quote) return asset;
|
|
12863
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
12864
|
+
return {
|
|
12865
|
+
...asset,
|
|
12866
|
+
priceUsd: quote.priceUsd,
|
|
12867
|
+
priceEur: quote.priceEur ?? null,
|
|
12868
|
+
change24h: quote.change24h ?? null,
|
|
12869
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
12870
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
12871
|
+
};
|
|
12872
|
+
}
|
|
12873
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
12874
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
12875
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
12876
|
+
transferring: true,
|
|
12877
|
+
suspectedSpent: false
|
|
12878
|
+
});
|
|
12879
|
+
}
|
|
12880
|
+
|
|
12881
|
+
// modules/payments-v2/send-whole.ts
|
|
12882
|
+
async function materializeWholeSpend(deps, input) {
|
|
12766
12883
|
const { engine } = deps;
|
|
12767
12884
|
const tokenId = input.request.tokenId;
|
|
12768
12885
|
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
@@ -12770,14 +12887,20 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12770
12887
|
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12771
12888
|
}
|
|
12772
12889
|
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12773
|
-
if (
|
|
12890
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
12891
|
+
throw new SphereError(
|
|
12892
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
12893
|
+
"VALIDATION_ERROR"
|
|
12894
|
+
);
|
|
12895
|
+
}
|
|
12896
|
+
if (input.requireCoinless && token.value !== null) {
|
|
12774
12897
|
throw new SphereError(
|
|
12775
|
-
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use
|
|
12898
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
12776
12899
|
"VALIDATION_ERROR"
|
|
12777
12900
|
);
|
|
12778
12901
|
}
|
|
12779
12902
|
const keys = await engine.deliveryKeys(bytes);
|
|
12780
|
-
const payload =
|
|
12903
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
12781
12904
|
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12782
12905
|
});
|
|
12783
12906
|
const plan = {
|
|
@@ -12788,7 +12911,10 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12788
12911
|
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12789
12912
|
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12790
12913
|
};
|
|
12791
|
-
|
|
12914
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
12915
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
12916
|
+
);
|
|
12917
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
12792
12918
|
}
|
|
12793
12919
|
|
|
12794
12920
|
// modules/payments-v2/send-errors.ts
|
|
@@ -12935,100 +13061,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
12935
13061
|
return blobs;
|
|
12936
13062
|
}
|
|
12937
13063
|
|
|
12938
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
12939
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12940
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12941
|
-
return {
|
|
12942
|
-
id: tokenId,
|
|
12943
|
-
coinId: asset.coinId,
|
|
12944
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
12945
|
-
name: registry.getName(asset.coinId),
|
|
12946
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
12947
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
12948
|
-
amount: asset.amount,
|
|
12949
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
12950
|
-
createdAt: snapshot.createdAt,
|
|
12951
|
-
updatedAt: snapshot.updatedAt,
|
|
12952
|
-
lazy: true,
|
|
12953
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12954
|
-
};
|
|
12955
|
-
}
|
|
12956
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
12957
|
-
const groups = /* @__PURE__ */ new Map();
|
|
12958
|
-
for (const [tokenId, entry] of entries) {
|
|
12959
|
-
const moving = isTransferring(tokenId);
|
|
12960
|
-
for (const asset of entry.assets) {
|
|
12961
|
-
let group = groups.get(asset.coinId);
|
|
12962
|
-
if (!group) {
|
|
12963
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12964
|
-
groups.set(asset.coinId, group);
|
|
12965
|
-
}
|
|
12966
|
-
if (moving) {
|
|
12967
|
-
group.transferring += BigInt(asset.amount);
|
|
12968
|
-
group.transferringCount += 1;
|
|
12969
|
-
} else {
|
|
12970
|
-
group.total += BigInt(asset.amount);
|
|
12971
|
-
group.count += 1;
|
|
12972
|
-
}
|
|
12973
|
-
}
|
|
12974
|
-
}
|
|
12975
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12976
|
-
}
|
|
12977
|
-
function toAsset(coinId, totals, registry) {
|
|
12978
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
12979
|
-
return {
|
|
12980
|
-
coinId,
|
|
12981
|
-
symbol: registry.getSymbol(coinId),
|
|
12982
|
-
name: registry.getName(coinId),
|
|
12983
|
-
decimals: registry.getDecimals(coinId),
|
|
12984
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
12985
|
-
totalAmount: totals.total.toString(),
|
|
12986
|
-
tokenCount: totals.count,
|
|
12987
|
-
confirmedAmount: totals.total.toString(),
|
|
12988
|
-
unconfirmedAmount: "0",
|
|
12989
|
-
confirmedTokenCount: totals.count,
|
|
12990
|
-
unconfirmedTokenCount: 0,
|
|
12991
|
-
transferringTokenCount: totals.transferringCount,
|
|
12992
|
-
transferringAmount: totals.transferring.toString(),
|
|
12993
|
-
priceUsd: null,
|
|
12994
|
-
priceEur: null,
|
|
12995
|
-
change24h: null,
|
|
12996
|
-
fiatValueUsd: null,
|
|
12997
|
-
fiatValueEur: null
|
|
12998
|
-
};
|
|
12999
|
-
}
|
|
13000
|
-
function priceIdOf(registry, coinId) {
|
|
13001
|
-
return registry.getName(coinId).toLowerCase();
|
|
13002
|
-
}
|
|
13003
|
-
async function withPrices(raw, registry, price) {
|
|
13004
|
-
try {
|
|
13005
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13006
|
-
const quotes = await price.getPrices(ids);
|
|
13007
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13008
|
-
} catch {
|
|
13009
|
-
return raw;
|
|
13010
|
-
}
|
|
13011
|
-
}
|
|
13012
|
-
function priceOne(asset, quote) {
|
|
13013
|
-
if (!quote) return asset;
|
|
13014
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
13015
|
-
return {
|
|
13016
|
-
...asset,
|
|
13017
|
-
priceUsd: quote.priceUsd,
|
|
13018
|
-
priceEur: quote.priceEur ?? null,
|
|
13019
|
-
change24h: quote.change24h ?? null,
|
|
13020
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
13021
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
13022
|
-
};
|
|
13023
|
-
}
|
|
13024
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
13025
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13026
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13027
|
-
transferring: true,
|
|
13028
|
-
suspectedSpent: false
|
|
13029
|
-
});
|
|
13030
|
-
}
|
|
13031
|
-
|
|
13032
13064
|
// modules/payments-v2/history/History.ts
|
|
13033
13065
|
function wireAssets(assets) {
|
|
13034
13066
|
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
@@ -13284,10 +13316,10 @@ var InventoryView = class {
|
|
|
13284
13316
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13285
13317
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13286
13318
|
}
|
|
13287
|
-
/**
|
|
13288
|
-
|
|
13319
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
13320
|
+
spendableToken(tokenId) {
|
|
13289
13321
|
const entry = this.mirror.get(tokenId);
|
|
13290
|
-
return entry !== void 0 &&
|
|
13322
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
13291
13323
|
}
|
|
13292
13324
|
/**
|
|
13293
13325
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
@@ -13660,7 +13692,7 @@ async function screen(deps, engine, entry) {
|
|
|
13660
13692
|
tokenId: keys.tokenId,
|
|
13661
13693
|
stateHash: keys.stateHash,
|
|
13662
13694
|
assets: toAssetAmounts(token),
|
|
13663
|
-
...
|
|
13695
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13664
13696
|
}
|
|
13665
13697
|
};
|
|
13666
13698
|
}
|
|
@@ -13797,9 +13829,6 @@ function claimAck(entry) {
|
|
|
13797
13829
|
function rejectAck(entry, reason) {
|
|
13798
13830
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13799
13831
|
}
|
|
13800
|
-
function isCoinlessEnvelope2(envelope) {
|
|
13801
|
-
return envelope.startsWith("none_");
|
|
13802
|
-
}
|
|
13803
13832
|
function toAssetAmounts(token) {
|
|
13804
13833
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13805
13834
|
coinId: asset.coinId,
|
|
@@ -14374,7 +14403,7 @@ var SpendQueue = class {
|
|
|
14374
14403
|
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14375
14404
|
* become possible.
|
|
14376
14405
|
*/
|
|
14377
|
-
|
|
14406
|
+
planWhole(reservationId, tokenId) {
|
|
14378
14407
|
if (this.destroyed) {
|
|
14379
14408
|
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14380
14409
|
}
|
|
@@ -14382,11 +14411,8 @@ var SpendQueue = class {
|
|
|
14382
14411
|
if (unproven !== null) {
|
|
14383
14412
|
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14384
14413
|
}
|
|
14385
|
-
if (this.deps.
|
|
14386
|
-
throw new SphereError(
|
|
14387
|
-
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14388
|
-
"VALIDATION_ERROR"
|
|
14389
|
-
);
|
|
14414
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
14415
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
14390
14416
|
}
|
|
14391
14417
|
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14392
14418
|
if (holder !== void 0) {
|
|
@@ -14587,7 +14613,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14587
14613
|
const queue = new SpendQueue({
|
|
14588
14614
|
ledger,
|
|
14589
14615
|
getPool: (coinId) => view.pool(coinId),
|
|
14590
|
-
|
|
14616
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
14591
14617
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14592
14618
|
});
|
|
14593
14619
|
const historyStore = new History({
|
|
@@ -14668,12 +14694,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14668
14694
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
14669
14695
|
emit: deps.emit,
|
|
14670
14696
|
now: deps.now ?? Date.now,
|
|
14671
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14697
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
14672
14698
|
await historyStore.recordSent({
|
|
14673
14699
|
transferId,
|
|
14674
14700
|
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14675
14701
|
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14676
|
-
...payload
|
|
14702
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14677
14703
|
recipientPubkey: payload.recipient,
|
|
14678
14704
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14679
14705
|
});
|
|
@@ -14888,10 +14914,14 @@ var PaymentsFacade = class {
|
|
|
14888
14914
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14889
14915
|
}
|
|
14890
14916
|
send(request) {
|
|
14891
|
-
return this.track(this.
|
|
14917
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
14918
|
+
}
|
|
14919
|
+
sendWholeToken(request) {
|
|
14920
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
14892
14921
|
}
|
|
14922
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
14893
14923
|
sendCoinless(request) {
|
|
14894
|
-
return this.track(this.
|
|
14924
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
14895
14925
|
}
|
|
14896
14926
|
async receive() {
|
|
14897
14927
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -14939,13 +14969,6 @@ var PaymentsFacade = class {
|
|
|
14939
14969
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14940
14970
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14941
14971
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14942
|
-
sendOutcome(request) {
|
|
14943
|
-
return this.runJob({ kind: "coin", request }, request.amount);
|
|
14944
|
-
}
|
|
14945
|
-
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
14946
|
-
sendCoinlessOutcome(request) {
|
|
14947
|
-
return this.runJob({ kind: "coinless", request }, "0");
|
|
14948
|
-
}
|
|
14949
14972
|
async runJob(job, amount) {
|
|
14950
14973
|
const run = {
|
|
14951
14974
|
amount,
|
|
@@ -14990,7 +15013,7 @@ var PaymentsFacade = class {
|
|
|
14990
15013
|
const disposition = await this.runAttempt(ctx);
|
|
14991
15014
|
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
14992
15015
|
if (disposition.kind === "retry-full") {
|
|
14993
|
-
if (job.kind === "
|
|
15016
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
14994
15017
|
continue;
|
|
14995
15018
|
}
|
|
14996
15019
|
if (disposition.kind === "success") {
|
|
@@ -15094,13 +15117,19 @@ var PaymentsFacade = class {
|
|
|
15094
15117
|
async planAndMaterialize(recipientPubkey, job, run) {
|
|
15095
15118
|
const transferId = this.newId();
|
|
15096
15119
|
run.lastTransferId = transferId;
|
|
15097
|
-
if (job.kind === "
|
|
15098
|
-
const spend2 = this.queue.
|
|
15120
|
+
if (job.kind === "whole") {
|
|
15121
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
15099
15122
|
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15100
15123
|
try {
|
|
15101
|
-
return await
|
|
15102
|
-
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15103
|
-
{
|
|
15124
|
+
return await materializeWholeSpend(
|
|
15125
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
15126
|
+
{
|
|
15127
|
+
transferId,
|
|
15128
|
+
recipientPubkey,
|
|
15129
|
+
request: job.request,
|
|
15130
|
+
sourceIds: sourceIds2,
|
|
15131
|
+
requireCoinless: job.requireCoinless
|
|
15132
|
+
}
|
|
15104
15133
|
);
|
|
15105
15134
|
} catch (err) {
|
|
15106
15135
|
this.settleFailure(transferId, "", sourceIds2);
|