mtok-relay 0.2.0 → 0.2.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/mtok-relay.mjs +256 -205
- package/package.json +1 -1
package/dist/mtok-relay.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// node_modules/@noble/hashes/esm/cryptoNode.js
|
|
5
5
|
import * as nc from "node:crypto";
|
|
6
|
-
var
|
|
6
|
+
var crypto2 = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
|
|
7
7
|
|
|
8
8
|
// node_modules/@noble/hashes/esm/utils.js
|
|
9
9
|
function isBytes(a) {
|
|
@@ -100,11 +100,11 @@ function createHasher(hashCons) {
|
|
|
100
100
|
return hashC;
|
|
101
101
|
}
|
|
102
102
|
function randomBytes(bytesLength = 32) {
|
|
103
|
-
if (
|
|
104
|
-
return
|
|
103
|
+
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
|
104
|
+
return crypto2.getRandomValues(new Uint8Array(bytesLength));
|
|
105
105
|
}
|
|
106
|
-
if (
|
|
107
|
-
return Uint8Array.from(
|
|
106
|
+
if (crypto2 && typeof crypto2.randomBytes === "function") {
|
|
107
|
+
return Uint8Array.from(crypto2.randomBytes(bytesLength));
|
|
108
108
|
}
|
|
109
109
|
throw new Error("crypto.getRandomValues must be defined");
|
|
110
110
|
}
|
|
@@ -2157,7 +2157,7 @@ var secp256k1 = createCurve({
|
|
|
2157
2157
|
}, sha256);
|
|
2158
2158
|
|
|
2159
2159
|
// node_modules/viem/_esm/errors/version.js
|
|
2160
|
-
var version = "2.55.
|
|
2160
|
+
var version = "2.55.1";
|
|
2161
2161
|
|
|
2162
2162
|
// node_modules/viem/_esm/errors/base.js
|
|
2163
2163
|
var errorConfig = {
|
|
@@ -4778,45 +4778,9 @@ function startRelayServer({ config, handleDraw }) {
|
|
|
4778
4778
|
return server;
|
|
4779
4779
|
}
|
|
4780
4780
|
|
|
4781
|
-
// lib.mjs
|
|
4782
|
-
function enforceModelEcho(upstreamModel, offerModel) {
|
|
4783
|
-
if (String(upstreamModel) !== String(offerModel))
|
|
4784
|
-
throw new Error(`model mismatch: upstream ${upstreamModel} != offer ${offerModel}`);
|
|
4785
|
-
}
|
|
4786
|
-
function configuredFeeAtomic({ sellerUsdAtomic, feeAddress, feeBps }) {
|
|
4787
|
-
const bps = BigInt(Math.trunc(Math.max(0, Number(feeBps) || 0)));
|
|
4788
|
-
if (!feeAddress || bps === 0n) return 0n;
|
|
4789
|
-
return (BigInt(sellerUsdAtomic || 0) * bps + 5000n) / 10000n;
|
|
4790
|
-
}
|
|
4791
|
-
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
4792
|
-
function estimateInputTokens(messages) {
|
|
4793
|
-
const utf8 = new TextEncoder();
|
|
4794
|
-
let tokens = 3;
|
|
4795
|
-
for (const m of messages ?? []) {
|
|
4796
|
-
tokens += MESSAGE_OVERHEAD_TOKENS;
|
|
4797
|
-
tokens += utf8.encode(String(m?.role ?? "")).length;
|
|
4798
|
-
tokens += utf8.encode(typeof m?.content === "string" ? m.content : JSON.stringify(m?.content ?? null)).length;
|
|
4799
|
-
}
|
|
4800
|
-
return tokens;
|
|
4801
|
-
}
|
|
4802
|
-
function boundServe({ messages, budgetUsd, inPrice, outPrice, reqMax, contextCeil = 4096 }) {
|
|
4803
|
-
const estIn = estimateInputTokens(messages);
|
|
4804
|
-
const estInCostUsd = estIn * (Number(inPrice) > 0 ? Number(inPrice) : 0) / 1e6;
|
|
4805
|
-
if (estInCostUsd >= budgetUsd) return { refuse: true, reason: "input", estIn, estInCostUsd };
|
|
4806
|
-
const outBudgetUsd = budgetUsd - estInCostUsd;
|
|
4807
|
-
let maxTok = contextCeil;
|
|
4808
|
-
if (Number(reqMax) > 0) maxTok = Math.min(maxTok, Math.floor(Number(reqMax)));
|
|
4809
|
-
if (!Number.isFinite(Number(outPrice)) || Number(outPrice) <= 0) {
|
|
4810
|
-
return { refuse: true, reason: "output_price", estIn, estInCostUsd };
|
|
4811
|
-
}
|
|
4812
|
-
maxTok = Math.min(maxTok, Math.floor(outBudgetUsd / Number(outPrice) * 1e6));
|
|
4813
|
-
if (maxTok < 1) return { refuse: true, reason: "output", estIn, estInCostUsd };
|
|
4814
|
-
return { refuse: false, maxTok, estIn, estInCostUsd };
|
|
4815
|
-
}
|
|
4816
|
-
|
|
4817
4781
|
// src/redemption.mjs
|
|
4818
4782
|
import fs from "node:fs";
|
|
4819
|
-
import
|
|
4783
|
+
import crypto3 from "node:crypto";
|
|
4820
4784
|
var DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4821
4785
|
function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS, now = () => Date.now(), log = console } = {}) {
|
|
4822
4786
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4881,6 +4845,14 @@ function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS
|
|
|
4881
4845
|
fs.rmSync(compact, { force: true });
|
|
4882
4846
|
}
|
|
4883
4847
|
fs.mkdirSync(claimsDir, { recursive: true });
|
|
4848
|
+
const markerCutoff = Date.now() - retentionMs;
|
|
4849
|
+
for (const name of fs.readdirSync(claimsDir)) {
|
|
4850
|
+
try {
|
|
4851
|
+
const marker = `${claimsDir}/${name}`;
|
|
4852
|
+
if (fs.statSync(marker).mtimeMs < markerCutoff) fs.unlinkSync(marker);
|
|
4853
|
+
} catch {
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4884
4856
|
durable = true;
|
|
4885
4857
|
seenVersion = fileVersion();
|
|
4886
4858
|
log.log?.(`mtok-relay: durable redemption at ${file} (${map.size} entries loaded)`);
|
|
@@ -4897,7 +4869,7 @@ function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS
|
|
|
4897
4869
|
syncWrite(file, "a", JSON.stringify(recordFor(key, entry)) + "\n");
|
|
4898
4870
|
seenVersion = fileVersion();
|
|
4899
4871
|
};
|
|
4900
|
-
const markerFor = (key) => `${claimsDir}/${
|
|
4872
|
+
const markerFor = (key) => `${claimsDir}/${crypto3.createHash("sha256").update(String(key)).digest("hex")}`;
|
|
4901
4873
|
const markClaimed = (key) => {
|
|
4902
4874
|
if (!durable) throw new Error("durable redemption unavailable");
|
|
4903
4875
|
const marker = markerFor(key);
|
|
@@ -5230,50 +5202,16 @@ function createOnchainVerifier({
|
|
|
5230
5202
|
};
|
|
5231
5203
|
}
|
|
5232
5204
|
|
|
5233
|
-
// bridge/
|
|
5234
|
-
function httpUpstream({ baseUrl, key }) {
|
|
5235
|
-
const url = String(baseUrl || "").replace(/\/$/, "") + "/chat/completions";
|
|
5236
|
-
return async (payload) => {
|
|
5237
|
-
const res = await fetch(url, {
|
|
5238
|
-
method: "POST",
|
|
5239
|
-
headers: {
|
|
5240
|
-
"content-type": "application/json",
|
|
5241
|
-
...key ? { authorization: `Bearer ${key}` } : {}
|
|
5242
|
-
},
|
|
5243
|
-
body: JSON.stringify(payload)
|
|
5244
|
-
});
|
|
5245
|
-
const text = await res.text();
|
|
5246
|
-
let json;
|
|
5247
|
-
try {
|
|
5248
|
-
json = JSON.parse(text);
|
|
5249
|
-
} catch {
|
|
5250
|
-
throw new Error(`non-JSON upstream response (${res.status})`);
|
|
5251
|
-
}
|
|
5252
|
-
if (!res.ok) throw new Error(json?.error?.message || `upstream ${res.status}`);
|
|
5253
|
-
return json;
|
|
5254
|
-
};
|
|
5255
|
-
}
|
|
5256
|
-
|
|
5257
|
-
// src/rpc.mjs
|
|
5258
|
-
var BASE_MAINNET_RPCS = [
|
|
5259
|
-
"https://mainnet.base.org",
|
|
5260
|
-
"https://base.llamarpc.com",
|
|
5261
|
-
"https://base-rpc.publicnode.com",
|
|
5262
|
-
"https://base.drpc.org"
|
|
5263
|
-
];
|
|
5264
|
-
var BASE_SEPOLIA_RPCS = [
|
|
5265
|
-
"https://sepolia.base.org",
|
|
5266
|
-
"https://base-sepolia-rpc.publicnode.com"
|
|
5267
|
-
];
|
|
5268
|
-
var rpcUrlsFor = (chainId, override) => override ? [override] : Number(chainId) === 8453 ? BASE_MAINNET_RPCS : BASE_SEPOLIA_RPCS;
|
|
5269
|
-
|
|
5270
|
-
// src/runtime.mjs
|
|
5271
|
-
import crypto3 from "node:crypto";
|
|
5205
|
+
// bridge/serve-core.mjs
|
|
5272
5206
|
var BALANCE_EPSILON = 1e-6;
|
|
5273
|
-
var hash32 = (v) => "0x" + crypto3.createHash("sha256").update(typeof v === "string" ? v : JSON.stringify(v ?? null)).digest("hex");
|
|
5274
5207
|
var REQUEST_NONCE_RE = /^0x[0-9a-fA-F]{32}$/;
|
|
5275
5208
|
var CHAT_ROLES = /* @__PURE__ */ new Set(["developer", "system", "user", "assistant"]);
|
|
5276
5209
|
var REQUEST_KEYS = /* @__PURE__ */ new Set(["model", "messages", "max_tokens", "temperature", "response_format", "stream", "n"]);
|
|
5210
|
+
async function hash32(v) {
|
|
5211
|
+
const bytes = new TextEncoder().encode(typeof v === "string" ? v : JSON.stringify(v ?? null));
|
|
5212
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
5213
|
+
return "0x" + [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5214
|
+
}
|
|
5277
5215
|
function legacyContentText(content) {
|
|
5278
5216
|
const render = (part) => {
|
|
5279
5217
|
if (typeof part === "string") return part;
|
|
@@ -5345,6 +5283,220 @@ function validateRequest(request, model, { legacy = false } = {}) {
|
|
|
5345
5283
|
}
|
|
5346
5284
|
};
|
|
5347
5285
|
}
|
|
5286
|
+
function enforceModelEcho(upstreamModel, offerModel) {
|
|
5287
|
+
if (String(upstreamModel) !== String(offerModel))
|
|
5288
|
+
throw new Error(`model mismatch: upstream ${upstreamModel} != offer ${offerModel}`);
|
|
5289
|
+
}
|
|
5290
|
+
function configuredFeeAtomic({ sellerUsdAtomic, feeAddress, feeBps }) {
|
|
5291
|
+
const bps = BigInt(Math.trunc(Math.max(0, Number(feeBps) || 0)));
|
|
5292
|
+
if (!feeAddress || bps === 0n) return 0n;
|
|
5293
|
+
return (BigInt(sellerUsdAtomic || 0) * bps + 5000n) / 10000n;
|
|
5294
|
+
}
|
|
5295
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
5296
|
+
function estimateInputTokens(messages) {
|
|
5297
|
+
const utf8 = new TextEncoder();
|
|
5298
|
+
let tokens = 3;
|
|
5299
|
+
for (const m of messages ?? []) {
|
|
5300
|
+
tokens += MESSAGE_OVERHEAD_TOKENS;
|
|
5301
|
+
tokens += utf8.encode(String(m?.role ?? "")).length;
|
|
5302
|
+
tokens += utf8.encode(typeof m?.content === "string" ? m.content : JSON.stringify(m?.content ?? null)).length;
|
|
5303
|
+
}
|
|
5304
|
+
return tokens;
|
|
5305
|
+
}
|
|
5306
|
+
function boundServe({ messages, budgetUsd, inPrice, outPrice, reqMax, contextCeil = 4096 }) {
|
|
5307
|
+
const estIn = estimateInputTokens(messages);
|
|
5308
|
+
const estInCostUsd = estIn * (Number(inPrice) > 0 ? Number(inPrice) : 0) / 1e6;
|
|
5309
|
+
if (estInCostUsd >= budgetUsd) return { refuse: true, reason: "input", estIn, estInCostUsd };
|
|
5310
|
+
const outBudgetUsd = budgetUsd - estInCostUsd;
|
|
5311
|
+
let maxTok = contextCeil;
|
|
5312
|
+
if (Number(reqMax) > 0) maxTok = Math.min(maxTok, Math.floor(Number(reqMax)));
|
|
5313
|
+
if (!Number.isFinite(Number(outPrice)) || Number(outPrice) <= 0) {
|
|
5314
|
+
return { refuse: true, reason: "output_price", estIn, estInCostUsd };
|
|
5315
|
+
}
|
|
5316
|
+
maxTok = Math.min(maxTok, Math.floor(outBudgetUsd / Number(outPrice) * 1e6));
|
|
5317
|
+
if (maxTok < 1) return { refuse: true, reason: "output", estIn, estInCostUsd };
|
|
5318
|
+
return { refuse: false, maxTok, estIn, estInCostUsd };
|
|
5319
|
+
}
|
|
5320
|
+
function createServeCore({
|
|
5321
|
+
model,
|
|
5322
|
+
inPrice,
|
|
5323
|
+
outPrice,
|
|
5324
|
+
verifier,
|
|
5325
|
+
redemption,
|
|
5326
|
+
upstream,
|
|
5327
|
+
log,
|
|
5328
|
+
offerId,
|
|
5329
|
+
sellerAgentId,
|
|
5330
|
+
sellerWallet,
|
|
5331
|
+
dripContractAddress,
|
|
5332
|
+
feeRecipient,
|
|
5333
|
+
feeBps,
|
|
5334
|
+
screenPayer
|
|
5335
|
+
}) {
|
|
5336
|
+
const serve = async (body) => {
|
|
5337
|
+
const { bookingId, n, buyerId, request, requestNonce, drawPaidTxHash } = body;
|
|
5338
|
+
const hasRequestNonce = Object.hasOwn(body, "requestNonce");
|
|
5339
|
+
if (!bookingId) return { status: 400, body: { error: "bad_request", detail: "DRAW needs bookingId" } };
|
|
5340
|
+
if (n == null) return { status: 400, body: { error: "bad_request", detail: "DRAW needs a delivery index n (per-booking idempotency key)" } };
|
|
5341
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 4294967295) {
|
|
5342
|
+
return { status: 400, body: { error: "bad_request", detail: "DRAW delivery index n must be a nonnegative uint32 integer" } };
|
|
5343
|
+
}
|
|
5344
|
+
if (hasRequestNonce && !REQUEST_NONCE_RE.test(requestNonce)) {
|
|
5345
|
+
return { status: 400, body: { error: "bad_request", detail: "DRAW needs requestNonce as 16 random bytes encoded as 0x-prefixed hex" } };
|
|
5346
|
+
}
|
|
5347
|
+
const checked = validateRequest(request, model, { legacy: !hasRequestNonce });
|
|
5348
|
+
if (checked.error) return { status: 400, body: { error: "bad_request", detail: checked.error } };
|
|
5349
|
+
const requestHashScheme = hasRequestNonce ? "nonce-v1" : "legacy-v0";
|
|
5350
|
+
const requestHash = hasRequestNonce ? await hash32({ request, requestNonce }) : await hash32(request);
|
|
5351
|
+
const cacheKey = `${requestHashScheme}:${bookingId}:${n}:${requestHash}`;
|
|
5352
|
+
const oldLegacyKey = hasRequestNonce ? null : `${bookingId}:${n}:${requestHash}`;
|
|
5353
|
+
if (!dripContractAddress) {
|
|
5354
|
+
return { status: 402, body: { error: "contract_mode_required", detail: "this relay only serves contract-mode draws; the platform is not reporting a dripContractAddress" } };
|
|
5355
|
+
}
|
|
5356
|
+
if (!drawPaidTxHash) return { status: 402, body: { error: "draw_payment_required", detail: "contract mode requires drawPaidTxHash before upstream delivery" } };
|
|
5357
|
+
let paid;
|
|
5358
|
+
try {
|
|
5359
|
+
paid = await verifier.verifyDrawPaid(drawPaidTxHash, {
|
|
5360
|
+
contractAddress: dripContractAddress,
|
|
5361
|
+
buyerAgentId: buyerId,
|
|
5362
|
+
sellerAgentId,
|
|
5363
|
+
// when set, enforces the offer-owner match (#codex review)
|
|
5364
|
+
bookingId,
|
|
5365
|
+
offerId,
|
|
5366
|
+
model,
|
|
5367
|
+
n,
|
|
5368
|
+
requestHash,
|
|
5369
|
+
sellerWallet,
|
|
5370
|
+
feeRecipient,
|
|
5371
|
+
// #580: refuse a payment older than the redemption window. The JSONL
|
|
5372
|
+
// payload cache AND the claim markers are both aged out at boot (#600),
|
|
5373
|
+
// so past retention this age bound is the sole replay defense (its
|
|
5374
|
+
// skip-on-unreadable-block residual is named in redemption.mjs). An
|
|
5375
|
+
// honest retry is seconds-to-minutes old, never days.
|
|
5376
|
+
maxPaidAgeMs: redemption.retentionMs
|
|
5377
|
+
});
|
|
5378
|
+
} catch (e) {
|
|
5379
|
+
return { status: 402, body: { error: "payment_unverified", detail: e.message } };
|
|
5380
|
+
}
|
|
5381
|
+
if (!paid?.ok) return { status: 402, body: { error: "payment_unverified", detail: paid?.reason || "unknown" } };
|
|
5382
|
+
const expectedFee = configuredFeeAtomic({
|
|
5383
|
+
sellerUsdAtomic: paid.event.sellerUsdAtomic,
|
|
5384
|
+
feeAddress: feeRecipient,
|
|
5385
|
+
feeBps
|
|
5386
|
+
});
|
|
5387
|
+
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
5388
|
+
return { status: 402, body: { error: "payment_unverified", detail: "fee_amount_too_low" } };
|
|
5389
|
+
}
|
|
5390
|
+
if (screenPayer) {
|
|
5391
|
+
try {
|
|
5392
|
+
if (await screenPayer(String(paid.from || "").toLowerCase())) {
|
|
5393
|
+
return { status: 403, body: { error: "payer_denied", detail: "the verified payer wallet is denylisted by this relay" } };
|
|
5394
|
+
}
|
|
5395
|
+
} catch (e) {
|
|
5396
|
+
return { status: 403, body: { error: "payer_denied", detail: "payer screening failed: " + e.message } };
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
let storedKey = cacheKey;
|
|
5400
|
+
let redemptionState = await redemption.state(storedKey);
|
|
5401
|
+
if (!redemptionState && oldLegacyKey) {
|
|
5402
|
+
const oldLegacyState = await redemption.state(oldLegacyKey);
|
|
5403
|
+
if (oldLegacyState) {
|
|
5404
|
+
storedKey = oldLegacyKey;
|
|
5405
|
+
redemptionState = oldLegacyState;
|
|
5406
|
+
} else {
|
|
5407
|
+
redemptionState = await redemption.state(cacheKey);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
if (redemptionState === "complete") return { status: 200, body: await redemption.get(storedKey) };
|
|
5411
|
+
if (redemptionState === "pending") {
|
|
5412
|
+
return { status: 409, body: { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId } };
|
|
5413
|
+
}
|
|
5414
|
+
const paidEvent = paid.event;
|
|
5415
|
+
const remainingUsd = Number(paid.event.sellerUsdAtomic || 0) / 1e6;
|
|
5416
|
+
if (remainingUsd < BALANCE_EPSILON) {
|
|
5417
|
+
return { status: 402, body: { error: "balance_exhausted", detail: `remainingUsd=${remainingUsd}`, _bookingId: bookingId, remainingUsd } };
|
|
5418
|
+
}
|
|
5419
|
+
const eventInPriceUsd = Number(paidEvent.inputPricePerMTokAtomic || 0) / 1e6;
|
|
5420
|
+
const eventOutPriceUsd = Number(paidEvent.outputPricePerMTokAtomic || 0) / 1e6;
|
|
5421
|
+
const boundInPrice = Math.max(Number(inPrice) || 0, eventInPriceUsd);
|
|
5422
|
+
const boundOutPrice = Math.max(Number(outPrice) || 0, eventOutPriceUsd);
|
|
5423
|
+
const bound = boundServe({ messages: checked.safeRequest.messages, budgetUsd: remainingUsd, inPrice: boundInPrice, outPrice: boundOutPrice, reqMax: checked.safeRequest.max_tokens });
|
|
5424
|
+
if (bound.refuse) {
|
|
5425
|
+
const error = bound.reason === "input" ? "input_too_large" : "output_unfunded";
|
|
5426
|
+
return { status: 402, body: { error, detail: `estimated input (~${bound.estIn} tokens, $${bound.estInCostUsd.toFixed(6)}) leaves no safely funded output in the paid amount ($${remainingUsd})`, _bookingId: bookingId, remainingUsd } };
|
|
5427
|
+
}
|
|
5428
|
+
const safeRequest = { ...checked.safeRequest, model, max_tokens: bound.maxTok };
|
|
5429
|
+
try {
|
|
5430
|
+
if (!await redemption.claim(cacheKey, oldLegacyKey ?? cacheKey)) {
|
|
5431
|
+
return { status: 409, body: { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId } };
|
|
5432
|
+
}
|
|
5433
|
+
} catch (e) {
|
|
5434
|
+
return { status: 503, body: { error: "redemption_unavailable", detail: `could not durably claim the paid draw: ${e.message}`, _bookingId: bookingId } };
|
|
5435
|
+
}
|
|
5436
|
+
let completion;
|
|
5437
|
+
try {
|
|
5438
|
+
completion = await upstream(safeRequest);
|
|
5439
|
+
} catch (e) {
|
|
5440
|
+
return { status: 502, body: { error: "upstream_error", detail: e.message } };
|
|
5441
|
+
}
|
|
5442
|
+
try {
|
|
5443
|
+
enforceModelEcho(completion.model, model);
|
|
5444
|
+
} catch (e) {
|
|
5445
|
+
return { status: 502, body: { error: "model_mismatch", detail: e.message } };
|
|
5446
|
+
}
|
|
5447
|
+
const usage = completion.usage ?? {};
|
|
5448
|
+
const inTok = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0) || 0;
|
|
5449
|
+
const outTok = Number(usage.completion_tokens ?? usage.output_tokens ?? 0) || 0;
|
|
5450
|
+
const usedUsd = (inTok * Number(paidEvent.inputPricePerMTokAtomic || 0) + outTok * Number(paidEvent.outputPricePerMTokAtomic || 0)) / 1e12;
|
|
5451
|
+
const payload = { ...completion, _bookingId: bookingId, remainingUsd: Math.max(0, Math.round((remainingUsd - usedUsd) * 1e6) / 1e6) };
|
|
5452
|
+
try {
|
|
5453
|
+
await redemption.complete(cacheKey, payload);
|
|
5454
|
+
} catch (e) {
|
|
5455
|
+
(log ?? console).error?.(`mtok serve core: completion for ${cacheKey} could not be persisted (${e.message}); retries will remain pending`);
|
|
5456
|
+
}
|
|
5457
|
+
return { status: 200, body: payload };
|
|
5458
|
+
};
|
|
5459
|
+
return { serve };
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5462
|
+
// bridge/bridge.mjs
|
|
5463
|
+
function httpUpstream({ baseUrl, key }) {
|
|
5464
|
+
const url = String(baseUrl || "").replace(/\/$/, "") + "/chat/completions";
|
|
5465
|
+
return async (payload) => {
|
|
5466
|
+
const res = await fetch(url, {
|
|
5467
|
+
method: "POST",
|
|
5468
|
+
headers: {
|
|
5469
|
+
"content-type": "application/json",
|
|
5470
|
+
...key ? { authorization: `Bearer ${key}` } : {}
|
|
5471
|
+
},
|
|
5472
|
+
body: JSON.stringify(payload)
|
|
5473
|
+
});
|
|
5474
|
+
const text = await res.text();
|
|
5475
|
+
let json;
|
|
5476
|
+
try {
|
|
5477
|
+
json = JSON.parse(text);
|
|
5478
|
+
} catch {
|
|
5479
|
+
throw new Error(`non-JSON upstream response (${res.status})`);
|
|
5480
|
+
}
|
|
5481
|
+
if (!res.ok) throw new Error(json?.error?.message || `upstream ${res.status}`);
|
|
5482
|
+
return json;
|
|
5483
|
+
};
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5486
|
+
// src/rpc.mjs
|
|
5487
|
+
var BASE_MAINNET_RPCS = [
|
|
5488
|
+
"https://mainnet.base.org",
|
|
5489
|
+
"https://base.llamarpc.com",
|
|
5490
|
+
"https://base-rpc.publicnode.com",
|
|
5491
|
+
"https://base.drpc.org"
|
|
5492
|
+
];
|
|
5493
|
+
var BASE_SEPOLIA_RPCS = [
|
|
5494
|
+
"https://sepolia.base.org",
|
|
5495
|
+
"https://base-sepolia-rpc.publicnode.com"
|
|
5496
|
+
];
|
|
5497
|
+
var rpcUrlsFor = (chainId, override) => override ? [override] : Number(chainId) === 8453 ? BASE_MAINNET_RPCS : BASE_SEPOLIA_RPCS;
|
|
5498
|
+
|
|
5499
|
+
// src/runtime.mjs
|
|
5348
5500
|
async function createRelayRuntime(config) {
|
|
5349
5501
|
const served = createRedemptionStore({ file: config.redemptionFile, log: config.log });
|
|
5350
5502
|
const drawLocks = /* @__PURE__ */ new Map();
|
|
@@ -5360,6 +5512,22 @@ async function createRelayRuntime(config) {
|
|
|
5360
5512
|
if (screenPayer && await screenPayer(payer)) return true;
|
|
5361
5513
|
return false;
|
|
5362
5514
|
};
|
|
5515
|
+
const core = createServeCore({
|
|
5516
|
+
model: config.model,
|
|
5517
|
+
inPrice: config.inPrice,
|
|
5518
|
+
outPrice: config.outPrice,
|
|
5519
|
+
verifier,
|
|
5520
|
+
redemption: served,
|
|
5521
|
+
upstream,
|
|
5522
|
+
log: config.log,
|
|
5523
|
+
offerId: config.offerId,
|
|
5524
|
+
sellerAgentId: config.sellerAgentId,
|
|
5525
|
+
sellerWallet: config.settlementAddr,
|
|
5526
|
+
dripContractAddress: platform.dripContractAddress,
|
|
5527
|
+
feeRecipient: platform.feeAddress,
|
|
5528
|
+
feeBps: platform.feeBps,
|
|
5529
|
+
screenPayer: payerDenied
|
|
5530
|
+
});
|
|
5363
5531
|
const withBookingLock = async (bookingId, fn) => {
|
|
5364
5532
|
const previous = drawLocks.get(bookingId) || Promise.resolve();
|
|
5365
5533
|
let release;
|
|
@@ -5378,127 +5546,10 @@ async function createRelayRuntime(config) {
|
|
|
5378
5546
|
if (drawLocks.get(bookingId) === tail) drawLocks.delete(bookingId);
|
|
5379
5547
|
}
|
|
5380
5548
|
};
|
|
5381
|
-
const handleDraw =
|
|
5382
|
-
const
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
if (n == null) return send(res, 400, { error: "bad_request", detail: "DRAW needs a delivery index n (per-booking idempotency key)" });
|
|
5386
|
-
if (!Number.isSafeInteger(n) || n < 0 || n > 4294967295) {
|
|
5387
|
-
return send(res, 400, { error: "bad_request", detail: "DRAW delivery index n must be a nonnegative uint32 integer" });
|
|
5388
|
-
}
|
|
5389
|
-
if (hasRequestNonce && !REQUEST_NONCE_RE.test(requestNonce)) {
|
|
5390
|
-
return send(res, 400, { error: "bad_request", detail: "DRAW needs requestNonce as 16 random bytes encoded as 0x-prefixed hex" });
|
|
5391
|
-
}
|
|
5392
|
-
const checked = validateRequest(request, config.model, { legacy: !hasRequestNonce });
|
|
5393
|
-
if (checked.error) return send(res, 400, { error: "bad_request", detail: checked.error });
|
|
5394
|
-
return withBookingLock(bookingId, async () => {
|
|
5395
|
-
const requestHashScheme = hasRequestNonce ? "nonce-v1" : "legacy-v0";
|
|
5396
|
-
const requestHash = hasRequestNonce ? hash32({ request, requestNonce }) : hash32(request);
|
|
5397
|
-
const cacheKey = `${requestHashScheme}:${bookingId}:${n}:${requestHash}`;
|
|
5398
|
-
const oldLegacyKey = hasRequestNonce ? null : `${bookingId}:${n}:${requestHash}`;
|
|
5399
|
-
if (!platform.dripContractAddress) {
|
|
5400
|
-
return send(res, 402, { error: "contract_mode_required", detail: "this relay only serves contract-mode draws; the platform is not reporting a dripContractAddress" });
|
|
5401
|
-
}
|
|
5402
|
-
if (!drawPaidTxHash) return send(res, 402, { error: "draw_payment_required", detail: "contract mode requires drawPaidTxHash before upstream delivery" });
|
|
5403
|
-
let paid;
|
|
5404
|
-
try {
|
|
5405
|
-
paid = await verifier.verifyDrawPaid(drawPaidTxHash, {
|
|
5406
|
-
contractAddress: platform.dripContractAddress,
|
|
5407
|
-
buyerAgentId: buyerId,
|
|
5408
|
-
sellerAgentId: config.sellerAgentId,
|
|
5409
|
-
// when set, enforces the offer-owner match (#codex review)
|
|
5410
|
-
bookingId,
|
|
5411
|
-
offerId: config.offerId,
|
|
5412
|
-
model: config.model,
|
|
5413
|
-
n,
|
|
5414
|
-
requestHash,
|
|
5415
|
-
sellerWallet: config.settlementAddr,
|
|
5416
|
-
feeRecipient: platform.feeAddress,
|
|
5417
|
-
// #580: refuse a payment older than the redemption window. The JSONL
|
|
5418
|
-
// payload cache is compacted by age (exclusive claim markers remain
|
|
5419
|
-
// fail-closed), and an honest retry is seconds-to-minutes old, never days.
|
|
5420
|
-
maxPaidAgeMs: served.retentionMs
|
|
5421
|
-
});
|
|
5422
|
-
} catch (e) {
|
|
5423
|
-
return send(res, 402, { error: "payment_unverified", detail: e.message });
|
|
5424
|
-
}
|
|
5425
|
-
if (!paid?.ok) return send(res, 402, { error: "payment_unverified", detail: paid?.reason || "unknown" });
|
|
5426
|
-
const expectedFee = configuredFeeAtomic({
|
|
5427
|
-
sellerUsdAtomic: paid.event.sellerUsdAtomic,
|
|
5428
|
-
feeAddress: platform.feeAddress,
|
|
5429
|
-
feeBps: platform.feeBps
|
|
5430
|
-
});
|
|
5431
|
-
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
5432
|
-
return send(res, 402, { error: "payment_unverified", detail: "fee_amount_too_low" });
|
|
5433
|
-
}
|
|
5434
|
-
try {
|
|
5435
|
-
if (await payerDenied(String(paid.from || "").toLowerCase())) {
|
|
5436
|
-
return send(res, 403, { error: "payer_denied", detail: "the verified payer wallet is denylisted by this relay" });
|
|
5437
|
-
}
|
|
5438
|
-
} catch (e) {
|
|
5439
|
-
return send(res, 403, { error: "payer_denied", detail: "payer screening failed: " + e.message });
|
|
5440
|
-
}
|
|
5441
|
-
let storedKey = cacheKey;
|
|
5442
|
-
let redemptionState = served.state(storedKey);
|
|
5443
|
-
if (!redemptionState && oldLegacyKey) {
|
|
5444
|
-
const oldLegacyState = served.state(oldLegacyKey);
|
|
5445
|
-
if (oldLegacyState) {
|
|
5446
|
-
storedKey = oldLegacyKey;
|
|
5447
|
-
redemptionState = oldLegacyState;
|
|
5448
|
-
} else {
|
|
5449
|
-
redemptionState = served.state(cacheKey);
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5452
|
-
if (redemptionState === "complete") return send(res, 200, served.get(storedKey));
|
|
5453
|
-
if (redemptionState === "pending") {
|
|
5454
|
-
return send(res, 409, { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId });
|
|
5455
|
-
}
|
|
5456
|
-
const paidEvent = paid.event;
|
|
5457
|
-
const remainingUsd = Number(paid.event.sellerUsdAtomic || 0) / 1e6;
|
|
5458
|
-
if (remainingUsd < BALANCE_EPSILON) {
|
|
5459
|
-
return send(res, 402, { error: "balance_exhausted", detail: `remainingUsd=${remainingUsd}`, _bookingId: bookingId, remainingUsd });
|
|
5460
|
-
}
|
|
5461
|
-
const eventInPriceUsd = Number(paidEvent.inputPricePerMTokAtomic || 0) / 1e6;
|
|
5462
|
-
const eventOutPriceUsd = Number(paidEvent.outputPricePerMTokAtomic || 0) / 1e6;
|
|
5463
|
-
const inPrice = Math.max(Number(config.inPrice) || 0, eventInPriceUsd);
|
|
5464
|
-
const outPrice = Math.max(Number(config.outPrice) || 0, eventOutPriceUsd);
|
|
5465
|
-
const bound = boundServe({ messages: checked.safeRequest.messages, budgetUsd: remainingUsd, inPrice, outPrice, reqMax: checked.safeRequest.max_tokens });
|
|
5466
|
-
if (bound.refuse) {
|
|
5467
|
-
const error = bound.reason === "input" ? "input_too_large" : "output_unfunded";
|
|
5468
|
-
return send(res, 402, { error, detail: `estimated input (~${bound.estIn} tokens, $${bound.estInCostUsd.toFixed(6)}) leaves no safely funded output in the paid amount ($${remainingUsd})`, _bookingId: bookingId, remainingUsd });
|
|
5469
|
-
}
|
|
5470
|
-
const safeRequest = { ...checked.safeRequest, model: config.model, max_tokens: bound.maxTok };
|
|
5471
|
-
try {
|
|
5472
|
-
if (!served.claim(cacheKey, oldLegacyKey ?? cacheKey)) {
|
|
5473
|
-
return send(res, 409, { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId });
|
|
5474
|
-
}
|
|
5475
|
-
} catch (e) {
|
|
5476
|
-
return send(res, 503, { error: "redemption_unavailable", detail: `could not durably claim the paid draw: ${e.message}`, _bookingId: bookingId });
|
|
5477
|
-
}
|
|
5478
|
-
let completion;
|
|
5479
|
-
try {
|
|
5480
|
-
completion = await upstream(safeRequest);
|
|
5481
|
-
} catch (e) {
|
|
5482
|
-
return send(res, 502, { error: "upstream_error", detail: e.message });
|
|
5483
|
-
}
|
|
5484
|
-
try {
|
|
5485
|
-
enforceModelEcho(completion.model, config.model);
|
|
5486
|
-
} catch (e) {
|
|
5487
|
-
return send(res, 502, { error: "model_mismatch", detail: e.message });
|
|
5488
|
-
}
|
|
5489
|
-
const usage = completion.usage ?? {};
|
|
5490
|
-
const inTok = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0) || 0;
|
|
5491
|
-
const outTok = Number(usage.completion_tokens ?? usage.output_tokens ?? 0) || 0;
|
|
5492
|
-
const usedUsd = (inTok * Number(paidEvent.inputPricePerMTokAtomic || 0) + outTok * Number(paidEvent.outputPricePerMTokAtomic || 0)) / 1e12;
|
|
5493
|
-
const payload = { ...completion, _bookingId: bookingId, remainingUsd: Math.max(0, Math.round((remainingUsd - usedUsd) * 1e6) / 1e6) };
|
|
5494
|
-
try {
|
|
5495
|
-
served.complete(cacheKey, payload);
|
|
5496
|
-
} catch (e) {
|
|
5497
|
-
(config.log ?? console).error?.(`mtok-relay: completion for ${cacheKey} could not be persisted (${e.message}); retries will remain pending`);
|
|
5498
|
-
}
|
|
5499
|
-
return send(res, 200, payload);
|
|
5500
|
-
});
|
|
5501
|
-
};
|
|
5549
|
+
const handleDraw = (body, res) => withBookingLock(String(body?.bookingId ?? ""), async () => {
|
|
5550
|
+
const out = await core.serve(body);
|
|
5551
|
+
return send(res, out.status, out.body);
|
|
5552
|
+
});
|
|
5502
5553
|
return { handleDraw };
|
|
5503
5554
|
}
|
|
5504
5555
|
async function fetchPlatformConfig(config) {
|
package/package.json
CHANGED