mtok-relay 0.2.0 → 0.2.2
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 +294 -224
- 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
|
}
|
|
@@ -2016,14 +2016,14 @@ function weierstrass(curveDef) {
|
|
|
2016
2016
|
const sg = signature;
|
|
2017
2017
|
msgHash = ensureBytes("msgHash", msgHash);
|
|
2018
2018
|
publicKey = ensureBytes("publicKey", publicKey);
|
|
2019
|
-
const { lowS, prehash, format } = opts;
|
|
2019
|
+
const { lowS, prehash, format: format2 } = opts;
|
|
2020
2020
|
validateSigVerOpts(opts);
|
|
2021
2021
|
if ("strict" in opts)
|
|
2022
2022
|
throw new Error("options.strict was renamed to lowS");
|
|
2023
|
-
if (
|
|
2023
|
+
if (format2 !== void 0 && format2 !== "compact" && format2 !== "der")
|
|
2024
2024
|
throw new Error("format must be compact or der");
|
|
2025
2025
|
const isHex2 = typeof sg === "string" || isBytes2(sg);
|
|
2026
|
-
const isObj = !isHex2 && !
|
|
2026
|
+
const isObj = !isHex2 && !format2 && typeof sg === "object" && sg !== null && typeof sg.r === "bigint" && typeof sg.s === "bigint";
|
|
2027
2027
|
if (!isHex2 && !isObj)
|
|
2028
2028
|
throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");
|
|
2029
2029
|
let _sig = void 0;
|
|
@@ -2033,13 +2033,13 @@ function weierstrass(curveDef) {
|
|
|
2033
2033
|
_sig = new Signature(sg.r, sg.s);
|
|
2034
2034
|
if (isHex2) {
|
|
2035
2035
|
try {
|
|
2036
|
-
if (
|
|
2036
|
+
if (format2 !== "compact")
|
|
2037
2037
|
_sig = Signature.fromDER(sg);
|
|
2038
2038
|
} catch (derError) {
|
|
2039
2039
|
if (!(derError instanceof DER.Err))
|
|
2040
2040
|
throw derError;
|
|
2041
2041
|
}
|
|
2042
|
-
if (!_sig &&
|
|
2042
|
+
if (!_sig && format2 !== "der")
|
|
2043
2043
|
_sig = Signature.fromCompact(sg);
|
|
2044
2044
|
}
|
|
2045
2045
|
P = Point.fromHex(publicKey);
|
|
@@ -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.10";
|
|
2161
2161
|
|
|
2162
2162
|
// node_modules/viem/_esm/errors/base.js
|
|
2163
2163
|
var errorConfig = {
|
|
@@ -3215,14 +3215,17 @@ async function signMessage({ message, privateKey }) {
|
|
|
3215
3215
|
return await sign({ hash: hashMessage(message), privateKey, to: "hex" });
|
|
3216
3216
|
}
|
|
3217
3217
|
|
|
3218
|
-
// node_modules/viem/_esm/
|
|
3219
|
-
var
|
|
3220
|
-
|
|
3221
|
-
|
|
3218
|
+
// node_modules/viem/_esm/utils/unit/Value.js
|
|
3219
|
+
var exponents = {
|
|
3220
|
+
wei: 0,
|
|
3221
|
+
gwei: 9,
|
|
3222
|
+
szabo: 12,
|
|
3223
|
+
finney: 15,
|
|
3224
|
+
ether: 18
|
|
3222
3225
|
};
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
+
function format(value, decimals = 0) {
|
|
3227
|
+
if (!Number.isInteger(decimals) || decimals < 0)
|
|
3228
|
+
throw new InvalidDecimalsError({ decimals });
|
|
3226
3229
|
let display = value.toString();
|
|
3227
3230
|
const negative = display.startsWith("-");
|
|
3228
3231
|
if (negative)
|
|
@@ -3235,10 +3238,24 @@ function formatUnits(value, decimals) {
|
|
|
3235
3238
|
fraction = fraction.replace(/(0+)$/, "");
|
|
3236
3239
|
return `${negative ? "-" : ""}${integer || "0"}${fraction ? `.${fraction}` : ""}`;
|
|
3237
3240
|
}
|
|
3241
|
+
function formatGwei(wei, unit = "wei") {
|
|
3242
|
+
return format(wei, exponents.gwei - exponents[unit]);
|
|
3243
|
+
}
|
|
3244
|
+
var InvalidDecimalsError = class extends Error {
|
|
3245
|
+
constructor({ decimals }) {
|
|
3246
|
+
super(`\`decimals\` must be a non-negative integer. Got \`${decimals}\`.`);
|
|
3247
|
+
Object.defineProperty(this, "name", {
|
|
3248
|
+
enumerable: true,
|
|
3249
|
+
configurable: true,
|
|
3250
|
+
writable: true,
|
|
3251
|
+
value: "Value.InvalidDecimalsError"
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3238
3255
|
|
|
3239
3256
|
// node_modules/viem/_esm/utils/unit/formatGwei.js
|
|
3240
|
-
function
|
|
3241
|
-
return
|
|
3257
|
+
function formatGwei2(wei, unit = "wei") {
|
|
3258
|
+
return formatGwei(wei, unit);
|
|
3242
3259
|
}
|
|
3243
3260
|
|
|
3244
3261
|
// node_modules/viem/_esm/errors/transaction.js
|
|
@@ -3591,7 +3608,7 @@ Object.defineProperty(ExecutionRevertedError, "nodeMessage", {
|
|
|
3591
3608
|
});
|
|
3592
3609
|
var FeeCapTooHighError = class extends BaseError {
|
|
3593
3610
|
constructor({ cause, maxFeePerGas } = {}) {
|
|
3594
|
-
super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${
|
|
3611
|
+
super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei2(maxFeePerGas)} gwei` : ""}) cannot be higher than the maximum allowed value (2^256-1).`, {
|
|
3595
3612
|
cause,
|
|
3596
3613
|
name: "FeeCapTooHighError"
|
|
3597
3614
|
});
|
|
@@ -3605,7 +3622,7 @@ Object.defineProperty(FeeCapTooHighError, "nodeMessage", {
|
|
|
3605
3622
|
});
|
|
3606
3623
|
var FeeCapTooLowError = class extends BaseError {
|
|
3607
3624
|
constructor({ cause, maxFeePerGas } = {}) {
|
|
3608
|
-
super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${
|
|
3625
|
+
super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei2(maxFeePerGas)}` : ""} gwei) cannot be lower than the block base fee.`, {
|
|
3609
3626
|
cause,
|
|
3610
3627
|
name: "FeeCapTooLowError"
|
|
3611
3628
|
});
|
|
@@ -3724,7 +3741,7 @@ Object.defineProperty(TransactionTypeNotSupportedError, "nodeMessage", {
|
|
|
3724
3741
|
var TipAboveFeeCapError = class extends BaseError {
|
|
3725
3742
|
constructor({ cause, maxPriorityFeePerGas, maxFeePerGas } = {}) {
|
|
3726
3743
|
super([
|
|
3727
|
-
`The provided tip (\`maxPriorityFeePerGas\`${maxPriorityFeePerGas ? ` = ${
|
|
3744
|
+
`The provided tip (\`maxPriorityFeePerGas\`${maxPriorityFeePerGas ? ` = ${formatGwei2(maxPriorityFeePerGas)} gwei` : ""}) cannot be higher than the fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei2(maxFeePerGas)} gwei` : ""}).`
|
|
3728
3745
|
].join("\n"), {
|
|
3729
3746
|
cause,
|
|
3730
3747
|
name: "TipAboveFeeCapError"
|
|
@@ -4778,45 +4795,9 @@ function startRelayServer({ config, handleDraw }) {
|
|
|
4778
4795
|
return server;
|
|
4779
4796
|
}
|
|
4780
4797
|
|
|
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
4798
|
// src/redemption.mjs
|
|
4818
4799
|
import fs from "node:fs";
|
|
4819
|
-
import
|
|
4800
|
+
import crypto3 from "node:crypto";
|
|
4820
4801
|
var DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4821
4802
|
function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS, now = () => Date.now(), log = console } = {}) {
|
|
4822
4803
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4881,6 +4862,14 @@ function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS
|
|
|
4881
4862
|
fs.rmSync(compact, { force: true });
|
|
4882
4863
|
}
|
|
4883
4864
|
fs.mkdirSync(claimsDir, { recursive: true });
|
|
4865
|
+
const markerCutoff = Date.now() - retentionMs;
|
|
4866
|
+
for (const name of fs.readdirSync(claimsDir)) {
|
|
4867
|
+
try {
|
|
4868
|
+
const marker = `${claimsDir}/${name}`;
|
|
4869
|
+
if (fs.statSync(marker).mtimeMs < markerCutoff) fs.unlinkSync(marker);
|
|
4870
|
+
} catch {
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4884
4873
|
durable = true;
|
|
4885
4874
|
seenVersion = fileVersion();
|
|
4886
4875
|
log.log?.(`mtok-relay: durable redemption at ${file} (${map.size} entries loaded)`);
|
|
@@ -4897,7 +4886,7 @@ function createRedemptionStore({ file = null, retentionMs = DEFAULT_RETENTION_MS
|
|
|
4897
4886
|
syncWrite(file, "a", JSON.stringify(recordFor(key, entry)) + "\n");
|
|
4898
4887
|
seenVersion = fileVersion();
|
|
4899
4888
|
};
|
|
4900
|
-
const markerFor = (key) => `${claimsDir}/${
|
|
4889
|
+
const markerFor = (key) => `${claimsDir}/${crypto3.createHash("sha256").update(String(key)).digest("hex")}`;
|
|
4901
4890
|
const markClaimed = (key) => {
|
|
4902
4891
|
if (!durable) throw new Error("durable redemption unavailable");
|
|
4903
4892
|
const marker = markerFor(key);
|
|
@@ -5230,50 +5219,16 @@ function createOnchainVerifier({
|
|
|
5230
5219
|
};
|
|
5231
5220
|
}
|
|
5232
5221
|
|
|
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";
|
|
5222
|
+
// bridge/serve-core.mjs
|
|
5272
5223
|
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
5224
|
var REQUEST_NONCE_RE = /^0x[0-9a-fA-F]{32}$/;
|
|
5275
5225
|
var CHAT_ROLES = /* @__PURE__ */ new Set(["developer", "system", "user", "assistant"]);
|
|
5276
5226
|
var REQUEST_KEYS = /* @__PURE__ */ new Set(["model", "messages", "max_tokens", "temperature", "response_format", "stream", "n"]);
|
|
5227
|
+
async function hash32(v) {
|
|
5228
|
+
const bytes = new TextEncoder().encode(typeof v === "string" ? v : JSON.stringify(v ?? null));
|
|
5229
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
5230
|
+
return "0x" + [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5231
|
+
}
|
|
5277
5232
|
function legacyContentText(content) {
|
|
5278
5233
|
const render = (part) => {
|
|
5279
5234
|
if (typeof part === "string") return part;
|
|
@@ -5329,8 +5284,8 @@ function validateRequest(request, model, { legacy = false } = {}) {
|
|
|
5329
5284
|
}
|
|
5330
5285
|
let validResponseFormat = false;
|
|
5331
5286
|
if (request.response_format != null) {
|
|
5332
|
-
const
|
|
5333
|
-
validResponseFormat = !!
|
|
5287
|
+
const format2 = request.response_format;
|
|
5288
|
+
validResponseFormat = !!format2 && typeof format2 === "object" && !Array.isArray(format2) && Object.keys(format2).length === 1 && ["json_object", "text"].includes(format2.type);
|
|
5334
5289
|
if (!legacy && !validResponseFormat) {
|
|
5335
5290
|
return { error: 'response_format must be exactly { type: "json_object" } or { type: "text" }' };
|
|
5336
5291
|
}
|
|
@@ -5345,6 +5300,222 @@ function validateRequest(request, model, { legacy = false } = {}) {
|
|
|
5345
5300
|
}
|
|
5346
5301
|
};
|
|
5347
5302
|
}
|
|
5303
|
+
function enforceModelEcho(upstreamModel, offerModel) {
|
|
5304
|
+
if (String(upstreamModel) !== String(offerModel))
|
|
5305
|
+
throw new Error(`model mismatch: upstream ${upstreamModel} != offer ${offerModel}`);
|
|
5306
|
+
}
|
|
5307
|
+
function configuredFeeAtomic({ sellerUsdAtomic, feeAddress, feeBps }) {
|
|
5308
|
+
const bps = BigInt(Math.trunc(Math.max(0, Number(feeBps) || 0)));
|
|
5309
|
+
if (!feeAddress || bps === 0n) return 0n;
|
|
5310
|
+
return (BigInt(sellerUsdAtomic || 0) * bps + 5000n) / 10000n;
|
|
5311
|
+
}
|
|
5312
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
5313
|
+
var BYTES_PER_TOKEN_EST = 3.2;
|
|
5314
|
+
function estimateInputTokens(messages) {
|
|
5315
|
+
const utf8 = new TextEncoder();
|
|
5316
|
+
let bytes = 0;
|
|
5317
|
+
let envelope = 3;
|
|
5318
|
+
for (const m of messages ?? []) {
|
|
5319
|
+
envelope += MESSAGE_OVERHEAD_TOKENS;
|
|
5320
|
+
bytes += utf8.encode(String(m?.role ?? "")).length;
|
|
5321
|
+
bytes += utf8.encode(typeof m?.content === "string" ? m.content : JSON.stringify(m?.content ?? null)).length;
|
|
5322
|
+
}
|
|
5323
|
+
return envelope + Math.ceil(bytes / BYTES_PER_TOKEN_EST);
|
|
5324
|
+
}
|
|
5325
|
+
function boundServe({ messages, budgetUsd, inPrice, outPrice, reqMax, contextCeil = 4096 }) {
|
|
5326
|
+
const estIn = estimateInputTokens(messages);
|
|
5327
|
+
const estInCostUsd = estIn * (Number(inPrice) > 0 ? Number(inPrice) : 0) / 1e6;
|
|
5328
|
+
if (estInCostUsd >= budgetUsd) return { refuse: true, reason: "input", estIn, estInCostUsd };
|
|
5329
|
+
const outBudgetUsd = budgetUsd - estInCostUsd;
|
|
5330
|
+
let maxTok = contextCeil;
|
|
5331
|
+
if (Number(reqMax) > 0) maxTok = Math.min(maxTok, Math.floor(Number(reqMax)));
|
|
5332
|
+
if (!Number.isFinite(Number(outPrice)) || Number(outPrice) <= 0) {
|
|
5333
|
+
return { refuse: true, reason: "output_price", estIn, estInCostUsd };
|
|
5334
|
+
}
|
|
5335
|
+
maxTok = Math.min(maxTok, Math.floor(outBudgetUsd / Number(outPrice) * 1e6));
|
|
5336
|
+
if (maxTok < 1) return { refuse: true, reason: "output", estIn, estInCostUsd };
|
|
5337
|
+
return { refuse: false, maxTok, estIn, estInCostUsd };
|
|
5338
|
+
}
|
|
5339
|
+
function createServeCore({
|
|
5340
|
+
model,
|
|
5341
|
+
inPrice,
|
|
5342
|
+
outPrice,
|
|
5343
|
+
verifier,
|
|
5344
|
+
redemption,
|
|
5345
|
+
upstream,
|
|
5346
|
+
log,
|
|
5347
|
+
offerId,
|
|
5348
|
+
sellerAgentId,
|
|
5349
|
+
sellerWallet,
|
|
5350
|
+
dripContractAddress,
|
|
5351
|
+
feeRecipient,
|
|
5352
|
+
feeBps,
|
|
5353
|
+
screenPayer
|
|
5354
|
+
}) {
|
|
5355
|
+
const serve = async (body) => {
|
|
5356
|
+
const { bookingId, n, buyerId, request, requestNonce, drawPaidTxHash } = body;
|
|
5357
|
+
const hasRequestNonce = Object.hasOwn(body, "requestNonce");
|
|
5358
|
+
if (!bookingId) return { status: 400, body: { error: "bad_request", detail: "DRAW needs bookingId" } };
|
|
5359
|
+
if (n == null) return { status: 400, body: { error: "bad_request", detail: "DRAW needs a delivery index n (per-booking idempotency key)" } };
|
|
5360
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 4294967295) {
|
|
5361
|
+
return { status: 400, body: { error: "bad_request", detail: "DRAW delivery index n must be a nonnegative uint32 integer" } };
|
|
5362
|
+
}
|
|
5363
|
+
if (hasRequestNonce && !REQUEST_NONCE_RE.test(requestNonce)) {
|
|
5364
|
+
return { status: 400, body: { error: "bad_request", detail: "DRAW needs requestNonce as 16 random bytes encoded as 0x-prefixed hex" } };
|
|
5365
|
+
}
|
|
5366
|
+
const checked = validateRequest(request, model, { legacy: !hasRequestNonce });
|
|
5367
|
+
if (checked.error) return { status: 400, body: { error: "bad_request", detail: checked.error } };
|
|
5368
|
+
const requestHashScheme = hasRequestNonce ? "nonce-v1" : "legacy-v0";
|
|
5369
|
+
const requestHash = hasRequestNonce ? await hash32({ request, requestNonce }) : await hash32(request);
|
|
5370
|
+
const cacheKey = `${requestHashScheme}:${bookingId}:${n}:${requestHash}`;
|
|
5371
|
+
const oldLegacyKey = hasRequestNonce ? null : `${bookingId}:${n}:${requestHash}`;
|
|
5372
|
+
if (!dripContractAddress) {
|
|
5373
|
+
return { status: 402, body: { error: "contract_mode_required", detail: "this relay only serves contract-mode draws; the platform is not reporting a dripContractAddress" } };
|
|
5374
|
+
}
|
|
5375
|
+
if (!drawPaidTxHash) return { status: 402, body: { error: "draw_payment_required", detail: "contract mode requires drawPaidTxHash before upstream delivery" } };
|
|
5376
|
+
let paid;
|
|
5377
|
+
try {
|
|
5378
|
+
paid = await verifier.verifyDrawPaid(drawPaidTxHash, {
|
|
5379
|
+
contractAddress: dripContractAddress,
|
|
5380
|
+
buyerAgentId: buyerId,
|
|
5381
|
+
sellerAgentId,
|
|
5382
|
+
// when set, enforces the offer-owner match (#codex review)
|
|
5383
|
+
bookingId,
|
|
5384
|
+
offerId,
|
|
5385
|
+
model,
|
|
5386
|
+
n,
|
|
5387
|
+
requestHash,
|
|
5388
|
+
sellerWallet,
|
|
5389
|
+
feeRecipient,
|
|
5390
|
+
// #580: refuse a payment older than the redemption window. The JSONL
|
|
5391
|
+
// payload cache AND the claim markers are both aged out at boot (#600),
|
|
5392
|
+
// so past retention this age bound is the sole replay defense (its
|
|
5393
|
+
// skip-on-unreadable-block residual is named in redemption.mjs). An
|
|
5394
|
+
// honest retry is seconds-to-minutes old, never days.
|
|
5395
|
+
maxPaidAgeMs: redemption.retentionMs
|
|
5396
|
+
});
|
|
5397
|
+
} catch (e) {
|
|
5398
|
+
return { status: 402, body: { error: "payment_unverified", detail: e.message } };
|
|
5399
|
+
}
|
|
5400
|
+
if (!paid?.ok) return { status: 402, body: { error: "payment_unverified", detail: paid?.reason || "unknown" } };
|
|
5401
|
+
const expectedFee = configuredFeeAtomic({
|
|
5402
|
+
sellerUsdAtomic: paid.event.sellerUsdAtomic,
|
|
5403
|
+
feeAddress: feeRecipient,
|
|
5404
|
+
feeBps
|
|
5405
|
+
});
|
|
5406
|
+
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
5407
|
+
return { status: 402, body: { error: "payment_unverified", detail: "fee_amount_too_low" } };
|
|
5408
|
+
}
|
|
5409
|
+
if (screenPayer) {
|
|
5410
|
+
try {
|
|
5411
|
+
if (await screenPayer(String(paid.from || "").toLowerCase())) {
|
|
5412
|
+
return { status: 403, body: { error: "payer_denied", detail: "the verified payer wallet is denylisted by this relay" } };
|
|
5413
|
+
}
|
|
5414
|
+
} catch (e) {
|
|
5415
|
+
return { status: 403, body: { error: "payer_denied", detail: "payer screening failed: " + e.message } };
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
let storedKey = cacheKey;
|
|
5419
|
+
let redemptionState = await redemption.state(storedKey);
|
|
5420
|
+
if (!redemptionState && oldLegacyKey) {
|
|
5421
|
+
const oldLegacyState = await redemption.state(oldLegacyKey);
|
|
5422
|
+
if (oldLegacyState) {
|
|
5423
|
+
storedKey = oldLegacyKey;
|
|
5424
|
+
redemptionState = oldLegacyState;
|
|
5425
|
+
} else {
|
|
5426
|
+
redemptionState = await redemption.state(cacheKey);
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
if (redemptionState === "complete") return { status: 200, body: await redemption.get(storedKey) };
|
|
5430
|
+
if (redemptionState === "pending") {
|
|
5431
|
+
return { status: 409, body: { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId } };
|
|
5432
|
+
}
|
|
5433
|
+
const paidEvent = paid.event;
|
|
5434
|
+
const remainingUsd = Number(paid.event.sellerUsdAtomic || 0) / 1e6;
|
|
5435
|
+
if (remainingUsd < BALANCE_EPSILON) {
|
|
5436
|
+
return { status: 402, body: { error: "balance_exhausted", detail: `remainingUsd=${remainingUsd}`, _bookingId: bookingId, remainingUsd } };
|
|
5437
|
+
}
|
|
5438
|
+
const eventInPriceUsd = Number(paidEvent.inputPricePerMTokAtomic || 0) / 1e6;
|
|
5439
|
+
const eventOutPriceUsd = Number(paidEvent.outputPricePerMTokAtomic || 0) / 1e6;
|
|
5440
|
+
const boundInPrice = Math.max(Number(inPrice) || 0, eventInPriceUsd);
|
|
5441
|
+
const boundOutPrice = Math.max(Number(outPrice) || 0, eventOutPriceUsd);
|
|
5442
|
+
const bound = boundServe({ messages: checked.safeRequest.messages, budgetUsd: remainingUsd, inPrice: boundInPrice, outPrice: boundOutPrice, reqMax: checked.safeRequest.max_tokens });
|
|
5443
|
+
if (bound.refuse) {
|
|
5444
|
+
const error = bound.reason === "input" ? "input_too_large" : "output_unfunded";
|
|
5445
|
+
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 } };
|
|
5446
|
+
}
|
|
5447
|
+
const safeRequest = { ...checked.safeRequest, model, max_tokens: bound.maxTok };
|
|
5448
|
+
try {
|
|
5449
|
+
if (!await redemption.claim(cacheKey, oldLegacyKey ?? cacheKey)) {
|
|
5450
|
+
return { status: 409, body: { error: "draw_pending", detail: "this paid draw was already claimed; refusing to run upstream again", _bookingId: bookingId } };
|
|
5451
|
+
}
|
|
5452
|
+
} catch (e) {
|
|
5453
|
+
return { status: 503, body: { error: "redemption_unavailable", detail: `could not durably claim the paid draw: ${e.message}`, _bookingId: bookingId } };
|
|
5454
|
+
}
|
|
5455
|
+
let completion;
|
|
5456
|
+
try {
|
|
5457
|
+
completion = await upstream(safeRequest);
|
|
5458
|
+
} catch (e) {
|
|
5459
|
+
return { status: 502, body: { error: "upstream_error", detail: e.message } };
|
|
5460
|
+
}
|
|
5461
|
+
try {
|
|
5462
|
+
enforceModelEcho(completion.model, model);
|
|
5463
|
+
} catch (e) {
|
|
5464
|
+
return { status: 502, body: { error: "model_mismatch", detail: e.message } };
|
|
5465
|
+
}
|
|
5466
|
+
const usage = completion.usage ?? {};
|
|
5467
|
+
const inTok = Number(usage.prompt_tokens ?? usage.input_tokens ?? 0) || 0;
|
|
5468
|
+
const outTok = Number(usage.completion_tokens ?? usage.output_tokens ?? 0) || 0;
|
|
5469
|
+
const usedUsd = (inTok * Number(paidEvent.inputPricePerMTokAtomic || 0) + outTok * Number(paidEvent.outputPricePerMTokAtomic || 0)) / 1e12;
|
|
5470
|
+
const payload = { ...completion, _bookingId: bookingId, remainingUsd: Math.max(0, Math.round((remainingUsd - usedUsd) * 1e6) / 1e6) };
|
|
5471
|
+
try {
|
|
5472
|
+
await redemption.complete(cacheKey, payload);
|
|
5473
|
+
} catch (e) {
|
|
5474
|
+
(log ?? console).error?.(`mtok serve core: completion for ${cacheKey} could not be persisted (${e.message}); retries will remain pending`);
|
|
5475
|
+
}
|
|
5476
|
+
return { status: 200, body: payload };
|
|
5477
|
+
};
|
|
5478
|
+
return { serve };
|
|
5479
|
+
}
|
|
5480
|
+
|
|
5481
|
+
// bridge/bridge.mjs
|
|
5482
|
+
function httpUpstream({ baseUrl, key }) {
|
|
5483
|
+
const url = String(baseUrl || "").replace(/\/$/, "") + "/chat/completions";
|
|
5484
|
+
return async (payload) => {
|
|
5485
|
+
const res = await fetch(url, {
|
|
5486
|
+
method: "POST",
|
|
5487
|
+
headers: {
|
|
5488
|
+
"content-type": "application/json",
|
|
5489
|
+
...key ? { authorization: `Bearer ${key}` } : {}
|
|
5490
|
+
},
|
|
5491
|
+
body: JSON.stringify(payload)
|
|
5492
|
+
});
|
|
5493
|
+
const text = await res.text();
|
|
5494
|
+
let json;
|
|
5495
|
+
try {
|
|
5496
|
+
json = JSON.parse(text);
|
|
5497
|
+
} catch {
|
|
5498
|
+
throw new Error(`non-JSON upstream response (${res.status})`);
|
|
5499
|
+
}
|
|
5500
|
+
if (!res.ok) throw new Error(json?.error?.message || `upstream ${res.status}`);
|
|
5501
|
+
return json;
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
|
|
5505
|
+
// src/rpc.mjs
|
|
5506
|
+
var BASE_MAINNET_RPCS = [
|
|
5507
|
+
"https://mainnet.base.org",
|
|
5508
|
+
"https://base.llamarpc.com",
|
|
5509
|
+
"https://base-rpc.publicnode.com",
|
|
5510
|
+
"https://base.drpc.org"
|
|
5511
|
+
];
|
|
5512
|
+
var BASE_SEPOLIA_RPCS = [
|
|
5513
|
+
"https://sepolia.base.org",
|
|
5514
|
+
"https://base-sepolia-rpc.publicnode.com"
|
|
5515
|
+
];
|
|
5516
|
+
var rpcUrlsFor = (chainId, override) => override ? [override] : Number(chainId) === 8453 ? BASE_MAINNET_RPCS : BASE_SEPOLIA_RPCS;
|
|
5517
|
+
|
|
5518
|
+
// src/runtime.mjs
|
|
5348
5519
|
async function createRelayRuntime(config) {
|
|
5349
5520
|
const served = createRedemptionStore({ file: config.redemptionFile, log: config.log });
|
|
5350
5521
|
const drawLocks = /* @__PURE__ */ new Map();
|
|
@@ -5360,6 +5531,22 @@ async function createRelayRuntime(config) {
|
|
|
5360
5531
|
if (screenPayer && await screenPayer(payer)) return true;
|
|
5361
5532
|
return false;
|
|
5362
5533
|
};
|
|
5534
|
+
const core = createServeCore({
|
|
5535
|
+
model: config.model,
|
|
5536
|
+
inPrice: config.inPrice,
|
|
5537
|
+
outPrice: config.outPrice,
|
|
5538
|
+
verifier,
|
|
5539
|
+
redemption: served,
|
|
5540
|
+
upstream,
|
|
5541
|
+
log: config.log,
|
|
5542
|
+
offerId: config.offerId,
|
|
5543
|
+
sellerAgentId: config.sellerAgentId,
|
|
5544
|
+
sellerWallet: config.settlementAddr,
|
|
5545
|
+
dripContractAddress: platform.dripContractAddress,
|
|
5546
|
+
feeRecipient: platform.feeAddress,
|
|
5547
|
+
feeBps: platform.feeBps,
|
|
5548
|
+
screenPayer: payerDenied
|
|
5549
|
+
});
|
|
5363
5550
|
const withBookingLock = async (bookingId, fn) => {
|
|
5364
5551
|
const previous = drawLocks.get(bookingId) || Promise.resolve();
|
|
5365
5552
|
let release;
|
|
@@ -5378,127 +5565,10 @@ async function createRelayRuntime(config) {
|
|
|
5378
5565
|
if (drawLocks.get(bookingId) === tail) drawLocks.delete(bookingId);
|
|
5379
5566
|
}
|
|
5380
5567
|
};
|
|
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
|
-
};
|
|
5568
|
+
const handleDraw = (body, res) => withBookingLock(String(body?.bookingId ?? ""), async () => {
|
|
5569
|
+
const out = await core.serve(body);
|
|
5570
|
+
return send(res, out.status, out.body);
|
|
5571
|
+
});
|
|
5502
5572
|
return { handleDraw };
|
|
5503
5573
|
}
|
|
5504
5574
|
async function fetchPlatformConfig(config) {
|
package/package.json
CHANGED