@armory-sh/base 0.2.28 → 0.2.29

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,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));
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Protocol Types - x402 V2 Only
3
+ *
4
+ * Simplified to support only x402 V2 format (Coinbase compatible)
5
+ */
6
+
7
+ import type {
8
+ Address,
9
+ PaymentPayloadV2,
10
+ PaymentRequiredV2,
11
+ PaymentRequirementsV2,
12
+ SettlementResponseV2,
13
+ } from "./v2";
14
+
15
+ // ============================================================================
16
+ // Type Aliases for V2 Only
17
+ // ============================================================================
18
+
19
+ export type PaymentPayload = PaymentPayloadV2;
20
+ export type PaymentRequirements = PaymentRequirementsV2;
21
+ export type SettlementResponse = SettlementResponseV2;
22
+ export type PaymentRequired = PaymentRequiredV2;
23
+
24
+ // ============================================================================
25
+ // Facilitator Communication Types (shared across middleware packages)
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Configuration for connecting to a facilitator service
30
+ */
31
+ export interface FacilitatorConfig {
32
+ url: string;
33
+ createHeaders?: () => Record<string, string>;
34
+ }
35
+
36
+ /**
37
+ * Result from facilitator verification
38
+ */
39
+ export interface FacilitatorVerifyResult {
40
+ success: boolean;
41
+ payerAddress?: string;
42
+ balance?: string;
43
+ requiredAmount?: string;
44
+ error?: string;
45
+ }
46
+
47
+ /**
48
+ * Result from facilitator settlement
49
+ */
50
+ export interface FacilitatorSettleResult {
51
+ success: boolean;
52
+ txHash?: string;
53
+ error?: string;
54
+ }
55
+
56
+ /**
57
+ * Settlement mode - verify only, settle only, or both
58
+ */
59
+ export type SettlementMode = "verify" | "settle" | "async";
60
+
61
+ /**
62
+ * Payment destination - address, CAIP-2 chain ID, or CAIP asset ID
63
+ */
64
+ export type PayToAddress = Address | CAIP2ChainId | CAIPAssetId;
65
+
66
+ /**
67
+ * CAIP-2 chain ID type (e.g., eip155:8453)
68
+ */
69
+ export type CAIP2ChainId = `eip155:${string}`;
70
+
71
+ /**
72
+ * CAIP-2 asset ID type (e.g., eip155:8453/erc20:0xa0b8691...)
73
+ */
74
+ export type CAIPAssetId = `eip155:${string}/erc20:${string}`;
75
+
76
+ // ============================================================================
77
+ // Type Guards
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Check if payload is x402 V2 format (Coinbase format)
82
+ */
83
+ export function isX402V2Payload(obj: unknown): obj is PaymentPayloadV2 {
84
+ return (
85
+ typeof obj === "object" &&
86
+ obj !== null &&
87
+ "x402Version" in obj &&
88
+ (obj as PaymentPayloadV2).x402Version === 2 &&
89
+ "accepted" in obj &&
90
+ "payload" in obj
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Check if requirements is x402 V2 format
96
+ */
97
+ export function isX402V2Requirements(
98
+ obj: unknown,
99
+ ): obj is PaymentRequirementsV2 {
100
+ return (
101
+ typeof obj === "object" &&
102
+ obj !== null &&
103
+ "scheme" in obj &&
104
+ (obj as PaymentRequirementsV2).scheme === "exact" &&
105
+ "network" in obj &&
106
+ typeof (obj as PaymentRequirementsV2).network === "string" &&
107
+ (obj as PaymentRequirementsV2).network.startsWith("eip155:") && // CAIP-2 format
108
+ "amount" in obj &&
109
+ "asset" in obj &&
110
+ "payTo" in obj &&
111
+ "maxTimeoutSeconds" in obj
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Check if settlement response is x402 V2 format
117
+ */
118
+ export function isX402V2Settlement(obj: unknown): obj is SettlementResponseV2 {
119
+ return (
120
+ typeof obj === "object" &&
121
+ obj !== null &&
122
+ "success" in obj &&
123
+ typeof (obj as SettlementResponseV2).success === "boolean" &&
124
+ "network" in obj &&
125
+ typeof (obj as SettlementResponseV2).network === "string" &&
126
+ (obj as SettlementResponseV2).network.startsWith("eip155:") // CAIP-2 format
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Check if payment required is x402 V2 format
132
+ */
133
+ export function isX402V2PaymentRequired(
134
+ obj: unknown,
135
+ ): obj is PaymentRequiredV2 {
136
+ return (
137
+ typeof obj === "object" &&
138
+ obj !== null &&
139
+ "x402Version" in obj &&
140
+ (obj as PaymentRequiredV2).x402Version === 2 &&
141
+ "resource" in obj &&
142
+ "accepts" in obj
143
+ );
144
+ }
145
+
146
+ // ============================================================================
147
+ // Header Names (V2 Hardcoded)
148
+ // ============================================================================
149
+
150
+ /**
151
+ * x402 V2 payment header name
152
+ */
153
+ export const PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE" as const;
154
+
155
+ /**
156
+ * x402 V2 payment response header name
157
+ */
158
+ export const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE" as const;
159
+
160
+ /**
161
+ * x402 V2 payment required header name
162
+ */
163
+ export const PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED" as const;
164
+
165
+ // ============================================================================
166
+ // Utility Functions
167
+ // ============================================================================
168
+
169
+ /**
170
+ * Check if settlement was successful
171
+ */
172
+ export function isSettlementSuccessful(response: SettlementResponse): boolean {
173
+ return "success" in response ? response.success : false;
174
+ }
175
+
176
+ /**
177
+ * Get transaction hash from settlement response (V2 only)
178
+ */
179
+ export function getTxHash(response: SettlementResponse): string | undefined {
180
+ if ("transaction" in response) return response.transaction;
181
+ return undefined;
182
+ }