@armory-sh/base 0.2.18 → 0.2.20

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 CHANGED
@@ -14,7 +14,10 @@ export { ERC20_ABI } from "./abi/erc20";
14
14
  export type { TypedDataDomain, EIP712Domain, TransferWithAuthorization, TypedDataField, EIP712Types, } from "./eip712";
15
15
  export { EIP712_TYPES, USDC_DOMAIN, createEIP712Domain, createTransferWithAuthorization, validateTransferWithAuthorization, } from "./eip712";
16
16
  export { resolveNetwork, resolveToken, resolveFacilitator, checkFacilitatorSupport, validatePaymentConfig, validateAcceptConfig, getAvailableNetworks, getAvailableTokens, isValidationError, isResolvedNetwork, isResolvedToken, createError, normalizeNetworkName, } from "./validation";
17
+ export type { RoutePattern, RouteMatcher, RouteConfig, ParsedPattern, RouteInputConfig, RouteValidationError, } from "./utils/routes";
18
+ export { parseRoutePattern, matchRoute, findMatchingRoute, validateRouteConfig, } from "./utils/routes";
17
19
  export { encodePaymentV2, decodePaymentV2, encodeSettlementV2, decodeSettlementV2, isPaymentV2, isSettlementV2, } from "./encoding";
18
20
  export type { NetworkId, TokenId, FacilitatorConfig, FacilitatorVerifyResult, FacilitatorSettleResult, SettlementMode, PayToAddress, AcceptPaymentOptions, PricingConfig, PaymentResult, PaymentError, PaymentErrorCode, ArmoryPaymentResult, ResolvedNetwork, ResolvedToken, ResolvedFacilitator, ResolvedPaymentConfig, ValidationError, } from "./types/api";
21
+ export type { PaymentRequiredContext, PaymentPayloadContext, HookResult, OnPaymentRequiredHook, BeforePaymentHook, ExtensionHook, HookConfig, HookRegistry, } from "./types/hooks";
19
22
  export { createX402V2Payload, createLegacyV2Payload, INVALID_PAYLOADS, TEST_PAYER_ADDRESS, TEST_PAY_TO_ADDRESS, TEST_CONTRACT_ADDRESS, TEST_PRIVATE_KEY, } from "./fixtures/payloads";
20
23
  export { DEFAULT_PAYMENT_CONFIG, type TestPaymentConfig } from "./fixtures/config";
package/dist/index.js CHANGED
@@ -1109,13 +1109,186 @@ var isResolvedToken = (value) => {
1109
1109
  return typeof value === "object" && value !== null && "config" in value && "caipAsset" in value;
1110
1110
  };
1111
1111
 
