@faremeter/payment-solana 0.20.1 → 0.21.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/charge/client.d.ts +10 -6
- package/dist/src/charge/client.d.ts.map +1 -1
- package/dist/src/charge/client.js +117 -102
- package/dist/src/charge/common.d.ts +3 -3
- package/dist/src/charge/server.d.ts +18 -7
- package/dist/src/charge/server.d.ts.map +1 -1
- package/dist/src/charge/server.js +22 -24
- package/dist/src/compat.d.ts +38 -0
- package/dist/src/compat.d.ts.map +1 -0
- package/dist/src/compat.js +86 -0
- package/dist/src/compat.test.d.ts +3 -0
- package/dist/src/compat.test.d.ts.map +1 -0
- package/dist/src/compat.test.js +70 -0
- package/dist/src/exact/client.d.ts +18 -15
- package/dist/src/exact/client.d.ts.map +1 -1
- package/dist/src/exact/client.js +124 -96
- package/dist/src/exact/common.d.ts +1 -1
- package/dist/src/exact/facilitator.d.ts +19 -12
- package/dist/src/exact/facilitator.d.ts.map +1 -1
- package/dist/src/exact/facilitator.js +19 -18
- package/dist/src/exact/memo.d.ts +0 -2
- package/dist/src/exact/memo.d.ts.map +1 -1
- package/dist/src/exact/memo.js +0 -9
- package/dist/src/exact/verify.d.ts +5 -1
- package/dist/src/exact/verify.d.ts.map +1 -1
- package/dist/src/exact/verify.js +8 -2
- package/dist/src/exact/verify.test.js +80 -3
- package/dist/src/flex/client/handler.d.ts +31 -0
- package/dist/src/flex/client/handler.d.ts.map +1 -0
- package/dist/src/flex/client/handler.js +104 -0
- package/dist/src/flex/client/index.d.ts +3 -0
- package/dist/src/flex/client/index.d.ts.map +1 -0
- package/dist/src/flex/client/index.js +1 -0
- package/dist/src/flex/common.d.ts +15 -0
- package/dist/src/flex/common.d.ts.map +1 -0
- package/dist/src/flex/common.js +7 -0
- package/dist/src/flex/facilitator/handler.d.ts +48 -0
- package/dist/src/flex/facilitator/handler.d.ts.map +1 -0
- package/dist/src/flex/facilitator/handler.js +705 -0
- package/dist/src/flex/facilitator/index.d.ts +5 -0
- package/dist/src/flex/facilitator/index.d.ts.map +1 -0
- package/dist/src/flex/facilitator/index.js +2 -0
- package/dist/src/flex/hono/index.d.ts +3 -0
- package/dist/src/flex/hono/index.d.ts.map +1 -0
- package/dist/src/flex/hono/index.js +1 -0
- package/dist/src/flex/hono/upto-handler.d.ts +20 -0
- package/dist/src/flex/hono/upto-handler.d.ts.map +1 -0
- package/dist/src/flex/hono/upto-handler.js +72 -0
- package/dist/src/flex/hono/upto-handler.test.d.ts +3 -0
- package/dist/src/flex/hono/upto-handler.test.d.ts.map +1 -0
- package/dist/src/flex/hono/upto-handler.test.js +381 -0
- package/dist/src/flex/index.d.ts +4 -0
- package/dist/src/flex/index.d.ts.map +1 -0
- package/dist/src/flex/index.js +3 -0
- package/dist/src/flex/logger.d.ts +2 -0
- package/dist/src/flex/logger.d.ts.map +1 -0
- package/dist/src/flex/logger.js +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -7
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFacilitatorHandler } from "./handler.js";
|
|
2
|
+
export type { FlexFacilitator, FlushResult } from "./handler.js";
|
|
3
|
+
export { fetchEscrowAccounting } from "@faremeter/flex-solana/facilitator";
|
|
4
|
+
export type { HoldEntry, EscrowAccounting, } from "@faremeter/flex-solana/facilitator";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/flex/facilitator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,YAAY,EACV,SAAS,EACT,gBAAgB,GACjB,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/flex/hono/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EACV,qBAAqB,EACrB,UAAU,EACV,cAAc,GACf,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createUptoHandler } from "./upto-handler.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Handler } from "hono";
|
|
2
|
+
import type { x402SettleResponse } from "@faremeter/types/x402v2";
|
|
3
|
+
export type UptoAccept = {
|
|
4
|
+
scheme: string;
|
|
5
|
+
network: string;
|
|
6
|
+
amount: string;
|
|
7
|
+
asset: string;
|
|
8
|
+
payTo: string;
|
|
9
|
+
maxTimeoutSeconds?: number;
|
|
10
|
+
};
|
|
11
|
+
export type SettleFunction = (amount: bigint) => Promise<x402SettleResponse>;
|
|
12
|
+
export type CreateUptoHandlerOpts = {
|
|
13
|
+
facilitatorURL: string;
|
|
14
|
+
accepts: UptoAccept[];
|
|
15
|
+
authorize: (body: unknown) => bigint | Promise<bigint>;
|
|
16
|
+
handle: (body: unknown, settle: SettleFunction) => Promise<Response>;
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
};
|
|
19
|
+
export declare function createUptoHandler(opts: CreateUptoHandlerOpts): Handler;
|
|
20
|
+
//# sourceMappingURL=upto-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upto-handler.d.ts","sourceRoot":"","sources":["../../../../src/flex/hono/upto-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,MAAM,CAAC;AAc7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAE7E,MAAM,MAAM,qBAAqB,GAAG;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAoFtE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { handleMiddlewareRequest, resolveSupportedVersions, deriveCapabilities, deriveResourceInfo, acceptsToPricing, relaxedRequirementsToV2, } from "@faremeter/middleware/common";
|
|
2
|
+
import { createHTTPFacilitatorHandler } from "@faremeter/middleware";
|
|
3
|
+
export function createUptoHandler(opts) {
|
|
4
|
+
const supportedVersions = resolveSupportedVersions({
|
|
5
|
+
x402v1: false,
|
|
6
|
+
x402v2: true,
|
|
7
|
+
});
|
|
8
|
+
const middlewareAccepts = opts.accepts.map(({ amount, ...rest }) => ({
|
|
9
|
+
...rest,
|
|
10
|
+
maxAmountRequired: amount,
|
|
11
|
+
}));
|
|
12
|
+
const capabilities = deriveCapabilities(middlewareAccepts);
|
|
13
|
+
const handler = createHTTPFacilitatorHandler(opts.facilitatorURL, {
|
|
14
|
+
capabilities,
|
|
15
|
+
acceptsOverride: middlewareAccepts.map(relaxedRequirementsToV2),
|
|
16
|
+
...(opts.fetch ? { fetch: opts.fetch } : {}),
|
|
17
|
+
});
|
|
18
|
+
const pricing = acceptsToPricing(middlewareAccepts);
|
|
19
|
+
const resourceInfo = deriveResourceInfo(middlewareAccepts, "");
|
|
20
|
+
return async (c) => {
|
|
21
|
+
const args = {
|
|
22
|
+
x402Handlers: [handler],
|
|
23
|
+
pricing,
|
|
24
|
+
supportedVersions,
|
|
25
|
+
resource: c.req.url,
|
|
26
|
+
resourceInfo: { ...resourceInfo, url: c.req.url },
|
|
27
|
+
getHeader: (key) => c.req.header(key),
|
|
28
|
+
setResponseHeader: (key, value) => c.header(key, value),
|
|
29
|
+
sendJSONResponse: (status, jsonBody, headers) => {
|
|
30
|
+
c.status(status);
|
|
31
|
+
if (headers) {
|
|
32
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
33
|
+
c.header(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return jsonBody ? c.json(jsonBody) : c.body(null);
|
|
37
|
+
},
|
|
38
|
+
body: async (ctx) => {
|
|
39
|
+
if (ctx.protocolVersion !== 2) {
|
|
40
|
+
return c.json({ error: "upto requires x402 v2" }, 400);
|
|
41
|
+
}
|
|
42
|
+
const verifyResult = await ctx.verify();
|
|
43
|
+
if (!verifyResult.success)
|
|
44
|
+
return verifyResult.errorResponse;
|
|
45
|
+
const body = await c.req.json();
|
|
46
|
+
const ceiling = await opts.authorize(body);
|
|
47
|
+
let settled = false;
|
|
48
|
+
const settle = async (amount) => {
|
|
49
|
+
if (settled) {
|
|
50
|
+
throw new Error("settle() has already been called");
|
|
51
|
+
}
|
|
52
|
+
if (amount < 0n) {
|
|
53
|
+
throw new Error("Settle amount must be non-negative");
|
|
54
|
+
}
|
|
55
|
+
if (amount > ceiling) {
|
|
56
|
+
throw new Error(`Settle amount ${amount} exceeds authorized ceiling ${ceiling}`);
|
|
57
|
+
}
|
|
58
|
+
settled = true;
|
|
59
|
+
// XXX - Mutate in place: ctx.settle() closes over the original object reference
|
|
60
|
+
ctx.paymentRequirements.amount = amount.toString();
|
|
61
|
+
const result = await ctx.settle();
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
throw new Error(`Settlement failed for amount ${amount} (ceiling ${ceiling})`);
|
|
64
|
+
}
|
|
65
|
+
return result.facilitatorResponse;
|
|
66
|
+
};
|
|
67
|
+
return opts.handle(body, settle);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return handleMiddlewareRequest(args);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upto-handler.test.d.ts","sourceRoot":"","sources":["../../../../src/flex/hono/upto-handler.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/usr/bin/env pnpm tsx
|
|
2
|
+
import t from "tap";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { createUptoHandler } from "./upto-handler.js";
|
|
5
|
+
const SCHEME = "upto";
|
|
6
|
+
const NETWORK = "solana:devnet";
|
|
7
|
+
const ASSET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
8
|
+
const PAY_TO = "J8tVV2K1Lf9P3E7vXqFm4TnUz4DqvJAA8bFkaiAAaaaa";
|
|
9
|
+
const REQUIREMENTS = {
|
|
10
|
+
scheme: SCHEME,
|
|
11
|
+
network: NETWORK,
|
|
12
|
+
amount: "10000",
|
|
13
|
+
asset: ASSET,
|
|
14
|
+
payTo: PAY_TO,
|
|
15
|
+
maxTimeoutSeconds: 60,
|
|
16
|
+
};
|
|
17
|
+
const PAYMENT_PAYLOAD = {
|
|
18
|
+
x402Version: 2,
|
|
19
|
+
accepted: REQUIREMENTS,
|
|
20
|
+
payload: { escrow: "test", signature: "test" },
|
|
21
|
+
};
|
|
22
|
+
function encodePaymentHeader(payload) {
|
|
23
|
+
return btoa(JSON.stringify(payload));
|
|
24
|
+
}
|
|
25
|
+
function createMockFetch(overrides) {
|
|
26
|
+
const calls = [];
|
|
27
|
+
const mockFetch = async (input, init) => {
|
|
28
|
+
const url = typeof input === "string"
|
|
29
|
+
? input
|
|
30
|
+
: input instanceof URL
|
|
31
|
+
? input.toString()
|
|
32
|
+
: new URL(input.url).toString();
|
|
33
|
+
const body = init?.body
|
|
34
|
+
? JSON.parse(init.body)
|
|
35
|
+
: { paymentRequirements: { amount: "" } };
|
|
36
|
+
calls.push({ url, body });
|
|
37
|
+
if (url.endsWith("/accepts")) {
|
|
38
|
+
return Response.json(overrides?.acceptsResponse ?? {
|
|
39
|
+
x402Version: 2,
|
|
40
|
+
resource: { url: "http://localhost/test" },
|
|
41
|
+
accepts: [REQUIREMENTS],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (url.endsWith("/verify")) {
|
|
45
|
+
return Response.json(overrides?.verifyResponse ?? {
|
|
46
|
+
isValid: true,
|
|
47
|
+
payer: "payer123",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (url.endsWith("/settle")) {
|
|
51
|
+
return Response.json(overrides?.settleResponse ?? {
|
|
52
|
+
success: true,
|
|
53
|
+
transaction: "tx123",
|
|
54
|
+
network: NETWORK,
|
|
55
|
+
payer: "payer123",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return new Response("Not found", { status: 404 });
|
|
59
|
+
};
|
|
60
|
+
return { fetch: mockFetch, calls };
|
|
61
|
+
}
|
|
62
|
+
function createTestApp(opts) {
|
|
63
|
+
const mock = createMockFetch(opts);
|
|
64
|
+
const authorize = opts?.authorize ?? ((_body) => 10000n);
|
|
65
|
+
const handle = opts?.handle ??
|
|
66
|
+
(async (_body, settle) => {
|
|
67
|
+
const settlement = await settle(5000n);
|
|
68
|
+
return Response.json({ result: "ok", payment: settlement });
|
|
69
|
+
});
|
|
70
|
+
const app = new Hono();
|
|
71
|
+
app.post("/test", createUptoHandler({
|
|
72
|
+
facilitatorURL: "http://facilitator",
|
|
73
|
+
accepts: [
|
|
74
|
+
{
|
|
75
|
+
scheme: SCHEME,
|
|
76
|
+
network: NETWORK,
|
|
77
|
+
amount: "10000",
|
|
78
|
+
asset: ASSET,
|
|
79
|
+
payTo: PAY_TO,
|
|
80
|
+
maxTimeoutSeconds: 60,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
authorize,
|
|
84
|
+
handle,
|
|
85
|
+
fetch: mock.fetch,
|
|
86
|
+
...opts,
|
|
87
|
+
}));
|
|
88
|
+
return { app, mock };
|
|
89
|
+
}
|
|
90
|
+
function findCall(calls, endpoint) {
|
|
91
|
+
const call = calls.find((c) => c.url.endsWith(endpoint));
|
|
92
|
+
if (!call)
|
|
93
|
+
throw new Error(`No call to ${endpoint} found`);
|
|
94
|
+
return call;
|
|
95
|
+
}
|
|
96
|
+
await t.test("createUptoHandler", async (t) => {
|
|
97
|
+
await t.test("returns 402 when no payment header is present", async (t) => {
|
|
98
|
+
const { app } = createTestApp();
|
|
99
|
+
const res = await app.request("/test", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "content-type": "application/json" },
|
|
102
|
+
body: JSON.stringify({ max_tokens: 100 }),
|
|
103
|
+
});
|
|
104
|
+
t.equal(res.status, 402);
|
|
105
|
+
const header = res.headers.get("PAYMENT-REQUIRED");
|
|
106
|
+
if (!header)
|
|
107
|
+
throw new Error("Missing PAYMENT-REQUIRED header");
|
|
108
|
+
const decoded = JSON.parse(atob(header));
|
|
109
|
+
t.equal(decoded.accepts.length, 1);
|
|
110
|
+
t.equal(decoded.accepts[0]?.scheme, SCHEME);
|
|
111
|
+
});
|
|
112
|
+
await t.test("verifies, authorizes, and settles a valid payment", async (t) => {
|
|
113
|
+
const { app, mock } = createTestApp();
|
|
114
|
+
const res = await app.request("/test", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"content-type": "application/json",
|
|
118
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ max_tokens: 100 }),
|
|
121
|
+
});
|
|
122
|
+
t.equal(res.status, 200);
|
|
123
|
+
const body = (await res.json());
|
|
124
|
+
t.equal(body.result, "ok");
|
|
125
|
+
t.equal(body.payment.success, true);
|
|
126
|
+
t.equal(body.payment.transaction, "tx123");
|
|
127
|
+
findCall(mock.calls, "/verify");
|
|
128
|
+
const settleCall = findCall(mock.calls, "/settle");
|
|
129
|
+
t.equal(settleCall.body.paymentRequirements.amount, "5000");
|
|
130
|
+
});
|
|
131
|
+
await t.test("passes parsed body to authorize callback", async (t) => {
|
|
132
|
+
let receivedBody;
|
|
133
|
+
const { app } = createTestApp({
|
|
134
|
+
authorize: (body) => {
|
|
135
|
+
receivedBody = body;
|
|
136
|
+
return 10000n;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await app.request("/test", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
"content-type": "application/json",
|
|
143
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({ max_tokens: 42 }),
|
|
146
|
+
});
|
|
147
|
+
t.strictSame(receivedBody, { max_tokens: 42 });
|
|
148
|
+
});
|
|
149
|
+
await t.test("passes parsed body to handle callback", async (t) => {
|
|
150
|
+
let receivedBody;
|
|
151
|
+
const { app } = createTestApp({
|
|
152
|
+
handle: async (body, settle) => {
|
|
153
|
+
receivedBody = body;
|
|
154
|
+
await settle(100n);
|
|
155
|
+
return Response.json({ ok: true });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
await app.request("/test", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: {
|
|
161
|
+
"content-type": "application/json",
|
|
162
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({ prompt: "hello" }),
|
|
165
|
+
});
|
|
166
|
+
t.strictSame(receivedBody, { prompt: "hello" });
|
|
167
|
+
});
|
|
168
|
+
await t.test("throws when settle amount exceeds ceiling", async (t) => {
|
|
169
|
+
const { app } = createTestApp({
|
|
170
|
+
authorize: () => 100n,
|
|
171
|
+
handle: async (_body, settle) => {
|
|
172
|
+
await settle(200n);
|
|
173
|
+
return Response.json({ ok: true });
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const res = await app.request("/test", {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
"content-type": "application/json",
|
|
180
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({}),
|
|
183
|
+
});
|
|
184
|
+
t.equal(res.status, 500);
|
|
185
|
+
});
|
|
186
|
+
await t.test("mutates requirements.amount before calling facilitator settle", async (t) => {
|
|
187
|
+
const { app, mock } = createTestApp({
|
|
188
|
+
authorize: () => 10000n,
|
|
189
|
+
handle: async (_body, settle) => {
|
|
190
|
+
await settle(7777n);
|
|
191
|
+
return Response.json({ ok: true });
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
await app.request("/test", {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"content-type": "application/json",
|
|
198
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({}),
|
|
201
|
+
});
|
|
202
|
+
const settleCall = findCall(mock.calls, "/settle");
|
|
203
|
+
t.equal(settleCall.body.paymentRequirements.amount, "7777");
|
|
204
|
+
});
|
|
205
|
+
await t.test("returns 402 when verification fails", async (t) => {
|
|
206
|
+
const mock = createMockFetch({
|
|
207
|
+
verifyResponse: { isValid: false, invalidReason: "bad signature" },
|
|
208
|
+
});
|
|
209
|
+
const app = new Hono();
|
|
210
|
+
app.post("/test", createUptoHandler({
|
|
211
|
+
facilitatorURL: "http://facilitator",
|
|
212
|
+
accepts: [
|
|
213
|
+
{
|
|
214
|
+
scheme: SCHEME,
|
|
215
|
+
network: NETWORK,
|
|
216
|
+
amount: "10000",
|
|
217
|
+
asset: ASSET,
|
|
218
|
+
payTo: PAY_TO,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
authorize: () => 10000n,
|
|
222
|
+
handle: async (_body, settle) => {
|
|
223
|
+
await settle(100n);
|
|
224
|
+
return Response.json({ ok: true });
|
|
225
|
+
},
|
|
226
|
+
fetch: mock.fetch,
|
|
227
|
+
}));
|
|
228
|
+
const res = await app.request("/test", {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: {
|
|
231
|
+
"content-type": "application/json",
|
|
232
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify({}),
|
|
235
|
+
});
|
|
236
|
+
t.equal(res.status, 402);
|
|
237
|
+
});
|
|
238
|
+
await t.test("throws when facilitator settlement fails", async (t) => {
|
|
239
|
+
const mock = createMockFetch({
|
|
240
|
+
settleResponse: {
|
|
241
|
+
success: false,
|
|
242
|
+
transaction: "",
|
|
243
|
+
network: NETWORK,
|
|
244
|
+
errorReason: "insufficient funds",
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const app = new Hono();
|
|
248
|
+
app.post("/test", createUptoHandler({
|
|
249
|
+
facilitatorURL: "http://facilitator",
|
|
250
|
+
accepts: [
|
|
251
|
+
{
|
|
252
|
+
scheme: SCHEME,
|
|
253
|
+
network: NETWORK,
|
|
254
|
+
amount: "10000",
|
|
255
|
+
asset: ASSET,
|
|
256
|
+
payTo: PAY_TO,
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
authorize: () => 10000n,
|
|
260
|
+
handle: async (_body, settle) => {
|
|
261
|
+
await settle(100n);
|
|
262
|
+
return Response.json({ ok: true });
|
|
263
|
+
},
|
|
264
|
+
fetch: mock.fetch,
|
|
265
|
+
}));
|
|
266
|
+
const res = await app.request("/test", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: {
|
|
269
|
+
"content-type": "application/json",
|
|
270
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
271
|
+
},
|
|
272
|
+
body: JSON.stringify({}),
|
|
273
|
+
});
|
|
274
|
+
t.equal(res.status, 500);
|
|
275
|
+
});
|
|
276
|
+
await t.test("supports async authorize callback", async (t) => {
|
|
277
|
+
const { app } = createTestApp({
|
|
278
|
+
authorize: async () => {
|
|
279
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
280
|
+
return 10000n;
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const res = await app.request("/test", {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
"content-type": "application/json",
|
|
287
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify({}),
|
|
290
|
+
});
|
|
291
|
+
t.equal(res.status, 200);
|
|
292
|
+
});
|
|
293
|
+
await t.test("settle can be called with zero amount", async (t) => {
|
|
294
|
+
const { app, mock } = createTestApp({
|
|
295
|
+
authorize: () => 10000n,
|
|
296
|
+
handle: async (_body, settle) => {
|
|
297
|
+
await settle(0n);
|
|
298
|
+
return Response.json({ ok: true });
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
const res = await app.request("/test", {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
"content-type": "application/json",
|
|
305
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify({}),
|
|
308
|
+
});
|
|
309
|
+
t.equal(res.status, 200);
|
|
310
|
+
const settleCall = findCall(mock.calls, "/settle");
|
|
311
|
+
t.equal(settleCall.body.paymentRequirements.amount, "0");
|
|
312
|
+
});
|
|
313
|
+
await t.test("settle at exactly the ceiling amount succeeds", async (t) => {
|
|
314
|
+
const { app } = createTestApp({
|
|
315
|
+
authorize: () => 5000n,
|
|
316
|
+
handle: async (_body, settle) => {
|
|
317
|
+
await settle(5000n);
|
|
318
|
+
return Response.json({ ok: true });
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
const res = await app.request("/test", {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: {
|
|
324
|
+
"content-type": "application/json",
|
|
325
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
326
|
+
},
|
|
327
|
+
body: JSON.stringify({}),
|
|
328
|
+
});
|
|
329
|
+
t.equal(res.status, 200);
|
|
330
|
+
});
|
|
331
|
+
await t.test("throws when settle is called twice", async (t) => {
|
|
332
|
+
const { app } = createTestApp({
|
|
333
|
+
authorize: () => 10000n,
|
|
334
|
+
handle: async (_body, settle) => {
|
|
335
|
+
await settle(1000n);
|
|
336
|
+
await settle(2000n);
|
|
337
|
+
return Response.json({ ok: true });
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
const res = await app.request("/test", {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: {
|
|
343
|
+
"content-type": "application/json",
|
|
344
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify({}),
|
|
347
|
+
});
|
|
348
|
+
t.equal(res.status, 500);
|
|
349
|
+
});
|
|
350
|
+
await t.test("handler returns streaming response", async (t) => {
|
|
351
|
+
const { app } = createTestApp({
|
|
352
|
+
handle: async (_body, settle) => {
|
|
353
|
+
const encoder = new TextEncoder();
|
|
354
|
+
const stream = new ReadableStream({
|
|
355
|
+
async start(controller) {
|
|
356
|
+
controller.enqueue(encoder.encode("data: chunk1\n\n"));
|
|
357
|
+
const settlement = await settle(100n);
|
|
358
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(settlement)}\n\n`));
|
|
359
|
+
controller.close();
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
return new Response(stream, {
|
|
363
|
+
headers: { "content-type": "text/event-stream" },
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
const res = await app.request("/test", {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: {
|
|
370
|
+
"content-type": "application/json",
|
|
371
|
+
"PAYMENT-SIGNATURE": encodePaymentHeader(PAYMENT_PAYLOAD),
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({}),
|
|
374
|
+
});
|
|
375
|
+
t.equal(res.status, 200);
|
|
376
|
+
t.equal(res.headers.get("content-type"), "text/event-stream");
|
|
377
|
+
const text = await res.text();
|
|
378
|
+
t.ok(text.includes("chunk1"));
|
|
379
|
+
t.ok(text.includes("tx123"));
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/flex/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,WAAW,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,IAAI,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/flex/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,kCAAgD,CAAC"}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
package/dist/src/index.js
CHANGED