@armory-sh/base 0.2.19 → 0.2.21

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,6 +1109,168 @@ 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
1275
  function safeBase64Decode2(str) {
1114
1276
  const padding = 4 - str.length % 4;
@@ -1275,6 +1437,7 @@ export {
1275
1437
  encodeSettlementV2,
1276
1438
  encodeX402Response,
1277
1439
  extractPaymentFromHeaders,
1440
+ findMatchingRoute,
1278
1441
  fromAtomicUnits,
1279
1442
  getAllCustomTokens,
1280
1443
  getAllTokens,
@@ -1318,9 +1481,11 @@ export {
1318
1481
  isX402V2Requirements,
1319
1482
  isX402V2Settlement,
1320
1483
  legacyToPaymentPayload,
1484
+ matchRoute,
1321
1485
  networkToCaip2,
1322
1486
  normalizeAddress,
1323
1487
  normalizeNetworkName,
1488
+ parseRoutePattern,
1324
1489
  parseSignature as parseSignatureV2,
1325
1490
  registerToken,
1326
1491
  resolveFacilitator,
@@ -1332,5 +1497,6 @@ export {
1332
1497
  unregisterToken,
1333
1498
  validateAcceptConfig,
1334
1499
  validatePaymentConfig,
1500
+ validateRouteConfig,
1335
1501
  validateTransferWithAuthorization
1336
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.19",
3
+ "version": "0.2.21",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",