1112
+ // src/utils/routes.ts
1113
+ var PRIORITY_EXACT = 3;
1114
+ var PRIORITY_PARAMETRIZED = 2;
1115
+ var PRIORITY_WILDCARD = 1;
1116
+ function parseRoutePattern(pattern) {
1117
+ const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
1118
+ const segments = normalizedPattern.split("/").filter(Boolean);
1119
+ let isWildcard = false;
1120
+ let isParametrized = false;
1121
+ const paramNames = [];
1122
+ const seenParamNames = /* @__PURE__ */ new Set();
1123
+ const recordParamName = (name) => {
1124
+ if (!name) {
1125
+ return;
1126
+ }
1127
+ if (seenParamNames.has(name)) {
1128
+ return;
1129
+ }
1130
+ seenParamNames.add(name);
1131
+ paramNames.push(name);
1132
+ };
1133
+ for (const segment of segments) {
1134
+ if (segment === "*") {
1135
+ isWildcard = true;
1136
+ continue;
1137
+ }
1138
+ const hasWildcardToken = segment.includes("*");
1139
+ if (segment.startsWith(":")) {
1140
+ isParametrized = true;
1141
+ const paramName = segment.replace(/\*+$/, "").slice(1);
1142
+ recordParamName(paramName);
1143
+ if (hasWildcardToken) {
1144
+ isWildcard = true;
1145
+ }
1146
+ continue;
1147
+ }
1148
+ if (hasWildcardToken) {
1149
+ const parts = segment.split("*");
1150
+ for (const part of parts) {
1151
+ if (part.startsWith(":")) {
1152
+ isParametrized = true;
1153
+ recordParamName(part.slice(1));
1154
+ }
1155
+ }
1156
+ isWildcard = true;
1157
+ }
1158
+ }
1159
+ let priority = PRIORITY_WILDCARD;
1160
+ if (!isWildcard && !isParametrized) {
1161
+ priority = PRIORITY_EXACT;
1162
+ } else if (isParametrized && !isWildcard) {
1163
+ priority = PRIORITY_PARAMETRIZED;
1164
+ } else if (isParametrized && isWildcard) {
1165
+ priority = PRIORITY_PARAMETRIZED;
1166
+ }
1167
+ return { segments, isWildcard, isParametrized, paramNames, priority };
1168
+ }
1169
+ function matchSegment(patternSegment, pathSegment) {
1170
+ if (patternSegment === "*") {
1171
+ return true;
1172
+ }
1173
+ if (patternSegment.startsWith(":")) {
1174
+ return true;
1175
+ }
1176
+ if (patternSegment.includes("*")) {
1177
+ const regex = new RegExp(
1178
+ "^" + patternSegment.replace(/\*/g, ".*").replace(/:/g, "") + "$"
1179
+ );
1180
+ return regex.test(pathSegment);
1181
+ }
1182
+ return patternSegment === pathSegment;
1183
+ }
1184
+ function matchWildcardPattern(patternSegments, pathSegments) {
1185
+ const requiredSegments = patternSegments.filter((s) => s !== "*");
1186
+ if (requiredSegments.length > pathSegments.length) {
1187
+ return false;
1188
+ }
1189
+ for (let i = 0; i < requiredSegments.length; i++) {
1190
+ const patternIndex = patternSegments.indexOf(requiredSegments[i]);
1191
+ if (pathSegments[patternIndex] !== requiredSegments[i].replace(/^\:/, "")) {
1192
+ if (!requiredSegments[i].startsWith(":") && requiredSegments[i] !== "*") {
1193
+ return false;
1194
+ }
1195
+ }
1196
+ }
1197
+ return true;
1198
+ }
1199
+ function matchRoute(pattern, path) {
1200
+ const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
1201
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1202
+ if (normalizedPattern === normalizedPath) {
1203
+ return true;
1204
+ }
1205
+ const parsed = parseRoutePattern(normalizedPattern);
1206
+ const patternSegments = parsed.segments;
1207
+ const pathSegments = normalizedPath.split("/").filter(Boolean);
1208
+ if (!parsed.isWildcard && patternSegments.length !== pathSegments.length) {
1209
+ return false;
1210
+ }
1211
+ if (parsed.isWildcard && patternSegments.length > pathSegments.length + 1) {
1212
+ return false;
1213
+ }
1214
+ if (parsed.isWildcard) {
1215
+ return matchWildcardPattern(patternSegments, pathSegments);
1216
+ }
1217
+ for (let i = 0; i < patternSegments.length; i++) {
1218
+ if (!matchSegment(patternSegments[i], pathSegments[i])) {
1219
+ return false;
1220
+ }
1221
+ }
1222
+ return true;
1223
+ }
1224
+ function findMatchingRoute(routes, path) {
1225
+ const matchingRoutes = [];
1226
+ for (const route of routes) {
1227
+ if (matchRoute(route.pattern, path)) {
1228
+ const parsed = parseRoutePattern(route.pattern);
1229
+ matchingRoutes.push({ route, parsed });
1230
+ }
1231
+ }
1232
+ if (matchingRoutes.length === 0) {
1233
+ return null;
1234
+ }
1235
+ matchingRoutes.sort((a, b) => {
1236
+ if (b.parsed.priority !== a.parsed.priority) {
1237
+ return b.parsed.priority - a.parsed.priority;
1238
+ }
1239
+ if (b.parsed.segments.length !== a.parsed.segments.length) {
1240
+ return b.parsed.segments.length - a.parsed.segments.length;
1241
+ }
1242
+ return b.route.pattern.length - a.route.pattern.length;
1243
+ });
1244
+ return matchingRoutes[0].route;
1245
+ }
1246
+ function containsWildcard(pattern) {
1247
+ return pattern.includes("*");
1248
+ }
1249
+ function validateRouteConfig(config) {
1250
+ const { route, routes } = config;
1251
+ if (!route && !routes) {
1252
+ return null;
1253
+ }
1254
+ if (route && routes) {
1255
+ return {
1256
+ code: "INVALID_ROUTE_CONFIG",
1257
+ message: "Cannot specify both 'route' and 'routes'. Use 'route' for a single exact path or 'routes' for multiple paths.",
1258
+ path: "route",
1259
+ value: { route, routes }
1260
+ };
1261
+ }
1262
+ if (route && containsWildcard(route)) {
1263
+ return {
1264
+ code: "INVALID_ROUTE_PATTERN",
1265
+ message: `Wildcard routes must use the routes array, not 'route'. Use 'routes: ["/api/*"]' instead of 'route: "/api/*"'.`,
1266
+ path: "route",
1267
+ value: route,
1268
+ validOptions: ['routes: ["/api/*"]', 'routes: ["/api/users", "/api/posts"]']
1269
+ };
1270
+ }
1271
+ return null;
1272
+ }
1273
+
1112
1274
  // src/encoding.ts
