@faremeter/facilitator 0.7.0 → 0.9.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/dist/src/promise.d.ts +4 -0
- package/dist/src/promise.d.ts.map +1 -0
- package/dist/src/promise.js +10 -0
- package/dist/src/promise.test.d.ts +3 -0
- package/dist/src/promise.test.d.ts.map +1 -0
- package/dist/src/promise.test.js +74 -0
- package/dist/src/routes.d.ts +4 -0
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +62 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function sleep<T>(sleepMs: number, value?: T): Promise<T | undefined>;
|
|
2
|
+
export declare function timeout<T>(timeoutMs: number, msg?: string): Promise<T>;
|
|
3
|
+
export declare function allSettledWithTimeout<T>(promises: readonly Promise<T>[], timeoutMs: number): Promise<PromiseSettledResult<Awaited<Awaited<T>>>[]>;
|
|
4
|
+
//# sourceMappingURL=promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/promise.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAE3E;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,cAIzD;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,QAAQ,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,EAAE,EAC/B,SAAS,EAAE,MAAM,wDAOlB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function sleep(sleepMs, value) {
|
|
2
|
+
return new Promise((resolve) => setTimeout(() => resolve(value), sleepMs));
|
|
3
|
+
}
|
|
4
|
+
export function timeout(timeoutMs, msg) {
|
|
5
|
+
return new Promise((_, reject) => setTimeout(() => reject(new Error(msg ?? "timed out")), timeoutMs));
|
|
6
|
+
}
|
|
7
|
+
export function allSettledWithTimeout(promises, timeoutMs) {
|
|
8
|
+
const timedPromises = promises.map((p) => Promise.race([p, timeout(timeoutMs, "request timed out")]));
|
|
9
|
+
return Promise.allSettled(timedPromises);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promise.test.d.ts","sourceRoot":"","sources":["../../src/promise.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env pnpm tsx
|
|
2
|
+
import t from "tap";
|
|
3
|
+
import { sleep, allSettledWithTimeout } from "./promise.js";
|
|
4
|
+
async function checkDuration(min, max, f) {
|
|
5
|
+
if (max <= min) {
|
|
6
|
+
throw new Error("max duration must be greater than min");
|
|
7
|
+
}
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
const res = await f();
|
|
10
|
+
const delta = Date.now() - start;
|
|
11
|
+
t.ok(delta >= min, `duration took ${delta} ms, which isn't >= ${min} ms`);
|
|
12
|
+
t.ok(delta <= max, `duration took ${delta} ms, which isn't <= ${max} ms`);
|
|
13
|
+
return res;
|
|
14
|
+
}
|
|
15
|
+
await t.test("checkAllSettledNoTimeout", async (t) => {
|
|
16
|
+
const results = await checkDuration(25, 50, () => allSettledWithTimeout([sleep(10, "good"), sleep(30, "to"), sleep(1, "go")], 100));
|
|
17
|
+
t.equal(results.length, 3);
|
|
18
|
+
results.forEach((x) => {
|
|
19
|
+
if (x === undefined) {
|
|
20
|
+
t.bailout();
|
|
21
|
+
}
|
|
22
|
+
t.matchOnly(x.status, "fulfilled");
|
|
23
|
+
});
|
|
24
|
+
t.matchOnly(results.map((x) => x.value), ["good", "to", "go"]);
|
|
25
|
+
t.pass();
|
|
26
|
+
t.end();
|
|
27
|
+
});
|
|
28
|
+
await t.test("checkAllSettledWithTimeout", async (t) => {
|
|
29
|
+
const results = await checkDuration(20, 125, () => allSettledWithTimeout([sleep(10), sleep(500)], 100));
|
|
30
|
+
t.equal(results.length, 2);
|
|
31
|
+
const [result0, result1] = results;
|
|
32
|
+
if (result0 == undefined) {
|
|
33
|
+
return t.bailout();
|
|
34
|
+
}
|
|
35
|
+
if (result1 == undefined) {
|
|
36
|
+
return t.bailout();
|
|
37
|
+
}
|
|
38
|
+
t.matchOnly(result0.status, "fulfilled");
|
|
39
|
+
t.matchOnly(result1.status, "rejected");
|
|
40
|
+
t.pass();
|
|
41
|
+
t.end();
|
|
42
|
+
});
|
|
43
|
+
await t.test("checkAllSettleException", async (t) => {
|
|
44
|
+
const results = await checkDuration(10, 45, async () => allSettledWithTimeout([
|
|
45
|
+
sleep(10, 42),
|
|
46
|
+
(async () => {
|
|
47
|
+
throw new Error("an error happened");
|
|
48
|
+
})(),
|
|
49
|
+
sleep(15, 1337),
|
|
50
|
+
], 50));
|
|
51
|
+
t.equal(results.length, 3);
|
|
52
|
+
const [result0, result1, result2] = results;
|
|
53
|
+
if (result0 === undefined) {
|
|
54
|
+
return t.bailout();
|
|
55
|
+
}
|
|
56
|
+
if (result1 === undefined) {
|
|
57
|
+
return t.bailout();
|
|
58
|
+
}
|
|
59
|
+
if (result2 === undefined) {
|
|
60
|
+
return t.bailout();
|
|
61
|
+
}
|
|
62
|
+
if (result0.status !== "fulfilled") {
|
|
63
|
+
return t.fail();
|
|
64
|
+
}
|
|
65
|
+
t.matchOnly(result0.value, 42);
|
|
66
|
+
if (result1.status !== "rejected") {
|
|
67
|
+
return t.fail();
|
|
68
|
+
}
|
|
69
|
+
t.matchOnly(result1.reason, new Error("an error happened"));
|
|
70
|
+
if (result2.status !== "fulfilled") {
|
|
71
|
+
return t.fail();
|
|
72
|
+
}
|
|
73
|
+
t.matchOnly(result2.value, 1337);
|
|
74
|
+
});
|
package/dist/src/routes.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { Hono } from "hono";
|
|
|
2
2
|
import type { FacilitatorHandler } from "@faremeter/types/facilitator";
|
|
3
3
|
type CreateFacilitatorRoutesArgs = {
|
|
4
4
|
handlers: FacilitatorHandler[];
|
|
5
|
+
timeout?: {
|
|
6
|
+
getRequirements?: number;
|
|
7
|
+
getSupported?: number;
|
|
8
|
+
};
|
|
5
9
|
};
|
|
6
10
|
export declare function createFacilitatorRoutes(args: CreateFacilitatorRoutesArgs): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
7
11
|
export {};
|
package/dist/src/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAI1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAKvE,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,2BAA2B,8EAmLxE"}
|
package/dist/src/routes.js
CHANGED
|
@@ -2,7 +2,17 @@ import { getLogger } from "@logtape/logtape";
|
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import * as x from "@faremeter/types/x402";
|
|
4
4
|
import { isValidationError } from "@faremeter/types";
|
|
5
|
+
import {} from "@faremeter/types/x402";
|
|
6
|
+
import { allSettledWithTimeout } from "./promise.js";
|
|
5
7
|
const logger = getLogger(["faremeter", "facilitator"]);
|
|
8
|
+
function summarizeRequirements({ scheme, network, asset, payTo, }) {
|
|
9
|
+
return {
|
|
10
|
+
scheme,
|
|
11
|
+
network,
|
|
12
|
+
asset,
|
|
13
|
+
payTo,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
6
16
|
export function createFacilitatorRoutes(args) {
|
|
7
17
|
const router = new Hono();
|
|
8
18
|
function sendSettleError(c, status, msg) {
|
|
@@ -30,6 +40,7 @@ export function createFacilitatorRoutes(args) {
|
|
|
30
40
|
if (isValidationError(paymentPayload)) {
|
|
31
41
|
return sendSettleError(c, 400, `couldn't validate x402 payload: ${paymentPayload.summary}`);
|
|
32
42
|
}
|
|
43
|
+
logger.debug("starting settlement attempt for request: {*}", x402Req);
|
|
33
44
|
for (const handler of args.handlers) {
|
|
34
45
|
let t;
|
|
35
46
|
try {
|
|
@@ -50,21 +61,71 @@ export function createFacilitatorRoutes(args) {
|
|
|
50
61
|
if (t === null) {
|
|
51
62
|
continue;
|
|
52
63
|
}
|
|
64
|
+
logger.info(`${t.success ? "succeeded" : "failed"} settlement request: {*}`, {
|
|
65
|
+
requirements: summarizeRequirements(x402Req.paymentRequirements),
|
|
66
|
+
txHash: t.txHash,
|
|
67
|
+
});
|
|
53
68
|
return c.json(t);
|
|
54
69
|
}
|
|
55
70
|
sendSettleError(c, 400, "no matching payment handler found");
|
|
71
|
+
logger.warning("attempt to settle was made with no handler found, requirements summary was: {*}", summarizeRequirements(x402Req.paymentRequirements));
|
|
56
72
|
});
|
|
57
73
|
router.post("/accepts", async (c) => {
|
|
58
74
|
const x402Req = x.x402PaymentRequiredResponse(await c.req.json());
|
|
59
75
|
if (isValidationError(x402Req)) {
|
|
60
76
|
return sendSettleError(c, 400, `couldn't parse required response: ${x402Req.summary}`);
|
|
61
77
|
}
|
|
62
|
-
const
|
|
78
|
+
const results = await allSettledWithTimeout(args.handlers.flatMap((x) => x.getRequirements(x402Req.accepts)), args.timeout?.getRequirements ?? 500);
|
|
79
|
+
const accepts = results
|
|
80
|
+
.filter((x) => x.status === "fulfilled")
|
|
81
|
+
.map((x) => x.value)
|
|
82
|
+
.flat();
|
|
83
|
+
results.forEach((x) => {
|
|
84
|
+
if (x.status === "rejected") {
|
|
85
|
+
let message;
|
|
86
|
+
if (x.reason instanceof Error) {
|
|
87
|
+
message = x.reason.message;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
message = "unknown reason";
|
|
91
|
+
}
|
|
92
|
+
logger.error(`failed to retrieve requirements from facilitator handler: ${message}`, x.reason);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
logger.debug(`returning ${accepts.length} accepts: {*}`, {
|
|
96
|
+
accepts: accepts.map(summarizeRequirements),
|
|
97
|
+
});
|
|
63
98
|
c.status(200);
|
|
64
99
|
return c.json({
|
|
65
100
|
x402Version: 1,
|
|
66
101
|
accepts,
|
|
67
102
|
});
|
|
68
103
|
});
|
|
104
|
+
router.get("/supported", async (c) => {
|
|
105
|
+
const results = await allSettledWithTimeout(args.handlers.flatMap((x) => (x.getSupported ? x.getSupported() : [])), args.timeout?.getSupported ?? 500);
|
|
106
|
+
const kinds = results
|
|
107
|
+
.filter((x) => x.status === "fulfilled")
|
|
108
|
+
.map((x) => x.value)
|
|
109
|
+
.flat();
|
|
110
|
+
results.forEach((x) => {
|
|
111
|
+
if (x.status === "rejected") {
|
|
112
|
+
let message;
|
|
113
|
+
if (x.reason instanceof Error) {
|
|
114
|
+
message = x.reason.message;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
message = "unknown reason";
|
|
118
|
+
}
|
|
119
|
+
logger.error(`failed to retrieve supported from facilitator handler: ${message}`, x.reason);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
logger.debug(`returning ${kinds.length} kinds supported: {*}`, {
|
|
123
|
+
kinds,
|
|
124
|
+
});
|
|
125
|
+
c.status(200);
|
|
126
|
+
return c.json({
|
|
127
|
+
kinds,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
69
130
|
return router;
|
|
70
131
|
}
|