@dvmkit/sdk 0.7.0-rc.1 → 0.7.0-rc.10
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/README.md +39 -2
- package/dist/{chunk-S3OY533W.js → chunk-2NUFE2PG.js} +47 -5
- package/dist/{chunk-RA2WIX7G.js → chunk-3E4C6W7W.js} +17 -6
- package/dist/{chunk-GY66THYI.js → chunk-53SNRGQQ.js} +372 -153
- package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
- package/dist/chunk-6HJEVCVI.js +17 -0
- package/dist/chunk-A262WVYX.js +6 -0
- package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
- package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
- package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
- package/dist/{chunk-OML32F6I.js → chunk-GLIAR7RN.js} +1 -1
- package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
- package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
- package/dist/{chunk-6XF3RVVX.js → chunk-IRXIH775.js} +5 -5
- package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
- package/dist/chunk-MDW7ZR7V.js +47 -0
- package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
- package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
- package/dist/{chunk-KMED3H3A.js → chunk-OBBVDKLE.js} +476 -230
- package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
- package/dist/{chunk-FWGWLPXF.js → chunk-PZ5IAWFD.js} +1 -1
- package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
- package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
- package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
- package/dist/chunk-WMGXJQI3.js +120 -0
- package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
- package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
- package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
- package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-MFIIOSZH.js} +4 -4
- package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
- package/dist/{fx-zMWFzfrU.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +248 -16
- package/dist/internal/caller.d.ts +5032 -8272
- package/dist/internal/caller.js +4302 -8084
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +30 -31
- package/dist/{job-store-DdLXBpol.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
- package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-CY5ML3km.d.ts} +2 -2
- package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-44NE4PZC.js} +5 -5
- package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
- package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
- package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
- package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
- package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
- package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
- package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
- package/dist/server/index.d.ts +12 -11
- package/dist/server/index.js +47 -48
- package/dist/{step-cache-2MkvenJn.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
- package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
- package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +7 -6
- package/dist/{usd-DI3d3gTK.d.ts → usd-MAtMp3DB.d.ts} +1 -1
- package/dist/{x402-FT4S4JS4.js → x402-3IQ3HXSG.js} +8 -26
- package/package.json +1 -1
- package/dist/chunk-AT6V3SY7.js +0 -102
- package/dist/chunk-EZXAJ6EI.js +0 -1082
- package/dist/chunk-HCLVYZUZ.js +0 -244
- package/dist/chunk-JEVLNAO3.js +0 -1668
- package/dist/chunk-NFMOPYBA.js +0 -357
- package/dist/chunk-O2OTZ5GP.js +0 -1869
- package/dist/chunk-OFVV37XC.js +0 -57
- package/dist/chunk-SZVXOQ75.js +0 -211
- package/dist/chunk-WBOKPC37.js +0 -795
- package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
- package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
|
@@ -204,90 +204,45 @@ function isOptionalRecord(value) {
|
|
|
204
204
|
return value === void 0 || value === null || isRecord(value);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// src/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
var X402_CHANNELS_FILE = join(CONFIG_DIR, "x402-channels.json");
|
|
222
|
-
var TEMPO_CHANNELS_FILE = join(CONFIG_DIR, "tempo-channels.json");
|
|
223
|
-
var FEEDBACK_NUDGES_FILE = join(CONFIG_DIR, "feedback-nudges.json");
|
|
224
|
-
var RECOVERY_MNEMONIC_FILE = join(CONFIG_DIR, "recovery.mnemonic");
|
|
225
|
-
var WALLET_FILE = join(CONFIG_DIR, "wallet.json");
|
|
226
|
-
var AGENT_MNEMONIC_FILE = join(CONFIG_DIR, "wallet.mnemonic");
|
|
227
|
-
var IDENTITIES_FILE = join(CONFIG_DIR, "identities.json");
|
|
228
|
-
var PAIRINGS_DIR = join(CONFIG_DIR, "pairings");
|
|
229
|
-
var BUDGET_FILE = join(CONFIG_DIR, "budget.json");
|
|
230
|
-
var BTC_RATE_FILE = join(CONFIG_DIR, "btc-rate.json");
|
|
231
|
-
var ADMIN_DIR = join(CONFIG_DIR, "admin");
|
|
232
|
-
var PROXY_STATE_FILE = join(ADMIN_DIR, "proxy.json");
|
|
233
|
-
var BUILDER_CONFIG_DIR = process.env.DVMKIT_CONFIG_DIR ?? join(homedir(), ".dvmkit");
|
|
234
|
-
var BUILDER_FILE = join(BUILDER_CONFIG_DIR, "builder.json");
|
|
235
|
-
var BUILDER_MNEMONIC_FILE = join(BUILDER_CONFIG_DIR, "recovery.mnemonic");
|
|
236
|
-
var BUILDER_KEY_FILE = join(BUILDER_CONFIG_DIR, "builder.key");
|
|
237
|
-
var MELT_PENDING_LOG_FILE = join(BUILDER_CONFIG_DIR, "melt-pending.log");
|
|
238
|
-
var DRAIN_PARK_LOG_FILE = join(BUILDER_CONFIG_DIR, "drain-park.log");
|
|
239
|
-
var DRAIN_HOLD_STATE_FILE = join(BUILDER_CONFIG_DIR, "drain-hold.json");
|
|
240
|
-
var MELT_SUCCESS_FILE = join(BUILDER_CONFIG_DIR, "melt-success.json");
|
|
241
|
-
|
|
242
|
-
// src/lib/config-dir.ts
|
|
243
|
-
import { chmodSync, mkdirSync, statSync } from "fs";
|
|
244
|
-
var CONFIG_DIR_MODE = 448;
|
|
245
|
-
function ensureConfigDir() {
|
|
246
|
-
ensureConfigSubdir(CONFIG_DIR);
|
|
247
|
-
}
|
|
248
|
-
function ensureConfigSubdir(path) {
|
|
249
|
-
mkdirSync(path, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
250
|
-
}
|
|
251
|
-
function repairConfigDirMode() {
|
|
252
|
-
const base = {
|
|
253
|
-
path: CONFIG_DIR,
|
|
254
|
-
exists: false,
|
|
255
|
-
modeOctal: null,
|
|
256
|
-
repaired: false,
|
|
257
|
-
skippedNonPosix: process.platform === "win32"
|
|
258
|
-
};
|
|
259
|
-
if (base.skippedNonPosix) return base;
|
|
260
|
-
let mode;
|
|
261
|
-
try {
|
|
262
|
-
mode = statSync(CONFIG_DIR).mode & 511;
|
|
263
|
-
} catch (err) {
|
|
264
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
265
|
-
const missing = err.code === "ENOENT";
|
|
266
|
-
return missing ? base : { ...base, error: message };
|
|
207
|
+
// src/lib/payment-amounts.ts
|
|
208
|
+
function msatsToUsd(msats, rate) {
|
|
209
|
+
return msats / 1e11 * rate;
|
|
210
|
+
}
|
|
211
|
+
function usdToMsats(usd, rate) {
|
|
212
|
+
return Math.round(usd / rate * 1e11);
|
|
213
|
+
}
|
|
214
|
+
function msatsToUsdCents(msats, btcPriceUsd) {
|
|
215
|
+
const usd = msatsToUsd(msats, btcPriceUsd);
|
|
216
|
+
return Math.round(usd * 100);
|
|
217
|
+
}
|
|
218
|
+
function msatsToUsdc(msats, btcUsdRate) {
|
|
219
|
+
if (!Number.isSafeInteger(msats) || msats < 0) {
|
|
220
|
+
throw new RangeError("msats must be a non-negative safe integer");
|
|
267
221
|
}
|
|
268
|
-
if ((
|
|
269
|
-
|
|
222
|
+
if (!Number.isFinite(btcUsdRate) || btcUsdRate <= 0) {
|
|
223
|
+
throw new RangeError("BTC/USD rate must be finite and positive");
|
|
270
224
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
225
|
+
const [mantissa, exponent = "0"] = btcUsdRate.toString().split("e");
|
|
226
|
+
const decimalPlaces = mantissa.split(".")[1]?.length ?? 0;
|
|
227
|
+
const shift = Number(exponent) - decimalPlaces;
|
|
228
|
+
let numerator = BigInt(msats) * BigInt(mantissa.replace(".", ""));
|
|
229
|
+
let denominator = 100000n;
|
|
230
|
+
if (shift > 0) numerator *= 10n ** BigInt(shift);
|
|
231
|
+
else if (shift < 0) denominator *= 10n ** BigInt(-shift);
|
|
232
|
+
const micro = (numerator + denominator - 1n) / denominator;
|
|
233
|
+
if (micro > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
234
|
+
throw new RangeError("USDC micro-unit amount exceeds the safe integer range");
|
|
280
235
|
}
|
|
281
|
-
return
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
236
|
+
return Number(micro);
|
|
237
|
+
}
|
|
238
|
+
function usdcToMsats(usdc, btcUsdRate) {
|
|
239
|
+
return usdToMsats(usdc, btcUsdRate);
|
|
240
|
+
}
|
|
241
|
+
function msatsToMicro(msats, btcRate) {
|
|
242
|
+
return Math.round(msatsToUsd(msats, btcRate) * 1e6);
|
|
288
243
|
}
|
|
289
|
-
function
|
|
290
|
-
return
|
|
244
|
+
function microToSats(micro, btcRate) {
|
|
245
|
+
return Math.ceil(usdToMsats(micro / 1e6, btcRate) / 1e3);
|
|
291
246
|
}
|
|
292
247
|
|
|
293
248
|
// src/lib/payment-rails/rpc.ts
|
|
@@ -339,33 +294,13 @@ export {
|
|
|
339
294
|
decodePaymentRequiredHeader,
|
|
340
295
|
exactEvmAuthorization,
|
|
341
296
|
encodeSettleResponseHeader,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
FAVORITES_FILE,
|
|
350
|
-
MESSAGES_DIR,
|
|
351
|
-
RECEIPTS_FILE,
|
|
352
|
-
CREDITS_FILE,
|
|
353
|
-
X402_CHANNELS_FILE,
|
|
354
|
-
TEMPO_CHANNELS_FILE,
|
|
355
|
-
FEEDBACK_NUDGES_FILE,
|
|
356
|
-
RECOVERY_MNEMONIC_FILE,
|
|
357
|
-
WALLET_FILE,
|
|
358
|
-
AGENT_MNEMONIC_FILE,
|
|
359
|
-
IDENTITIES_FILE,
|
|
360
|
-
PAIRINGS_DIR,
|
|
361
|
-
BUDGET_FILE,
|
|
362
|
-
BTC_RATE_FILE,
|
|
363
|
-
BUILDER_CONFIG_DIR,
|
|
364
|
-
BUILDER_FILE,
|
|
365
|
-
BUILDER_KEY_FILE,
|
|
366
|
-
ensureConfigDir,
|
|
367
|
-
ensureConfigSubdir,
|
|
368
|
-
repairConfigDirMode,
|
|
297
|
+
msatsToUsd,
|
|
298
|
+
usdToMsats,
|
|
299
|
+
msatsToUsdCents,
|
|
300
|
+
msatsToUsdc,
|
|
301
|
+
usdcToMsats,
|
|
302
|
+
msatsToMicro,
|
|
303
|
+
microToSats,
|
|
369
304
|
resolveRpcOverride,
|
|
370
305
|
resolveRpcHttpTransportOptions
|
|
371
306
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateCurrency
|
|
3
|
+
} from "./chunk-HPCXF3DV.js";
|
|
4
|
+
|
|
5
|
+
// src/sdk/pricing/upfront.ts
|
|
6
|
+
function buildUpfront(input) {
|
|
7
|
+
const currency = validateCurrency(input.currency);
|
|
8
|
+
const amount = input.amount;
|
|
9
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
10
|
+
throw new Error(`invalid upfront amount: ${String(amount)} (must be a non-negative number)`);
|
|
11
|
+
}
|
|
12
|
+
return { amount, currency };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
buildUpfront
|
|
17
|
+
};
|
|
@@ -1,12 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diagnosticConsole
|
|
3
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
1
4
|
import {
|
|
2
5
|
withSdkInitLock
|
|
3
6
|
} from "./chunk-COYKJRMM.js";
|
|
4
|
-
import {
|
|
5
|
-
diagnosticConsole
|
|
6
|
-
} from "./chunk-ETXDTUJO.js";
|
|
7
7
|
|
|
8
8
|
// src/sdk/server/payout-reporter.ts
|
|
9
9
|
import { createHash, randomUUID } from "crypto";
|
|
10
|
+
async function x402EarnedCeiling(channel, earnedNative) {
|
|
11
|
+
let earned;
|
|
12
|
+
try {
|
|
13
|
+
earned = await earnedNative(channel.channelId.toLowerCase());
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return { kind: "unresolved", error };
|
|
16
|
+
}
|
|
17
|
+
if (earned === void 0) return { kind: "unbound" };
|
|
18
|
+
const charged = BigInt(channel.chargedCumulativeAmount);
|
|
19
|
+
const wanted = BigInt(earned);
|
|
20
|
+
return { kind: "ceiling", ceiling: wanted < charged ? wanted : charged };
|
|
21
|
+
}
|
|
10
22
|
var MemoryX402BatchStore = class {
|
|
11
23
|
batches = /* @__PURE__ */ new Map();
|
|
12
24
|
get(scope) {
|
|
@@ -719,7 +731,8 @@ var PayoutReporter = class {
|
|
|
719
731
|
}
|
|
720
732
|
/**
|
|
721
733
|
* x402: the open batch (claimed, not yet transferred) plus every channel's
|
|
722
|
-
*
|
|
734
|
+
* earned value not yet claimed — a channel whose earned figure cannot be
|
|
735
|
+
* read contributes nothing; wedged when the settle keeps failing, and the
|
|
723
736
|
* refund settlements stuck between chain and ledger beside it.
|
|
724
737
|
*/
|
|
725
738
|
async snapshotX402() {
|
|
@@ -751,12 +764,29 @@ var PayoutReporter = class {
|
|
|
751
764
|
}
|
|
752
765
|
const unclaimed = [];
|
|
753
766
|
for (const channel of await ctx.storage.list()) {
|
|
754
|
-
const
|
|
767
|
+
const claimed = BigInt(channel.totalClaimed);
|
|
768
|
+
if (BigInt(channel.chargedCumulativeAmount) <= claimed) continue;
|
|
769
|
+
let resolved;
|
|
770
|
+
try {
|
|
771
|
+
resolved = await x402EarnedCeiling(channel, ctx.earnedNative);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
resolved = { kind: "unresolved", error: err };
|
|
774
|
+
}
|
|
775
|
+
if (resolved.kind === "unresolved") {
|
|
776
|
+
warn("payout_pending_snapshot_failed", {
|
|
777
|
+
rail: "x402",
|
|
778
|
+
"channel.id": channel.channelId,
|
|
779
|
+
error: message(resolved.error)
|
|
780
|
+
});
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (resolved.kind === "unbound") continue;
|
|
784
|
+
const owed = resolved.ceiling - claimed;
|
|
755
785
|
if (owed > 0n) {
|
|
756
786
|
unclaimed.push({
|
|
757
787
|
id: channel.channelId,
|
|
758
788
|
native: Number(owed),
|
|
759
|
-
label: "
|
|
789
|
+
label: "earned, awaiting claim"
|
|
760
790
|
});
|
|
761
791
|
}
|
|
762
792
|
}
|
|
@@ -902,6 +932,7 @@ function warn(level, fields) {
|
|
|
902
932
|
}
|
|
903
933
|
|
|
904
934
|
export {
|
|
935
|
+
x402EarnedCeiling,
|
|
905
936
|
MemoryX402BatchStore,
|
|
906
937
|
PostgresX402BatchStore,
|
|
907
938
|
PAYOUT_SNAPSHOT_INTERVAL_MS,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diagnosticConsole
|
|
3
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
1
4
|
import {
|
|
2
5
|
withSdkInitLock
|
|
3
6
|
} from "./chunk-COYKJRMM.js";
|
|
4
|
-
import {
|
|
5
|
-
diagnosticConsole
|
|
6
|
-
} from "./chunk-ETXDTUJO.js";
|
|
7
7
|
|
|
8
8
|
// src/sdk/server/revenue-reporter.ts
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
parseStoreValue,
|
|
3
3
|
stringifyStoreValue
|
|
4
4
|
} from "./chunk-YG7G4DPZ.js";
|
|
5
|
+
import {
|
|
6
|
+
withPostgresTransactionRecovery
|
|
7
|
+
} from "./chunk-NV7Z7377.js";
|
|
5
8
|
import {
|
|
6
9
|
withSdkInitLock
|
|
7
10
|
} from "./chunk-COYKJRMM.js";
|
|
8
|
-
import {
|
|
9
|
-
withPostgresTransactionRecovery
|
|
10
|
-
} from "./chunk-P6QON7ER.js";
|
|
11
11
|
|
|
12
12
|
// src/sdk/server/tempo-session-store.ts
|
|
13
13
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
diagnosticConsole
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZCNPIILZ.js";
|
|
4
4
|
|
|
5
5
|
// src/sdk/pricing/currency.ts
|
|
6
6
|
var InvalidCurrencyError = class extends Error {
|
|
@@ -35,6 +35,98 @@ function validateCurrency(raw) {
|
|
|
35
35
|
return raw;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/sdk/pricing/usd.ts
|
|
39
|
+
var InvalidFxRateError = class extends Error {
|
|
40
|
+
code = "invalid_fx_rate";
|
|
41
|
+
ratePerBtc;
|
|
42
|
+
constructor(ratePerBtc) {
|
|
43
|
+
super(
|
|
44
|
+
`invalid fx rate ${String(ratePerBtc)} \u2014 expected a finite positive number of fiat per BTC`
|
|
45
|
+
);
|
|
46
|
+
this.ratePerBtc = ratePerBtc;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function fiatToSatsCeil(amount, ratePerBtc) {
|
|
50
|
+
if (!Number.isFinite(ratePerBtc) || ratePerBtc <= 0) {
|
|
51
|
+
throw new InvalidFxRateError(ratePerBtc);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isFinite(amount) || amount < 0) return 0;
|
|
54
|
+
return Math.ceil(amount * 1e8 / ratePerBtc);
|
|
55
|
+
}
|
|
56
|
+
function satsToFiat(sats, ratePerBtc) {
|
|
57
|
+
if (!Number.isFinite(ratePerBtc) || ratePerBtc <= 0) {
|
|
58
|
+
throw new InvalidFxRateError(ratePerBtc);
|
|
59
|
+
}
|
|
60
|
+
return sats / 1e8 * ratePerBtc;
|
|
61
|
+
}
|
|
62
|
+
var InvalidUsdPriceError = class extends Error {
|
|
63
|
+
constructor(field, value, detail) {
|
|
64
|
+
super(`${field}: ${detail}`);
|
|
65
|
+
this.field = field;
|
|
66
|
+
this.value = value;
|
|
67
|
+
this.name = "InvalidUsdPriceError";
|
|
68
|
+
}
|
|
69
|
+
field;
|
|
70
|
+
value;
|
|
71
|
+
code = "invalid_usd_price";
|
|
72
|
+
};
|
|
73
|
+
function parseUsdPrice(value, field) {
|
|
74
|
+
if (!USD_PRICE_RE.test(value)) {
|
|
75
|
+
throw new InvalidUsdPriceError(
|
|
76
|
+
field,
|
|
77
|
+
value,
|
|
78
|
+
`${JSON.stringify(value)} is not a USD price literal. Use a "$X.XX" string, e.g. "$0.05".`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const usd = parseFloat(value.slice(1));
|
|
82
|
+
if (!Number.isFinite(usd) || usd < MIN_USD_PRICE) {
|
|
83
|
+
throw new InvalidUsdPriceError(
|
|
84
|
+
field,
|
|
85
|
+
value,
|
|
86
|
+
`${JSON.stringify(value)} is below ${MIN_USD_PRICE_LITERAL}, the smallest price the wire can carry \u2014 every price surface rounds to four decimals, so this would advertise as $0 and then charge anyway. Charge at least ${MIN_USD_PRICE_LITERAL}, or omit the price for a free capability.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (roundUsd(usd) !== usd) {
|
|
90
|
+
throw new InvalidUsdPriceError(
|
|
91
|
+
field,
|
|
92
|
+
value,
|
|
93
|
+
`${JSON.stringify(value)} carries more than four decimal places. Every price surface rounds to four, so this would advertise ${formatUsd(roundUsd(usd))} and charge something else. Use at most four decimals.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (!Number.isSafeInteger(Math.round(usd * 1e6))) {
|
|
97
|
+
throw new InvalidUsdPriceError(
|
|
98
|
+
field,
|
|
99
|
+
value,
|
|
100
|
+
`${JSON.stringify(value)} is too large to denominate. Use an amount under $9,007,199,254.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return usd;
|
|
104
|
+
}
|
|
105
|
+
function roundUsd(usd) {
|
|
106
|
+
return Math.round(usd * 1e4) / 1e4;
|
|
107
|
+
}
|
|
108
|
+
function formatUsd(usd) {
|
|
109
|
+
if (usd === 0) return "$0.00";
|
|
110
|
+
if (usd >= 0.01) return `$${usd.toFixed(2)}`;
|
|
111
|
+
return `$${usd.toFixed(4)}`;
|
|
112
|
+
}
|
|
113
|
+
function formatFiat(amount, currency) {
|
|
114
|
+
const code = currency.toUpperCase();
|
|
115
|
+
const symbol = FIAT_SYMBOLS[currency] ?? "";
|
|
116
|
+
const digits = currency === "jpy" ? 0 : amount >= 0.01 || amount === 0 ? 2 : 4;
|
|
117
|
+
const body = `${amount.toFixed(digits)} ${code}`;
|
|
118
|
+
return symbol ? `${symbol}${body}` : body;
|
|
119
|
+
}
|
|
120
|
+
var USD_PRICE_RE = /^\$\d+(\.\d+)?$/;
|
|
121
|
+
var MIN_USD_PRICE = 1e-4;
|
|
122
|
+
var MIN_USD_PRICE_LITERAL = '"$0.0001"';
|
|
123
|
+
var FIAT_SYMBOLS = {
|
|
124
|
+
usd: "$",
|
|
125
|
+
eur: "\u20AC",
|
|
126
|
+
gbp: "\xA3",
|
|
127
|
+
jpy: "\xA5"
|
|
128
|
+
};
|
|
129
|
+
|
|
38
130
|
// src/sdk/pricing/fx.ts
|
|
39
131
|
var DEFAULT_FX_CURRENCIES = ["usd", "eur", "gbp", "jpy"];
|
|
40
132
|
var DEFAULT_FX_RATE_SOURCE = `https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${DEFAULT_FX_CURRENCIES.join(",")}`;
|
|
@@ -52,14 +144,53 @@ var FxRateUnavailableError = class extends Error {
|
|
|
52
144
|
* the last-known rate without it. See {@link FxKnownStale}.
|
|
53
145
|
*/
|
|
54
146
|
knownStale;
|
|
55
|
-
|
|
147
|
+
sourceFailure;
|
|
148
|
+
constructor(message, copy, knownStale, sourceFailure) {
|
|
56
149
|
super(message);
|
|
57
150
|
this.name = "FxRateUnavailableError";
|
|
58
151
|
this.display = copy?.display ?? "Couldn't fetch the BTC reference rate to lock pricing";
|
|
59
152
|
this.hint = copy?.hint ?? "transient \u2014 try the quote again in a few seconds";
|
|
60
153
|
this.knownStale = knownStale;
|
|
154
|
+
this.sourceFailure = normalizeFxSourceFailure(sourceFailure);
|
|
61
155
|
}
|
|
62
156
|
};
|
|
157
|
+
var FX_SOURCE_FAILURE_CATEGORIES = /* @__PURE__ */ new Set([
|
|
158
|
+
"network_error",
|
|
159
|
+
"http_error",
|
|
160
|
+
"invalid_body",
|
|
161
|
+
"no_usable_rate",
|
|
162
|
+
"missing_currency",
|
|
163
|
+
"unknown"
|
|
164
|
+
]);
|
|
165
|
+
function normalizeFxSourceFailure(input) {
|
|
166
|
+
if (input === void 0) return void 0;
|
|
167
|
+
try {
|
|
168
|
+
if (typeof input !== "object" || input === null) return { category: "unknown" };
|
|
169
|
+
const categoryDescriptor = Object.getOwnPropertyDescriptor(input, "category");
|
|
170
|
+
const category = categoryDescriptor && "value" in categoryDescriptor ? categoryDescriptor.value : void 0;
|
|
171
|
+
if (typeof category !== "string" || !FX_SOURCE_FAILURE_CATEGORIES.has(category)) {
|
|
172
|
+
return { category: "unknown" };
|
|
173
|
+
}
|
|
174
|
+
const statusDescriptor = Object.getOwnPropertyDescriptor(input, "httpStatus");
|
|
175
|
+
const status = statusDescriptor && "value" in statusDescriptor ? statusDescriptor.value : void 0;
|
|
176
|
+
return {
|
|
177
|
+
category,
|
|
178
|
+
...category === "http_error" && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 && { httpStatus: status }
|
|
179
|
+
};
|
|
180
|
+
} catch {
|
|
181
|
+
return { category: "unknown" };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function readFxSourceFailure(error) {
|
|
185
|
+
try {
|
|
186
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "sourceFailure");
|
|
187
|
+
if (!descriptor) return void 0;
|
|
188
|
+
if (!("value" in descriptor)) return { category: "unknown" };
|
|
189
|
+
return normalizeFxSourceFailure(descriptor.value);
|
|
190
|
+
} catch {
|
|
191
|
+
return { category: "unknown" };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
63
194
|
function createFxFetcher(opts) {
|
|
64
195
|
const currencies = opts?.currencies ?? DEFAULT_FX_CURRENCIES;
|
|
65
196
|
const source = opts?.source ?? DEFAULT_FX_RATE_SOURCE;
|
|
@@ -100,7 +231,10 @@ function createFxFetcher(opts) {
|
|
|
100
231
|
}
|
|
101
232
|
}
|
|
102
233
|
throw new FxRateUnavailableError(
|
|
103
|
-
lastErr instanceof Error ? lastErr.message : String(lastErr)
|
|
234
|
+
lastErr instanceof Error ? lastErr.message : String(lastErr),
|
|
235
|
+
void 0,
|
|
236
|
+
void 0,
|
|
237
|
+
lastErr instanceof FxRateUnavailableError ? readFxSourceFailure(lastErr) : { category: "unknown" }
|
|
104
238
|
);
|
|
105
239
|
})();
|
|
106
240
|
inFlight = promise;
|
|
@@ -127,7 +261,9 @@ function fxRateFor(snapshot, currency) {
|
|
|
127
261
|
{
|
|
128
262
|
display: `The ${currency.toUpperCase()} reference rate is unavailable right now.`,
|
|
129
263
|
hint: `The rate source stopped carrying ${currency.toUpperCase()}. Retry in a minute; if it persists the source needs to be reconfigured.`
|
|
130
|
-
}
|
|
264
|
+
},
|
|
265
|
+
void 0,
|
|
266
|
+
{ category: "missing_currency" }
|
|
131
267
|
);
|
|
132
268
|
}
|
|
133
269
|
throw new UnsupportedCurrencyError(currency, Object.keys(snapshot.rates));
|
|
@@ -139,19 +275,32 @@ async function fetchOnce(source, currencies, fetchFn) {
|
|
|
139
275
|
return fetchPlatformShape(source, currencies, fetchFn);
|
|
140
276
|
}
|
|
141
277
|
async function fetchCoinGeckoShape(source, currencies, fetchFn) {
|
|
142
|
-
const res = await fetchFn
|
|
278
|
+
const res = await fetchSource(fetchFn, source);
|
|
143
279
|
if (!res.ok) {
|
|
144
|
-
throw new
|
|
280
|
+
throw new FxRateUnavailableError(
|
|
281
|
+
`fx source ${source} returned ${res.status.toString()}`,
|
|
282
|
+
void 0,
|
|
283
|
+
void 0,
|
|
284
|
+
{ category: "http_error", httpStatus: res.status }
|
|
285
|
+
);
|
|
145
286
|
}
|
|
146
|
-
const body = await res
|
|
147
|
-
const bitcoin = body
|
|
148
|
-
if (!bitcoin || typeof bitcoin !== "object") {
|
|
149
|
-
throw new
|
|
287
|
+
const body = await parseRateBody(res);
|
|
288
|
+
const bitcoin = body?.bitcoin;
|
|
289
|
+
if (!bitcoin || typeof bitcoin !== "object" || Array.isArray(bitcoin)) {
|
|
290
|
+
throw new FxRateUnavailableError(
|
|
291
|
+
`fx source ${source} returned an unparsable rate body`,
|
|
292
|
+
void 0,
|
|
293
|
+
void 0,
|
|
294
|
+
{ category: "invalid_body" }
|
|
295
|
+
);
|
|
150
296
|
}
|
|
151
|
-
return {
|
|
297
|
+
return {
|
|
298
|
+
rates: collectRates(source, bitcoin, currencies),
|
|
299
|
+
as_of: (/* @__PURE__ */ new Date()).toISOString()
|
|
300
|
+
};
|
|
152
301
|
}
|
|
153
302
|
async function fetchPlatformShape(source, currencies, fetchFn) {
|
|
154
|
-
const res = await fetchFn
|
|
303
|
+
const res = await fetchSource(fetchFn, source.url, {
|
|
155
304
|
headers: { Authorization: `Bearer ${source.token}`, Accept: "application/json" }
|
|
156
305
|
});
|
|
157
306
|
if (!res.ok) {
|
|
@@ -160,20 +309,57 @@ async function fetchPlatformShape(source, currencies, fetchFn) {
|
|
|
160
309
|
throw new FxRateUnavailableError(
|
|
161
310
|
`fx source ${source.url} refused: rate ${knownStale.staleDays.toString()}d stale` + (knownStale.thresholdDays !== void 0 ? ` (threshold ${knownStale.thresholdDays.toString()}d)` : ""),
|
|
162
311
|
void 0,
|
|
163
|
-
knownStale
|
|
312
|
+
knownStale,
|
|
313
|
+
{ category: "http_error", httpStatus: res.status }
|
|
164
314
|
);
|
|
165
315
|
}
|
|
166
|
-
throw new
|
|
316
|
+
throw new FxRateUnavailableError(
|
|
317
|
+
`fx source ${source.url} returned ${res.status.toString()}`,
|
|
318
|
+
void 0,
|
|
319
|
+
void 0,
|
|
320
|
+
{ category: "http_error", httpStatus: res.status }
|
|
321
|
+
);
|
|
167
322
|
}
|
|
168
|
-
const body = await res
|
|
169
|
-
const raw = body
|
|
170
|
-
if (!raw || typeof raw !== "object") {
|
|
171
|
-
throw new
|
|
323
|
+
const body = await parseRateBody(res);
|
|
324
|
+
const raw = body?.rates;
|
|
325
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
326
|
+
throw new FxRateUnavailableError(
|
|
327
|
+
`fx source ${source.url} returned an unparsable rate body`,
|
|
328
|
+
void 0,
|
|
329
|
+
void 0,
|
|
330
|
+
{ category: "invalid_body" }
|
|
331
|
+
);
|
|
172
332
|
}
|
|
173
333
|
const rates = collectRates(source.url, raw, currencies);
|
|
174
334
|
const as_of = typeof body.as_of === "string" ? body.as_of : (/* @__PURE__ */ new Date()).toISOString();
|
|
175
335
|
return { rates, as_of };
|
|
176
336
|
}
|
|
337
|
+
async function fetchSource(fetchFn, url, init) {
|
|
338
|
+
try {
|
|
339
|
+
return await fetchFn(url, init);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
if (err instanceof FxRateUnavailableError) throw err;
|
|
342
|
+
throw new FxRateUnavailableError(
|
|
343
|
+
err instanceof Error ? err.message : String(err),
|
|
344
|
+
void 0,
|
|
345
|
+
void 0,
|
|
346
|
+
{ category: "network_error" }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function parseRateBody(res) {
|
|
351
|
+
try {
|
|
352
|
+
const body = await res.json();
|
|
353
|
+
return typeof body === "object" && body !== null && !Array.isArray(body) ? body : null;
|
|
354
|
+
} catch {
|
|
355
|
+
throw new FxRateUnavailableError(
|
|
356
|
+
"fx source returned an unparsable rate body",
|
|
357
|
+
void 0,
|
|
358
|
+
void 0,
|
|
359
|
+
{ category: "invalid_body" }
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
177
363
|
function collectRates(source, raw, currencies) {
|
|
178
364
|
const rates = {};
|
|
179
365
|
for (const currency of currencies) {
|
|
@@ -182,8 +368,11 @@ function collectRates(source, raw, currencies) {
|
|
|
182
368
|
rates[currency] = value;
|
|
183
369
|
}
|
|
184
370
|
if (Object.keys(rates).length === 0) {
|
|
185
|
-
throw new
|
|
186
|
-
`fx source ${source} returned no usable rate for any of [${currencies.join(", ")}]
|
|
371
|
+
throw new FxRateUnavailableError(
|
|
372
|
+
`fx source ${source} returned no usable rate for any of [${currencies.join(", ")}]`,
|
|
373
|
+
void 0,
|
|
374
|
+
void 0,
|
|
375
|
+
{ category: "no_usable_rate" }
|
|
187
376
|
);
|
|
188
377
|
}
|
|
189
378
|
return rates;
|
|
@@ -240,6 +429,7 @@ async function parsePlatformStaleRefusal(res) {
|
|
|
240
429
|
} catch {
|
|
241
430
|
return null;
|
|
242
431
|
}
|
|
432
|
+
if (typeof body !== "object" || body === null) return null;
|
|
243
433
|
const error = body.error;
|
|
244
434
|
if (!error || typeof error !== "object") return null;
|
|
245
435
|
const { code, detail } = error;
|
|
@@ -268,28 +458,26 @@ function resolveFxSourceFromEnv(env) {
|
|
|
268
458
|
return raw;
|
|
269
459
|
}
|
|
270
460
|
|
|
271
|
-
// src/sdk/pricing/upfront.ts
|
|
272
|
-
function buildUpfront(input) {
|
|
273
|
-
const currency = validateCurrency(input.currency);
|
|
274
|
-
const amount = input.amount;
|
|
275
|
-
if (!Number.isFinite(amount) || amount < 0) {
|
|
276
|
-
throw new Error(`invalid upfront amount: ${String(amount)} (must be a non-negative number)`);
|
|
277
|
-
}
|
|
278
|
-
return { amount, currency };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
461
|
export {
|
|
282
462
|
InvalidCurrencyError,
|
|
283
463
|
UnsupportedCurrencyError,
|
|
284
464
|
validateCurrency,
|
|
465
|
+
InvalidFxRateError,
|
|
466
|
+
fiatToSatsCeil,
|
|
467
|
+
satsToFiat,
|
|
468
|
+
InvalidUsdPriceError,
|
|
469
|
+
parseUsdPrice,
|
|
470
|
+
roundUsd,
|
|
471
|
+
formatUsd,
|
|
472
|
+
formatFiat,
|
|
285
473
|
DEFAULT_FX_CURRENCIES,
|
|
286
474
|
DEFAULT_FX_RATE_SOURCE,
|
|
287
475
|
FX_CACHE_TTL_MS,
|
|
288
476
|
FX_RETRY_COUNT,
|
|
289
477
|
FxRateUnavailableError,
|
|
478
|
+
readFxSourceFailure,
|
|
290
479
|
createFxFetcher,
|
|
291
480
|
warmFxSnapshot,
|
|
292
481
|
fxRateFor,
|
|
293
|
-
resolveFxSourceFromEnv
|
|
294
|
-
buildUpfront
|
|
482
|
+
resolveFxSourceFromEnv
|
|
295
483
|
};
|