1113
- var jsonEncode = (data) => JSON.stringify(data);
1114
- var jsonDecode = (encoded) => JSON.parse(encoded);
1115
- var encodePaymentV2 = (payload) => jsonEncode(payload);
1116
- var decodePaymentV2 = (encoded) => jsonDecode(encoded);
1117
- var encodeSettlementV2 = (response) => jsonEncode(response);
1118
- var decodeSettlementV2 = (encoded) => jsonDecode(encoded);
1275
+ function safeBase64Decode2(str) {
1276
+ const padding = 4 - str.length % 4;
1277
+ if (padding !== 4) {
1278
+ str += "=".repeat(padding);
1279
+ }
1280
+ str = str.replace(/-/g, "+").replace(/_/g, "/");
1281
+ return Buffer.from(str, "base64").toString("utf-8");
1282
+ }
1283
+ function safeBase64Encode2(str) {
1284
+ return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1285
+ }
1286
+ var base64JsonEncode = (data) => safeBase64Encode2(JSON.stringify(data));
1287
+ var base64JsonDecode = (encoded) => JSON.parse(safeBase64Decode2(encoded));
1288
+ var encodePaymentV2 = (payload) => base64JsonEncode(payload);
1289
+ var decodePaymentV2 = (encoded) => base64JsonDecode(encoded);
1290
+ var encodeSettlementV2 = (response) => base64JsonEncode(response);
1291
+ var decodeSettlementV2 = (encoded) => base64JsonDecode(encoded);
1119
1292
  var isPaymentV2 = (payload) => "signature" in payload && typeof payload.signature === "object";
1120
1293
  var isSettlementV2 = (response) => "status" in response;
1121
1294
 
