@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.cjs
CHANGED
|
@@ -11888,6 +11888,9 @@ function createMachineStores(kv) {
|
|
|
11888
11888
|
}
|
|
11889
11889
|
|
|
11890
11890
|
// modules/payments-v2/machine/payload-view.ts
|
|
11891
|
+
function isWholeIntent(p) {
|
|
11892
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
11893
|
+
}
|
|
11891
11894
|
function splitOf(p) {
|
|
11892
11895
|
return p.kind === "coin" ? p.split : void 0;
|
|
11893
11896
|
}
|
|
@@ -12256,7 +12259,14 @@ var TransferMachine = class {
|
|
|
12256
12259
|
try {
|
|
12257
12260
|
const coinId = coinIdOf(payload);
|
|
12258
12261
|
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12259
|
-
await this.
|
|
12262
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
12263
|
+
await this.deps.recordHistory?.({
|
|
12264
|
+
transferId,
|
|
12265
|
+
payload,
|
|
12266
|
+
phase,
|
|
12267
|
+
committedAmount,
|
|
12268
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
12269
|
+
});
|
|
12260
12270
|
} catch {
|
|
12261
12271
|
}
|
|
12262
12272
|
try {
|
|
@@ -12270,6 +12280,18 @@ var TransferMachine = class {
|
|
|
12270
12280
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
12271
12281
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
12272
12282
|
*/
|
|
12283
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
12284
|
+
async settledAssets(engine, committed) {
|
|
12285
|
+
const total = /* @__PURE__ */ new Map();
|
|
12286
|
+
for (const outcome of committed) {
|
|
12287
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
12288
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
12289
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
12290
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12293
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
12294
|
+
}
|
|
12273
12295
|
async settledAmount(engine, coinId, committed) {
|
|
12274
12296
|
let total = 0n;
|
|
12275
12297
|
for (const outcome of committed) {
|
|
@@ -12409,7 +12431,7 @@ async function runOne(ctx, job, report) {
|
|
|
12409
12431
|
report.failed.push(job.transferId);
|
|
12410
12432
|
return;
|
|
12411
12433
|
}
|
|
12412
|
-
if (job.payload.
|
|
12434
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
12413
12435
|
logger.warn(
|
|
12414
12436
|
"PaymentsV2",
|
|
12415
12437
|
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
@@ -12457,7 +12479,7 @@ function validateCoinPayload(c) {
|
|
|
12457
12479
|
}
|
|
12458
12480
|
return { ...c, kind: "coin" };
|
|
12459
12481
|
}
|
|
12460
|
-
function
|
|
12482
|
+
function validateWholePayload(p) {
|
|
12461
12483
|
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12462
12484
|
throw new SphereError(
|
|
12463
12485
|
"token intent payload must name exactly one source token",
|
|
@@ -12467,7 +12489,7 @@ function validateCoinlessPayload(p) {
|
|
|
12467
12489
|
if (p.split !== void 0) {
|
|
12468
12490
|
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12469
12491
|
}
|
|
12470
|
-
return { ...p, kind: "
|
|
12492
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
12471
12493
|
}
|
|
12472
12494
|
function validatePayload(raw) {
|
|
12473
12495
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
@@ -12481,13 +12503,13 @@ function validatePayload(raw) {
|
|
|
12481
12503
|
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12482
12504
|
}
|
|
12483
12505
|
const kind = p.kind ?? "coin";
|
|
12484
|
-
if (kind !== "coin" && kind !== "coinless") {
|
|
12506
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
12485
12507
|
throw new SphereError(
|
|
12486
12508
|
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12487
12509
|
"VALIDATION_ERROR"
|
|
12488
12510
|
);
|
|
12489
12511
|
}
|
|
12490
|
-
return kind === "
|
|
12512
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
12491
12513
|
}
|
|
12492
12514
|
|
|
12493
12515
|
// modules/payments-v2/convergence.ts
|
|
@@ -12700,8 +12722,9 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12700
12722
|
};
|
|
12701
12723
|
}
|
|
12702
12724
|
function subject(payload) {
|
|
12703
|
-
|
|
12704
|
-
|
|
12725
|
+
const rawKind = payload?.kind;
|
|
12726
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
12727
|
+
const tokenId = payload?.direct?.[0];
|
|
12705
12728
|
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12706
12729
|
}
|
|
12707
12730
|
const coin = payload;
|
|
@@ -12774,10 +12797,10 @@ function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
|
12774
12797
|
function messageOf(err) {
|
|
12775
12798
|
return err instanceof Error ? err.message : String(err);
|
|
12776
12799
|
}
|
|
12777
|
-
function
|
|
12800
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
12778
12801
|
return {
|
|
12779
12802
|
v: 2,
|
|
12780
|
-
kind: "
|
|
12803
|
+
kind: "whole",
|
|
12781
12804
|
recipient: recipientPubkey,
|
|
12782
12805
|
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12783
12806
|
direct: [request.tokenId],
|
|
@@ -12846,8 +12869,102 @@ async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
|
12846
12869
|
deps.refreshView();
|
|
12847
12870
|
}
|
|
12848
12871
|
|
|
12849
|
-
// modules/payments-v2/
|
|
12850
|
-
|
|
12872
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
12873
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12874
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
12875
|
+
return {
|
|
12876
|
+
id: tokenId,
|
|
12877
|
+
coinId: asset.coinId,
|
|
12878
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
12879
|
+
name: registry.getName(asset.coinId),
|
|
12880
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
12881
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12882
|
+
amount: asset.amount,
|
|
12883
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
12884
|
+
createdAt: snapshot.createdAt,
|
|
12885
|
+
updatedAt: snapshot.updatedAt,
|
|
12886
|
+
lazy: true,
|
|
12887
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
12888
|
+
};
|
|
12889
|
+
}
|
|
12890
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
12891
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12892
|
+
for (const [tokenId, entry] of entries) {
|
|
12893
|
+
const moving = isTransferring(tokenId);
|
|
12894
|
+
for (const asset of entry.assets) {
|
|
12895
|
+
let group = groups.get(asset.coinId);
|
|
12896
|
+
if (!group) {
|
|
12897
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
12898
|
+
groups.set(asset.coinId, group);
|
|
12899
|
+
}
|
|
12900
|
+
if (moving) {
|
|
12901
|
+
group.transferring += BigInt(asset.amount);
|
|
12902
|
+
group.transferringCount += 1;
|
|
12903
|
+
} else {
|
|
12904
|
+
group.total += BigInt(asset.amount);
|
|
12905
|
+
group.count += 1;
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
}
|
|
12909
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
12910
|
+
}
|
|
12911
|
+
function toAsset(coinId, totals, registry) {
|
|
12912
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
12913
|
+
return {
|
|
12914
|
+
coinId,
|
|
12915
|
+
symbol: registry.getSymbol(coinId),
|
|
12916
|
+
name: registry.getName(coinId),
|
|
12917
|
+
decimals: registry.getDecimals(coinId),
|
|
12918
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
12919
|
+
totalAmount: totals.total.toString(),
|
|
12920
|
+
tokenCount: totals.count,
|
|
12921
|
+
confirmedAmount: totals.total.toString(),
|
|
12922
|
+
unconfirmedAmount: "0",
|
|
12923
|
+
confirmedTokenCount: totals.count,
|
|
12924
|
+
unconfirmedTokenCount: 0,
|
|
12925
|
+
transferringTokenCount: totals.transferringCount,
|
|
12926
|
+
transferringAmount: totals.transferring.toString(),
|
|
12927
|
+
priceUsd: null,
|
|
12928
|
+
priceEur: null,
|
|
12929
|
+
change24h: null,
|
|
12930
|
+
fiatValueUsd: null,
|
|
12931
|
+
fiatValueEur: null
|
|
12932
|
+
};
|
|
12933
|
+
}
|
|
12934
|
+
function priceIdOf(registry, coinId) {
|
|
12935
|
+
return registry.getName(coinId).toLowerCase();
|
|
12936
|
+
}
|
|
12937
|
+
async function withPrices(raw, registry, price) {
|
|
12938
|
+
try {
|
|
12939
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
12940
|
+
const quotes = await price.getPrices(ids);
|
|
12941
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
12942
|
+
} catch {
|
|
12943
|
+
return raw;
|
|
12944
|
+
}
|
|
12945
|
+
}
|
|
12946
|
+
function priceOne(asset, quote) {
|
|
12947
|
+
if (!quote) return asset;
|
|
12948
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
12949
|
+
return {
|
|
12950
|
+
...asset,
|
|
12951
|
+
priceUsd: quote.priceUsd,
|
|
12952
|
+
priceEur: quote.priceEur ?? null,
|
|
12953
|
+
change24h: quote.change24h ?? null,
|
|
12954
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
12955
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
12956
|
+
};
|
|
12957
|
+
}
|
|
12958
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
12959
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
12960
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
12961
|
+
transferring: true,
|
|
12962
|
+
suspectedSpent: false
|
|
12963
|
+
});
|
|
12964
|
+
}
|
|
12965
|
+
|
|
12966
|
+
// modules/payments-v2/send-whole.ts
|
|
12967
|
+
async function materializeWholeSpend(deps, input) {
|
|
12851
12968
|
const { engine } = deps;
|
|
12852
12969
|
const tokenId = input.request.tokenId;
|
|
12853
12970
|
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
@@ -12855,14 +12972,20 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12855
12972
|
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12856
12973
|
}
|
|
12857
12974
|
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12858
|
-
if (
|
|
12975
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
12976
|
+
throw new SphereError(
|
|
12977
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
12978
|
+
"VALIDATION_ERROR"
|
|
12979
|
+
);
|
|
12980
|
+
}
|
|
12981
|
+
if (input.requireCoinless && token.value !== null) {
|
|
12859
12982
|
throw new SphereError(
|
|
12860
|
-
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use
|
|
12983
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
12861
12984
|
"VALIDATION_ERROR"
|
|
12862
12985
|
);
|
|
12863
12986
|
}
|
|
12864
12987
|
const keys = await engine.deliveryKeys(bytes);
|
|
12865
|
-
const payload =
|
|
12988
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
12866
12989
|
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12867
12990
|
});
|
|
12868
12991
|
const plan = {
|
|
@@ -12873,7 +12996,10 @@ async function materializeCoinlessSpend(deps, input) {
|
|
|
12873
12996
|
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12874
12997
|
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12875
12998
|
};
|
|
12876
|
-
|
|
12999
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
13000
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
13001
|
+
);
|
|
13002
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
12877
13003
|
}
|
|
12878
13004
|
|
|
12879
13005
|
// modules/payments-v2/send-errors.ts
|
|
@@ -13020,100 +13146,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
13020
13146
|
return blobs;
|
|
13021
13147
|
}
|
|
13022
13148
|
|
|
13023
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
13024
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
13025
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
13026
|
-
return {
|
|
13027
|
-
id: tokenId,
|
|
13028
|
-
coinId: asset.coinId,
|
|
13029
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
13030
|
-
name: registry.getName(asset.coinId),
|
|
13031
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
13032
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
13033
|
-
amount: asset.amount,
|
|
13034
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
13035
|
-
createdAt: snapshot.createdAt,
|
|
13036
|
-
updatedAt: snapshot.updatedAt,
|
|
13037
|
-
lazy: true,
|
|
13038
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
13039
|
-
};
|
|
13040
|
-
}
|
|
13041
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
13042
|
-
const groups = /* @__PURE__ */ new Map();
|
|
13043
|
-
for (const [tokenId, entry] of entries) {
|
|
13044
|
-
const moving = isTransferring(tokenId);
|
|
13045
|
-
for (const asset of entry.assets) {
|
|
13046
|
-
let group = groups.get(asset.coinId);
|
|
13047
|
-
if (!group) {
|
|
13048
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13049
|
-
groups.set(asset.coinId, group);
|
|
13050
|
-
}
|
|
13051
|
-
if (moving) {
|
|
13052
|
-
group.transferring += BigInt(asset.amount);
|
|
13053
|
-
group.transferringCount += 1;
|
|
13054
|
-
} else {
|
|
13055
|
-
group.total += BigInt(asset.amount);
|
|
13056
|
-
group.count += 1;
|
|
13057
|
-
}
|
|
13058
|
-
}
|
|
13059
|
-
}
|
|
13060
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
13061
|
-
}
|
|
13062
|
-
function toAsset(coinId, totals, registry) {
|
|
13063
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
13064
|
-
return {
|
|
13065
|
-
coinId,
|
|
13066
|
-
symbol: registry.getSymbol(coinId),
|
|
13067
|
-
name: registry.getName(coinId),
|
|
13068
|
-
decimals: registry.getDecimals(coinId),
|
|
13069
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
13070
|
-
totalAmount: totals.total.toString(),
|
|
13071
|
-
tokenCount: totals.count,
|
|
13072
|
-
confirmedAmount: totals.total.toString(),
|
|
13073
|
-
unconfirmedAmount: "0",
|
|
13074
|
-
confirmedTokenCount: totals.count,
|
|
13075
|
-
unconfirmedTokenCount: 0,
|
|
13076
|
-
transferringTokenCount: totals.transferringCount,
|
|
13077
|
-
transferringAmount: totals.transferring.toString(),
|
|
13078
|
-
priceUsd: null,
|
|
13079
|
-
priceEur: null,
|
|
13080
|
-
change24h: null,
|
|
13081
|
-
fiatValueUsd: null,
|
|
13082
|
-
fiatValueEur: null
|
|
13083
|
-
};
|
|
13084
|
-
}
|
|
13085
|
-
function priceIdOf(registry, coinId) {
|
|
13086
|
-
return registry.getName(coinId).toLowerCase();
|
|
13087
|
-
}
|
|
13088
|
-
async function withPrices(raw, registry, price) {
|
|
13089
|
-
try {
|
|
13090
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13091
|
-
const quotes = await price.getPrices(ids);
|
|
13092
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13093
|
-
} catch {
|
|
13094
|
-
return raw;
|
|
13095
|
-
}
|
|
13096
|
-
}
|
|
13097
|
-
function priceOne(asset, quote) {
|
|
13098
|
-
if (!quote) return asset;
|
|
13099
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
13100
|
-
return {
|
|
13101
|
-
...asset,
|
|
13102
|
-
priceUsd: quote.priceUsd,
|
|
13103
|
-
priceEur: quote.priceEur ?? null,
|
|
13104
|
-
change24h: quote.change24h ?? null,
|
|
13105
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
13106
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
13107
|
-
};
|
|
13108
|
-
}
|
|
13109
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
13110
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13111
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13112
|
-
transferring: true,
|
|
13113
|
-
suspectedSpent: false
|
|
13114
|
-
});
|
|
13115
|
-
}
|
|
13116
|
-
|
|
13117
13149
|
// modules/payments-v2/history/History.ts
|
|
13118
13150
|
function wireAssets(assets) {
|
|
13119
13151
|
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
@@ -13369,10 +13401,10 @@ var InventoryView = class {
|
|
|
13369
13401
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13370
13402
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13371
13403
|
}
|
|
13372
|
-
/**
|
|
13373
|
-
|
|
13404
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
13405
|
+
spendableToken(tokenId) {
|
|
13374
13406
|
const entry = this.mirror.get(tokenId);
|
|
13375
|
-
return entry !== void 0 &&
|
|
13407
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
13376
13408
|
}
|
|
13377
13409
|
/**
|
|
13378
13410
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
@@ -13745,7 +13777,7 @@ async function screen(deps, engine, entry) {
|
|
|
13745
13777
|
tokenId: keys.tokenId,
|
|
13746
13778
|
stateHash: keys.stateHash,
|
|
13747
13779
|
assets: toAssetAmounts(token),
|
|
13748
|
-
...
|
|
13780
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13749
13781
|
}
|
|
13750
13782
|
};
|
|
13751
13783
|
}
|
|
@@ -13882,9 +13914,6 @@ function claimAck(entry) {
|
|
|
13882
13914
|
function rejectAck(entry, reason) {
|
|
13883
13915
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13884
13916
|
}
|
|
13885
|
-
function isCoinlessEnvelope2(envelope) {
|
|
13886
|
-
return envelope.startsWith("none_");
|
|
13887
|
-
}
|
|
13888
13917
|
function toAssetAmounts(token) {
|
|
13889
13918
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13890
13919
|
coinId: asset.coinId,
|
|
@@ -14459,7 +14488,7 @@ var SpendQueue = class {
|
|
|
14459
14488
|
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14460
14489
|
* become possible.
|
|
14461
14490
|
*/
|
|
14462
|
-
|
|
14491
|
+
planWhole(reservationId, tokenId) {
|
|
14463
14492
|
if (this.destroyed) {
|
|
14464
14493
|
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14465
14494
|
}
|
|
@@ -14467,11 +14496,8 @@ var SpendQueue = class {
|
|
|
14467
14496
|
if (unproven !== null) {
|
|
14468
14497
|
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14469
14498
|
}
|
|
14470
|
-
if (this.deps.
|
|
14471
|
-
throw new SphereError(
|
|
14472
|
-
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14473
|
-
"VALIDATION_ERROR"
|
|
14474
|
-
);
|
|
14499
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
14500
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
14475
14501
|
}
|
|
14476
14502
|
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14477
14503
|
if (holder !== void 0) {
|
|
@@ -14672,7 +14698,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14672
14698
|
const queue = new SpendQueue({
|
|
14673
14699
|
ledger,
|
|
14674
14700
|
getPool: (coinId) => view.pool(coinId),
|
|
14675
|
-
|
|
14701
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
14676
14702
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14677
14703
|
});
|
|
14678
14704
|
const historyStore = new History({
|
|
@@ -14753,12 +14779,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14753
14779
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
14754
14780
|
emit: deps.emit,
|
|
14755
14781
|
now: deps.now ?? Date.now,
|
|
14756
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14782
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
14757
14783
|
await historyStore.recordSent({
|
|
14758
14784
|
transferId,
|
|
14759
14785
|
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14760
14786
|
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14761
|
-
...payload
|
|
14787
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14762
14788
|
recipientPubkey: payload.recipient,
|
|
14763
14789
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14764
14790
|
});
|
|
@@ -14973,10 +14999,14 @@ var PaymentsFacade = class {
|
|
|
14973
14999
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14974
15000
|
}
|
|
14975
15001
|
send(request) {
|
|
14976
|
-
return this.track(this.
|
|
15002
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
15003
|
+
}
|
|
15004
|
+
sendWholeToken(request) {
|
|
15005
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
14977
15006
|
}
|
|
15007
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
14978
15008
|
sendCoinless(request) {
|
|
14979
|
-
return this.track(this.
|
|
15009
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
14980
15010
|
}
|
|
14981
15011
|
async receive() {
|
|
14982
15012
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -15024,13 +15054,6 @@ var PaymentsFacade = class {
|
|
|
15024
15054
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
15025
15055
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
15026
15056
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
15027
|
-
sendOutcome(request) {
|
|
15028
|
-
return this.runJob({ kind: "coin", request }, request.amount);
|
|
15029
|
-
}
|
|
15030
|
-
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
15031
|
-
sendCoinlessOutcome(request) {
|
|
15032
|
-
return this.runJob({ kind: "coinless", request }, "0");
|
|
15033
|
-
}
|
|
15034
15057
|
async runJob(job, amount) {
|
|
15035
15058
|
const run = {
|
|
15036
15059
|
amount,
|
|
@@ -15075,7 +15098,7 @@ var PaymentsFacade = class {
|
|
|
15075
15098
|
const disposition = await this.runAttempt(ctx);
|
|
15076
15099
|
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
15077
15100
|
if (disposition.kind === "retry-full") {
|
|
15078
|
-
if (job.kind === "
|
|
15101
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
15079
15102
|
continue;
|
|
15080
15103
|
}
|
|
15081
15104
|
if (disposition.kind === "success") {
|
|
@@ -15179,13 +15202,19 @@ var PaymentsFacade = class {
|
|
|
15179
15202
|
async planAndMaterialize(recipientPubkey, job, run) {
|
|
15180
15203
|
const transferId = this.newId();
|
|
15181
15204
|
run.lastTransferId = transferId;
|
|
15182
|
-
if (job.kind === "
|
|
15183
|
-
const spend2 = this.queue.
|
|
15205
|
+
if (job.kind === "whole") {
|
|
15206
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
15184
15207
|
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15185
15208
|
try {
|
|
15186
|
-
return await
|
|
15187
|
-
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15188
|
-
{
|
|
15209
|
+
return await materializeWholeSpend(
|
|
15210
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
15211
|
+
{
|
|
15212
|
+
transferId,
|
|
15213
|
+
recipientPubkey,
|
|
15214
|
+
request: job.request,
|
|
15215
|
+
sourceIds: sourceIds2,
|
|
15216
|
+
requireCoinless: job.requireCoinless
|
|
15217
|
+
}
|
|
15189
15218
|
);
|
|
15190
15219
|
} catch (err) {
|
|
15191
15220
|
this.settleFailure(transferId, "", sourceIds2);
|