@armory-sh/middleware 0.2.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 +31 -0
- package/dist/index.d.ts +163 -0
- package/dist/index.js +403 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sawyer Cutler
|
|
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,31 @@
|
|
|
1
|
+
# @armory-sh/middleware
|
|
2
|
+
|
|
3
|
+
Payment verification middleware for Express, Hono, Bun, Elysia.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @armory-sh/middleware
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Express
|
|
15
|
+
import { paymentMiddleware } from '@armory-sh/middleware'
|
|
16
|
+
|
|
17
|
+
app.use(paymentMiddleware({
|
|
18
|
+
requirements: {
|
|
19
|
+
to: '0x...',
|
|
20
|
+
amount: 1000000n,
|
|
21
|
+
expiry: Date.now() + 3600,
|
|
22
|
+
chainId: 'eip155:8453',
|
|
23
|
+
assetId: 'eip155:8453/erc20:0x...'
|
|
24
|
+
},
|
|
25
|
+
facilitatorUrl: 'https://facilitator.example.com'
|
|
26
|
+
}))
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
MIT License | Sawyer Cutler 2026 | Provided "AS IS" without warranty
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Address, CAIP2ChainId, CAIPAssetId, PaymentRequirements, SettlementResponse, PaymentPayload, ResolvedPaymentConfig, AcceptPaymentOptions, NetworkId, TokenId, ValidationError } from '@armory-sh/base';
|
|
2
|
+
export { AcceptPaymentOptions, NetworkId, FacilitatorConfig as SimpleFacilitatorConfig, TokenId } from '@armory-sh/base';
|
|
3
|
+
import { VerifyPaymentOptions } from '@armory-sh/facilitator';
|
|
4
|
+
|
|
5
|
+
interface FacilitatorConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
createHeaders?: () => Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
type SettlementMode = "verify" | "settle" | "async";
|
|
10
|
+
type PayToAddress = Address | CAIP2ChainId | CAIPAssetId;
|
|
11
|
+
interface MiddlewareConfig {
|
|
12
|
+
payTo: PayToAddress;
|
|
13
|
+
network: string | number;
|
|
14
|
+
amount: string;
|
|
15
|
+
facilitator?: FacilitatorConfig;
|
|
16
|
+
settlementMode?: SettlementMode;
|
|
17
|
+
}
|
|
18
|
+
interface HttpRequest {
|
|
19
|
+
headers: Record<string, string | string[] | undefined>;
|
|
20
|
+
body?: unknown;
|
|
21
|
+
method?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
}
|
|
24
|
+
interface HttpResponse {
|
|
25
|
+
status: number;
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
body?: unknown;
|
|
28
|
+
}
|
|
29
|
+
interface FacilitatorVerifyResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
payerAddress?: string;
|
|
32
|
+
balance?: string;
|
|
33
|
+
requiredAmount?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
interface FacilitatorSettleResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
txHash?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare const createPaymentRequirements: (config: MiddlewareConfig, version?: 1 | 2) => PaymentRequirements;
|
|
43
|
+
declare const verifyWithFacilitator: (request: HttpRequest, facilitator: FacilitatorConfig) => Promise<FacilitatorVerifyResult>;
|
|
44
|
+
declare const settleWithFacilitator: (request: HttpRequest, facilitator: FacilitatorConfig) => Promise<FacilitatorSettleResult>;
|
|
45
|
+
declare const createPaymentRequiredHeaders: (requirements: PaymentRequirements, version: 1 | 2) => Record<string, string>;
|
|
46
|
+
declare const createSettlementHeaders: (response: SettlementResponse, version: 1 | 2) => Record<string, string>;
|
|
47
|
+
|
|
48
|
+
type PaymentVersion = 1 | 2;
|
|
49
|
+
interface PaymentVerificationResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
payload?: PaymentPayload;
|
|
52
|
+
version?: PaymentVersion;
|
|
53
|
+
payerAddress?: string;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
interface PaymentHeaders {
|
|
57
|
+
payment: string;
|
|
58
|
+
required: string;
|
|
59
|
+
response: string;
|
|
60
|
+
}
|
|
61
|
+
declare const getHeadersForVersion: (version: PaymentVersion) => PaymentHeaders;
|
|
62
|
+
declare const getRequirementsVersion: (requirements: PaymentRequirements) => PaymentVersion;
|
|
63
|
+
declare const encodeRequirements: (requirements: PaymentRequirements) => string;
|
|
64
|
+
declare const decodePayload: (headerValue: string) => {
|
|
65
|
+
payload: PaymentPayload;
|
|
66
|
+
version: PaymentVersion;
|
|
67
|
+
};
|
|
68
|
+
declare const verifyPaymentWithRetry: (payload: PaymentPayload, requirements: PaymentRequirements, facilitatorUrl?: string, verifyOptions?: VerifyPaymentOptions) => Promise<PaymentVerificationResult>;
|
|
69
|
+
declare const extractPayerAddress: (payload: PaymentPayload) => string;
|
|
70
|
+
declare const createResponseHeaders: (payerAddress: string, version: PaymentVersion) => Record<string, string>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Simple one-line middleware API for Armory merchants
|
|
74
|
+
* Focus on DX/UX - "everything just magically works"
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Simple configuration for acceptPaymentsViaArmory middleware
|
|
79
|
+
*/
|
|
80
|
+
interface SimpleMiddlewareConfig {
|
|
81
|
+
/** Address to receive payments */
|
|
82
|
+
payTo: Address;
|
|
83
|
+
/** Amount to charge (default: "1.0") */
|
|
84
|
+
amount?: string;
|
|
85
|
+
/** Payment acceptance options */
|
|
86
|
+
accept?: AcceptPaymentOptions;
|
|
87
|
+
/** Fallback facilitator URL (if not using accept.facilitators) */
|
|
88
|
+
facilitatorUrl?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolved middleware configuration with all options validated
|
|
92
|
+
*/
|
|
93
|
+
interface ResolvedMiddlewareConfig {
|
|
94
|
+
/** All valid payment configurations (network/token combinations) */
|
|
95
|
+
configs: ResolvedPaymentConfig[];
|
|
96
|
+
/** Protocol version */
|
|
97
|
+
version: 1 | 2 | "auto";
|
|
98
|
+
/** Facilitator configs */
|
|
99
|
+
facilitators: FacilitatorConfig[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve simple middleware config to full config
|
|
103
|
+
*/
|
|
104
|
+
declare const resolveMiddlewareConfig: (config: SimpleMiddlewareConfig) => ResolvedMiddlewareConfig | ValidationError;
|
|
105
|
+
/**
|
|
106
|
+
* Get payment requirements for a specific network/token combination
|
|
107
|
+
*/
|
|
108
|
+
declare const getRequirements: (config: ResolvedMiddlewareConfig, network: NetworkId, token: TokenId) => PaymentRequirements | ValidationError;
|
|
109
|
+
/**
|
|
110
|
+
* Get the primary/default middleware config for legacy middlewares
|
|
111
|
+
*/
|
|
112
|
+
declare const getPrimaryConfig: (resolved: ResolvedMiddlewareConfig) => MiddlewareConfig;
|
|
113
|
+
/**
|
|
114
|
+
* Get all supported networks from config
|
|
115
|
+
*/
|
|
116
|
+
declare const getSupportedNetworks: (config: ResolvedMiddlewareConfig) => string[];
|
|
117
|
+
/**
|
|
118
|
+
* Get all supported tokens from config
|
|
119
|
+
*/
|
|
120
|
+
declare const getSupportedTokens: (config: ResolvedMiddlewareConfig) => string[];
|
|
121
|
+
/**
|
|
122
|
+
* Check if a network/token combination is supported
|
|
123
|
+
*/
|
|
124
|
+
declare const isSupported: (config: ResolvedMiddlewareConfig, network: NetworkId, token: TokenId) => boolean;
|
|
125
|
+
|
|
126
|
+
type BunMiddleware = (request: Request) => Promise<Response | null>;
|
|
127
|
+
interface BunMiddlewareConfig extends MiddlewareConfig {
|
|
128
|
+
defaultVersion?: 1 | 2;
|
|
129
|
+
waitForSettlement?: boolean;
|
|
130
|
+
}
|
|
131
|
+
declare const createBunMiddleware: (config: BunMiddlewareConfig) => BunMiddleware;
|
|
132
|
+
/**
|
|
133
|
+
* One-line middleware setup for accepting Armory payments
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // Simple usage - default USDC on Base
|
|
138
|
+
* app.use(acceptPaymentsViaArmory({
|
|
139
|
+
* payTo: "0xMerchantAddress...",
|
|
140
|
+
* amount: "1.0"
|
|
141
|
+
* }));
|
|
142
|
+
*
|
|
143
|
+
* // Multi-network, multi-token support
|
|
144
|
+
* app.use(acceptPaymentsViaArmory({
|
|
145
|
+
* payTo: "0xMerchantAddress...",
|
|
146
|
+
* amount: "1.0",
|
|
147
|
+
* accept: {
|
|
148
|
+
* networks: ["base", "skale-base", "ethereum"],
|
|
149
|
+
* tokens: ["usdc", "eurc"],
|
|
150
|
+
* facilitators: [
|
|
151
|
+
* { url: "https://facilitator1.com" },
|
|
152
|
+
* { url: "https://facilitator2.com" }
|
|
153
|
+
* ]
|
|
154
|
+
* }
|
|
155
|
+
* }));
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
declare const acceptPaymentsViaArmory: (config: SimpleMiddlewareConfig & {
|
|
159
|
+
defaultVersion?: 1 | 2;
|
|
160
|
+
waitForSettlement?: boolean;
|
|
161
|
+
}) => BunMiddleware;
|
|
162
|
+
|
|
163
|
+
export { type BunMiddleware, type BunMiddlewareConfig, type FacilitatorConfig, type FacilitatorSettleResult, type FacilitatorVerifyResult, type HttpRequest, type HttpResponse, type MiddlewareConfig, type PayToAddress, type PaymentVerificationResult, type PaymentVersion, type ResolvedMiddlewareConfig, type SettlementMode, type SimpleMiddlewareConfig, acceptPaymentsViaArmory, createBunMiddleware, createPaymentRequiredHeaders, createPaymentRequirements, createResponseHeaders, createSettlementHeaders, decodePayload, encodeRequirements, extractPayerAddress, getHeadersForVersion, getPrimaryConfig, getRequirements, getRequirementsVersion, getSupportedNetworks, getSupportedTokens, isSupported, resolveMiddlewareConfig, settleWithFacilitator, verifyPaymentWithRetry, verifyWithFacilitator };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
import { getNetworkConfig, getNetworkByChainId } from "@armory-sh/base";
|
|
3
|
+
var getNetworkName = (network) => {
|
|
4
|
+
if (typeof network === "string") return network;
|
|
5
|
+
const net = getNetworkByChainId(network);
|
|
6
|
+
if (!net) throw new Error(`No network found for chainId: ${network}`);
|
|
7
|
+
return net.name.toLowerCase().replace(" mainnet", "").replace(" sepolia", "-sepolia");
|
|
8
|
+
};
|
|
9
|
+
var createV1Requirements = (config, expiry) => {
|
|
10
|
+
const networkName = getNetworkName(config.network);
|
|
11
|
+
const network = getNetworkConfig(networkName);
|
|
12
|
+
if (!network) throw new Error(`Unsupported network: ${networkName}`);
|
|
13
|
+
return {
|
|
14
|
+
amount: config.amount,
|
|
15
|
+
network: networkName,
|
|
16
|
+
contractAddress: network.usdcAddress,
|
|
17
|
+
payTo: config.payTo,
|
|
18
|
+
expiry
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
var createV2Requirements = (config, expiry) => {
|
|
22
|
+
const networkName = getNetworkName(config.network);
|
|
23
|
+
const network = getNetworkConfig(networkName);
|
|
24
|
+
if (!network) throw new Error(`Unsupported network: ${networkName}`);
|
|
25
|
+
return {
|
|
26
|
+
amount: config.amount,
|
|
27
|
+
to: config.payTo,
|
|
28
|
+
chainId: network.caip2Id,
|
|
29
|
+
assetId: network.caipAssetId,
|
|
30
|
+
nonce: `${Date.now()}-${crypto.randomUUID()}`,
|
|
31
|
+
expiry
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
var createPaymentRequirements = (config, version = 1) => {
|
|
35
|
+
const expiry = Math.floor(Date.now() / 1e3) + 3600;
|
|
36
|
+
return version === 1 ? createV1Requirements(config, expiry) : createV2Requirements(config, expiry);
|
|
37
|
+
};
|
|
38
|
+
var findHeaderValue = (headers, name) => {
|
|
39
|
+
const value = headers[name];
|
|
40
|
+
if (typeof value === "string") return value;
|
|
41
|
+
if (Array.isArray(value) && value.length > 0) return value[0];
|
|
42
|
+
const lowerName = name.toLowerCase();
|
|
43
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
44
|
+
if (key.toLowerCase() === lowerName) {
|
|
45
|
+
if (typeof val === "string") return val;
|
|
46
|
+
if (Array.isArray(val) && val.length > 0) return val[0];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
};
|
|
51
|
+
var parseHeader = (header) => {
|
|
52
|
+
try {
|
|
53
|
+
if (header.startsWith("{")) return JSON.parse(header);
|
|
54
|
+
return JSON.parse(atob(header));
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var extractPaymentPayload = (request) => {
|
|
60
|
+
const v1Header = findHeaderValue(request.headers, "X-PAYMENT");
|
|
61
|
+
if (v1Header) return parseHeader(v1Header);
|
|
62
|
+
const v2Header = findHeaderValue(request.headers, "PAYMENT-SIGNATURE");
|
|
63
|
+
if (v2Header) return parseHeader(v2Header);
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
var postFacilitator = async (url, headers, body) => {
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
70
|
+
body: JSON.stringify(body)
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const error = await response.text();
|
|
74
|
+
throw new Error(error || `Request failed: ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
return response.json();
|
|
77
|
+
};
|
|
78
|
+
var verifyWithFacilitator = async (request, facilitator) => {
|
|
79
|
+
const payload = extractPaymentPayload(request);
|
|
80
|
+
if (!payload) {
|
|
81
|
+
return { success: false, error: "No payment payload found in request headers" };
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const url = new URL("/verify", facilitator.url);
|
|
85
|
+
const headers = facilitator.createHeaders?.() ?? {};
|
|
86
|
+
const data = await postFacilitator(url.toString(), headers, { payload, headers: request.headers });
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
payerAddress: data.payerAddress,
|
|
90
|
+
balance: data.balance,
|
|
91
|
+
requiredAmount: data.requiredAmount
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: error instanceof Error ? error.message : "Unknown verification error"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var settleWithFacilitator = async (request, facilitator) => {
|
|
101
|
+
const payload = extractPaymentPayload(request);
|
|
102
|
+
if (!payload) {
|
|
103
|
+
return { success: false, error: "No payment payload found in request headers" };
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL("/settle", facilitator.url);
|
|
107
|
+
const headers = facilitator.createHeaders?.() ?? {};
|
|
108
|
+
const data = await postFacilitator(url.toString(), headers, { payload, headers: request.headers });
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
txHash: data.txHash
|
|
112
|
+
};
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: error instanceof Error ? error.message : "Unknown settlement error"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var encode = (data) => btoa(JSON.stringify(data));
|
|
121
|
+
var createPaymentRequiredHeaders = (requirements, version) => {
|
|
122
|
+
if (version === 1) {
|
|
123
|
+
return { "X-PAYMENT-REQUIRED": encode(requirements) };
|
|
124
|
+
}
|
|
125
|
+
return { "PAYMENT-REQUIRED": encode(requirements) };
|
|
126
|
+
};
|
|
127
|
+
var createSettlementHeaders = (response, version) => {
|
|
128
|
+
if (version === 1) {
|
|
129
|
+
return { "X-PAYMENT-RESPONSE": encode(response) };
|
|
130
|
+
}
|
|
131
|
+
return { "PAYMENT-RESPONSE": encode(response) };
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/payment-utils.ts
|
|
135
|
+
import { decodePayment, detectPaymentVersion, isPaymentV1 } from "@armory-sh/base";
|
|
136
|
+
import { verifyPayment } from "@armory-sh/facilitator";
|
|
137
|
+
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" };
|
|
138
|
+
var getRequirementsVersion = (requirements) => "contractAddress" in requirements && "network" in requirements ? 1 : 2;
|
|
139
|
+
var encodeRequirements = (requirements) => JSON.stringify(requirements);
|
|
140
|
+
var decodePayload = (headerValue) => {
|
|
141
|
+
const headers = new Headers();
|
|
142
|
+
headers.set(headerValue.startsWith("{") ? "PAYMENT-SIGNATURE" : "X-PAYMENT", headerValue);
|
|
143
|
+
const payload = decodePayment(headers);
|
|
144
|
+
const version = detectPaymentVersion(headers) ?? 2;
|
|
145
|
+
return { payload, version };
|
|
146
|
+
};
|
|
147
|
+
var verifyWithFacilitator2 = async (facilitatorUrl, payload, requirements, verifyOptions) => {
|
|
148
|
+
try {
|
|
149
|
+
const response = await fetch(`${facilitatorUrl}/verify`, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: { "Content-Type": "application/json" },
|
|
152
|
+
body: JSON.stringify({ payload, requirements, options: verifyOptions })
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const error = await response.json();
|
|
156
|
+
return { success: false, error: JSON.stringify(error) };
|
|
157
|
+
}
|
|
158
|
+
const result = await response.json();
|
|
159
|
+
if (!result.success) {
|
|
160
|
+
return { success: false, error: result.error ?? "Verification failed" };
|
|
161
|
+
}
|
|
162
|
+
return { success: true, payerAddress: result.payerAddress ?? "" };
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: error instanceof Error ? error.message : "Unknown facilitator error"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var verifyLocally = async (payload, requirements, verifyOptions) => {
|
|
171
|
+
const result = await verifyPayment(payload, requirements, verifyOptions);
|
|
172
|
+
if (!result.success) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: JSON.stringify({
|
|
176
|
+
error: "Payment verification failed",
|
|
177
|
+
reason: result.error.name,
|
|
178
|
+
message: result.error.message
|
|
179
|
+
})
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return { success: true, payerAddress: result.payerAddress };
|
|
183
|
+
};
|
|
184
|
+
var verifyPaymentWithRetry = async (payload, requirements, facilitatorUrl, verifyOptions) => facilitatorUrl ? verifyWithFacilitator2(facilitatorUrl, payload, requirements, verifyOptions) : verifyLocally(payload, requirements, verifyOptions);
|
|
185
|
+
var extractPayerAddress = (payload) => isPaymentV1(payload) ? payload.from : payload.from;
|
|
186
|
+
var createResponseHeaders = (payerAddress, version) => ({
|
|
187
|
+
[getHeadersForVersion(version).response]: JSON.stringify({
|
|
188
|
+
status: "verified",
|
|
189
|
+
payerAddress,
|
|
190
|
+
version
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// src/bun.ts
|
|
195
|
+
import { decodePayment as decodePayment2, isV1, isV2 } from "@armory-sh/base";
|
|
196
|
+
|
|
197
|
+
// src/simple.ts
|
|
198
|
+
import {
|
|
199
|
+
resolveNetwork,
|
|
200
|
+
resolveToken,
|
|
201
|
+
validateAcceptConfig,
|
|
202
|
+
isValidationError
|
|
203
|
+
} from "@armory-sh/base";
|
|
204
|
+
var resolveMiddlewareConfig = (config) => {
|
|
205
|
+
const { payTo, amount = "1.0", accept = {}, facilitatorUrl } = config;
|
|
206
|
+
const acceptOptions = facilitatorUrl ? {
|
|
207
|
+
...accept,
|
|
208
|
+
facilitators: accept.facilitators ? [...Array.isArray(accept.facilitators) ? accept.facilitators : [accept.facilitators], { url: facilitatorUrl }] : { url: facilitatorUrl }
|
|
209
|
+
} : accept;
|
|
210
|
+
const result = validateAcceptConfig(acceptOptions, payTo, amount);
|
|
211
|
+
if (!result.success) {
|
|
212
|
+
return result.error;
|
|
213
|
+
}
|
|
214
|
+
const facilitatorConfigs = result.config[0]?.facilitators.map((f) => ({
|
|
215
|
+
url: f.url,
|
|
216
|
+
createHeaders: f.input.headers
|
|
217
|
+
})) ?? [];
|
|
218
|
+
return {
|
|
219
|
+
configs: result.config,
|
|
220
|
+
version: acceptOptions.version ?? "auto",
|
|
221
|
+
facilitators: facilitatorConfigs
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
var getRequirements = (config, network, token) => {
|
|
225
|
+
const resolvedNetwork = resolveNetwork(network);
|
|
226
|
+
if (isValidationError(resolvedNetwork)) {
|
|
227
|
+
return resolvedNetwork;
|
|
228
|
+
}
|
|
229
|
+
const resolvedToken = resolveToken(token, resolvedNetwork);
|
|
230
|
+
if (isValidationError(resolvedToken)) {
|
|
231
|
+
return resolvedToken;
|
|
232
|
+
}
|
|
233
|
+
const matchingConfig = config.configs.find(
|
|
234
|
+
(c) => c.network.config.chainId === resolvedNetwork.config.chainId && c.token.config.contractAddress.toLowerCase() === resolvedToken.config.contractAddress.toLowerCase()
|
|
235
|
+
);
|
|
236
|
+
if (!matchingConfig) {
|
|
237
|
+
return {
|
|
238
|
+
code: "TOKEN_NOT_ON_NETWORK",
|
|
239
|
+
message: `No configuration found for network "${network}" with token "${token}"`
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const version = config.version === "auto" ? 2 : config.version;
|
|
243
|
+
return createPaymentRequirements(
|
|
244
|
+
{
|
|
245
|
+
payTo: matchingConfig.payTo,
|
|
246
|
+
network: matchingConfig.network.config.name,
|
|
247
|
+
amount: matchingConfig.amount,
|
|
248
|
+
facilitator: config.facilitators[0]
|
|
249
|
+
},
|
|
250
|
+
version
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
var getPrimaryConfig = (resolved) => {
|
|
254
|
+
const primary = resolved.configs[0];
|
|
255
|
+
if (!primary) {
|
|
256
|
+
throw new Error("No valid payment configurations found");
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
payTo: primary.payTo,
|
|
260
|
+
network: primary.network.config.name,
|
|
261
|
+
amount: primary.amount,
|
|
262
|
+
facilitator: resolved.facilitators[0],
|
|
263
|
+
settlementMode: "verify"
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
var getSupportedNetworks = (config) => {
|
|
267
|
+
const networks = new Set(config.configs.map((c) => c.network.config.name));
|
|
268
|
+
return Array.from(networks);
|
|
269
|
+
};
|
|
270
|
+
var getSupportedTokens = (config) => {
|
|
271
|
+
const tokens = new Set(config.configs.map((c) => c.token.config.symbol));
|
|
272
|
+
return Array.from(tokens);
|
|
273
|
+
};
|
|
274
|
+
var isSupported = (config, network, token) => {
|
|
275
|
+
const resolvedNetwork = resolveNetwork(network);
|
|
276
|
+
if (isValidationError(resolvedNetwork)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const resolvedToken = resolveToken(token, resolvedNetwork);
|
|
280
|
+
if (isValidationError(resolvedToken)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return config.configs.some(
|
|
284
|
+
(c) => c.network.config.chainId === resolvedNetwork.config.chainId && c.token.config.contractAddress.toLowerCase() === resolvedToken.config.contractAddress.toLowerCase()
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/bun.ts
|
|
289
|
+
var parsePaymentHeader = async (request) => {
|
|
290
|
+
const v1 = request.headers.get("X-PAYMENT");
|
|
291
|
+
if (v1) {
|
|
292
|
+
const headers = new Headers({ "X-PAYMENT": v1 });
|
|
293
|
+
const payload = decodePayment2(headers);
|
|
294
|
+
return isV1(payload) ? { payload, version: 1, payerAddress: payload.from } : null;
|
|
295
|
+
}
|
|
296
|
+
const v2 = request.headers.get("PAYMENT-SIGNATURE");
|
|
297
|
+
if (v2) {
|
|
298
|
+
const headers = new Headers({ "PAYMENT-SIGNATURE": v2 });
|
|
299
|
+
const payload = decodePayment2(headers);
|
|
300
|
+
return isV2(payload) ? { payload, version: 2, payerAddress: payload.from } : null;
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
};
|
|
304
|
+
var toHttpRequest = (request) => {
|
|
305
|
+
const headers = {};
|
|
306
|
+
request.headers.forEach((v, k) => {
|
|
307
|
+
headers[k] = v;
|
|
308
|
+
});
|
|
309
|
+
return { headers, method: request.method, url: request.url };
|
|
310
|
+
};
|
|
311
|
+
var errorResponse = (error, status, headers) => new Response(JSON.stringify({ error }), {
|
|
312
|
+
status,
|
|
313
|
+
headers: { "Content-Type": "application/json", ...headers }
|
|
314
|
+
});
|
|
315
|
+
var createSettlementResponse = (success, txHash) => ({
|
|
316
|
+
status: success ? "success" : "failed",
|
|
317
|
+
txHash,
|
|
318
|
+
txId: txHash,
|
|
319
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
320
|
+
});
|
|
321
|
+
var successResponse = (payerAddress, version, settlement) => {
|
|
322
|
+
const isSuccess = settlement ? "success" in settlement ? settlement.success : settlement.status === "success" : void 0;
|
|
323
|
+
const txHash = settlement?.txHash;
|
|
324
|
+
return new Response(
|
|
325
|
+
JSON.stringify({
|
|
326
|
+
verified: true,
|
|
327
|
+
payerAddress,
|
|
328
|
+
version,
|
|
329
|
+
settlement: settlement ? { success: isSuccess, txHash } : void 0
|
|
330
|
+
}),
|
|
331
|
+
{
|
|
332
|
+
status: 200,
|
|
333
|
+
headers: {
|
|
334
|
+
"Content-Type": "application/json",
|
|
335
|
+
"X-Payment-Verified": "true",
|
|
336
|
+
"X-Payer-Address": payerAddress,
|
|
337
|
+
...settlement ? createSettlementHeaders(settlement, version) : {}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
var createBunMiddleware = (config) => {
|
|
343
|
+
const { facilitator, settlementMode = "verify", defaultVersion = 2, waitForSettlement = false } = config;
|
|
344
|
+
const requirementsV1 = createPaymentRequirements(config, 1);
|
|
345
|
+
const requirementsV2 = createPaymentRequirements(config, 2);
|
|
346
|
+
return async (request) => {
|
|
347
|
+
const paymentResult = await parsePaymentHeader(request);
|
|
348
|
+
if (!paymentResult) {
|
|
349
|
+
const requirements = defaultVersion === 1 ? requirementsV1 : requirementsV2;
|
|
350
|
+
return errorResponse("Payment required", 402, createPaymentRequiredHeaders(requirements, defaultVersion));
|
|
351
|
+
}
|
|
352
|
+
const { version, payerAddress } = paymentResult;
|
|
353
|
+
if (facilitator) {
|
|
354
|
+
const verifyResult = await verifyWithFacilitator(toHttpRequest(request), facilitator);
|
|
355
|
+
if (!verifyResult.success) {
|
|
356
|
+
const requirements = version === 1 ? requirementsV1 : requirementsV2;
|
|
357
|
+
return errorResponse(`Payment verification failed: ${verifyResult.error}`, 402, createPaymentRequiredHeaders(requirements, version));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (settlementMode === "settle" && facilitator) {
|
|
361
|
+
const settle = async () => {
|
|
362
|
+
const result = await settleWithFacilitator(toHttpRequest(request), facilitator);
|
|
363
|
+
return result.success ? successResponse(payerAddress, version, createSettlementResponse(true, result.txHash)) : errorResponse(result.error ?? "Settlement failed", 400);
|
|
364
|
+
};
|
|
365
|
+
return waitForSettlement ? await settle() : (settle().catch(console.error), successResponse(payerAddress, version));
|
|
366
|
+
}
|
|
367
|
+
return successResponse(payerAddress, version);
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
var acceptPaymentsViaArmory = (config) => {
|
|
371
|
+
const resolved = resolveMiddlewareConfig(config);
|
|
372
|
+
if ("code" in resolved) {
|
|
373
|
+
throw new Error(`Invalid payment configuration: ${resolved.message}`);
|
|
374
|
+
}
|
|
375
|
+
const primaryConfig = getPrimaryConfig(resolved);
|
|
376
|
+
return createBunMiddleware({
|
|
377
|
+
...primaryConfig,
|
|
378
|
+
defaultVersion: config.defaultVersion ?? 2,
|
|
379
|
+
waitForSettlement: config.waitForSettlement ?? false
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
export {
|
|
383
|
+
acceptPaymentsViaArmory,
|
|
384
|
+
createBunMiddleware,
|
|
385
|
+
createPaymentRequiredHeaders,
|
|
386
|
+
createPaymentRequirements,
|
|
387
|
+
createResponseHeaders,
|
|
388
|
+
createSettlementHeaders,
|
|
389
|
+
decodePayload,
|
|
390
|
+
encodeRequirements,
|
|
391
|
+
extractPayerAddress,
|
|
392
|
+
getHeadersForVersion,
|
|
393
|
+
getPrimaryConfig,
|
|
394
|
+
getRequirements,
|
|
395
|
+
getRequirementsVersion,
|
|
396
|
+
getSupportedNetworks,
|
|
397
|
+
getSupportedTokens,
|
|
398
|
+
isSupported,
|
|
399
|
+
resolveMiddlewareConfig,
|
|
400
|
+
settleWithFacilitator,
|
|
401
|
+
verifyPaymentWithRetry,
|
|
402
|
+
verifyWithFacilitator
|
|
403
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@armory-sh/middleware",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"bun": "./src/index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./dist/*": "./dist/*.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/thegreataxios/armory.git",
|
|
27
|
+
"directory": "packages/middleware"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@armory-sh/base": "workspace:*",
|
|
31
|
+
"@armory-sh/facilitator": "workspace:*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
|
+
"bun-types": "latest"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"test": "bun test"
|
|
40
|
+
}
|
|
41
|
+
}
|