@@ -1264,6 +1437,7 @@ export {
1264
1437
  encodeSettlementV2,
1265
1438
  encodeX402Response,
1266
1439
  extractPaymentFromHeaders,
1440
+ findMatchingRoute,
1267
1441
  fromAtomicUnits,
1268
1442
  getAllCustomTokens,
1269
1443
  getAllTokens,
@@ -1307,9 +1481,11 @@ export {
1307
1481
  isX402V2Requirements,
1308
1482
  isX402V2Settlement,
1309
1483
  legacyToPaymentPayload,
1484
+ matchRoute,
1310
1485
  networkToCaip2,
1311
1486
  normalizeAddress,
1312
1487
  normalizeNetworkName,
1488
+ parseRoutePattern,
1313
1489
  parseSignature as parseSignatureV2,
1314
1490
  registerToken,
1315
1491
  resolveFacilitator,
@@ -1321,5 +1497,6 @@ export {
1321
1497
  unregisterToken,
1322
1498
  validateAcceptConfig,
1323
1499
  validatePaymentConfig,
1500
+ validateRouteConfig,
1324
1501
  validateTransferWithAuthorization
1325
1502
  };
@@ -194,3 +194,59 @@ export interface ValidationError {
194
194
  /** Valid options */
195
195
  validOptions?: string[];
196
196
  }
197
+ /**
198
+ * Route pattern type for matching paths
199
+ */
200
+ export type RoutePattern = string;
201
+ /**
202
+ * Route matcher function type
203
+ */
204
+ export type RouteMatcher = (path: string) => boolean;
205
+ /**
206
+ * Route configuration with associated config data
207
+ */
208
+ export interface RouteConfig<T = unknown> {
209
+ /** Route pattern (e.g., "/api/users", "/api/*", "/api/users/:id") */
210
+ pattern: RoutePattern;
211
+ /** Configuration associated with this route */
212
+ config: T;
213
+ }
214
+ /**
215
+ * Parsed route pattern information
216
+ */
217
+ export interface ParsedPattern {
218
+ /** Route segments split by "/" */
219
+ segments: string[];
220
+ /** Whether this pattern contains a wildcard */
221
+ isWildcard: boolean;
222
+ /** Whether this pattern contains parameters (e.g., :id) */
223
+ isParametrized: boolean;
224
+ /** Parameter names extracted from the pattern */
225
+ paramNames: string[];
226
+ /** Match priority (higher = more specific) */
227
+ priority: number;
228
+ }
229
+ /**
230
+ * Route input configuration for middleware
231
+ */
232
+ export interface RouteInputConfig {
233
+ /** Single exact route (no wildcards allowed) */
234
+ route?: string;
235
+ /** Multiple routes (allows wildcards) */
236
+ routes?: string[];
237
+ }
238
+ /**
239
+ * Route-specific validation error details
240
+ */
241
+ export interface RouteValidationError {
242
+ /** Error code (custom string, not PaymentErrorCode) */
243
+ code: string;
244
+ /** Error message */
245
+ message: string;
246
+ /** Path to the invalid value */
247
+ path?: string;
248
+ /** Invalid value */
249
+ value?: unknown;
250
+ /** Valid options */
251
+ validOptions?: string[];
252
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Extension Hook System Types
3
+ *
4
+ * Provides a generic hook system for x402 clients to register
5
+ * extension handlers that can modify payment payloads or respond
6
+ * to payment requirements.
7
+ *
8
+ * Hooks allow extensibility without custom code in each client package.
9
+ */
10
+ import type { PaymentPayloadV2, PaymentRequirementsV2, Extensions, Address } from "./v2";
11
+ export interface PaymentRequiredContext {
12
+ url: RequestInfo | URL;
13
+ requestInit: RequestInit | undefined;
14
+ requirements: PaymentRequirementsV2;
15
+ serverExtensions: Extensions | undefined;
16
+ fromAddress: Address;
17
+ nonce: `0x${string}`;
18
+ validBefore: number;
19
+ }
20
+ export interface PaymentPayloadContext<TWallet = unknown> {
21
+ payload: PaymentPayloadV2;
22
+ requirements: PaymentRequirementsV2;
23
+ wallet: TWallet;
24
+ paymentContext: PaymentRequiredContext;
25
+ }
26
+ export type HookResult = void | Promise<void>;
27
+ export type OnPaymentRequiredHook<TWallet = unknown> = (context: PaymentRequiredContext) => HookResult;
28
+ export type BeforePaymentHook<TWallet = unknown> = (context: PaymentPayloadContext<TWallet>) => HookResult;
29
+ export type ExtensionHook<TWallet = unknown> = OnPaymentRequiredHook<TWallet> | BeforePaymentHook<TWallet>;
30
+ export interface HookConfig<TWallet = unknown> {
31
+ hook: ExtensionHook<TWallet>;
32
+ priority?: number;
33
+ name?: string;
34
+ }
35
+ export type HookRegistry<TWallet = unknown> = Record<string, HookConfig<TWallet>>;
@@ -0,0 +1,28 @@
1
+ export type RoutePattern = string;
2
+ export type RouteMatcher = (path: string) => boolean;
3
+ export interface RouteConfig<T = unknown> {
4
+ pattern: RoutePattern;
5
+ config: T;
6
+ }
7
+ export interface ParsedPattern {
8
+ segments: string[];
9
+ isWildcard: boolean;
10
+ isParametrized: boolean;
11
+ paramNames: string[];
12
+ priority: number;
13
+ }
14
+ export declare function parseRoutePattern(pattern: string): ParsedPattern;
15
+ export declare function matchRoute(pattern: string, path: string): boolean;
16
+ export declare function findMatchingRoute<T>(routes: RouteConfig<T>[], path: string): RouteConfig<T> | null;
17
+ export interface RouteInputConfig {
18
+ route?: string;
19
+ routes?: string[];
20
+ }
21
+ export interface RouteValidationError {
22
+ code: string;
23
+ message: string;
24
+ path?: string;
25
+ value?: unknown;
26
+ validOptions?: string[];
27
+ }
28
+ export declare function validateRouteConfig(config: RouteInputConfig): RouteValidationError | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/base",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",