mtok-relay 0.2.5 → 0.2.6
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/mtok-relay.mjs +64 -36
- package/package.json +1 -1
package/dist/mtok-relay.mjs
CHANGED
|
@@ -5295,9 +5295,9 @@ function createOnchainVerifier({
|
|
|
5295
5295
|
if (requestHash != null && lc(event.requestHash) !== lc(requestHash)) return { ok: false, reason: "request_hash_mismatch" };
|
|
5296
5296
|
if (BigInt(event.sellerUsdAtomic) < BigInt(minSellerAtomic)) return { ok: false, reason: "amount_too_low" };
|
|
5297
5297
|
const maxAge = Number(maxPaidAgeMs);
|
|
5298
|
+
let paidAtMs = null;
|
|
5298
5299
|
if (Number.isFinite(maxAge) && maxAge > 0) {
|
|
5299
5300
|
const bn = matchedLog?.blockNumber ?? got.receipt.blockNumber;
|
|
5300
|
-
let paidAtMs = null;
|
|
5301
5301
|
if (bn != null) {
|
|
5302
5302
|
for (let i = 0; i <= receiptRetries; i++) {
|
|
5303
5303
|
try {
|
|
@@ -5330,11 +5330,37 @@ function createOnchainVerifier({
|
|
|
5330
5330
|
feeTransfer = findUsdcTransfer(got.receipt, { to: feeRecipient, minAtomic: BigInt(event.feeUsdAtomic), from: event.buyer, consumed });
|
|
5331
5331
|
if (!feeTransfer.ok) return { ok: false, reason: "fee_transfer_" + feeTransfer.reason };
|
|
5332
5332
|
}
|
|
5333
|
-
return { ok: true, event, from: sellerTransfer?.from ?? feeTransfer?.from ?? null };
|
|
5333
|
+
return { ok: true, event, paidAtMs, from: sellerTransfer?.from ?? feeTransfer?.from ?? null };
|
|
5334
5334
|
}
|
|
5335
5335
|
};
|
|
5336
5336
|
}
|
|
5337
5337
|
|
|
5338
|
+
// core/fee-policy.js
|
|
5339
|
+
function normalizeFeeSchedule(schedule) {
|
|
5340
|
+
if (!Array.isArray(schedule) || !schedule.length || schedule.length > 64) throw new TypeError("fee schedule must contain 1..64 activation records");
|
|
5341
|
+
let previous = -1;
|
|
5342
|
+
return schedule.map(({ effectiveAtMs, feeBps }, index) => {
|
|
5343
|
+
if (!Number.isSafeInteger(effectiveAtMs) || effectiveAtMs <= previous || index === 0 && effectiveAtMs !== 0) {
|
|
5344
|
+
throw new TypeError("fee activations must start at 0 and increase in integer milliseconds");
|
|
5345
|
+
}
|
|
5346
|
+
if (!Number.isSafeInteger(feeBps) || feeBps < 0 || feeBps > 1e4) throw new TypeError("feeBps must be an integer from 0 to 10000");
|
|
5347
|
+
previous = effectiveAtMs;
|
|
5348
|
+
return { effectiveAtMs, feeBps };
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5351
|
+
function feeBpsAt(schedule, timeMs) {
|
|
5352
|
+
if (!Number.isSafeInteger(timeMs) || timeMs < 0) throw new TypeError("verified payment time is required for fee policy");
|
|
5353
|
+
return schedule.findLast((entry) => entry.effectiveAtMs <= timeMs).feeBps;
|
|
5354
|
+
}
|
|
5355
|
+
function paymentFeeBpsAt(schedule, paidAtMs) {
|
|
5356
|
+
const start = Math.max(0, paidAtMs - 6e4);
|
|
5357
|
+
let minimum = Math.min(feeBpsAt(schedule, paidAtMs), feeBpsAt(schedule, start));
|
|
5358
|
+
for (const entry of schedule) {
|
|
5359
|
+
if (entry.effectiveAtMs > start && entry.effectiveAtMs <= paidAtMs) minimum = Math.min(minimum, entry.feeBps);
|
|
5360
|
+
}
|
|
5361
|
+
return minimum;
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5338
5364
|
// bridge/serve-core.mjs
|
|
5339
5365
|
var BALANCE_EPSILON = 1e-6;
|
|
5340
5366
|
var REQUEST_NONCE_RE = /^0x[0-9a-fA-F]{32}$/;
|
|
@@ -5489,7 +5515,6 @@ function createServeCore({
|
|
|
5489
5515
|
maxOutputTokens
|
|
5490
5516
|
}) {
|
|
5491
5517
|
const serve = async (body) => {
|
|
5492
|
-
const currentFeeBps = typeof feeBps === "function" ? feeBps() : feeBps;
|
|
5493
5518
|
const currentFeeRecipient = typeof feeRecipient === "function" ? feeRecipient() : feeRecipient;
|
|
5494
5519
|
const { bookingId, n, buyerId, request, requestNonce, drawPaidTxHash } = body;
|
|
5495
5520
|
const hasRequestNonce = Object.hasOwn(body, "requestNonce");
|
|
@@ -5536,9 +5561,9 @@ function createServeCore({
|
|
|
5536
5561
|
requestHash,
|
|
5537
5562
|
sellerWallet,
|
|
5538
5563
|
feeRecipient: currentFeeRecipient,
|
|
5539
|
-
// A known
|
|
5564
|
+
// A known claim spends no new inference. New claims need a verified
|
|
5540
5565
|
// age because both payload and claim markers expire after retention.
|
|
5541
|
-
maxPaidAgeMs: redemptionState === "complete" ? void 0 : redemption.retentionMs
|
|
5566
|
+
maxPaidAgeMs: redemptionState === "complete" || redemptionState === "pending" ? void 0 : redemption.retentionMs
|
|
5542
5567
|
});
|
|
5543
5568
|
} catch (e) {
|
|
5544
5569
|
if (e.name === "TimeoutError") return { status: 503, body: { error: "relay_timeout", _bookingId: bookingId } };
|
|
@@ -5546,14 +5571,6 @@ function createServeCore({
|
|
|
5546
5571
|
}
|
|
5547
5572
|
if (paid?.reason === "payment_age_unavailable") return { status: 503, body: { error: "payment_age_unavailable", detail: "payment age could not be verified; retry this same paid draw", _bookingId: bookingId } };
|
|
5548
5573
|
if (!paid?.ok) return { status: 402, body: { error: "payment_unverified", detail: paid?.reason || "unknown" } };
|
|
5549
|
-
const expectedFee = configuredFeeAtomic({
|
|
5550
|
-
sellerUsdAtomic: paid.event.sellerUsdAtomic,
|
|
5551
|
-
feeAddress: currentFeeRecipient,
|
|
5552
|
-
feeBps: currentFeeBps
|
|
5553
|
-
});
|
|
5554
|
-
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
5555
|
-
return { status: 402, body: { error: "payment_unverified", detail: "fee_amount_too_low" } };
|
|
5556
|
-
}
|
|
5557
5574
|
if (screenPayer) {
|
|
5558
5575
|
try {
|
|
5559
5576
|
if (await screenPayer(String(paid.from || "").toLowerCase())) {
|
|
@@ -5567,6 +5584,17 @@ function createServeCore({
|
|
|
5567
5584
|
if (redemptionState === "pending") {
|
|
5568
5585
|
return { status: 409, body: { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId } };
|
|
5569
5586
|
}
|
|
5587
|
+
let currentFeeBps;
|
|
5588
|
+
try {
|
|
5589
|
+
currentFeeBps = typeof feeBps === "function" ? await feeBps(paid) : feeBps ?? 0;
|
|
5590
|
+
if (!Number.isSafeInteger(currentFeeBps) || currentFeeBps < 0 || currentFeeBps > 1e4) throw new TypeError("invalid fee rate");
|
|
5591
|
+
} catch {
|
|
5592
|
+
return { status: 503, body: { error: "fee_policy_unavailable", detail: "fee policy could not be verified; retry this same paid draw", _bookingId: bookingId } };
|
|
5593
|
+
}
|
|
5594
|
+
const expectedFee = configuredFeeAtomic({ sellerUsdAtomic: paid.event.sellerUsdAtomic, feeAddress: currentFeeRecipient, feeBps: currentFeeBps });
|
|
5595
|
+
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
5596
|
+
return { status: 402, body: { error: "payment_unverified", detail: "fee_amount_too_low" } };
|
|
5597
|
+
}
|
|
5570
5598
|
const paidEvent = paid.event;
|
|
5571
5599
|
const remainingUsd = Number(paid.event.sellerUsdAtomic || 0) / 1e6;
|
|
5572
5600
|
if (remainingUsd < BALANCE_EPSILON) {
|
|
@@ -5660,23 +5688,24 @@ async function createRelayRuntime(config) {
|
|
|
5660
5688
|
const served = createRedemptionStore({ file: config.redemptionFile, log: config.log });
|
|
5661
5689
|
const drawLocks = /* @__PURE__ */ new Map();
|
|
5662
5690
|
const platform = await fetchPlatformConfig(config);
|
|
5663
|
-
const bootFeeBps = platform.feeBps;
|
|
5664
5691
|
const FEE_REFRESH_MS = 6e4;
|
|
5665
5692
|
let lastConfigFetch = Date.now();
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5693
|
+
let refreshing;
|
|
5694
|
+
const refreshPlatformFee = () => {
|
|
5695
|
+
if (!refreshing) refreshing = (async () => {
|
|
5696
|
+
try {
|
|
5697
|
+
const fresh = await fetchPlatformConfig(config);
|
|
5698
|
+
platform.feeSchedule = fresh.feeSchedule;
|
|
5699
|
+
lastConfigFetch = Date.now();
|
|
5700
|
+
return true;
|
|
5701
|
+
} catch (e) {
|
|
5702
|
+
(config.log ?? console).error?.(`mtok relay: platform config refresh failed (${e.message}); new claims require current policy`);
|
|
5703
|
+
return false;
|
|
5704
|
+
}
|
|
5705
|
+
})().finally(() => {
|
|
5706
|
+
refreshing = null;
|
|
5707
|
+
});
|
|
5708
|
+
return refreshing;
|
|
5680
5709
|
};
|
|
5681
5710
|
const verifier = createOnchainVerifier({ rpcUrls: rpcUrlsFor(platform.chainId, config.rpcFlag), usdcAddress: platform.usdcAddress, expectedChainId: platform.chainId });
|
|
5682
5711
|
if (!verifier.configured) throw new Error("onchain verifier not configured (missing usdcAddress in /api/config)");
|
|
@@ -5701,11 +5730,11 @@ async function createRelayRuntime(config) {
|
|
|
5701
5730
|
sellerAgentId: config.sellerAgentId,
|
|
5702
5731
|
sellerWallet: config.settlementAddr,
|
|
5703
5732
|
dripContractAddress: platform.dripContractAddress,
|
|
5704
|
-
// #654: the fee floor is min(boot, current) so neither a fee increase nor a
|
|
5705
|
-
// decrease can over-demand and strand an honest already-paid draw; the recipient
|
|
5706
|
-
// stays pinned to the boot address (exact-match verify, see the note above).
|
|
5707
5733
|
feeRecipient: platform.feeAddress,
|
|
5708
|
-
feeBps: () =>
|
|
5734
|
+
feeBps: async ({ paidAtMs }) => {
|
|
5735
|
+
if (Date.now() - lastConfigFetch >= FEE_REFRESH_MS && !await refreshPlatformFee()) throw new Error("fee policy refresh failed");
|
|
5736
|
+
return paymentFeeBpsAt(platform.feeSchedule, paidAtMs);
|
|
5737
|
+
},
|
|
5709
5738
|
// #654: per-relay output sanity ceiling (unset => the shared generous default).
|
|
5710
5739
|
maxOutputTokens: config.maxOutputTokens,
|
|
5711
5740
|
screenPayer: payerDenied
|
|
@@ -5734,10 +5763,9 @@ async function createRelayRuntime(config) {
|
|
|
5734
5763
|
active++;
|
|
5735
5764
|
try {
|
|
5736
5765
|
return await withBookingLock(String(body?.bookingId ?? ""), async () => {
|
|
5737
|
-
await refreshPlatformFeeIfStale();
|
|
5738
5766
|
let out = await core.serve(body);
|
|
5739
|
-
if (out.status === 402 && out.body?.detail === "fee_amount_too_low"
|
|
5740
|
-
out = await core.serve(body);
|
|
5767
|
+
if (out.status === 402 && out.body?.detail === "fee_amount_too_low") {
|
|
5768
|
+
out = await refreshPlatformFee() ? await core.serve(body) : { status: 503, body: { error: "fee_policy_unavailable", _bookingId: body.bookingId } };
|
|
5741
5769
|
}
|
|
5742
5770
|
return send(res, out.status, out.body);
|
|
5743
5771
|
});
|
|
@@ -5753,7 +5781,7 @@ async function fetchPlatformConfig(config) {
|
|
|
5753
5781
|
const body = await r.json();
|
|
5754
5782
|
return {
|
|
5755
5783
|
feeAddress: body.feeAddress,
|
|
5756
|
-
feeBps: body.feeBps,
|
|
5784
|
+
feeSchedule: normalizeFeeSchedule(body.feeSchedule ?? [{ effectiveAtMs: 0, feeBps: body.feeBps ?? (body.feeAddress ? void 0 : 0) }]),
|
|
5757
5785
|
dustThresholdUsd: Number(body.dustThresholdUsd) || 1e-3,
|
|
5758
5786
|
chainId: Number(body.chainId ?? 8453),
|
|
5759
5787
|
usdcAddress: body.usdcAddress,
|
package/package.json
CHANGED