@armory-sh/base 0.2.27-alpha.23.76 → 0.2.27-alpha.23.78

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.
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Simple, developer-friendly API types for Armory
3
+ * Focuses on DX/UX - "everything just magically works"
4
+ */
5
+
6
+ import type { CustomToken, NetworkConfig } from "./networks";
7
+ import type { Address, CAIPAssetId } from "./v2";
8
+
9
+ // ═══════════════════════════════════════════════════════════════
10
+ // Simple API Types
11
+ // ═══════════════════════════════════════════════════════════════
12
+
13
+ /**
14
+ * Network identifier - flexible input that resolves to a chain
15
+ * - Network name: "base", "ethereum", "skale-base"
16
+ * - CAIP-2: "eip155:8453"
17
+ * - Chain ID: 8453
18
+ */
19
+ export type NetworkId = string | number;
20
+
21
+ /**
22
+ * Token identifier - flexible input that resolves to a token
23
+ * - Symbol: "usdc", "eurc", "usdt" (case-insensitive)
24
+ * - CAIP Asset ID: "eip155:8453/erc20:0x..."
25
+ * - Full token config object
26
+ */
27
+ export type TokenId = string | CustomToken;
28
+
29
+ /**
30
+ * Facilitator configuration
31
+ */
32
+ export interface FacilitatorConfig {
33
+ /** Facilitator URL for verification/settlement */
34
+ url: string;
35
+ /** Optional authentication headers */
36
+ headers?: () => Record<string, string>;
37
+ /** Networks this facilitator supports (auto-detected if not provided) */
38
+ networks?: NetworkId[];
39
+ /** Tokens this facilitator supports (optional) */
40
+ tokens?: TokenId[];
41
+ }
42
+
43
+ /**
44
+ * Result from facilitator verification
45
+ */
46
+ export interface FacilitatorVerifyResult {
47
+ success: boolean;
48
+ payerAddress?: string;
49
+ balance?: string;
50
+ requiredAmount?: string;
51
+ error?: string;
52
+ }
53
+
54
+ /**
55
+ * Result from facilitator settlement
56
+ */
57
+ export interface FacilitatorSettleResult {
58
+ success: boolean;
59
+ txHash?: string;
60
+ error?: string;
61
+ }
62
+
63
+ /**
64
+ * Settlement mode - verify only, settle only, or both
65
+ */
66
+ export type SettlementMode = "verify" | "settle" | "async";
67
+
68
+ /**
69
+ * CAIP-2 chain ID type (e.g., eip155:8453)
70
+ */
71
+ export type CAIP2ChainId = `eip155:${string}`;
72
+
73
+ /**
74
+ * CAIP-2 asset ID type (e.g., eip155:8453/erc20:0xa0b8691...)
75
+ */
76
+ export type CAIP2AssetId = `eip155:${string}/erc20:${string}`;
77
+
78
+ /**
79
+ * Payment destination - address, CAIP-2 chain ID, or CAIP asset ID
80
+ */
81
+ export type PayToAddress = `0x${string}` | CAIP2ChainId | CAIP2AssetId;
82
+
83
+ /**
84
+ * Pricing configuration for a specific network/token/facilitator combination
85
+ */
86
+ export interface PricingConfig {
87
+ /** Network for this pricing */
88
+ network?: NetworkId;
89
+ /** Token for this pricing */
90
+ token?: TokenId;
91
+ /** Facilitator URL for this pricing (optional - applies to all facilitators if not specified) */
92
+ facilitator?: string;
93
+ /** Amount to charge */
94
+ amount: string;
95
+ }
96
+
97
+ /**
98
+ * Payment accept options for merchants (v2 multi-support)
99
+ */
100
+ export interface AcceptPaymentOptions {
101
+ /** Networks to accept payments on */
102
+ networks?: NetworkId[];
103
+ /** Tokens to accept (defaults to ["usdc"]) */
104
+ tokens?: TokenId[];
105
+ /** Facilitator(s) for payment verification/settlement */
106
+ facilitators?: FacilitatorConfig | FacilitatorConfig[];
107
+ /** Protocol version (V2 only) */
108
+ version?: 2;
109
+ /** Pricing configurations - if not provided, uses amount from top-level config */
110
+ pricing?: PricingConfig[];
111
+ }
112
+
113
+ /**
114
+ * Result of a payment attempt
115
+ */
116
+ export interface PaymentResult<T = unknown> {
117
+ /** Payment successful */
118
+ success: true;
119
+ /** Response data */
120
+ data: T;
121
+ /** Transaction hash (if settled) */
122
+ txHash?: string;
123
+ }
124
+
125
+ /**
126
+ * Payment error result
127
+ */
128
+ export interface PaymentError {
129
+ /** Payment failed */
130
+ success: false;
131
+ /** Error code */
132
+ code: PaymentErrorCode;
133
+ /** Human-readable error message */
134
+ message: string;
135
+ /** Underlying error details */
136
+ details?: unknown;
137
+ }
138
+
139
+ /**
140
+ * Payment error codes
141
+ */
142
+ export type PaymentErrorCode =
143
+ | "UNKNOWN_NETWORK"
144
+ | "UNKNOWN_TOKEN"
145
+ | "TOKEN_NOT_ON_NETWORK"
146
+ | "FACILITATOR_UNSUPPORTED"
147
+ | "FACILITATOR_NO_NETWORK_SUPPORT"
148
+ | "FACILITATOR_NO_TOKEN_SUPPORT"
149
+ | "INVALID_CAIP_FORMAT"
150
+ | "CHAIN_MISMATCH"
151
+ | "PAYMENT_REQUIRED"
152
+ | "PAYMENT_DECLINED"
153
+ | "SIGNING_FAILED"
154
+ | "NETWORK_ERROR"
155
+ | "VALIDATION_FAILED";
156
+
157
+ /**
158
+ * Combined payment result type
159
+ */
160
+ export type ArmoryPaymentResult<T = unknown> = PaymentResult<T> | PaymentError;
161
+
162
+ // ═══════════════════════════════════════════════════════════════
163
+ // Resolved Configuration (internal use)
164
+ // ═══════════════════════════════════════════════════════════════
165
+
166
+ /**
167
+ * Fully resolved network configuration
168
+ */
169
+ export interface ResolvedNetwork {
170
+ /** Original input */
171
+ input: NetworkId;
172
+ /** Network config */
173
+ config: NetworkConfig;
174
+ /** CAIP-2 ID */
175
+ caip2: `eip155:${string}`;
176
+ }
177
+
178
+ /**
179
+ * Fully resolved token configuration
180
+ */
181
+ export interface ResolvedToken {
182
+ /** Original input */
183
+ input: TokenId;
184
+ /** Token config */
185
+ config: CustomToken;
186
+ /** CAIP Asset ID */
187
+ caipAsset: CAIPAssetId;
188
+ /** Network this token is on */
189
+ network: ResolvedNetwork;
190
+ }
191
+
192
+ /**
193
+ * Fully resolved facilitator configuration
194
+ */
195
+ export interface ResolvedFacilitator {
196
+ /** Original input */
197
+ input: FacilitatorConfig;
198
+ /** URL */
199
+ url: string;
200
+ /** Supported networks (resolved) */
201
+ networks: ResolvedNetwork[];
202
+ /** Supported tokens (resolved) */
203
+ tokens: ResolvedToken[];
204
+ }
205
+
206
+ /**
207
+ * Fully resolved payment configuration
208
+ */
209
+ export interface ResolvedPaymentConfig {
210
+ /** Network to use */
211
+ network: ResolvedNetwork;
212
+ /** Token to use */
213
+ token: ResolvedToken;
214
+ /** Facilitator(s) to use */
215
+ facilitators: ResolvedFacilitator[];
216
+ /** Protocol version (V2 only) */
217
+ version: 2;
218
+ /** Recipient address */
219
+ payTo: Address;
220
+ /** Amount to charge */
221
+ amount: string;
222
+ }
223
+
224
+ /**
225
+ * Validation error details
226
+ */
227
+ export interface ValidationError {
228
+ /** Error code */
229
+ code: PaymentErrorCode;
230
+ /** Error message */
231
+ message: string;
232
+ /** Path to the invalid value */
233
+ path?: string;
234
+ /** Invalid value */
235
+ value?: unknown;
236
+ /** Valid options */
237
+ validOptions?: string[];
238
+ }
239
+
240
+ // ═══════════════════════════════════════════════════════════════
241
+ // Route Configuration Types
242
+ // ═══════════════════════════════════════════════════════════════
243
+
244
+ /**
245
+ * Route pattern type for matching paths
246
+ */
247
+ export type RoutePattern = string;
248
+
249
+ /**
250
+ * Route matcher function type
251
+ */
252
+ export type RouteMatcher = (path: string) => boolean;
253
+
254
+ /**
255
+ * Route configuration with associated config data
256
+ */
257
+ export interface RouteConfig<T = unknown> {
258
+ /** Route pattern (e.g., "/api/users", "/api/*", "/api/users/:id") */
259
+ pattern: RoutePattern;
260
+ /** Configuration associated with this route */
261
+ config: T;
262
+ }
263
+
264
+ /**
265
+ * Parsed route pattern information
266
+ */
267
+ export interface ParsedPattern {
268
+ /** Route segments split by "/" */
269
+ segments: string[];
270
+ /** Whether this pattern contains a wildcard */
271
+ isWildcard: boolean;
272
+ /** Whether this pattern contains parameters (e.g., :id) */
273
+ isParametrized: boolean;
274
+ /** Parameter names extracted from the pattern */
275
+ paramNames: string[];
276
+ /** Match priority (higher = more specific) */
277
+ priority: number;
278
+ }
279
+
280
+ /**
281
+ * Route input configuration for middleware
282
+ */
283
+ export interface RouteInputConfig {
284
+ /** Single exact route (no wildcards allowed) */
285
+ route?: string;
286
+ /** Multiple routes (allows wildcards) */
287
+ routes?: string[];
288
+ }
289
+
290
+ /**
291
+ * Route-specific validation error details
292
+ */
293
+ export interface RouteValidationError {
294
+ /** Error code (custom string, not PaymentErrorCode) */
295
+ code: string;
296
+ /** Error message */
297
+ message: string;
298
+ /** Path to the invalid value */
299
+ path?: string;
300
+ /** Invalid value */
301
+ value?: unknown;
302
+ /** Valid options */
303
+ validOptions?: string[];
304
+ }
@@ -0,0 +1,85 @@
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
+
11
+ import type {
12
+ Address,
13
+ Extensions,
14
+ PaymentPayloadV2,
15
+ PaymentRequirementsV2,
16
+ } from "./v2";
17
+
18
+ export interface PaymentRequiredContext {
19
+ url: RequestInfo | URL;
20
+ requestInit: RequestInit | undefined;
21
+ accepts: PaymentRequirementsV2[];
22
+ requirements: PaymentRequirementsV2;
23
+ selectedRequirement?: PaymentRequirementsV2;
24
+ serverExtensions: Extensions | undefined;
25
+ fromAddress: Address;
26
+ nonce: `0x${string}`;
27
+ validBefore: number;
28
+ }
29
+
30
+ export interface PaymentPayloadContext<TWallet = unknown> {
31
+ payload: PaymentPayloadV2;
32
+ requirements: PaymentRequirementsV2;
33
+ wallet: TWallet;
34
+ paymentContext: PaymentRequiredContext;
35
+ }
36
+
37
+ export type HookResult = void | Promise<void>;
38
+
39
+ export type OnPaymentRequiredHook<TWallet = unknown> = (
40
+ context: PaymentRequiredContext,
41
+ ) => HookResult;
42
+
43
+ export type BeforePaymentHook<TWallet = unknown> = (
44
+ context: PaymentPayloadContext<TWallet>,
45
+ ) => HookResult;
46
+
47
+ export type ExtensionHook<TWallet = unknown> =
48
+ | OnPaymentRequiredHook<TWallet>
49
+ | BeforePaymentHook<TWallet>;
50
+
51
+ export interface HookConfig<TWallet = unknown> {
52
+ hook: ExtensionHook<TWallet>;
53
+ priority?: number;
54
+ name?: string;
55
+ }
56
+
57
+ export type HookRegistry<TWallet = unknown> = Record<
58
+ string,
59
+ HookConfig<TWallet>
60
+ >;
61
+
62
+ export interface ClientHookErrorContext {
63
+ error: unknown;
64
+ phase:
65
+ | "onPaymentRequired"
66
+ | "selectRequirement"
67
+ | "beforeSignPayment"
68
+ | "afterPaymentResponse";
69
+ }
70
+
71
+ export interface ClientHook<TWallet = unknown> {
72
+ name?: string;
73
+ onPaymentRequired?: (context: PaymentRequiredContext) => HookResult;
74
+ selectRequirement?: (
75
+ context: PaymentRequiredContext,
76
+ ) =>
77
+ | PaymentRequirementsV2
78
+ | undefined
79
+ | Promise<PaymentRequirementsV2 | undefined>;
80
+ beforeSignPayment?: (context: PaymentPayloadContext<TWallet>) => HookResult;
81
+ afterPaymentResponse?: (
82
+ context: PaymentPayloadContext<TWallet> & { response: Response },
83
+ ) => HookResult;
84
+ onError?: (context: ClientHookErrorContext) => HookResult;
85
+ }
@@ -0,0 +1,175 @@
1
+ const isEvmAddress = (value: string): boolean =>
2
+ /^0x[a-fA-F0-9]{40}$/.test(value);
3
+
4
+ export interface NetworkConfig {
5
+ name: string;
6
+ chainId: number;
7
+ usdcAddress: `0x${string}`;
8
+ rpcUrl: string;
9
+ caip2Id: string;
10
+ caipAssetId: string;
11
+ }
12
+
13
+ export interface CustomToken {
14
+ symbol: string;
15
+ name: string;
16
+ version: string;
17
+ contractAddress: `0x${string}`;
18
+ chainId: number;
19
+ decimals?: number;
20
+ }
21
+
22
+ const tokenRegistry = new Map<string, CustomToken>();
23
+ const tokenKey = (chainId: number, contractAddress: string): string =>
24
+ `${chainId}:${contractAddress.toLowerCase()}`;
25
+
26
+ export const NETWORKS: Record<string, NetworkConfig> = {
27
+ ethereum: {
28
+ name: "Ethereum Mainnet",
29
+ chainId: 1,
30
+ usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as const,
31
+ rpcUrl: "https://eth.llamarpc.com",
32
+ caip2Id: "eip155:1",
33
+ caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
34
+ },
35
+ base: {
36
+ name: "Base Mainnet",
37
+ chainId: 8453,
38
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const,
39
+ rpcUrl: "https://mainnet.base.org",
40
+ caip2Id: "eip155:8453",
41
+ caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
42
+ },
43
+ "base-sepolia": {
44
+ name: "Base Sepolia",
45
+ chainId: 84532,
46
+ usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" as const,
47
+ rpcUrl: "https://sepolia.base.org",
48
+ caip2Id: "eip155:84532",
49
+ caipAssetId:
50
+ "eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
51
+ },
52
+ "skale-base": {
53
+ name: "SKALE Base",
54
+ chainId: 1187947933,
55
+ usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20" as const,
56
+ rpcUrl: "https://skale-base.skalenodes.com/v1/base",
57
+ caip2Id: "eip155:1187947933",
58
+ caipAssetId:
59
+ "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
60
+ },
61
+ "skale-base-sepolia": {
62
+ name: "SKALE Base Sepolia",
63
+ chainId: 324705682,
64
+ usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD" as const,
65
+ rpcUrl:
66
+ "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
67
+ caip2Id: "eip155:324705682",
68
+ caipAssetId:
69
+ "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
70
+ },
71
+ "ethereum-sepolia": {
72
+ name: "Ethereum Sepolia",
73
+ chainId: 11155111,
74
+ usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" as const,
75
+ rpcUrl: "https://rpc.sepolia.org",
76
+ caip2Id: "eip155:11155111",
77
+ caipAssetId:
78
+ "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
79
+ },
80
+ };
81
+
82
+ export const getNetworkConfig = (name: string): NetworkConfig | undefined =>
83
+ NETWORKS[name];
84
+ export const getNetworkByChainId = (
85
+ chainId: number,
86
+ ): NetworkConfig | undefined =>
87
+ Object.values(NETWORKS).find((c) => c.chainId === chainId);
88
+ export const getMainnets = (): NetworkConfig[] =>
89
+ Object.values(NETWORKS).filter(
90
+ (c) => !c.name.toLowerCase().includes("sepolia"),
91
+ );
92
+ export const getTestnets = (): NetworkConfig[] =>
93
+ Object.values(NETWORKS).filter((c) =>
94
+ c.name.toLowerCase().includes("sepolia"),
95
+ );
96
+
97
+ export const registerToken = (token: unknown): CustomToken => {
98
+ if (typeof token !== "object" || token === null) {
99
+ throw new Error("Invalid token: must be an object");
100
+ }
101
+
102
+ const t = token as Record<string, unknown>;
103
+
104
+ if (typeof t.symbol !== "string") {
105
+ throw new Error("Invalid token: symbol must be a string");
106
+ }
107
+
108
+ if (typeof t.name !== "string") {
109
+ throw new Error("Invalid token: name must be a string");
110
+ }
111
+
112
+ if (typeof t.version !== "string") {
113
+ throw new Error("Invalid token: version must be a string");
114
+ }
115
+
116
+ if (
117
+ typeof t.contractAddress !== "string" ||
118
+ !isEvmAddress(t.contractAddress)
119
+ ) {
120
+ throw new Error(
121
+ "Invalid token: contractAddress must be a valid EVM address",
122
+ );
123
+ }
124
+
125
+ if (
126
+ typeof t.chainId !== "number" ||
127
+ !Number.isInteger(t.chainId) ||
128
+ t.chainId <= 0
129
+ ) {
130
+ throw new Error("Invalid token: chainId must be a positive integer");
131
+ }
132
+
133
+ if (
134
+ t.decimals !== undefined &&
135
+ (typeof t.decimals !== "number" ||
136
+ !Number.isInteger(t.decimals) ||
137
+ t.decimals < 0)
138
+ ) {
139
+ throw new Error("Invalid token: decimals must be a non-negative integer");
140
+ }
141
+
142
+ const validated: CustomToken = {
143
+ symbol: t.symbol,
144
+ name: t.name,
145
+ version: t.version,
146
+ contractAddress: t.contractAddress as `0x${string}`,
147
+ chainId: t.chainId,
148
+ decimals: t.decimals,
149
+ };
150
+
151
+ tokenRegistry.set(
152
+ tokenKey(validated.chainId, validated.contractAddress),
153
+ validated,
154
+ );
155
+ return validated;
156
+ };
157
+
158
+ export const getCustomToken = (
159
+ chainId: number,
160
+ contractAddress: string,
161
+ ): CustomToken | undefined =>
162
+ tokenRegistry.get(tokenKey(chainId, contractAddress));
163
+
164
+ export const getAllCustomTokens = (): CustomToken[] =>
165
+ Array.from(tokenRegistry.values());
166
+
167
+ export const unregisterToken = (
168
+ chainId: number,
169
+ contractAddress: string,
170
+ ): boolean => tokenRegistry.delete(tokenKey(chainId, contractAddress));
171
+
172
+ export const isCustomToken = (
173
+ chainId: number,
174
+ contractAddress: string,
175
+ ): boolean => tokenRegistry.has(tokenKey(chainId, contractAddress));