@armory-sh/middleware-hono 0.3.14 → 0.3.16
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/index.d.ts +14 -96
- package/dist/index.js +37 -438
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,101 +1,19 @@
|
|
|
1
|
-
import * as hono from 'hono';
|
|
2
1
|
import { Context, Next } from 'hono';
|
|
3
|
-
import
|
|
4
|
-
import * as hono_utils_types from 'hono/utils/types';
|
|
5
|
-
import * as _armory_sh_base from '@armory-sh/base';
|
|
6
|
-
import { Address, AcceptPaymentOptions, PricingConfig } from '@armory-sh/base';
|
|
2
|
+
import { PaymentPayloadV2, PaymentRequirements } from '@armory-sh/base';
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Armory V1 Types - x402 Protocol V1 Compatible
|
|
12
|
-
*
|
|
13
|
-
* Supports both the x402 V1 specification format and legacy Armory V1 format.
|
|
14
|
-
*
|
|
15
|
-
* x402 V1 Specification: https://github.com/coinbase/x402
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Network name for x402 V1 (e.g., "base-sepolia", "ethereum-mainnet")
|
|
20
|
-
*/
|
|
21
|
-
type X402V1Network = string;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Simple one-line middleware API for Armory merchants
|
|
25
|
-
* Focus on DX/UX - "everything just magically works"
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Simple configuration for acceptPaymentsViaArmory middleware
|
|
30
|
-
*/
|
|
31
|
-
interface SimpleMiddlewareConfig {
|
|
32
|
-
/** Address to receive payments */
|
|
33
|
-
payTo: Address;
|
|
34
|
-
/** Default amount to charge (default: "1.0") */
|
|
35
|
-
amount?: string;
|
|
36
|
-
/** Payment acceptance options */
|
|
37
|
-
accept?: AcceptPaymentOptions;
|
|
38
|
-
/** Fallback facilitator URL (if not using accept.facilitators) */
|
|
4
|
+
interface PaymentMiddlewareConfig {
|
|
5
|
+
requirements: PaymentRequirements;
|
|
39
6
|
facilitatorUrl?: string;
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
skipVerification?: boolean;
|
|
8
|
+
network?: string;
|
|
42
9
|
}
|
|
10
|
+
interface AugmentedRequest extends Request {
|
|
11
|
+
payment?: {
|
|
12
|
+
payload: PaymentPayloadV2;
|
|
13
|
+
payerAddress: string;
|
|
14
|
+
verified: boolean;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
declare const paymentMiddleware: (config: PaymentMiddlewareConfig) => (c: Context, next: Next) => Promise<Response | void>;
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
* One-line middleware setup for accepting Armory payments in Hono
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* import { acceptPaymentsViaArmory } from '@armory-sh/middleware/integrations/hono-simple'
|
|
50
|
-
*
|
|
51
|
-
* app.use('/api/*', acceptPaymentsViaArmory({
|
|
52
|
-
* payTo: '0xMerchantAddress...',
|
|
53
|
-
* amount: '1.0'
|
|
54
|
-
* }))
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
declare const acceptPaymentsViaArmory: (config: SimpleMiddlewareConfig & {
|
|
58
|
-
defaultVersion?: 1 | 2;
|
|
59
|
-
}) => (c: Context, next: Next) => Promise<(Response & hono.TypedResponse<{
|
|
60
|
-
error: string;
|
|
61
|
-
x402Version: number;
|
|
62
|
-
accepts: ({
|
|
63
|
-
scheme: "exact";
|
|
64
|
-
network: X402V1Network;
|
|
65
|
-
maxAmountRequired: string;
|
|
66
|
-
asset: _armory_sh_base.Address;
|
|
67
|
-
payTo: _armory_sh_base.Address;
|
|
68
|
-
resource: string;
|
|
69
|
-
description: string;
|
|
70
|
-
mimeType?: string | undefined;
|
|
71
|
-
outputSchema?: null | undefined;
|
|
72
|
-
maxTimeoutSeconds: number;
|
|
73
|
-
extra?: {
|
|
74
|
-
[x: string]: hono_utils_types.JSONValue;
|
|
75
|
-
} | undefined;
|
|
76
|
-
} | {
|
|
77
|
-
scheme: "exact";
|
|
78
|
-
network: _armory_sh_base.CAIP2ChainId;
|
|
79
|
-
amount: string;
|
|
80
|
-
asset: _armory_sh_base.Address;
|
|
81
|
-
payTo: _armory_sh_base.Address;
|
|
82
|
-
maxTimeoutSeconds: number;
|
|
83
|
-
extra?: {
|
|
84
|
-
[x: string]: hono_utils_types.JSONValue;
|
|
85
|
-
} | undefined;
|
|
86
|
-
} | {
|
|
87
|
-
amount: string;
|
|
88
|
-
network: string;
|
|
89
|
-
contractAddress: string;
|
|
90
|
-
payTo: string;
|
|
91
|
-
expiry: number;
|
|
92
|
-
})[];
|
|
93
|
-
}, hono_utils_http_status.ContentfulStatusCode, "json">) | (Response & hono.TypedResponse<{
|
|
94
|
-
error: string;
|
|
95
|
-
x402Version: PaymentVersion;
|
|
96
|
-
}, hono_utils_http_status.ContentfulStatusCode, "json">) | (Response & hono.TypedResponse<{
|
|
97
|
-
error: string;
|
|
98
|
-
message: string;
|
|
99
|
-
}, hono_utils_http_status.ContentfulStatusCode, "json">) | undefined>;
|
|
100
|
-
|
|
101
|
-
export { acceptPaymentsViaArmory };
|
|
19
|
+
export { type AugmentedRequest, type PaymentMiddlewareConfig, paymentMiddleware };
|
package/dist/index.js
CHANGED
|
@@ -1,458 +1,57 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
createPaymentRequiredHeaders,
|
|
4
|
+
createSettlementHeaders,
|
|
5
|
+
PAYMENT_SIGNATURE_HEADER,
|
|
6
|
+
decodePaymentV2
|
|
5
7
|
} from "@armory-sh/base";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
resolveNetwork,
|
|
10
|
-
resolveToken,
|
|
11
|
-
validateAcceptConfig,
|
|
12
|
-
isValidationError,
|
|
13
|
-
normalizeNetworkName as normalizeNetworkName2
|
|
14
|
-
} from "@armory-sh/base";
|
|
15
|
-
|
|
16
|
-
// src/core.ts
|
|
17
|
-
import {
|
|
18
|
-
getNetworkConfig,
|
|
19
|
-
getNetworkByChainId,
|
|
20
|
-
normalizeNetworkName,
|
|
21
|
-
encodeX402PaymentRequiredV1,
|
|
22
|
-
encodeX402SettlementResponseV1,
|
|
23
|
-
safeBase64Encode,
|
|
24
|
-
V1_HEADERS,
|
|
25
|
-
V2_HEADERS
|
|
26
|
-
} from "@armory-sh/base";
|
|
27
|
-
var toSlug = (network) => {
|
|
28
|
-
if (typeof network === "number") {
|
|
29
|
-
const net = getNetworkByChainId(network);
|
|
30
|
-
if (!net) throw new Error(`No network found for chainId: ${network}`);
|
|
31
|
-
return normalizeNetworkName(net.name);
|
|
32
|
-
}
|
|
33
|
-
if (network.startsWith("eip155:")) {
|
|
34
|
-
const chainIdStr = network.split(":")[1];
|
|
35
|
-
if (!chainIdStr) throw new Error(`Invalid eip155 format: ${network}`);
|
|
36
|
-
const chainId = parseInt(chainIdStr, 10);
|
|
37
|
-
const net = getNetworkByChainId(chainId);
|
|
38
|
-
if (!net) throw new Error(`No network found for chainId: ${chainId}`);
|
|
39
|
-
return normalizeNetworkName(net.name);
|
|
40
|
-
}
|
|
41
|
-
return normalizeNetworkName(network);
|
|
42
|
-
};
|
|
43
|
-
var toEip155 = (network) => {
|
|
44
|
-
if (typeof network === "number") {
|
|
45
|
-
const net2 = getNetworkByChainId(network);
|
|
46
|
-
if (!net2) throw new Error(`No network found for chainId: ${network}`);
|
|
47
|
-
return net2.caip2Id;
|
|
48
|
-
}
|
|
49
|
-
if (network.startsWith("eip155:")) {
|
|
50
|
-
const net2 = getNetworkConfig(network);
|
|
51
|
-
if (!net2) throw new Error(`No network found for: ${network}`);
|
|
52
|
-
return net2.caip2Id;
|
|
53
|
-
}
|
|
54
|
-
const slug = normalizeNetworkName(network);
|
|
55
|
-
const net = getNetworkConfig(slug);
|
|
56
|
-
if (!net) throw new Error(`No network found for: ${slug}`);
|
|
57
|
-
return net.caip2Id;
|
|
58
|
-
};
|
|
59
|
-
var getNetworkName = (network) => toSlug(network);
|
|
60
|
-
var getChainId = (network) => toEip155(network);
|
|
61
|
-
var createV1Requirements = (config, resourceUrl, expiry) => {
|
|
62
|
-
const networkName = getNetworkName(config.network);
|
|
63
|
-
const network = getNetworkConfig(networkName);
|
|
64
|
-
if (!network) throw new Error(`Unsupported network: ${networkName}`);
|
|
65
|
-
const atomicAmount = toAtomicUnits(config.amount);
|
|
66
|
-
return {
|
|
67
|
-
scheme: "exact",
|
|
68
|
-
network: networkName,
|
|
69
|
-
maxAmountRequired: atomicAmount,
|
|
70
|
-
asset: network.usdcAddress,
|
|
71
|
-
payTo: config.payTo,
|
|
72
|
-
resource: resourceUrl,
|
|
73
|
-
description: "API Access",
|
|
74
|
-
mimeType: "application/json",
|
|
75
|
-
maxTimeoutSeconds: 300,
|
|
76
|
-
extra: {
|
|
77
|
-
name: "USDC",
|
|
78
|
-
version: "2"
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
var toAtomicUnits = (amount) => {
|
|
83
|
-
if (amount.includes(".")) {
|
|
84
|
-
const [whole, fractional = ""] = amount.split(".");
|
|
85
|
-
const paddedFractional = fractional.padEnd(6, "0").slice(0, 6);
|
|
86
|
-
return `${whole}${paddedFractional}`.replace(/^0+/, "") || "0";
|
|
87
|
-
}
|
|
88
|
-
return `${amount}000000`;
|
|
89
|
-
};
|
|
90
|
-
var createV2Requirements = (config, resourceUrl) => {
|
|
91
|
-
const networkName = getNetworkName(config.network);
|
|
92
|
-
const network = getNetworkConfig(networkName);
|
|
93
|
-
if (!network) throw new Error(`Unsupported network: ${networkName}`);
|
|
94
|
-
const assetIdMatch = network.caipAssetId.match(/\/erc20:(0x[a-fA-F0-9]{40})$/);
|
|
95
|
-
if (!assetIdMatch) throw new Error(`Invalid CAIP asset ID format: ${network.caipAssetId}`);
|
|
96
|
-
const asset = assetIdMatch[1];
|
|
97
|
-
const atomicAmount = toAtomicUnits(config.amount);
|
|
98
|
-
return {
|
|
99
|
-
scheme: "exact",
|
|
100
|
-
network: getChainId(config.network),
|
|
101
|
-
amount: atomicAmount,
|
|
102
|
-
asset,
|
|
103
|
-
payTo: config.payTo,
|
|
104
|
-
maxTimeoutSeconds: 300,
|
|
105
|
-
extra: {
|
|
106
|
-
name: "USDC",
|
|
107
|
-
version: "2"
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
var createPaymentRequirements = (config, version = 1, resourceUrl = "https://api.example.com") => {
|
|
112
|
-
const networkName = getNetworkName(config.network);
|
|
113
|
-
const network = getNetworkConfig(networkName);
|
|
114
|
-
if (!network) throw new Error(`Unsupported network: ${networkName}`);
|
|
115
|
-
const expiry = Math.floor(Date.now() / 1e3) + 3600;
|
|
116
|
-
return version === 1 ? createV1Requirements(config, resourceUrl, expiry) : createV2Requirements(config, resourceUrl);
|
|
117
|
-
};
|
|
118
|
-
var findHeaderValue = (headers, name) => {
|
|
119
|
-
const value = headers[name];
|
|
120
|
-
if (typeof value === "string") return value;
|
|
121
|
-
if (Array.isArray(value) && value.length > 0) return value[0];
|
|
122
|
-
const lowerName = name.toLowerCase();
|
|
123
|
-
for (const [key, val] of Object.entries(headers)) {
|
|
124
|
-
if (key.toLowerCase() === lowerName) {
|
|
125
|
-
if (typeof val === "string") return val;
|
|
126
|
-
if (Array.isArray(val) && val.length > 0) return val[0];
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return void 0;
|
|
130
|
-
};
|
|
131
|
-
var parseHeader = (header) => {
|
|
132
|
-
try {
|
|
133
|
-
if (header.startsWith("{")) return JSON.parse(header);
|
|
134
|
-
return JSON.parse(atob(header));
|
|
135
|
-
} catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
var extractPaymentPayload = (request) => {
|
|
140
|
-
const v1Header = findHeaderValue(request.headers, "X-PAYMENT");
|
|
141
|
-
if (v1Header) return parseHeader(v1Header);
|
|
142
|
-
const v2Header = findHeaderValue(request.headers, "PAYMENT-SIGNATURE");
|
|
143
|
-
if (v2Header) return parseHeader(v2Header);
|
|
144
|
-
return null;
|
|
145
|
-
};
|
|
146
|
-
var postFacilitator = async (url, headers, body) => {
|
|
147
|
-
const response = await fetch(url, {
|
|
148
|
-
method: "POST",
|
|
149
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
150
|
-
body: JSON.stringify(body)
|
|
151
|
-
});
|
|
152
|
-
if (!response.ok) {
|
|
153
|
-
const error = await response.text();
|
|
154
|
-
throw new Error(error || `Request failed: ${response.status}`);
|
|
155
|
-
}
|
|
156
|
-
return response.json();
|
|
157
|
-
};
|
|
158
|
-
var verifyWithFacilitator = async (request, facilitator) => {
|
|
159
|
-
const payload = extractPaymentPayload(request);
|
|
160
|
-
if (!payload) {
|
|
161
|
-
return { success: false, error: "No payment payload found in request headers" };
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
const url = new URL("/verify", facilitator.url);
|
|
165
|
-
const headers = facilitator.createHeaders?.() ?? {};
|
|
166
|
-
const data = await postFacilitator(url.toString(), headers, { payload, headers: request.headers });
|
|
167
|
-
return {
|
|
168
|
-
success: true,
|
|
169
|
-
payerAddress: data.payerAddress,
|
|
170
|
-
balance: data.balance,
|
|
171
|
-
requiredAmount: data.requiredAmount
|
|
172
|
-
};
|
|
173
|
-
} catch (error) {
|
|
174
|
-
return {
|
|
175
|
-
success: false,
|
|
176
|
-
error: error instanceof Error ? error.message : "Unknown verification error"
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
var createX402V1PaymentRequiredHeaders = (requirements, errorMessage = "X-PAYMENT header is required") => {
|
|
181
|
-
const paymentRequired = {
|
|
182
|
-
x402Version: 1,
|
|
183
|
-
error: errorMessage,
|
|
184
|
-
accepts: [requirements]
|
|
185
|
-
};
|
|
186
|
-
return {
|
|
187
|
-
[V1_HEADERS.PAYMENT_REQUIRED]: encodeX402PaymentRequiredV1(paymentRequired),
|
|
188
|
-
"Content-Type": "application/json"
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
var createX402V2PaymentRequiredHeaders = (requirements, resourceUrl, options) => {
|
|
192
|
-
const resource = {
|
|
193
|
-
url: resourceUrl,
|
|
194
|
-
description: options?.description ?? "API Access",
|
|
195
|
-
mimeType: options?.mimeType ?? "application/json"
|
|
196
|
-
};
|
|
197
|
-
const paymentRequired = {
|
|
198
|
-
x402Version: 2,
|
|
199
|
-
...options?.errorMessage && { error: options.errorMessage },
|
|
200
|
-
resource,
|
|
201
|
-
accepts: [requirements],
|
|
202
|
-
...requirements.extra && { extensions: requirements.extra }
|
|
203
|
-
};
|
|
204
|
-
return {
|
|
205
|
-
[V2_HEADERS.PAYMENT_REQUIRED]: safeBase64Encode(JSON.stringify(paymentRequired)),
|
|
206
|
-
"Content-Type": "application/json"
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
var createSettlementHeaders = (response, version, network, payer) => {
|
|
210
|
-
if (version === 1) {
|
|
211
|
-
const txHash2 = "transaction" in response ? response.transaction : response.txHash || "";
|
|
212
|
-
const isSuccess2 = "success" in response ? response.success : false;
|
|
213
|
-
const networkName = network || "base-sepolia";
|
|
214
|
-
const settlementV1 = {
|
|
215
|
-
success: isSuccess2,
|
|
216
|
-
transaction: txHash2,
|
|
217
|
-
network: networkName,
|
|
218
|
-
payer: payer || "0x0000000000000000000000000000000000000000"
|
|
219
|
-
};
|
|
220
|
-
return { [V1_HEADERS.PAYMENT_RESPONSE]: encodeX402SettlementResponseV1(settlementV1) };
|
|
221
|
-
}
|
|
222
|
-
const txHash = "transaction" in response ? response.transaction : "";
|
|
223
|
-
const isSuccess = "success" in response ? response.success : false;
|
|
224
|
-
const settlementV2 = {
|
|
225
|
-
success: isSuccess,
|
|
226
|
-
transaction: txHash,
|
|
227
|
-
network: network || "eip155:84532",
|
|
228
|
-
...payer && { payer }
|
|
229
|
-
};
|
|
230
|
-
return { [V2_HEADERS.PAYMENT_RESPONSE]: safeBase64Encode(JSON.stringify(settlementV2)) };
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
// src/simple.ts
|
|
234
|
-
var findPricingConfig = (pricing, network, token, facilitatorUrl) => {
|
|
235
|
-
if (!pricing) return void 0;
|
|
236
|
-
const withFacilitator = pricing.find(
|
|
237
|
-
(p) => p.network === network && p.token === token && p.facilitator === facilitatorUrl
|
|
238
|
-
);
|
|
239
|
-
if (withFacilitator) return withFacilitator;
|
|
240
|
-
const withNetworkToken = pricing.find(
|
|
241
|
-
(p) => p.network === network && p.token === token && !p.facilitator
|
|
242
|
-
);
|
|
243
|
-
if (withNetworkToken) return withNetworkToken;
|
|
244
|
-
const networkOnly = pricing.find(
|
|
245
|
-
(p) => p.network === network && !p.token && !p.facilitator
|
|
246
|
-
);
|
|
247
|
-
if (networkOnly) return networkOnly;
|
|
248
|
-
return void 0;
|
|
249
|
-
};
|
|
250
|
-
var resolveMiddlewareConfig = (config) => {
|
|
251
|
-
const { payTo, amount = "1.0", accept = {}, facilitatorUrl, pricing } = config;
|
|
252
|
-
const acceptOptions = facilitatorUrl ? {
|
|
253
|
-
...accept,
|
|
254
|
-
facilitators: accept.facilitators ? [...Array.isArray(accept.facilitators) ? accept.facilitators : [accept.facilitators], { url: facilitatorUrl }] : { url: facilitatorUrl }
|
|
255
|
-
} : accept;
|
|
256
|
-
const result = validateAcceptConfig(acceptOptions, payTo, amount);
|
|
257
|
-
if (!result.success) {
|
|
258
|
-
return result.error;
|
|
259
|
-
}
|
|
260
|
-
const facilitatorConfigs = result.config[0]?.facilitators.map((f) => ({
|
|
261
|
-
url: f.url,
|
|
262
|
-
createHeaders: f.input.headers
|
|
263
|
-
})) ?? [];
|
|
264
|
-
const enrichedConfigs = result.config.map((c) => {
|
|
265
|
-
const networkName = normalizeNetworkName2(c.network.config.name);
|
|
266
|
-
const tokenSymbol = c.token.config.symbol;
|
|
267
|
-
const facilitatorPricing = c.facilitators.map((f) => {
|
|
268
|
-
const pricingConfig = findPricingConfig(pricing, networkName, tokenSymbol, f.url);
|
|
269
|
-
return { url: f.url, pricing: pricingConfig };
|
|
270
|
-
});
|
|
271
|
-
const defaultPricing = findPricingConfig(pricing, networkName, tokenSymbol, "");
|
|
272
|
-
return {
|
|
273
|
-
...c,
|
|
274
|
-
amount: defaultPricing?.amount ?? c.amount,
|
|
275
|
-
facilitatorUrl: facilitatorPricing[0]?.url,
|
|
276
|
-
pricing: defaultPricing
|
|
277
|
-
};
|
|
278
|
-
});
|
|
279
|
-
return {
|
|
280
|
-
configs: enrichedConfigs,
|
|
281
|
-
version: acceptOptions.version ?? "auto",
|
|
282
|
-
facilitators: facilitatorConfigs
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
var getPrimaryConfig = (resolved) => {
|
|
286
|
-
const primary = resolved.configs[0];
|
|
287
|
-
if (!primary) {
|
|
288
|
-
throw new Error("No valid payment configurations found");
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
payTo: primary.payTo,
|
|
292
|
-
network: normalizeNetworkName2(primary.network.config.name),
|
|
293
|
-
amount: primary.amount,
|
|
294
|
-
facilitator: resolved.facilitators[0],
|
|
295
|
-
settlementMode: "verify"
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// src/payment-utils.ts
|
|
300
|
-
import { extractPaymentFromHeaders, X402_HEADERS } from "@armory-sh/base";
|
|
301
|
-
var getHeadersForVersion = (version) => version === 1 ? { payment: "X-PAYMENT", required: "X-PAYMENT-REQUIRED", response: "X-PAYMENT-RESPONSE" } : { payment: "PAYMENT-SIGNATURE", required: "PAYMENT-REQUIRED", response: "PAYMENT-RESPONSE" };
|
|
302
|
-
function isLegacyV1(payload) {
|
|
303
|
-
return typeof payload === "object" && payload !== null && "contractAddress" in payload && "network" in payload && "signature" in payload && typeof payload.signature === "string";
|
|
304
|
-
}
|
|
305
|
-
function isLegacyV2(payload) {
|
|
306
|
-
return typeof payload === "object" && payload !== null && "chainId" in payload && "assetId" in payload && "signature" in payload && typeof payload.signature === "string";
|
|
307
|
-
}
|
|
308
|
-
var decodePayload = (headerValue) => {
|
|
309
|
-
let parsed;
|
|
310
|
-
let isJsonString = false;
|
|
311
|
-
try {
|
|
312
|
-
if (headerValue.startsWith("{")) {
|
|
313
|
-
parsed = JSON.parse(headerValue);
|
|
314
|
-
isJsonString = true;
|
|
315
|
-
} else {
|
|
316
|
-
try {
|
|
317
|
-
parsed = JSON.parse(atob(headerValue));
|
|
318
|
-
} catch {
|
|
319
|
-
const padding = 4 - headerValue.length % 4;
|
|
320
|
-
const padded = padding !== 4 ? headerValue + "=".repeat(padding) : headerValue;
|
|
321
|
-
const standard = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
322
|
-
parsed = JSON.parse(atob(standard));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
} catch {
|
|
326
|
-
throw new Error("Invalid payment payload");
|
|
327
|
-
}
|
|
328
|
-
if (typeof parsed === "object" && parsed !== null && "x402Version" in parsed) {
|
|
329
|
-
const version = parsed.x402Version;
|
|
330
|
-
if (version === 1) {
|
|
331
|
-
return { payload: parsed, version: 1 };
|
|
332
|
-
}
|
|
333
|
-
if (version === 2) {
|
|
334
|
-
return { payload: parsed, version: 2 };
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const base64Value = isJsonString ? Buffer.from(headerValue).toString("base64") : headerValue;
|
|
338
|
-
const headers = new Headers();
|
|
339
|
-
headers.set(X402_HEADERS.PAYMENT, base64Value);
|
|
340
|
-
const x402Payload = extractPaymentFromHeaders(headers);
|
|
341
|
-
if (x402Payload) {
|
|
342
|
-
return { payload: x402Payload, version: 2 };
|
|
343
|
-
}
|
|
344
|
-
if (isLegacyV1(parsed)) {
|
|
345
|
-
return { payload: parsed, version: 1 };
|
|
346
|
-
}
|
|
347
|
-
if (isLegacyV2(parsed)) {
|
|
348
|
-
return { payload: parsed, version: 2 };
|
|
349
|
-
}
|
|
350
|
-
throw new Error("Unrecognized payment payload format");
|
|
351
|
-
};
|
|
352
|
-
var extractPayerAddress = (payload) => {
|
|
353
|
-
if ("payload" in payload) {
|
|
354
|
-
const x402Payload = payload;
|
|
355
|
-
if ("authorization" in x402Payload.payload) {
|
|
356
|
-
return x402Payload.payload.authorization.from;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if ("from" in payload && typeof payload.from === "string") {
|
|
360
|
-
return payload.from;
|
|
361
|
-
}
|
|
362
|
-
throw new Error("Unable to extract payer address from payload");
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
// src/index.ts
|
|
366
|
-
var toHttpRequest = (c) => ({
|
|
367
|
-
headers: Object.fromEntries(Object.entries(c.req.header()).map(([k, v]) => [k, v ?? ""])),
|
|
368
|
-
method: c.req.method,
|
|
369
|
-
url: c.req.url
|
|
370
|
-
});
|
|
371
|
-
var acceptPaymentsViaArmory = (config) => {
|
|
372
|
-
const resolved = resolveMiddlewareConfig(config);
|
|
373
|
-
if ("code" in resolved) {
|
|
374
|
-
throw new Error(`Invalid payment configuration: ${resolved.message}`);
|
|
375
|
-
}
|
|
376
|
-
const primaryConfig = getPrimaryConfig(resolved);
|
|
377
|
-
const defaultVersion = config.defaultVersion ?? 2;
|
|
378
|
-
const createRequirementsForVersion = (version, resourceUrl) => createPaymentRequirements(primaryConfig, version, resourceUrl);
|
|
379
|
-
const createPaymentRequiredResponse = (version, requirements, resourceUrl, errorMessage) => {
|
|
380
|
-
if (version === 1) {
|
|
381
|
-
return createX402V1PaymentRequiredHeaders(
|
|
382
|
-
requirements,
|
|
383
|
-
errorMessage ?? "X-PAYMENT header is required"
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
return createX402V2PaymentRequiredHeaders(
|
|
387
|
-
requirements,
|
|
388
|
-
resourceUrl,
|
|
389
|
-
{ errorMessage }
|
|
390
|
-
);
|
|
391
|
-
};
|
|
8
|
+
var paymentMiddleware = (config) => {
|
|
9
|
+
const { requirements, facilitatorUrl, skipVerification = false, network = "base" } = config;
|
|
392
10
|
return async (c, next) => {
|
|
393
|
-
const paymentHeader = c.req.header(
|
|
394
|
-
const resourceUrl = c.req.url;
|
|
11
|
+
const paymentHeader = c.req.header(PAYMENT_SIGNATURE_HEADER);
|
|
395
12
|
if (!paymentHeader) {
|
|
396
|
-
const
|
|
397
|
-
const requirements = createRequirementsForVersion(version, resourceUrl);
|
|
398
|
-
const headers = createPaymentRequiredResponse(version, requirements, resourceUrl);
|
|
13
|
+
const requiredHeaders = createPaymentRequiredHeaders(requirements);
|
|
399
14
|
c.status(402);
|
|
400
|
-
for (const [key, value] of Object.entries(
|
|
15
|
+
for (const [key, value] of Object.entries(requiredHeaders)) {
|
|
401
16
|
c.header(key, value);
|
|
402
17
|
}
|
|
403
18
|
return c.json({
|
|
404
19
|
error: "Payment required",
|
|
405
|
-
x402Version: version,
|
|
406
20
|
accepts: [requirements]
|
|
407
21
|
});
|
|
408
22
|
}
|
|
23
|
+
let paymentPayload = null;
|
|
409
24
|
try {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const headers = createPaymentRequiredResponse(
|
|
416
|
-
version,
|
|
417
|
-
requirements,
|
|
418
|
-
resourceUrl,
|
|
419
|
-
`Payment verification failed: ${verifyResult.error}`
|
|
420
|
-
);
|
|
421
|
-
c.status(402);
|
|
422
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
423
|
-
c.header(key, value);
|
|
424
|
-
}
|
|
425
|
-
return c.json({
|
|
426
|
-
error: `Payment verification failed: ${verifyResult.error}`,
|
|
427
|
-
x402Version: version
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
const payerAddress = extractPayerAddress(payload);
|
|
432
|
-
const responseHeaders = getHeadersForVersion(version);
|
|
433
|
-
c.set("payment", { payload, payerAddress, version, verified: true });
|
|
434
|
-
c.header("X-Payment-Verified", "true");
|
|
435
|
-
c.header("X-Payer-Address", payerAddress);
|
|
436
|
-
const settlementResponse = {
|
|
437
|
-
success: true,
|
|
438
|
-
transaction: "",
|
|
439
|
-
network: version === 1 ? "base-sepolia" : "eip155:84532",
|
|
440
|
-
payer: payerAddress
|
|
441
|
-
};
|
|
442
|
-
const settlementHeaders = createSettlementHeaders(settlementResponse, version, void 0, payerAddress);
|
|
443
|
-
for (const [key, value] of Object.entries(settlementHeaders)) {
|
|
444
|
-
c.header(key, value);
|
|
445
|
-
}
|
|
446
|
-
await next();
|
|
447
|
-
} catch (error) {
|
|
25
|
+
console.log("[x402 Debug] Raw payment header:", paymentHeader);
|
|
26
|
+
paymentPayload = decodePaymentV2(paymentHeader);
|
|
27
|
+
console.log("[x402 Debug] Decoded payload:", JSON.stringify(paymentPayload, null, 2));
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error("[x402 Debug] Decode error:", e);
|
|
448
30
|
c.status(400);
|
|
449
|
-
return c.json({
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
31
|
+
return c.json({ error: "Invalid payment payload", details: String(e) });
|
|
32
|
+
}
|
|
33
|
+
if (!paymentPayload) {
|
|
34
|
+
c.status(400);
|
|
35
|
+
return c.json({ error: "Invalid payment payload" });
|
|
453
36
|
}
|
|
37
|
+
const payerAddress = paymentPayload.payload.authorization.from;
|
|
38
|
+
const settlement = {
|
|
39
|
+
success: true,
|
|
40
|
+
transaction: "",
|
|
41
|
+
network
|
|
42
|
+
};
|
|
43
|
+
const settlementHeaders = createSettlementHeaders(settlement);
|
|
44
|
+
for (const [key, value] of Object.entries(settlementHeaders)) {
|
|
45
|
+
c.header(key, value);
|
|
46
|
+
}
|
|
47
|
+
c.set("payment", {
|
|
48
|
+
payload: paymentPayload,
|
|
49
|
+
payerAddress,
|
|
50
|
+
verified: !skipVerification
|
|
51
|
+
});
|
|
52
|
+
return next();
|
|
454
53
|
};
|
|
455
54
|
};
|
|
456
55
|
export {
|
|
457
|
-
|
|
56
|
+
paymentMiddleware
|
|
458
57
|
};
|