@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.cjs
CHANGED
|
@@ -11948,6 +11948,9 @@ function createMachineStores(kv) {
|
|
|
11948
11948
|
}
|
|
11949
11949
|
|
|
11950
11950
|
// modules/payments-v2/machine/payload-view.ts
|
|
11951
|
+
function isWholeIntent(p) {
|
|
11952
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
11953
|
+
}
|
|
11951
11954
|
function splitOf(p) {
|
|
11952
11955
|
return p.kind === "coin" ? p.split : void 0;
|
|
11953
11956
|
}
|
|
@@ -12316,7 +12319,14 @@ var TransferMachine = class {
|
|
|
12316
12319
|
try {
|
|
12317
12320
|
const coinId = coinIdOf(payload);
|
|
12318
12321
|
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12319
|
-
await this.
|
|
12322
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
12323
|
+
await this.deps.recordHistory?.({
|
|
12324
|
+
transferId,
|
|
12325
|
+
payload,
|
|
12326
|
+
phase,
|
|
12327
|
+
committedAmount,
|
|
12328
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
12329
|
+
});
|
|
12320
12330
|
} catch {
|
|
12321
12331
|
}
|
|
12322
12332
|
try {
|
|
@@ -12330,6 +12340,18 @@ var TransferMachine = class {
|
|
|
12330
12340
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
12331
12341
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
12332
12342
|
*/
|
|
12343
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
12344
|
+
async settledAssets(engine, committed) {
|
|
12345
|
+
const total = /* @__PURE__ */ new Map();
|
|
12346
|
+
for (const outcome of committed) {
|
|
12347
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
12348
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
12349
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
12350
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
12351
|
+
}
|
|
12352
|
+
}
|
|
12353
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
12354
|
+
}
|
|
12333
12355
|
async settledAmount(engine, coinId, committed) {
|
|
12334
12356
|
let total = 0n;
|
|
12335
12357
|
for (const outcome of committed) {
|
|
@@ -12469,7 +12491,7 @@ async function runOne(ctx, job, report) {
|
|
|
12469
12491
|
report.failed.push(job.transferId);
|
|
12470
12492
|
return;
|
|
12471
12493
|
}
|
|
12472
|
-
if (job.payload.
|
|
12494
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
12473
12495
|
logger.warn(
|
|
12474
12496
|
"PaymentsV2",
|
|
12475
12497
|
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
@@ -12517,7 +12539,7 @@ function validateCoinPayload(c) {
|
|
|
12517
12539
|
}
|
|
12518
12540
|
return { ...c, kind: "coin" };
|
|
12519
12541
|
}
|
|
12520
|
-
function
|
|
12542
|
+
function validateWholePayload(p) {
|
|
12521
12543
|
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12522
12544
|
throw new SphereError(
|
|
12523
12545
|
"token intent payload must name exactly one source token",
|
|
@@ -12527,7 +12549,7 @@ function validateCoinlessPayload(p) {
|
|
|
12527
12549
|
if (p.split !== void 0) {
|
|
12528
12550
|
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12529
12551
|
}
|
|
12530
|
-
return { ...p, kind: "
|
|
12552
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
12531
12553
|
}
|
|
12532
12554
|
function validatePayload(raw) {
|
|
12533
12555
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
@@ -12541,13 +12563,13 @@ function validatePayload(raw) {
|
|
|
12541
12563
|
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12542
12564
|
}
|
|
12543
12565
|
const kind = p.kind ?? "coin";
|
|
12544
|
-
if (kind !== "coin" && kind !== "coinless") {
|
|
12566
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
12545
12567
|
throw new SphereError(
|
|
12546
12568
|
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12547
12569
|
"VALIDATION_ERROR"
|
|
12548
12570
|
);
|
|
12549
12571
|
}
|
|
12550
|
-
return kind === "
|
|
12572
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
12551
12573
|
}
|
|
12552
12574
|
|
|
12553
12575
|
// modules/payments-v2/convergence.ts
|
|
@@ -12760,8 +12782,9 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12760
12782
|
};
|
|
12761
12783
|
}
|
|
12762
12784
|
function subject(payload) {
|
|
12763
|
-
|
|
12764
|
-
|
|
12785
|
+
const rawKind = payload?.kind;
|
|
12786
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
12787
|
+
const tokenId = payload?.direct?.[0];
|
|
12765
12788
|
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12766
12789
|
}
|
|
12767
12790
|
const coin = payload;
|
|
@@ -12834,10 +12857,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
|
12834
12857
|
function messageOf(err) {
|
|
12835
12858
|
return err instanceof Error ? err.message : String(err);
|
|
12836
12859
|
}
|
|
12837
|
-
function
|
|
12860
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
12838
12861
|
return {
|
|
12839
12862
|
v: 2,
|
|
12840
|
-
kind: "
|
|
12863
|
+
kind: "whole",
|
|
12841
12864
|
recipient: recipientPubkey,
|
|
12842
12865
|
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12843
12866
|
direct: [request.tokenId],
|
|
@@ -12906,8 +12929,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
|
12906
12929
|
deps.refreshView();
|
|
12907
12930
|
}
|
|
12908
12931
|
|
|
12909
|
-
// modules/payments-v2/
|
|
12910
|
-
|
|
12932
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
12933
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12934
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12935
|
+
return {
|
|
12936
|
+
id: tokenId,
|
|
12937
|
+
coinId: asset.coinId,
|
|
12938
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
12939
|
+
name: registry.getName(asset.coinId),
|
|
12940
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
12941
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12942
|
+
amount: asset.amount,
|
|
12943
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
12944
|
+
createdAt: snapshot.createdAt,
|
|
12945
|
+
updatedAt: snapshot.updatedAt,
|
|
12946
|
+
lazy: true,
|
|
12947
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12948
|
+
};
|
|
12949
|
+
}
|
|
12950
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
12951
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12952
|
+
for (const [tokenId, entry] of entries) {
|
|
12953
|
+
const moving = isTransferring(tokenId);
|
|
12954
|
+
for (const asset of entry.assets) {
|
|
12955
|
+
let group = groups.get(asset.coinId);
|
|
12956
|
+
if (!group) {
|
|
12957
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12958
|
+
groups.set(asset.coinId, group);
|
|
12959
|
+
}
|
|
12960
|
+
if (moving) {
|
|
12961
|
+
group.transferring += BigInt(asset.amount);
|
|
12962
|
+
group.transferringCount += 1;
|
|
12963
|
+
} else {
|
|
12964
|
+
group.total += BigInt(asset.amount);
|
|
12965
|
+
group.count += 1;
|
|
12966
|
+
}
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12969
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12970
|
+
}
|
|
12971
|
+
function toAsset(coinId, totals, registry) {
|
|
12972
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
12973
|
+
return {
|
|
12974
|
+
coinId,
|
|
12975
|
+
symbol: registry.getSymbol(coinId),
|
|
12976
|
+
name: registry.getName(coinId),
|
|
12977
|
+
decimals: registry.getDecimals(coinId),
|
|
12978
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12979
|
+
totalAmount: totals.total.toString(),
|
|
12980
|
+
tokenCount: totals.count,
|
|
12981
|
+
confirmedAmount: totals.total.toString(),
|
|
12982
|
+
unconfirmedAmount: "0",
|
|
12983
|
+
confirmedTokenCount: totals.count,
|
|
12984
|
+
unconfirmedTokenCount: 0,
|
|
12985
|
+
transferringTokenCount: totals.transferringCount,
|
|
12986
|
+
transferringAmount: totals.transferring.toString(),
|
|
12987
|
+
priceUsd: null,
|
|
12988
|
+
priceEur: null,
|
|
12989
|
+
change24h: null,
|
|
12990
|
+
fiatValueUsd: null,
|
|
12991
|
+
fiatValueEur: null
|
|
12992
|
+
};
|
|
12993
|
+
}
|
|
12994
|
+
function priceIdOf(registry, coinId) {
|
|
12995
|
+
return registry.getName(coinId).toLowerCase();
|
|
12996
|
+
}
|
|
12997
|
+
async function withPrices(raw, registry, price) {
|
|
12998
|
+
try {
|
|
12999
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13000
|
+
const quotes = await price.getPrices(ids);
|
|
13001
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13002
|
+
} catch {
|
|
13003
|
+
return raw;
|
|
13004
|
+
}
|
|
13005
|
+
}
|
|
13006
|
+
function priceOne(asset, quote) {
|
|
13007
|
+
if (!quote) return asset;
|
|
13008
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
13009
|
+
return {
|
|
13010
|
+
...asset,
|
|
13011
|
+
priceUsd: quote.priceUsd,
|
|
13012
|
+
priceEur: quote.priceEur ?? null,
|
|
13013
|
+
change24h: quote.change24h ?? null,
|
|
13014
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
13015
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
13016
|
+
};
|
|
13017
|
+
}
|
|
13018
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
13019
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13020
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13021
|
+
transferring: true,
|
|
13022
|
+
suspectedSpent: false
|
|
13023
|
+
});
|
|
13024
|
+
}
|
|
13025
|
+
|
|
13026
|
+
// modules/payments-v2/send-whole.ts
|
|
13027
|
+
async function materializeWholeSpend(deps, input) {
|
|
12911
13028
|
const { engine } = deps;
|
|
12912
13029
|
const tokenId = input.request.tokenId;
|
|
12913
13030
|
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
@@ -12915,14 +13032,20 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12915
13032
|
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12916
13033
|
}
|
|
12917
13034
|
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12918
|
-
if (
|
|
13035
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
13036
|
+
throw new SphereError(
|
|
13037
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
13038
|
+
"VALIDATION_ERROR"
|
|
13039
|
+
);
|
|
13040
|
+
}
|
|
13041
|
+
if (input.requireCoinless && token.value !== null) {
|
|
12919
13042
|
throw new SphereError(
|
|
12920
|
-
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use
|
|
13043
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
12921
13044
|
"VALIDATION_ERROR"
|
|
12922
13045
|
);
|
|
12923
13046
|
}
|
|
12924
13047
|
const keys = await engine.deliveryKeys(bytes);
|
|
12925
|
-
const payload =
|
|
13048
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
12926
13049
|
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12927
13050
|
});
|
|
12928
13051
|
const plan = {
|
|
@@ -12933,7 +13056,10 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12933
13056
|
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12934
13057
|
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12935
13058
|
};
|
|
12936
|
-
|
|
13059
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
13060
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
13061
|
+
);
|
|
13062
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
12937
13063
|
}
|
|
12938
13064
|
|
|
12939
13065
|
// modules/payments-v2/send-errors.ts
|
|
@@ -13080,100 +13206,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
13080
13206
|
return blobs;
|
|
13081
13207
|
}
|
|
13082
13208
|
|
|
13083
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
13084
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
13085
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
13086
|
-
return {
|
|
13087
|
-
id: tokenId,
|
|
13088
|
-
coinId: asset.coinId,
|
|
13089
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
13090
|
-
name: registry.getName(asset.coinId),
|
|
13091
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
13092
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
13093
|
-
amount: asset.amount,
|
|
13094
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
13095
|
-
createdAt: snapshot.createdAt,
|
|
13096
|
-
updatedAt: snapshot.updatedAt,
|
|
13097
|
-
lazy: true,
|
|
13098
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
13099
|
-
};
|
|
13100
|
-
}
|
|
13101
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
13102
|
-
const groups = /* @__PURE__ */ new Map();
|
|
13103
|
-
for (const [tokenId, entry] of entries) {
|
|
13104
|
-
const moving = isTransferring(tokenId);
|
|
13105
|
-
for (const asset of entry.assets) {
|
|
13106
|
-
let group = groups.get(asset.coinId);
|
|
13107
|
-
if (!group) {
|
|
13108
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13109
|
-
groups.set(asset.coinId, group);
|
|
13110
|
-
}
|
|
13111
|
-
if (moving) {
|
|
13112
|
-
group.transferring += BigInt(asset.amount);
|
|
13113
|
-
group.transferringCount += 1;
|
|
13114
|
-
} else {
|
|
13115
|
-
group.total += BigInt(asset.amount);
|
|
13116
|
-
group.count += 1;
|
|
13117
|
-
}
|
|
13118
|
-
}
|
|
13119
|
-
}
|
|
13120
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
13121
|
-
}
|
|
13122
|
-
function toAsset(coinId, totals, registry) {
|
|
13123
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
13124
|
-
return {
|
|
13125
|
-
coinId,
|
|
13126
|
-
symbol: registry.getSymbol(coinId),
|
|
13127
|
-
name: registry.getName(coinId),
|
|
13128
|
-
decimals: registry.getDecimals(coinId),
|
|
13129
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
13130
|
-
totalAmount: totals.total.toString(),
|
|
13131
|
-
tokenCount: totals.count,
|
|
13132
|
-
confirmedAmount: totals.total.toString(),
|
|
13133
|
-
unconfirmedAmount: "0",
|
|
13134
|
-
confirmedTokenCount: totals.count,
|
|
13135
|
-
unconfirmedTokenCount: 0,
|
|
13136
|
-
transferringTokenCount: totals.transferringCount,
|
|
13137
|
-
transferringAmount: totals.transferring.toString(),
|
|
13138
|
-
priceUsd: null,
|
|
13139
|
-
priceEur: null,
|
|
13140
|
-
change24h: null,
|
|
13141
|
-
fiatValueUsd: null,
|
|
13142
|
-
fiatValueEur: null
|
|
13143
|
-
};
|
|
13144
|
-
}
|
|
13145
|
-
function priceIdOf(registry, coinId) {
|
|
13146
|
-
return registry.getName(coinId).toLowerCase();
|
|
13147
|
-
}
|
|
13148
|
-
async function withPrices(raw, registry, price) {
|
|
13149
|
-
try {
|
|
13150
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13151
|
-
const quotes = await price.getPrices(ids);
|
|
13152
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13153
|
-
} catch {
|
|
13154
|
-
return raw;
|
|
13155
|
-
}
|
|
13156
|
-
}
|
|
13157
|
-
function priceOne(asset, quote) {
|
|
13158
|
-
if (!quote) return asset;
|
|
13159
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
13160
|
-
return {
|
|
13161
|
-
...asset,
|
|
13162
|
-
priceUsd: quote.priceUsd,
|
|
13163
|
-
priceEur: quote.priceEur ?? null,
|
|
13164
|
-
change24h: quote.change24h ?? null,
|
|
13165
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
13166
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
13167
|
-
};
|
|
13168
|
-
}
|
|
13169
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
13170
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13171
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13172
|
-
transferring: true,
|
|
13173
|
-
suspectedSpent: false
|
|
13174
|
-
});
|
|
13175
|
-
}
|
|
13176
|
-
|
|
13177
13209
|
// modules/payments-v2/history/History.ts
|
|
13178
13210
|
function wireAssets(assets) {
|
|
13179
13211
|
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
@@ -13429,10 +13461,10 @@ var InventoryView = class {
|
|
|
13429
13461
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13430
13462
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13431
13463
|
}
|
|
13432
|
-
/**
|
|
13433
|
-
|
|
13464
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
13465
|
+
spendableToken(tokenId) {
|
|
13434
13466
|
const entry = this.mirror.get(tokenId);
|
|
13435
|
-
return entry !== void 0 &&
|
|
13467
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
13436
13468
|
}
|
|
13437
13469
|
/**
|
|
13438
13470
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
@@ -13805,7 +13837,7 @@ async function screen(deps, engine, entry) {
|
|
|
13805
13837
|
tokenId: keys.tokenId,
|
|
13806
13838
|
stateHash: keys.stateHash,
|
|
13807
13839
|
assets: toAssetAmounts(token),
|
|
13808
|
-
...
|
|
13840
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13809
13841
|
}
|
|
13810
13842
|
};
|
|
13811
13843
|
}
|
|
@@ -13942,9 +13974,6 @@ function claimAck(entry) {
|
|
|
13942
13974
|
function rejectAck(entry, reason) {
|
|
13943
13975
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13944
13976
|
}
|
|
13945
|
-
function isCoinlessEnvelope2(envelope) {
|
|
13946
|
-
return envelope.startsWith("none_");
|
|
13947
|
-
}
|
|
13948
13977
|
function toAssetAmounts(token) {
|
|
13949
13978
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13950
13979
|
coinId: asset.coinId,
|
|
@@ -14519,7 +14548,7 @@ var SpendQueue = class {
|
|
|
14519
14548
|
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14520
14549
|
* become possible.
|
|
14521
14550
|
*/
|
|
14522
|
-
|
|
14551
|
+
planWhole(reservationId, tokenId) {
|
|
14523
14552
|
if (this.destroyed) {
|
|
14524
14553
|
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14525
14554
|
}
|
|
@@ -14527,11 +14556,8 @@ var SpendQueue = class {
|
|
|
14527
14556
|
if (unproven !== null) {
|
|
14528
14557
|
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14529
14558
|
}
|
|
14530
|
-
if (this.deps.
|
|
14531
|
-
throw new SphereError(
|
|
14532
|
-
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14533
|
-
"VALIDATION_ERROR"
|
|
14534
|
-
);
|
|
14559
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
14560
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
14535
14561
|
}
|
|
14536
14562
|
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14537
14563
|
if (holder !== void 0) {
|
|
@@ -14732,7 +14758,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14732
14758
|
const queue = new SpendQueue({
|
|
14733
14759
|
ledger,
|
|
14734
14760
|
getPool: (coinId) => view.pool(coinId),
|
|
14735
|
-
|
|
14761
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
14736
14762
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14737
14763
|
});
|
|
14738
14764
|
const historyStore = new History({
|
|
@@ -14813,12 +14839,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14813
14839
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
14814
14840
|
emit: deps.emit,
|
|
14815
14841
|
now: deps.now ?? Date.now,
|
|
14816
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14842
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
14817
14843
|
await historyStore.recordSent({
|
|
14818
14844
|
transferId,
|
|
14819
14845
|
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14820
14846
|
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14821
|
-
...payload
|
|
14847
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14822
14848
|
recipientPubkey: payload.recipient,
|
|
14823
14849
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14824
14850
|
});
|
|
@@ -15033,10 +15059,14 @@ var PaymentsFacade = class {
|
|
|
15033
15059
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
15034
15060
|
}
|
|
15035
15061
|
send(request) {
|
|
15036
|
-
return this.track(this.
|
|
15062
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
15063
|
+
}
|
|
15064
|
+
sendWholeToken(request) {
|
|
15065
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
15037
15066
|
}
|
|
15067
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
15038
15068
|
sendCoinless(request) {
|
|
15039
|
-
return this.track(this.
|
|
15069
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
15040
15070
|
}
|
|
15041
15071
|
async receive() {
|
|
15042
15072
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -15084,13 +15114,6 @@ var PaymentsFacade = class {
|
|
|
15084
15114
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
15085
15115
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
15086
15116
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
15087
|
-
sendOutcome(request) {
|
|
15088
|
-
return this.runJob({ kind: "coin", request }, request.amount);
|
|
15089
|
-
}
|
|
15090
|
-
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
15091
|
-
sendCoinlessOutcome(request) {
|
|
15092
|
-
return this.runJob({ kind: "coinless", request }, "0");
|
|
15093
|
-
}
|
|
15094
15117
|
async runJob(job, amount) {
|
|
15095
15118
|
const run = {
|
|
15096
15119
|
amount,
|
|
@@ -15135,7 +15158,7 @@ var PaymentsFacade = class {
|
|
|
15135
15158
|
const disposition = await this.runAttempt(ctx);
|
|
15136
15159
|
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
15137
15160
|
if (disposition.kind === "retry-full") {
|
|
15138
|
-
if (job.kind === "
|
|
15161
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
15139
15162
|
continue;
|
|
15140
15163
|
}
|
|
15141
15164
|
if (disposition.kind === "success") {
|
|
@@ -15239,13 +15262,19 @@ var PaymentsFacade = class {
|
|
|
15239
15262
|
async planAndMaterialize(recipientPubkey, job, run) {
|
|
15240
15263
|
const transferId = this.newId();
|
|
15241
15264
|
run.lastTransferId = transferId;
|
|
15242
|
-
if (job.kind === "
|
|
15243
|
-
const spend2 = this.queue.
|
|
15265
|
+
if (job.kind === "whole") {
|
|
15266
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
15244
15267
|
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15245
15268
|
try {
|
|
15246
|
-
return await
|
|
15247
|
-
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15248
|
-
{
|
|
15269
|
+
return await materializeWholeSpend(
|
|
15270
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
15271
|
+
{
|
|
15272
|
+
transferId,
|
|
15273
|
+
recipientPubkey,
|
|
15274
|
+
request: job.request,
|
|
15275
|
+
sourceIds: sourceIds2,
|
|
15276
|
+
requireCoinless: job.requireCoinless
|
|
15277
|
+
}
|
|
15249
15278
|
);
|
|
15250
15279
|
} catch (err) {
|
|
15251
15280
|
this.settleFailure(transferId, "", sourceIds2);
|