@ampvaleo/x402-hyperliquid-server 0.1.0
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/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/index.cjs +461 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Valeo Protocol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @ampvaleo/x402-hyperliquid-server
|
|
2
|
+
|
|
3
|
+
Verify EIP-712 payments, optionally settle on Hyperliquid’s `/exchange` endpoint, and wire into **Express** or **Next.js App Router** handlers.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import express from "express";
|
|
7
|
+
import { InMemoryNonceStore, x402Hyperliquid } from "@ampvaleo/x402-hyperliquid-server";
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
app.get(
|
|
11
|
+
"/resource",
|
|
12
|
+
x402Hyperliquid({
|
|
13
|
+
price: "0.01",
|
|
14
|
+
payTo: "0x…",
|
|
15
|
+
chain: "testnet",
|
|
16
|
+
sourceDex: "spot",
|
|
17
|
+
destinationDex: "spot",
|
|
18
|
+
nonceStore: new InMemoryNonceStore(),
|
|
19
|
+
}),
|
|
20
|
+
(req, res) => res.json({ ok: true }),
|
|
21
|
+
);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For App Router, use `runX402HyperliquidGate(request, options)` and short-circuit with the returned `Response` when `ok` is false.
|
|
25
|
+
|
|
26
|
+
**Settlement**: omit `settle` to default to posting to Hyperliquid. For local dev, `X402_HL_DEV_VERIFY_ONLY=1` skips settlement (blocked in production unless `X402_HL_ALLOW_VERIFY_ONLY_IN_PROD=1`). See `resolveSettlementMode` / `assertDevVerifyOnlyEnvAllowed`.
|
|
27
|
+
|
|
28
|
+
See the [root README](../../README.md).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ALLOW_VERIFY_ONLY_IN_PROD_ENV: () => ALLOW_VERIFY_ONLY_IN_PROD_ENV,
|
|
24
|
+
DEV_VERIFY_ONLY_ENV: () => DEV_VERIFY_ONLY_ENV,
|
|
25
|
+
HyperliquidExchangeError: () => HyperliquidExchangeError,
|
|
26
|
+
InMemoryNonceStore: () => InMemoryNonceStore,
|
|
27
|
+
assertDevVerifyOnlyEnvAllowed: () => assertDevVerifyOnlyEnvAllowed,
|
|
28
|
+
assertValidPayment: () => assertValidPayment,
|
|
29
|
+
buildPaymentRequirements: () => buildPaymentRequirements,
|
|
30
|
+
mismatchesAcceptedAttack: () => mismatchesAcceptedAttack,
|
|
31
|
+
resolveSettlementMode: () => resolveSettlementMode,
|
|
32
|
+
runX402HyperliquidGate: () => runX402HyperliquidGate,
|
|
33
|
+
submitToExchange: () => submitToExchange,
|
|
34
|
+
verifyPayment: () => verifyPayment,
|
|
35
|
+
x402Hyperliquid: () => x402Hyperliquid
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/nonce.ts
|
|
40
|
+
var InMemoryNonceStore = class {
|
|
41
|
+
used = /* @__PURE__ */ new Set();
|
|
42
|
+
async tryConsume(key) {
|
|
43
|
+
if (this.used.has(key)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
this.used.add(key);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/requirements.ts
|
|
52
|
+
var import_x402_hyperliquid_core = require("@ampvaleo/x402-hyperliquid-core");
|
|
53
|
+
function buildPaymentRequirements(input) {
|
|
54
|
+
const cfg = (0, import_x402_hyperliquid_core.getChainConfig)(input.chain);
|
|
55
|
+
const token = input.token ?? import_x402_hyperliquid_core.DEFAULT_USDC_TOKEN[input.chain];
|
|
56
|
+
return {
|
|
57
|
+
scheme: import_x402_hyperliquid_core.HYPERLIQUID_SENDASSET_SCHEME,
|
|
58
|
+
network: cfg.network,
|
|
59
|
+
maxAmountRequired: input.price,
|
|
60
|
+
resource: input.resource,
|
|
61
|
+
description: input.description,
|
|
62
|
+
payTo: input.payTo,
|
|
63
|
+
maxTimeoutSeconds: input.maxTimeoutSeconds ?? 300,
|
|
64
|
+
asset: "USDC",
|
|
65
|
+
extra: {
|
|
66
|
+
chain: input.chain,
|
|
67
|
+
token,
|
|
68
|
+
sourceDex: input.sourceDex,
|
|
69
|
+
destinationDex: input.destinationDex
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/settle.ts
|
|
75
|
+
var HyperliquidExchangeError = class extends Error {
|
|
76
|
+
status;
|
|
77
|
+
body;
|
|
78
|
+
constructor(message, status, body) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "HyperliquidExchangeError";
|
|
81
|
+
this.status = status;
|
|
82
|
+
this.body = body;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
async function submitToExchange(payload, chain, options = {}) {
|
|
86
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
87
|
+
const body = {
|
|
88
|
+
action: payload.action,
|
|
89
|
+
nonce: payload.nonce,
|
|
90
|
+
signature: payload.signature
|
|
91
|
+
};
|
|
92
|
+
const res = await fetchImpl(chain.exchangeUrl, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify(body)
|
|
96
|
+
});
|
|
97
|
+
const text = await res.text();
|
|
98
|
+
let json;
|
|
99
|
+
try {
|
|
100
|
+
json = text ? JSON.parse(text) : null;
|
|
101
|
+
} catch {
|
|
102
|
+
json = text;
|
|
103
|
+
}
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new HyperliquidExchangeError(
|
|
106
|
+
`Hyperliquid exchange HTTP ${res.status}`,
|
|
107
|
+
res.status,
|
|
108
|
+
json
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const obj = json;
|
|
112
|
+
if (obj && typeof obj === "object" && obj.status === "err") {
|
|
113
|
+
throw new HyperliquidExchangeError(
|
|
114
|
+
"Hyperliquid exchange returned error status",
|
|
115
|
+
res.status,
|
|
116
|
+
json
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return json;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/settlementMode.ts
|
|
123
|
+
var DEV_VERIFY_ONLY = "X402_HL_DEV_VERIFY_ONLY";
|
|
124
|
+
var ALLOW_VERIFY_ONLY_IN_PROD = "X402_HL_ALLOW_VERIFY_ONLY_IN_PROD";
|
|
125
|
+
function assertDevVerifyOnlyEnvAllowed() {
|
|
126
|
+
if (process.env[DEV_VERIFY_ONLY] !== "1") {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (process.env.NODE_ENV === "production" && process.env[ALLOW_VERIFY_ONLY_IN_PROD] !== "1") {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`${DEV_VERIFY_ONLY}=1 cannot be used with NODE_ENV=production unless ${ALLOW_VERIFY_ONLY_IN_PROD}=1 is also set (acknowledges verify-only is unsafe for real paywalls).`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function resolveSettlementMode(explicitSettle) {
|
|
136
|
+
if (explicitSettle !== void 0) {
|
|
137
|
+
return {
|
|
138
|
+
settle: explicitSettle,
|
|
139
|
+
warnDevVerifyOnlyEachPaidRequest: false
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (process.env[DEV_VERIFY_ONLY] === "1") {
|
|
143
|
+
assertDevVerifyOnlyEnvAllowed();
|
|
144
|
+
return {
|
|
145
|
+
settle: false,
|
|
146
|
+
warnDevVerifyOnlyEachPaidRequest: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { settle: true, warnDevVerifyOnlyEachPaidRequest: false };
|
|
150
|
+
}
|
|
151
|
+
var DEV_VERIFY_ONLY_ENV = DEV_VERIFY_ONLY;
|
|
152
|
+
var ALLOW_VERIFY_ONLY_IN_PROD_ENV = ALLOW_VERIFY_ONLY_IN_PROD;
|
|
153
|
+
|
|
154
|
+
// src/verify.ts
|
|
155
|
+
var import_x402_hyperliquid_core2 = require("@ampvaleo/x402-hyperliquid-core");
|
|
156
|
+
var import_viem = require("viem");
|
|
157
|
+
function lower(a) {
|
|
158
|
+
return a.toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
async function verifyPayment(payload, requirements, ctx) {
|
|
161
|
+
if (payload.scheme !== import_x402_hyperliquid_core2.HYPERLIQUID_SENDASSET_SCHEME) {
|
|
162
|
+
return { valid: false, error: "unsupported scheme" };
|
|
163
|
+
}
|
|
164
|
+
if (requirements.scheme !== import_x402_hyperliquid_core2.HYPERLIQUID_SENDASSET_SCHEME) {
|
|
165
|
+
return { valid: false, error: "requirements scheme mismatch" };
|
|
166
|
+
}
|
|
167
|
+
if (payload.network !== requirements.network) {
|
|
168
|
+
return { valid: false, error: "network mismatch" };
|
|
169
|
+
}
|
|
170
|
+
const chain = ctx.chain;
|
|
171
|
+
if (payload.signatureChainId !== chain.signatureChainId) {
|
|
172
|
+
return { valid: false, error: "signatureChainId mismatch" };
|
|
173
|
+
}
|
|
174
|
+
const action = payload.action;
|
|
175
|
+
if (action.type !== "sendAsset") {
|
|
176
|
+
return { valid: false, error: "invalid action type" };
|
|
177
|
+
}
|
|
178
|
+
if (action.signatureChainId !== chain.signatureChainId) {
|
|
179
|
+
return { valid: false, error: "action.signatureChainId mismatch" };
|
|
180
|
+
}
|
|
181
|
+
if (action.hyperliquidChain !== chain.hyperliquidLabel) {
|
|
182
|
+
return { valid: false, error: "hyperliquidChain mismatch" };
|
|
183
|
+
}
|
|
184
|
+
if (action.fromSubAccount !== "") {
|
|
185
|
+
return { valid: false, error: "fromSubAccount must be empty" };
|
|
186
|
+
}
|
|
187
|
+
const extra = requirements.extra;
|
|
188
|
+
if (lower(action.destination) !== lower(requirements.payTo)) {
|
|
189
|
+
return { valid: false, error: "destination does not match payTo" };
|
|
190
|
+
}
|
|
191
|
+
if (action.token !== extra.token) {
|
|
192
|
+
return { valid: false, error: "token mismatch" };
|
|
193
|
+
}
|
|
194
|
+
if (action.sourceDex !== extra.sourceDex) {
|
|
195
|
+
return { valid: false, error: "sourceDex mismatch" };
|
|
196
|
+
}
|
|
197
|
+
if (action.destinationDex !== extra.destinationDex) {
|
|
198
|
+
return { valid: false, error: "destinationDex mismatch" };
|
|
199
|
+
}
|
|
200
|
+
let unitDecimals;
|
|
201
|
+
try {
|
|
202
|
+
unitDecimals = (0, import_x402_hyperliquid_core2.getAmountDecimalsForToken)(extra.token);
|
|
203
|
+
} catch {
|
|
204
|
+
return { valid: false, error: "unknown token amount decimals (register weiDecimals from spotMeta)" };
|
|
205
|
+
}
|
|
206
|
+
let paid;
|
|
207
|
+
let min;
|
|
208
|
+
try {
|
|
209
|
+
paid = (0, import_viem.parseUnits)(action.amount, unitDecimals);
|
|
210
|
+
min = (0, import_viem.parseUnits)(requirements.maxAmountRequired, unitDecimals);
|
|
211
|
+
} catch {
|
|
212
|
+
return { valid: false, error: "invalid decimal amount" };
|
|
213
|
+
}
|
|
214
|
+
if (paid < min) {
|
|
215
|
+
return { valid: false, error: "amount below maxAmountRequired" };
|
|
216
|
+
}
|
|
217
|
+
if (payload.accepted && mismatchesAcceptedAttack(payload)) {
|
|
218
|
+
return { valid: false, error: "accepted does not match signed action" };
|
|
219
|
+
}
|
|
220
|
+
const window = ctx.nonceWindowMs ?? 6e4;
|
|
221
|
+
const now = ctx.nowMs ?? Date.now();
|
|
222
|
+
if (Math.abs(now - action.nonce) > window) {
|
|
223
|
+
return { valid: false, error: "nonce outside allowed window" };
|
|
224
|
+
}
|
|
225
|
+
const message = {
|
|
226
|
+
hyperliquidChain: action.hyperliquidChain,
|
|
227
|
+
destination: action.destination,
|
|
228
|
+
sourceDex: action.sourceDex,
|
|
229
|
+
destinationDex: action.destinationDex,
|
|
230
|
+
token: action.token,
|
|
231
|
+
amount: action.amount,
|
|
232
|
+
fromSubAccount: action.fromSubAccount,
|
|
233
|
+
nonce: BigInt(action.nonce)
|
|
234
|
+
};
|
|
235
|
+
const typed = (0, import_x402_hyperliquid_core2.buildSendAssetTypedData)({
|
|
236
|
+
chainId: chain.chainId,
|
|
237
|
+
message
|
|
238
|
+
});
|
|
239
|
+
const yParity = payload.signature.v >= 27 ? payload.signature.v - 27 : payload.signature.v;
|
|
240
|
+
if (yParity !== 0 && yParity !== 1) {
|
|
241
|
+
return { valid: false, error: "invalid signature v" };
|
|
242
|
+
}
|
|
243
|
+
let signer;
|
|
244
|
+
try {
|
|
245
|
+
signer = await (0, import_viem.recoverTypedDataAddress)({
|
|
246
|
+
domain: typed.domain,
|
|
247
|
+
types: import_x402_hyperliquid_core2.VIEM_SEND_ASSET_TYPES,
|
|
248
|
+
primaryType: typed.primaryType,
|
|
249
|
+
message: typed.message,
|
|
250
|
+
signature: {
|
|
251
|
+
r: payload.signature.r,
|
|
252
|
+
s: payload.signature.s,
|
|
253
|
+
yParity
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
signer = (0, import_viem.getAddress)(signer);
|
|
257
|
+
} catch {
|
|
258
|
+
return { valid: false, error: "signature recovery failed" };
|
|
259
|
+
}
|
|
260
|
+
const nonceKey = `${signer}:${action.nonce}`;
|
|
261
|
+
const fresh = await ctx.nonceStore.tryConsume(nonceKey);
|
|
262
|
+
if (!fresh) {
|
|
263
|
+
return { valid: false, error: "nonce replay" };
|
|
264
|
+
}
|
|
265
|
+
return { valid: true, signer };
|
|
266
|
+
}
|
|
267
|
+
function mismatchesAcceptedAttack(payload) {
|
|
268
|
+
const a = payload.accepted;
|
|
269
|
+
if (!a) return false;
|
|
270
|
+
if (a.scheme !== payload.scheme || a.network !== payload.network) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
if (lower(a.payTo) !== lower(payload.action.destination)) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
if (a.extra.token !== payload.action.token) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (a.extra.sourceDex !== payload.action.sourceDex) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (a.extra.destinationDex !== payload.action.destinationDex) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
function assertValidPayment(result) {
|
|
288
|
+
if (!result.valid) {
|
|
289
|
+
throw new Error(result.error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/middleware/express.ts
|
|
294
|
+
var import_x402_hyperliquid_core3 = require("@ampvaleo/x402-hyperliquid-core");
|
|
295
|
+
function x402Hyperliquid(options) {
|
|
296
|
+
const chain = (0, import_x402_hyperliquid_core3.getChainConfig)(options.chain);
|
|
297
|
+
const settlement = resolveSettlementMode(options.settle);
|
|
298
|
+
return async (req, res, next) => {
|
|
299
|
+
const resource = options.resource ?? `${req.protocol}://${req.get("host") ?? "localhost"}${req.originalUrl}`;
|
|
300
|
+
const requirements = buildPaymentRequirements({
|
|
301
|
+
price: options.price,
|
|
302
|
+
payTo: options.payTo,
|
|
303
|
+
chain: options.chain,
|
|
304
|
+
token: options.token,
|
|
305
|
+
sourceDex: options.sourceDex,
|
|
306
|
+
destinationDex: options.destinationDex,
|
|
307
|
+
resource,
|
|
308
|
+
description: options.description,
|
|
309
|
+
maxTimeoutSeconds: options.maxTimeoutSeconds
|
|
310
|
+
});
|
|
311
|
+
const header = req.header("X-PAYMENT");
|
|
312
|
+
if (!header) {
|
|
313
|
+
res.status(402).json({
|
|
314
|
+
error: "Payment Required",
|
|
315
|
+
accepts: [requirements]
|
|
316
|
+
});
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
let payload;
|
|
320
|
+
try {
|
|
321
|
+
payload = (0, import_x402_hyperliquid_core3.decodePaymentHeader)(header);
|
|
322
|
+
} catch {
|
|
323
|
+
res.status(400).json({ error: "Invalid X-PAYMENT header" });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const ctx = {
|
|
327
|
+
chain,
|
|
328
|
+
nonceStore: options.nonceStore,
|
|
329
|
+
nonceWindowMs: options.nonceWindowMs
|
|
330
|
+
};
|
|
331
|
+
const result = await verifyPayment(payload, requirements, ctx);
|
|
332
|
+
if (!result.valid) {
|
|
333
|
+
res.status(402).json({
|
|
334
|
+
error: result.error,
|
|
335
|
+
accepts: [requirements]
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (settlement.warnDevVerifyOnlyEachPaidRequest) {
|
|
340
|
+
console.warn(
|
|
341
|
+
"\u26A0\uFE0F VERIFY-ONLY MODE: payment verified but NOT settled. Do not use in production."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (settlement.settle) {
|
|
345
|
+
try {
|
|
346
|
+
await submitToExchange(payload, chain, { fetch: options.fetch });
|
|
347
|
+
} catch (e) {
|
|
348
|
+
res.status(502).json({
|
|
349
|
+
error: e instanceof Error ? e.message : "Settlement failed"
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
req.x402 = {
|
|
355
|
+
signer: result.signer,
|
|
356
|
+
payload
|
|
357
|
+
};
|
|
358
|
+
next();
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/middleware/next.ts
|
|
363
|
+
var import_x402_hyperliquid_core4 = require("@ampvaleo/x402-hyperliquid-core");
|
|
364
|
+
async function runX402HyperliquidGate(request, options) {
|
|
365
|
+
const chain = (0, import_x402_hyperliquid_core4.getChainConfig)(options.chain);
|
|
366
|
+
const settlement = resolveSettlementMode(options.settle);
|
|
367
|
+
const url = new URL(request.url);
|
|
368
|
+
const resource = options.resource ?? `${url.origin}${url.pathname}${url.search}`;
|
|
369
|
+
const requirements = buildPaymentRequirements({
|
|
370
|
+
price: options.price,
|
|
371
|
+
payTo: options.payTo,
|
|
372
|
+
chain: options.chain,
|
|
373
|
+
token: options.token,
|
|
374
|
+
sourceDex: options.sourceDex,
|
|
375
|
+
destinationDex: options.destinationDex,
|
|
376
|
+
resource,
|
|
377
|
+
description: options.description,
|
|
378
|
+
maxTimeoutSeconds: options.maxTimeoutSeconds
|
|
379
|
+
});
|
|
380
|
+
const header = request.headers.get("X-PAYMENT");
|
|
381
|
+
if (!header) {
|
|
382
|
+
return {
|
|
383
|
+
ok: false,
|
|
384
|
+
response: new Response(
|
|
385
|
+
JSON.stringify({ error: "Payment Required", accepts: [requirements] }),
|
|
386
|
+
{
|
|
387
|
+
status: 402,
|
|
388
|
+
headers: { "Content-Type": "application/json" }
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
let payload;
|
|
394
|
+
try {
|
|
395
|
+
payload = (0, import_x402_hyperliquid_core4.decodePaymentHeader)(header);
|
|
396
|
+
} catch {
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
response: new Response(JSON.stringify({ error: "Invalid X-PAYMENT header" }), {
|
|
400
|
+
status: 400,
|
|
401
|
+
headers: { "Content-Type": "application/json" }
|
|
402
|
+
})
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const ctx = {
|
|
406
|
+
chain,
|
|
407
|
+
nonceStore: options.nonceStore,
|
|
408
|
+
nonceWindowMs: options.nonceWindowMs
|
|
409
|
+
};
|
|
410
|
+
const result = await verifyPayment(payload, requirements, ctx);
|
|
411
|
+
if (!result.valid) {
|
|
412
|
+
return {
|
|
413
|
+
ok: false,
|
|
414
|
+
response: new Response(
|
|
415
|
+
JSON.stringify({ error: result.error, accepts: [requirements] }),
|
|
416
|
+
{
|
|
417
|
+
status: 402,
|
|
418
|
+
headers: { "Content-Type": "application/json" }
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
if (settlement.warnDevVerifyOnlyEachPaidRequest) {
|
|
424
|
+
console.warn(
|
|
425
|
+
"\u26A0\uFE0F VERIFY-ONLY MODE: payment verified but NOT settled. Do not use in production."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
if (settlement.settle) {
|
|
429
|
+
try {
|
|
430
|
+
await submitToExchange(payload, chain, { fetch: options.fetch });
|
|
431
|
+
} catch (e) {
|
|
432
|
+
return {
|
|
433
|
+
ok: false,
|
|
434
|
+
response: new Response(
|
|
435
|
+
JSON.stringify({
|
|
436
|
+
error: e instanceof Error ? e.message : "Settlement failed"
|
|
437
|
+
}),
|
|
438
|
+
{ status: 502, headers: { "Content-Type": "application/json" } }
|
|
439
|
+
)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return { ok: true, signer: result.signer, payload };
|
|
444
|
+
}
|
|
445
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
446
|
+
0 && (module.exports = {
|
|
447
|
+
ALLOW_VERIFY_ONLY_IN_PROD_ENV,
|
|
448
|
+
DEV_VERIFY_ONLY_ENV,
|
|
449
|
+
HyperliquidExchangeError,
|
|
450
|
+
InMemoryNonceStore,
|
|
451
|
+
assertDevVerifyOnlyEnvAllowed,
|
|
452
|
+
assertValidPayment,
|
|
453
|
+
buildPaymentRequirements,
|
|
454
|
+
mismatchesAcceptedAttack,
|
|
455
|
+
resolveSettlementMode,
|
|
456
|
+
runX402HyperliquidGate,
|
|
457
|
+
submitToExchange,
|
|
458
|
+
verifyPayment,
|
|
459
|
+
x402Hyperliquid
|
|
460
|
+
});
|
|
461
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/nonce.ts","../src/requirements.ts","../src/settle.ts","../src/settlementMode.ts","../src/verify.ts","../src/middleware/express.ts","../src/middleware/next.ts"],"sourcesContent":["export { InMemoryNonceStore, type NonceStore } from \"./nonce.js\";\nexport {\n buildPaymentRequirements,\n type BuildRequirementsInput,\n} from \"./requirements.js\";\nexport {\n HyperliquidExchangeError,\n submitToExchange,\n type SubmitOptions,\n} from \"./settle.js\";\nexport {\n ALLOW_VERIFY_ONLY_IN_PROD_ENV,\n DEV_VERIFY_ONLY_ENV,\n assertDevVerifyOnlyEnvAllowed,\n resolveSettlementMode,\n type SettlementMode,\n} from \"./settlementMode.js\";\nexport {\n assertValidPayment,\n mismatchesAcceptedAttack,\n verifyPayment,\n type VerifyContext,\n} from \"./verify.js\";\nexport {\n x402Hyperliquid,\n type X402ExpressLocals,\n type X402HyperliquidExpressOptions,\n} from \"./middleware/express.js\";\nexport {\n runX402HyperliquidGate,\n type X402GateResult,\n type X402HyperliquidNextOptions,\n} from \"./middleware/next.js\";\n","/**\n * Tracks seen payment nonces to prevent replay. Keys are opaque (e.g. `signer:nonceMs`).\n */\nexport interface NonceStore {\n /** Returns true if the nonce was unused and is now recorded */\n tryConsume(key: string): Promise<boolean>;\n}\n\nexport class InMemoryNonceStore implements NonceStore {\n private readonly used = new Set<string>();\n\n async tryConsume(key: string): Promise<boolean> {\n if (this.used.has(key)) {\n return false;\n }\n this.used.add(key);\n return true;\n }\n}\n","import {\n DEFAULT_USDC_TOKEN,\n HYPERLIQUID_SENDASSET_SCHEME,\n getChainConfig,\n type HyperliquidChainName,\n type PaymentRequirements,\n} from \"@ampvaleo/x402-hyperliquid-core\";\nimport type { Hex } from \"viem\";\n\nexport interface BuildRequirementsInput {\n price: string;\n payTo: Hex;\n chain: HyperliquidChainName;\n /** Full HL token string; defaults to canonical USDC for the chain */\n token?: string;\n sourceDex: \"spot\" | \"\";\n destinationDex: \"spot\" | \"\";\n resource: string;\n description?: string;\n maxTimeoutSeconds?: number;\n}\n\nexport function buildPaymentRequirements(\n input: BuildRequirementsInput,\n): PaymentRequirements {\n const cfg = getChainConfig(input.chain);\n const token = input.token ?? DEFAULT_USDC_TOKEN[input.chain];\n return {\n scheme: HYPERLIQUID_SENDASSET_SCHEME,\n network: cfg.network,\n maxAmountRequired: input.price,\n resource: input.resource,\n description: input.description,\n payTo: input.payTo,\n maxTimeoutSeconds: input.maxTimeoutSeconds ?? 300,\n asset: \"USDC\",\n extra: {\n chain: input.chain,\n token,\n sourceDex: input.sourceDex,\n destinationDex: input.destinationDex,\n },\n };\n}\n","import type { HyperliquidChainConfig, PaymentPayload } from \"@ampvaleo/x402-hyperliquid-core\";\n\nexport class HyperliquidExchangeError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"HyperliquidExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface SubmitOptions {\n fetch?: typeof fetch;\n}\n\n/**\n * Submits a signed sendAsset payment to Hyperliquid's public exchange API.\n */\nexport async function submitToExchange(\n payload: PaymentPayload,\n chain: HyperliquidChainConfig,\n options: SubmitOptions = {},\n): Promise<unknown> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const body = {\n action: payload.action,\n nonce: payload.nonce,\n signature: payload.signature,\n };\n\n const res = await fetchImpl(chain.exchangeUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n const text = await res.text();\n let json: unknown;\n try {\n json = text ? JSON.parse(text) : null;\n } catch {\n json = text;\n }\n\n if (!res.ok) {\n throw new HyperliquidExchangeError(\n `Hyperliquid exchange HTTP ${res.status}`,\n res.status,\n json,\n );\n }\n\n const obj = json as { status?: string; response?: unknown };\n if (obj && typeof obj === \"object\" && obj.status === \"err\") {\n throw new HyperliquidExchangeError(\n \"Hyperliquid exchange returned error status\",\n res.status,\n json,\n );\n }\n\n return json;\n}\n","const DEV_VERIFY_ONLY = \"X402_HL_DEV_VERIFY_ONLY\";\nconst ALLOW_VERIFY_ONLY_IN_PROD = \"X402_HL_ALLOW_VERIFY_ONLY_IN_PROD\";\n\nexport interface SettlementMode {\n settle: boolean;\n /**\n * When true, log a loud warning on each successful verification when settlement is skipped\n * because `X402_HL_DEV_VERIFY_ONLY=1` was used (not because `settle: false` was passed explicitly).\n */\n warnDevVerifyOnlyEachPaidRequest: boolean;\n}\n\n/**\n * Throws if `X402_HL_DEV_VERIFY_ONLY=1` is set in production without the explicit escape hatch.\n * Call once at process startup (e.g. when creating middleware) or before handling traffic.\n */\nexport function assertDevVerifyOnlyEnvAllowed(): void {\n if (process.env[DEV_VERIFY_ONLY] !== \"1\") {\n return;\n }\n if (\n process.env.NODE_ENV === \"production\" &&\n process.env[ALLOW_VERIFY_ONLY_IN_PROD] !== \"1\"\n ) {\n throw new Error(\n `${DEV_VERIFY_ONLY}=1 cannot be used with NODE_ENV=production unless ${ALLOW_VERIFY_ONLY_IN_PROD}=1 is also set (acknowledges verify-only is unsafe for real paywalls).`,\n );\n }\n}\n\n/**\n * Resolves whether to POST to Hyperliquid after verification.\n *\n * - If `explicitSettle` is set, it wins (no env). No dev-verify warning.\n * - Otherwise, if `X402_HL_DEV_VERIFY_ONLY=1`, settlement is off and warnings apply (after assert).\n * - Default: settle (direct mode).\n */\nexport function resolveSettlementMode(\n explicitSettle?: boolean,\n): SettlementMode {\n if (explicitSettle !== undefined) {\n return {\n settle: explicitSettle,\n warnDevVerifyOnlyEachPaidRequest: false,\n };\n }\n if (process.env[DEV_VERIFY_ONLY] === \"1\") {\n assertDevVerifyOnlyEnvAllowed();\n return {\n settle: false,\n warnDevVerifyOnlyEachPaidRequest: true,\n };\n }\n return { settle: true, warnDevVerifyOnlyEachPaidRequest: false };\n}\n\nexport const DEV_VERIFY_ONLY_ENV = DEV_VERIFY_ONLY;\nexport const ALLOW_VERIFY_ONLY_IN_PROD_ENV = ALLOW_VERIFY_ONLY_IN_PROD;\n","import {\n HYPERLIQUID_SENDASSET_SCHEME,\n VIEM_SEND_ASSET_TYPES,\n buildSendAssetTypedData,\n getAmountDecimalsForToken,\n type HyperliquidChainConfig,\n type PaymentPayload,\n type PaymentRequirements,\n} from \"@ampvaleo/x402-hyperliquid-core\";\nimport type { Address } from \"viem\";\nimport { getAddress, parseUnits, recoverTypedDataAddress } from \"viem\";\n\nimport type { NonceStore } from \"./nonce.js\";\n\nexport interface VerifyContext {\n chain: HyperliquidChainConfig;\n nonceStore: NonceStore;\n /** Max age of nonce timestamp in ms from now (default 60_000) */\n nonceWindowMs?: number;\n nowMs?: number;\n}\n\nfunction lower(a: string): string {\n return a.toLowerCase();\n}\n\n/**\n * Verifies EIP-712 signature and that `payload.action` matches server `requirements`.\n * Does **not** trust `payload.accepted` — compare only against `requirements`.\n */\nexport async function verifyPayment(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n ctx: VerifyContext,\n): Promise<\n { valid: true; signer: Address } | { valid: false; error: string }\n> {\n if (payload.scheme !== HYPERLIQUID_SENDASSET_SCHEME) {\n return { valid: false, error: \"unsupported scheme\" };\n }\n if (requirements.scheme !== HYPERLIQUID_SENDASSET_SCHEME) {\n return { valid: false, error: \"requirements scheme mismatch\" };\n }\n if (payload.network !== requirements.network) {\n return { valid: false, error: \"network mismatch\" };\n }\n\n const chain = ctx.chain;\n if (payload.signatureChainId !== chain.signatureChainId) {\n return { valid: false, error: \"signatureChainId mismatch\" };\n }\n\n const action = payload.action;\n if (action.type !== \"sendAsset\") {\n return { valid: false, error: \"invalid action type\" };\n }\n if (action.signatureChainId !== chain.signatureChainId) {\n return { valid: false, error: \"action.signatureChainId mismatch\" };\n }\n if (action.hyperliquidChain !== chain.hyperliquidLabel) {\n return { valid: false, error: \"hyperliquidChain mismatch\" };\n }\n if (action.fromSubAccount !== \"\") {\n return { valid: false, error: \"fromSubAccount must be empty\" };\n }\n\n const extra = requirements.extra;\n if (lower(action.destination) !== lower(requirements.payTo)) {\n return { valid: false, error: \"destination does not match payTo\" };\n }\n if (action.token !== extra.token) {\n return { valid: false, error: \"token mismatch\" };\n }\n if (action.sourceDex !== extra.sourceDex) {\n return { valid: false, error: \"sourceDex mismatch\" };\n }\n if (action.destinationDex !== extra.destinationDex) {\n return { valid: false, error: \"destinationDex mismatch\" };\n }\n\n let unitDecimals: number;\n try {\n unitDecimals = getAmountDecimalsForToken(extra.token);\n } catch {\n return { valid: false, error: \"unknown token amount decimals (register weiDecimals from spotMeta)\" };\n }\n let paid: bigint;\n let min: bigint;\n try {\n paid = parseUnits(action.amount, unitDecimals);\n min = parseUnits(requirements.maxAmountRequired, unitDecimals);\n } catch {\n return { valid: false, error: \"invalid decimal amount\" };\n }\n if (paid < min) {\n return { valid: false, error: \"amount below maxAmountRequired\" };\n }\n\n if (payload.accepted && mismatchesAcceptedAttack(payload)) {\n return { valid: false, error: \"accepted does not match signed action\" };\n }\n\n const window = ctx.nonceWindowMs ?? 60_000;\n const now = ctx.nowMs ?? Date.now();\n if (Math.abs(now - action.nonce) > window) {\n return { valid: false, error: \"nonce outside allowed window\" };\n }\n\n const message = {\n hyperliquidChain: action.hyperliquidChain,\n destination: action.destination,\n sourceDex: action.sourceDex,\n destinationDex: action.destinationDex,\n token: action.token,\n amount: action.amount,\n fromSubAccount: action.fromSubAccount,\n nonce: BigInt(action.nonce),\n };\n\n const typed = buildSendAssetTypedData({\n chainId: chain.chainId,\n message,\n });\n\n const yParity =\n payload.signature.v >= 27 ? payload.signature.v - 27 : payload.signature.v;\n if (yParity !== 0 && yParity !== 1) {\n return { valid: false, error: \"invalid signature v\" };\n }\n\n let signer: Address;\n try {\n signer = await recoverTypedDataAddress({\n domain: typed.domain,\n types: VIEM_SEND_ASSET_TYPES,\n primaryType: typed.primaryType,\n message: typed.message as unknown as Record<string, unknown>,\n signature: {\n r: payload.signature.r,\n s: payload.signature.s,\n yParity: yParity as 0 | 1,\n },\n });\n signer = getAddress(signer);\n } catch {\n return { valid: false, error: \"signature recovery failed\" };\n }\n\n const nonceKey = `${signer}:${action.nonce}`;\n const fresh = await ctx.nonceStore.tryConsume(nonceKey);\n if (!fresh) {\n return { valid: false, error: \"nonce replay\" };\n }\n\n return { valid: true, signer };\n}\n\n/**\n * Detects the case where the client tampers with `accepted` so it no longer reflects\n * the signed `action` (servers must never rely on `accepted` alone).\n */\nexport function mismatchesAcceptedAttack(payload: PaymentPayload): boolean {\n const a = payload.accepted;\n if (!a) return false;\n if (a.scheme !== payload.scheme || a.network !== payload.network) {\n return true;\n }\n if (lower(a.payTo) !== lower(payload.action.destination)) {\n return true;\n }\n if (a.extra.token !== payload.action.token) {\n return true;\n }\n if (a.extra.sourceDex !== payload.action.sourceDex) {\n return true;\n }\n if (a.extra.destinationDex !== payload.action.destinationDex) {\n return true;\n }\n return false;\n}\n\nexport function assertValidPayment(\n result: { valid: true; signer: Address } | { valid: false; error: string },\n): asserts result is { valid: true; signer: Address } {\n if (!result.valid) {\n throw new Error(result.error);\n }\n}\n","import {\n decodePaymentHeader,\n getChainConfig,\n type HyperliquidChainName,\n} from \"@ampvaleo/x402-hyperliquid-core\";\nimport type { PaymentPayload } from \"@ampvaleo/x402-hyperliquid-core\";\nimport type { Address } from \"viem\";\n\nimport type { NonceStore } from \"../nonce.js\";\nimport { buildPaymentRequirements, type BuildRequirementsInput } from \"../requirements.js\";\nimport { submitToExchange, type SubmitOptions } from \"../settle.js\";\nimport { resolveSettlementMode } from \"../settlementMode.js\";\nimport { verifyPayment, type VerifyContext } from \"../verify.js\";\n\nexport interface X402HyperliquidExpressOptions\n extends Omit<BuildRequirementsInput, \"resource\"> {\n /** Defaults to request URL */\n resource?: string;\n nonceStore: NonceStore;\n nonceWindowMs?: number;\n /**\n * When set, forces settlement on/off. When omitted, `X402_HL_DEV_VERIFY_ONLY=1` can disable\n * settlement for local dev (see `settlementMode.ts`).\n */\n settle?: boolean;\n fetch?: SubmitOptions[\"fetch\"];\n}\n\nexport type X402ExpressLocals = {\n signer: Address;\n payload: PaymentPayload;\n};\n\n/** Express middleware — returns 402 + `accepts` when unpaid; verifies + settles (default) when `X-PAYMENT` present */\nexport function x402Hyperliquid(options: X402HyperliquidExpressOptions) {\n const chain = getChainConfig(options.chain);\n const settlement = resolveSettlementMode(options.settle);\n\n return async (\n req: import(\"express\").Request,\n res: import(\"express\").Response,\n next: import(\"express\").NextFunction,\n ): Promise<void> => {\n const resource =\n options.resource ??\n `${req.protocol}://${req.get(\"host\") ?? \"localhost\"}${req.originalUrl}`;\n\n const requirements = buildPaymentRequirements({\n price: options.price,\n payTo: options.payTo,\n chain: options.chain as HyperliquidChainName,\n token: options.token,\n sourceDex: options.sourceDex,\n destinationDex: options.destinationDex,\n resource,\n description: options.description,\n maxTimeoutSeconds: options.maxTimeoutSeconds,\n });\n\n const header = req.header(\"X-PAYMENT\");\n if (!header) {\n res.status(402).json({\n error: \"Payment Required\",\n accepts: [requirements],\n });\n return;\n }\n\n let payload: import(\"@ampvaleo/x402-hyperliquid-core\").PaymentPayload;\n try {\n payload = decodePaymentHeader(header);\n } catch {\n res.status(400).json({ error: \"Invalid X-PAYMENT header\" });\n return;\n }\n\n const ctx: VerifyContext = {\n chain,\n nonceStore: options.nonceStore,\n nonceWindowMs: options.nonceWindowMs,\n };\n\n const result = await verifyPayment(payload, requirements, ctx);\n if (!result.valid) {\n res.status(402).json({\n error: result.error,\n accepts: [requirements],\n });\n return;\n }\n\n if (settlement.warnDevVerifyOnlyEachPaidRequest) {\n // eslint-disable-next-line no-console\n console.warn(\n \"⚠️ VERIFY-ONLY MODE: payment verified but NOT settled. Do not use in production.\",\n );\n }\n\n if (settlement.settle) {\n try {\n await submitToExchange(payload, chain, { fetch: options.fetch });\n } catch (e) {\n res.status(502).json({\n error: e instanceof Error ? e.message : \"Settlement failed\",\n });\n return;\n }\n }\n\n (req as import(\"express\").Request & { x402: X402ExpressLocals }).x402 = {\n signer: result.signer,\n payload,\n };\n next();\n };\n}\n","import {\n decodePaymentHeader,\n getChainConfig,\n type HyperliquidChainName,\n} from \"@ampvaleo/x402-hyperliquid-core\";\nimport type { Address } from \"viem\";\n\nimport type { NonceStore } from \"../nonce.js\";\nimport {\n buildPaymentRequirements,\n type BuildRequirementsInput,\n} from \"../requirements.js\";\nimport { submitToExchange, type SubmitOptions } from \"../settle.js\";\nimport { resolveSettlementMode } from \"../settlementMode.js\";\nimport { verifyPayment, type VerifyContext } from \"../verify.js\";\n\nexport interface X402HyperliquidNextOptions\n extends Omit<BuildRequirementsInput, \"resource\"> {\n resource?: string;\n nonceStore: NonceStore;\n nonceWindowMs?: number;\n settle?: boolean;\n fetch?: SubmitOptions[\"fetch\"];\n}\n\nexport type X402GateResult =\n | { ok: true; signer: Address; payload: import(\"@ampvaleo/x402-hyperliquid-core\").PaymentPayload }\n | { ok: false; response: Response };\n\n/**\n * App Router / edge-friendly gate: returns a `Response` to return early (402/400/502),\n * or `{ ok: true, ... }` when the caller should continue the route.\n */\nexport async function runX402HyperliquidGate(\n request: Request,\n options: X402HyperliquidNextOptions,\n): Promise<X402GateResult> {\n const chain = getChainConfig(options.chain);\n const settlement = resolveSettlementMode(options.settle);\n const url = new URL(request.url);\n const resource =\n options.resource ?? `${url.origin}${url.pathname}${url.search}`;\n\n const requirements = buildPaymentRequirements({\n price: options.price,\n payTo: options.payTo,\n chain: options.chain as HyperliquidChainName,\n token: options.token,\n sourceDex: options.sourceDex,\n destinationDex: options.destinationDex,\n resource,\n description: options.description,\n maxTimeoutSeconds: options.maxTimeoutSeconds,\n });\n\n const header = request.headers.get(\"X-PAYMENT\");\n if (!header) {\n return {\n ok: false,\n response: new Response(\n JSON.stringify({ error: \"Payment Required\", accepts: [requirements] }),\n {\n status: 402,\n headers: { \"Content-Type\": \"application/json\" },\n },\n ),\n };\n }\n\n let payload: import(\"@ampvaleo/x402-hyperliquid-core\").PaymentPayload;\n try {\n payload = decodePaymentHeader(header);\n } catch {\n return {\n ok: false,\n response: new Response(JSON.stringify({ error: \"Invalid X-PAYMENT header\" }), {\n status: 400,\n headers: { \"Content-Type\": \"application/json\" },\n }),\n };\n }\n\n const ctx: VerifyContext = {\n chain,\n nonceStore: options.nonceStore,\n nonceWindowMs: options.nonceWindowMs,\n };\n\n const result = await verifyPayment(payload, requirements, ctx);\n if (!result.valid) {\n return {\n ok: false,\n response: new Response(\n JSON.stringify({ error: result.error, accepts: [requirements] }),\n {\n status: 402,\n headers: { \"Content-Type\": \"application/json\" },\n },\n ),\n };\n }\n\n if (settlement.warnDevVerifyOnlyEachPaidRequest) {\n // eslint-disable-next-line no-console\n console.warn(\n \"⚠️ VERIFY-ONLY MODE: payment verified but NOT settled. Do not use in production.\",\n );\n }\n\n if (settlement.settle) {\n try {\n await submitToExchange(payload, chain, { fetch: options.fetch });\n } catch (e) {\n return {\n ok: false,\n response: new Response(\n JSON.stringify({\n error: e instanceof Error ? e.message : \"Settlement failed\",\n }),\n { status: 502, headers: { \"Content-Type\": \"application/json\" } },\n ),\n };\n }\n }\n\n return { ok: true, signer: result.signer, payload };\n}\n"],"mappings":"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","names":["import_x402_hyperliquid_core","import_x402_hyperliquid_core","import_x402_hyperliquid_core"]}
|