@armory-sh/client-viem 0.2.18 → 0.2.19
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 +130 -23
- package/dist/index.js +398 -6
- package/package.json +8 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CustomToken, PaymentPayloadV2,
|
|
1
|
+
import { Account, Address, WalletClient, Transport } from 'viem';
|
|
2
|
+
import { PaymentRequirementsV2, HookConfig, HookRegistry, PaymentPayloadContext, CustomToken, PaymentPayloadV2, PaymentRequiredContext, Extensions, NetworkId, TokenId, ArmoryPaymentResult, ValidationError, Address as Address$1, PaymentRequiredV2 } from '@armory-sh/base';
|
|
3
3
|
export { ArmoryPaymentResult, EIP3009Authorization, FacilitatorConfig, NetworkId, PaymentPayloadV2, PaymentRequiredV2, PaymentRequirementsV2, ResourceInfo, SchemePayloadV2, SettlementResponseV2, TokenId, V2_HEADERS } from '@armory-sh/base';
|
|
4
|
+
import { BazaarExtensionInfo, SIWxExtensionInfo } from '@armory-sh/extensions';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* X402 Protocol Implementation for Viem Client (V2 Only)
|
|
8
|
+
*
|
|
9
|
+
* Handles parsing x402 V2 PAYMENT-REQUIRED headers
|
|
10
|
+
* and generating x402 V2 PAYMENT-SIGNATURE payloads
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type X402Wallet$1 = {
|
|
14
|
+
type: "account";
|
|
15
|
+
account: Account;
|
|
16
|
+
} | {
|
|
17
|
+
type: "walletClient";
|
|
18
|
+
walletClient: {
|
|
19
|
+
account: Account;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type X402Version = 2;
|
|
23
|
+
declare function detectX402Version(_response: Response): X402Version;
|
|
24
|
+
type ParsedPaymentRequirements = PaymentRequirementsV2;
|
|
25
|
+
declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Viem-Specific Hook Types
|
|
29
|
+
*
|
|
30
|
+
* Extends the base hook types with viem-specific wallet context.
|
|
31
|
+
* Provides type-safe hook registration for client-viem.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface ViemPaymentPayloadContext extends PaymentPayloadContext {
|
|
35
|
+
wallet: X402Wallet$1;
|
|
36
|
+
}
|
|
37
|
+
type ViemHookConfig = HookConfig<X402Wallet$1>;
|
|
38
|
+
type ViemHookRegistry = HookRegistry<X402Wallet$1>;
|
|
4
39
|
|
|
5
40
|
/**
|
|
6
41
|
* X402 Client Types - V2 Only
|
|
@@ -28,6 +63,8 @@ interface X402ClientConfig {
|
|
|
28
63
|
domainName?: string;
|
|
29
64
|
/** Override EIP-712 domain version for custom tokens */
|
|
30
65
|
domainVersion?: string;
|
|
66
|
+
/** Extension hooks for handling protocol extensions */
|
|
67
|
+
hooks?: ViemHookRegistry;
|
|
31
68
|
}
|
|
32
69
|
interface X402Client {
|
|
33
70
|
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
@@ -48,6 +85,8 @@ interface X402TransportConfig {
|
|
|
48
85
|
domainName?: string;
|
|
49
86
|
/** Override EIP-712 domain version */
|
|
50
87
|
domainVersion?: string;
|
|
88
|
+
/** Extension hooks for handling protocol extensions */
|
|
89
|
+
hooks?: ViemHookRegistry;
|
|
51
90
|
}
|
|
52
91
|
interface PaymentResult {
|
|
53
92
|
success: boolean;
|
|
@@ -73,6 +112,16 @@ interface UnsignedPaymentPayload {
|
|
|
73
112
|
declare const createX402Client: (config: X402ClientConfig) => X402Client;
|
|
74
113
|
declare const createX402Transport: (config: X402TransportConfig) => ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>);
|
|
75
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Hook Execution Engine
|
|
117
|
+
*
|
|
118
|
+
* Executes hooks in priority order and handles errors gracefully.
|
|
119
|
+
* Hooks are sorted by priority (higher priority runs first).
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
declare function executeHooks(hooks: HookRegistry, context: PaymentRequiredContext | PaymentPayloadContext): Promise<void>;
|
|
123
|
+
declare function mergeExtensions(baseExtensions: Extensions | undefined, hookExtensions: Record<string, unknown>): Extensions;
|
|
124
|
+
|
|
76
125
|
declare class X402ClientError extends Error {
|
|
77
126
|
readonly cause?: unknown;
|
|
78
127
|
constructor(message: string, cause?: unknown);
|
|
@@ -84,32 +133,33 @@ declare class PaymentError extends X402ClientError {
|
|
|
84
133
|
constructor(message: string, cause?: unknown);
|
|
85
134
|
}
|
|
86
135
|
|
|
87
|
-
/**
|
|
88
|
-
* X402 Protocol Implementation for Viem Client (V2 Only)
|
|
89
|
-
*
|
|
90
|
-
* Handles parsing x402 V2 PAYMENT-REQUIRED headers
|
|
91
|
-
* and generating x402 V2 PAYMENT-SIGNATURE payloads
|
|
92
|
-
*/
|
|
93
|
-
|
|
94
|
-
type X402Version = 2;
|
|
95
|
-
declare function detectX402Version(_response: Response): X402Version;
|
|
96
|
-
type ParsedPaymentRequirements = PaymentRequirementsV2;
|
|
97
|
-
declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
|
|
98
|
-
|
|
99
136
|
/**
|
|
100
137
|
* Simple one-line payment API for Armory
|
|
101
138
|
* Focus on DX/UX - "everything just magically works"
|
|
102
139
|
*/
|
|
103
140
|
|
|
104
141
|
/**
|
|
105
|
-
* Simple wallet
|
|
106
|
-
|
|
142
|
+
* Simple wallet input - accepts wallet directly or wrapped for backward compatibility
|
|
143
|
+
*/
|
|
144
|
+
type SimpleWalletInput = Account | WalletClient | {
|
|
145
|
+
account: Account;
|
|
146
|
+
} | {
|
|
147
|
+
walletClient: WalletClient;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Normalized wallet (internal use)
|
|
107
151
|
*/
|
|
108
|
-
type
|
|
152
|
+
type NormalizedWallet = {
|
|
153
|
+
type: "account";
|
|
109
154
|
account: Account;
|
|
110
155
|
} | {
|
|
156
|
+
type: "walletClient";
|
|
111
157
|
walletClient: WalletClient;
|
|
112
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* Normalize wallet input to internal format
|
|
161
|
+
*/
|
|
162
|
+
declare const normalizeWallet: (wallet: SimpleWalletInput) => NormalizedWallet;
|
|
113
163
|
/**
|
|
114
164
|
* Make a payment-protected API request with one line of code
|
|
115
165
|
*
|
|
@@ -129,7 +179,7 @@ type SimpleWallet = {
|
|
|
129
179
|
* }
|
|
130
180
|
* ```
|
|
131
181
|
*/
|
|
132
|
-
declare const armoryPay: <T = unknown>(wallet:
|
|
182
|
+
declare const armoryPay: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: {
|
|
133
183
|
/** Request method (default: GET) */
|
|
134
184
|
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
135
185
|
/** Request body (for POST/PUT/PATCH) */
|
|
@@ -146,15 +196,27 @@ declare const armoryPay: <T = unknown>(wallet: SimpleWallet, url: string, networ
|
|
|
146
196
|
/**
|
|
147
197
|
* Make a GET request with payment
|
|
148
198
|
*/
|
|
149
|
-
declare const armoryGet: <T = unknown>(wallet:
|
|
199
|
+
declare const armoryGet: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
|
|
150
200
|
/**
|
|
151
201
|
* Make a POST request with payment
|
|
152
202
|
*/
|
|
153
|
-
declare const armoryPost: <T = unknown>(wallet:
|
|
203
|
+
declare const armoryPost: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
|
|
204
|
+
/**
|
|
205
|
+
* Make a PUT request with payment
|
|
206
|
+
*/
|
|
207
|
+
declare const armoryPut: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
|
|
154
208
|
/**
|
|
155
|
-
*
|
|
209
|
+
* Make a DELETE request with payment
|
|
156
210
|
*/
|
|
157
|
-
declare const
|
|
211
|
+
declare const armoryDelete: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, options?: Omit<Parameters<typeof armoryPay>[4], "method">) => Promise<ArmoryPaymentResult<T>>;
|
|
212
|
+
/**
|
|
213
|
+
* Make a PATCH request with payment
|
|
214
|
+
*/
|
|
215
|
+
declare const armoryPatch: <T = unknown>(wallet: SimpleWalletInput, url: string, network: NetworkId, token: TokenId, body?: unknown, options?: Omit<Parameters<typeof armoryPay>[4], "method" | "body">) => Promise<ArmoryPaymentResult<T>>;
|
|
216
|
+
/**
|
|
217
|
+
* Get the wallet address from a SimpleWalletInput
|
|
218
|
+
*/
|
|
219
|
+
declare const getWalletAddress: (wallet: SimpleWalletInput) => Address;
|
|
158
220
|
/**
|
|
159
221
|
* Validate a network identifier without making a request
|
|
160
222
|
*/
|
|
@@ -179,4 +241,49 @@ declare const getNetworks: () => string[];
|
|
|
179
241
|
*/
|
|
180
242
|
declare const getTokens: () => string[];
|
|
181
243
|
|
|
182
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Armory API - Simplified payment interface
|
|
246
|
+
* Provides a configurable object with method-based payment functions
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
250
|
+
interface ArmoryConfig {
|
|
251
|
+
wallet: SimpleWalletInput;
|
|
252
|
+
methods?: HttpMethod[] | HttpMethod;
|
|
253
|
+
tokens?: TokenId[] | TokenId;
|
|
254
|
+
chains?: NetworkId[] | NetworkId;
|
|
255
|
+
debug?: boolean;
|
|
256
|
+
}
|
|
257
|
+
interface PaymentOptions {
|
|
258
|
+
method?: HttpMethod;
|
|
259
|
+
body?: unknown;
|
|
260
|
+
}
|
|
261
|
+
interface ArmoryInstance {
|
|
262
|
+
get<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
|
|
263
|
+
post<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
|
|
264
|
+
put<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
|
|
265
|
+
delete<T>(url: string): Promise<ArmoryPaymentResult<T>>;
|
|
266
|
+
patch<T>(url: string, body?: unknown): Promise<ArmoryPaymentResult<T>>;
|
|
267
|
+
pay<T>(url: string, options?: PaymentOptions): Promise<ArmoryPaymentResult<T>>;
|
|
268
|
+
call<T>(url: string): Promise<ArmoryPaymentResult<T>>;
|
|
269
|
+
}
|
|
270
|
+
declare const createArmory: (config: ArmoryConfig) => ArmoryInstance;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Client-side extension handling for x402
|
|
274
|
+
* Integrates with @armory-sh/extensions package
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
interface ClientExtensionContext {
|
|
278
|
+
bazaar?: BazaarExtensionInfo;
|
|
279
|
+
signInWithX?: SIWxExtensionInfo;
|
|
280
|
+
}
|
|
281
|
+
declare function parseExtensions(paymentRequired: PaymentRequiredV2): ClientExtensionContext;
|
|
282
|
+
declare function extractExtension<T>(extensions: Extensions | undefined, key: string): T | null;
|
|
283
|
+
declare function createSIWxProof(challenge: SIWxExtensionInfo, wallet: X402Wallet$1, nonce: string, expirationSeconds?: number): Promise<{
|
|
284
|
+
header: string;
|
|
285
|
+
address: Address$1;
|
|
286
|
+
}>;
|
|
287
|
+
declare function addExtensionsToPayload(payload: PaymentPayloadV2, extensions?: Extensions): PaymentPayloadV2;
|
|
288
|
+
|
|
289
|
+
export { type ArmoryConfig, type ArmoryInstance, type ClientExtensionContext, type HttpMethod, type NormalizedWallet, type ParsedPaymentRequirements, PaymentError, type PaymentOptions, type PaymentResult, SigningError, type SimpleWalletInput, type Token, type ViemHookConfig, type ViemHookRegistry, type ViemPaymentPayloadContext, type X402Client, type X402ClientConfig, X402ClientError, type X402ProtocolVersion, type X402TransportConfig, type X402Wallet, addExtensionsToPayload, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, createArmory, createSIWxProof, createX402Client, createX402Transport, detectX402Version, executeHooks, extractExtension, getNetworks, getTokens, getWalletAddress, mergeExtensions, normalizeWallet, parseExtensions, parsePaymentRequired, validateNetwork, validateToken };
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,28 @@ async function createX402Payment(wallet, requirements, from, nonce = `0x${Date.n
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// src/hooks-engine.ts
|
|
86
|
+
async function executeHooks(hooks, context) {
|
|
87
|
+
const sortedHooks = Object.entries(hooks).sort(([, a], [, b]) => {
|
|
88
|
+
const priorityA = a.priority ?? 0;
|
|
89
|
+
const priorityB = b.priority ?? 0;
|
|
90
|
+
return priorityB - priorityA;
|
|
91
|
+
});
|
|
92
|
+
for (const [key, config] of sortedHooks) {
|
|
93
|
+
try {
|
|
94
|
+
await config.hook(context);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn(`[X402] Hook "${key}" failed:`, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function mergeExtensions(baseExtensions, hookExtensions) {
|
|
101
|
+
return {
|
|
102
|
+
...baseExtensions ?? {},
|
|
103
|
+
...hookExtensions
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
// src/client.ts
|
|
86
108
|
var DEFAULT_EXPIRY = 3600;
|
|
87
109
|
var toCaip2Id = (chainId) => `eip155:${chainId}`;
|
|
@@ -119,7 +141,7 @@ var checkSettlement = (response) => {
|
|
|
119
141
|
var createFetch = (wallet, config) => {
|
|
120
142
|
const protocolWallet = toProtocolWallet(wallet);
|
|
121
143
|
const getAddress = () => getWalletAddress(protocolWallet);
|
|
122
|
-
const { defaultExpiry, nonceGenerator, debug, domainName, domainVersion } = config;
|
|
144
|
+
const { defaultExpiry, nonceGenerator, debug, domainName, domainVersion, hooks } = config;
|
|
123
145
|
return async (input, init) => {
|
|
124
146
|
let response = await fetch(input, init);
|
|
125
147
|
if (response.status === 402) {
|
|
@@ -133,6 +155,18 @@ var createFetch = (wallet, config) => {
|
|
|
133
155
|
const fromAddress = getAddress();
|
|
134
156
|
const nonce = generateNonce(nonceGenerator);
|
|
135
157
|
const validBefore = Math.floor(Date.now() / 1e3) + defaultExpiry;
|
|
158
|
+
const paymentRequiredContext = {
|
|
159
|
+
url: input,
|
|
160
|
+
requestInit: init,
|
|
161
|
+
requirements: parsed,
|
|
162
|
+
serverExtensions: parsed.extra,
|
|
163
|
+
fromAddress,
|
|
164
|
+
nonce,
|
|
165
|
+
validBefore
|
|
166
|
+
};
|
|
167
|
+
if (hooks) {
|
|
168
|
+
await executeHooks(hooks, paymentRequiredContext);
|
|
169
|
+
}
|
|
136
170
|
const payment = await createX402Payment(
|
|
137
171
|
protocolWallet,
|
|
138
172
|
parsed,
|
|
@@ -145,6 +179,15 @@ var createFetch = (wallet, config) => {
|
|
|
145
179
|
if (debug) {
|
|
146
180
|
console.log("[X402] Created payment payload");
|
|
147
181
|
}
|
|
182
|
+
if (hooks) {
|
|
183
|
+
const paymentPayloadContext = {
|
|
184
|
+
payload: payment,
|
|
185
|
+
requirements: parsed,
|
|
186
|
+
wallet: protocolWallet,
|
|
187
|
+
paymentContext: paymentRequiredContext
|
|
188
|
+
};
|
|
189
|
+
await executeHooks(hooks, paymentPayloadContext);
|
|
190
|
+
}
|
|
148
191
|
const headers = new Headers(init?.headers);
|
|
149
192
|
addPaymentHeader(headers, payment);
|
|
150
193
|
response = await fetch(input, { ...init, headers });
|
|
@@ -157,7 +200,7 @@ var createFetch = (wallet, config) => {
|
|
|
157
200
|
};
|
|
158
201
|
};
|
|
159
202
|
var createX402Client = (config) => {
|
|
160
|
-
const { wallet, version = 2, defaultExpiry = DEFAULT_EXPIRY, nonceGenerator } = config;
|
|
203
|
+
const { wallet, version = 2, defaultExpiry = DEFAULT_EXPIRY, nonceGenerator, hooks } = config;
|
|
161
204
|
const { domainName, domainVersion } = extractDomainConfig(config);
|
|
162
205
|
const protocolWallet = toProtocolWallet(wallet);
|
|
163
206
|
const getAddress = () => getWalletAddress(protocolWallet);
|
|
@@ -167,7 +210,8 @@ var createX402Client = (config) => {
|
|
|
167
210
|
nonceGenerator,
|
|
168
211
|
debug: config.debug,
|
|
169
212
|
domainName,
|
|
170
|
-
domainVersion
|
|
213
|
+
domainVersion,
|
|
214
|
+
hooks
|
|
171
215
|
});
|
|
172
216
|
return {
|
|
173
217
|
fetch: fetchFn,
|
|
@@ -215,7 +259,8 @@ var createX402Transport = (config) => {
|
|
|
215
259
|
debug: config.debug,
|
|
216
260
|
token: config.token,
|
|
217
261
|
domainName: config.domainName,
|
|
218
|
-
domainVersion: config.domainVersion
|
|
262
|
+
domainVersion: config.domainVersion,
|
|
263
|
+
hooks: config.hooks
|
|
219
264
|
});
|
|
220
265
|
return client.fetch;
|
|
221
266
|
};
|
|
@@ -229,9 +274,30 @@ import {
|
|
|
229
274
|
getAvailableNetworks,
|
|
230
275
|
getAvailableTokens
|
|
231
276
|
} from "@armory-sh/base";
|
|
277
|
+
var normalizeWallet = (wallet) => {
|
|
278
|
+
if (typeof wallet === "object" && wallet !== null) {
|
|
279
|
+
if ("account" in wallet && "type" in wallet) {
|
|
280
|
+
return wallet;
|
|
281
|
+
}
|
|
282
|
+
if ("account" in wallet) {
|
|
283
|
+
return { type: "account", account: wallet.account };
|
|
284
|
+
}
|
|
285
|
+
if ("walletClient" in wallet) {
|
|
286
|
+
return { type: "walletClient", walletClient: wallet.walletClient };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if ("address" in wallet && "type" in wallet) {
|
|
290
|
+
return { type: "account", account: wallet };
|
|
291
|
+
}
|
|
292
|
+
if ("account" in wallet) {
|
|
293
|
+
return { type: "walletClient", walletClient: wallet };
|
|
294
|
+
}
|
|
295
|
+
throw new Error("Invalid wallet input");
|
|
296
|
+
};
|
|
232
297
|
var armoryPay = async (wallet, url, network, token, options) => {
|
|
233
298
|
try {
|
|
234
|
-
const
|
|
299
|
+
const normalized = normalizeWallet(wallet);
|
|
300
|
+
const x402Wallet = normalized.type === "account" ? { type: "account", account: normalized.account } : { type: "walletClient", walletClient: normalized.walletClient };
|
|
235
301
|
const config = validatePaymentConfig(network, token);
|
|
236
302
|
if (isValidationError(config)) {
|
|
237
303
|
return {
|
|
@@ -296,8 +362,18 @@ var armoryGet = (wallet, url, network, token, options) => {
|
|
|
296
362
|
var armoryPost = (wallet, url, network, token, body, options) => {
|
|
297
363
|
return armoryPay(wallet, url, network, token, { ...options, method: "POST", body });
|
|
298
364
|
};
|
|
365
|
+
var armoryPut = (wallet, url, network, token, body, options) => {
|
|
366
|
+
return armoryPay(wallet, url, network, token, { ...options, method: "PUT", body });
|
|
367
|
+
};
|
|
368
|
+
var armoryDelete = (wallet, url, network, token, options) => {
|
|
369
|
+
return armoryPay(wallet, url, network, token, { ...options, method: "DELETE" });
|
|
370
|
+
};
|
|
371
|
+
var armoryPatch = (wallet, url, network, token, body, options) => {
|
|
372
|
+
return armoryPay(wallet, url, network, token, { ...options, method: "PATCH", body });
|
|
373
|
+
};
|
|
299
374
|
var getWalletAddress2 = (wallet) => {
|
|
300
|
-
|
|
375
|
+
const normalized = normalizeWallet(wallet);
|
|
376
|
+
return normalized.type === "account" ? normalized.account.address : normalized.walletClient.account.address;
|
|
301
377
|
};
|
|
302
378
|
var validateNetwork = (network) => {
|
|
303
379
|
const resolved = resolveNetwork(network);
|
|
@@ -332,22 +408,338 @@ var getTokens = () => {
|
|
|
332
408
|
return getAvailableTokens();
|
|
333
409
|
};
|
|
334
410
|
|
|
411
|
+
// src/armory-api.ts
|
|
412
|
+
import {
|
|
413
|
+
resolveNetwork as resolveNetwork2,
|
|
414
|
+
resolveToken as resolveToken2,
|
|
415
|
+
getNetworkByChainId as getNetworkByChainId2
|
|
416
|
+
} from "@armory-sh/base";
|
|
417
|
+
var ALL_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]);
|
|
418
|
+
var arrayify = (value) => {
|
|
419
|
+
if (value === void 0) return void 0;
|
|
420
|
+
return Array.isArray(value) ? value : [value];
|
|
421
|
+
};
|
|
422
|
+
var normalizeArmoryConfig = (config) => ({
|
|
423
|
+
wallet: normalizeWallet(config.wallet),
|
|
424
|
+
allowedMethods: config.methods ? new Set(arrayify(config.methods)) : ALL_METHODS,
|
|
425
|
+
allowedTokens: arrayify(config.tokens) ?? [],
|
|
426
|
+
allowedChains: arrayify(config.chains) ?? [],
|
|
427
|
+
debug: config.debug ?? false
|
|
428
|
+
});
|
|
429
|
+
var scorePaymentOption = (option, allowedTokens, allowedChains) => {
|
|
430
|
+
let score = 0;
|
|
431
|
+
const chainId = parseInt(option.network.split(":")[1], 10);
|
|
432
|
+
const tokenAddress = option.asset.toLowerCase();
|
|
433
|
+
for (const chain of allowedChains) {
|
|
434
|
+
const resolved = resolveNetwork2(chain);
|
|
435
|
+
if (!("code" in resolved) && resolved.config.chainId === chainId) {
|
|
436
|
+
score += 10;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
for (const token of allowedTokens) {
|
|
441
|
+
const resolved = resolveToken2(token);
|
|
442
|
+
if (!("code" in resolved) && resolved.config.contractAddress.toLowerCase() === tokenAddress) {
|
|
443
|
+
score += 5;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (chainId === 8453) score += 2;
|
|
448
|
+
if (tokenAddress === "0x833589fcd6edb6e08f4c7c32d4f71b54bdA02913") {
|
|
449
|
+
score += 1;
|
|
450
|
+
}
|
|
451
|
+
const amount = parseInt(option.amount, 10);
|
|
452
|
+
if (amount < 1e6) score += 1;
|
|
453
|
+
return score;
|
|
454
|
+
};
|
|
455
|
+
var selectPaymentOption = (accepts, allowedTokens, allowedChains, allowedMethods) => {
|
|
456
|
+
const validOptions = accepts.filter((option) => {
|
|
457
|
+
const chainId = parseInt(option.network.split(":")[1], 10);
|
|
458
|
+
for (const chain of allowedChains) {
|
|
459
|
+
const resolved = resolveNetwork2(chain);
|
|
460
|
+
if (!("code" in resolved) && resolved.config.chainId === chainId) {
|
|
461
|
+
for (const token of allowedTokens) {
|
|
462
|
+
const resolvedToken = resolveToken2(token, resolved);
|
|
463
|
+
if (!("code" in resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === option.asset.toLowerCase()) {
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (allowedChains.length === 0 && allowedTokens.length === 0) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
});
|
|
474
|
+
if (validOptions.length === 0) return null;
|
|
475
|
+
if (validOptions.length === 1) return validOptions[0];
|
|
476
|
+
return validOptions.sort(
|
|
477
|
+
(a, b) => scorePaymentOption(b, allowedTokens, allowedChains) - scorePaymentOption(a, allowedTokens, allowedChains)
|
|
478
|
+
)[0];
|
|
479
|
+
};
|
|
480
|
+
var createArmory = (config) => {
|
|
481
|
+
const internal = normalizeArmoryConfig(config);
|
|
482
|
+
const x402Wallet = internal.wallet.type === "account" ? { type: "account", account: internal.wallet.account } : { type: "walletClient", walletClient: internal.wallet.walletClient };
|
|
483
|
+
const makeRequest = async (url, method, body) => {
|
|
484
|
+
try {
|
|
485
|
+
const headers = new Headers();
|
|
486
|
+
let response = await fetch(url, { method, headers });
|
|
487
|
+
if (response.status === 402) {
|
|
488
|
+
const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
|
|
489
|
+
if (!paymentRequiredHeader) {
|
|
490
|
+
return {
|
|
491
|
+
success: false,
|
|
492
|
+
code: "PAYMENT_REQUIRED",
|
|
493
|
+
message: "Payment required but no PAYMENT-REQUIRED header found"
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
|
|
497
|
+
const paymentRequired = JSON.parse(decoded);
|
|
498
|
+
if (paymentRequired.x402Version !== 2 || !paymentRequired.accepts) {
|
|
499
|
+
return {
|
|
500
|
+
success: false,
|
|
501
|
+
code: "PAYMENT_REQUIRED",
|
|
502
|
+
message: "Invalid payment required format"
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const selectedOption = selectPaymentOption(
|
|
506
|
+
paymentRequired.accepts,
|
|
507
|
+
internal.allowedTokens,
|
|
508
|
+
internal.allowedChains,
|
|
509
|
+
internal.allowedMethods
|
|
510
|
+
);
|
|
511
|
+
if (!selectedOption) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
code: "PAYMENT_REQUIRED",
|
|
515
|
+
message: "No compatible payment option found"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const chainId = parseInt(selectedOption.network.split(":")[1], 10);
|
|
519
|
+
const networkConfig = getNetworkByChainId2(chainId) ?? {
|
|
520
|
+
chainId,
|
|
521
|
+
name: "Unknown",
|
|
522
|
+
rpcUrl: "",
|
|
523
|
+
usdcAddress: selectedOption.asset,
|
|
524
|
+
caip2Id: selectedOption.network,
|
|
525
|
+
caipAssetId: `${selectedOption.network}/erc20:${selectedOption.asset}`
|
|
526
|
+
};
|
|
527
|
+
const client = createX402Client({
|
|
528
|
+
wallet: x402Wallet,
|
|
529
|
+
version: 2,
|
|
530
|
+
token: {
|
|
531
|
+
symbol: "CUSTOM",
|
|
532
|
+
name: "Payment Token",
|
|
533
|
+
version: "1",
|
|
534
|
+
chainId,
|
|
535
|
+
contractAddress: selectedOption.asset,
|
|
536
|
+
decimals: 6
|
|
537
|
+
},
|
|
538
|
+
debug: internal.debug
|
|
539
|
+
});
|
|
540
|
+
const fromAddress = internal.wallet.type === "account" ? internal.wallet.account.address : internal.wallet.walletClient.account.address;
|
|
541
|
+
const nonce = `0x${Date.now().toString(16).padStart(64, "0")}`;
|
|
542
|
+
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
543
|
+
const payment = await client.createPayment(
|
|
544
|
+
selectedOption.amount,
|
|
545
|
+
selectedOption.payTo,
|
|
546
|
+
selectedOption.asset,
|
|
547
|
+
chainId,
|
|
548
|
+
validBefore
|
|
549
|
+
);
|
|
550
|
+
const signatureHeader = Buffer.from(JSON.stringify(payment)).toString("base64");
|
|
551
|
+
headers.set("PAYMENT-SIGNATURE", signatureHeader);
|
|
552
|
+
response = await fetch(url, {
|
|
553
|
+
method,
|
|
554
|
+
headers,
|
|
555
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
556
|
+
});
|
|
557
|
+
if (internal.debug) {
|
|
558
|
+
console.log("[Armory] Payment completed, status:", response.status);
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
response = await fetch(url, {
|
|
562
|
+
method,
|
|
563
|
+
headers,
|
|
564
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
if (!response.ok) {
|
|
568
|
+
const error = await response.text();
|
|
569
|
+
return {
|
|
570
|
+
success: false,
|
|
571
|
+
code: "PAYMENT_DECLINED",
|
|
572
|
+
message: `Request failed: ${response.status} ${error}`
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const data = await response.json();
|
|
576
|
+
const txHash = response.headers.get("X-PAYMENT-RESPONSE") || response.headers.get("PAYMENT-RESPONSE");
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
data,
|
|
580
|
+
...txHash && { txHash }
|
|
581
|
+
};
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
success: false,
|
|
585
|
+
code: "NETWORK_ERROR",
|
|
586
|
+
message: error instanceof Error ? error.message : "Unknown error occurred",
|
|
587
|
+
details: error
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
return {
|
|
592
|
+
get: (url, body) => makeRequest(url, "GET", body),
|
|
593
|
+
post: (url, body) => makeRequest(url, "POST", body),
|
|
594
|
+
put: (url, body) => makeRequest(url, "PUT", body),
|
|
595
|
+
delete: (url) => makeRequest(url, "DELETE"),
|
|
596
|
+
patch: (url, body) => makeRequest(url, "PATCH", body),
|
|
597
|
+
pay: (url, options) => makeRequest(url, options?.method ?? "GET", options?.body),
|
|
598
|
+
call: (url) => makeRequest(url, "GET")
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
|
|
335
602
|
// src/index.ts
|
|
336
603
|
import { V2_HEADERS as V2_HEADERS3 } from "@armory-sh/base";
|
|
604
|
+
|
|
605
|
+
// src/extensions.ts
|
|
606
|
+
function parseExtensions(paymentRequired) {
|
|
607
|
+
const extensions = paymentRequired.extensions || {};
|
|
608
|
+
const result = {};
|
|
609
|
+
if (extensions.bazaar) {
|
|
610
|
+
const bazaarExt = extensions.bazaar;
|
|
611
|
+
if (bazaarExt && typeof bazaarExt === "object" && "info" in bazaarExt) {
|
|
612
|
+
result.bazaar = bazaarExt.info;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (extensions["sign-in-with-x"]) {
|
|
616
|
+
const siwxExt = extensions["sign-in-with-x"];
|
|
617
|
+
if (siwxExt && typeof siwxExt === "object" && "info" in siwxExt) {
|
|
618
|
+
result.signInWithX = siwxExt.info;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
function extractExtension(extensions, key) {
|
|
624
|
+
if (!extensions || typeof extensions !== "object") {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const extension = extensions[key];
|
|
628
|
+
if (!extension || typeof extension !== "object") {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
const ext = extension;
|
|
632
|
+
return ext.info || null;
|
|
633
|
+
}
|
|
634
|
+
async function createSIWxProof(challenge, wallet, nonce, expirationSeconds = 3600) {
|
|
635
|
+
const getDefaultDomain = () => {
|
|
636
|
+
if (typeof window !== "undefined" && window.location) {
|
|
637
|
+
return window.location.hostname;
|
|
638
|
+
}
|
|
639
|
+
return "localhost";
|
|
640
|
+
};
|
|
641
|
+
const address = getWalletAddress(wallet);
|
|
642
|
+
const now = /* @__PURE__ */ new Date();
|
|
643
|
+
const expiration = new Date(now.getTime() + expirationSeconds * 1e3);
|
|
644
|
+
const payload = {
|
|
645
|
+
domain: challenge.domain || getDefaultDomain(),
|
|
646
|
+
resourceUri: challenge.resourceUri,
|
|
647
|
+
address,
|
|
648
|
+
statement: challenge.statement,
|
|
649
|
+
version: challenge.version || "1",
|
|
650
|
+
chainId: challenge.network,
|
|
651
|
+
nonce,
|
|
652
|
+
issuedAt: now.toISOString(),
|
|
653
|
+
expirationTime: expiration.toISOString()
|
|
654
|
+
};
|
|
655
|
+
const message = createSIWxMessage(payload);
|
|
656
|
+
let signature;
|
|
657
|
+
if (wallet.type === "account") {
|
|
658
|
+
if (!wallet.account.signTypedData) {
|
|
659
|
+
throw new Error("Wallet does not support signing");
|
|
660
|
+
}
|
|
661
|
+
signature = await wallet.account.signMessage({ message });
|
|
662
|
+
} else {
|
|
663
|
+
if (!wallet.walletClient.account.signTypedData) {
|
|
664
|
+
throw new Error("Wallet does not support signing");
|
|
665
|
+
}
|
|
666
|
+
signature = await wallet.walletClient.account.signMessage({ message });
|
|
667
|
+
}
|
|
668
|
+
payload.signature = signature;
|
|
669
|
+
const header = encodeSIWxHeader(payload);
|
|
670
|
+
return { header, address };
|
|
671
|
+
}
|
|
672
|
+
function base64UrlEncode(str) {
|
|
673
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
674
|
+
}
|
|
675
|
+
function createSIWxMessage(payload) {
|
|
676
|
+
const lines = [];
|
|
677
|
+
lines.push(`${payload.domain} wants you to sign in`);
|
|
678
|
+
if (payload.statement) {
|
|
679
|
+
lines.push("");
|
|
680
|
+
lines.push(payload.statement);
|
|
681
|
+
}
|
|
682
|
+
lines.push("");
|
|
683
|
+
if (payload.resourceUri) {
|
|
684
|
+
lines.push(`URI: ${payload.resourceUri}`);
|
|
685
|
+
}
|
|
686
|
+
if (payload.version) {
|
|
687
|
+
lines.push(`Version: ${payload.version}`);
|
|
688
|
+
}
|
|
689
|
+
if (payload.nonce) {
|
|
690
|
+
lines.push(`Nonce: ${payload.nonce}`);
|
|
691
|
+
}
|
|
692
|
+
if (payload.issuedAt) {
|
|
693
|
+
lines.push(`Issued At: ${payload.issuedAt}`);
|
|
694
|
+
}
|
|
695
|
+
if (payload.expirationTime) {
|
|
696
|
+
lines.push(`Expiration Time: ${payload.expirationTime}`);
|
|
697
|
+
}
|
|
698
|
+
if (payload.chainId) {
|
|
699
|
+
const chains = Array.isArray(payload.chainId) ? payload.chainId.join(", ") : payload.chainId;
|
|
700
|
+
lines.push(`Chain ID(s): ${chains}`);
|
|
701
|
+
}
|
|
702
|
+
return lines.join("\n");
|
|
703
|
+
}
|
|
704
|
+
function encodeSIWxHeader(payload) {
|
|
705
|
+
const payloadStr = JSON.stringify(payload);
|
|
706
|
+
const encoded = base64UrlEncode(payloadStr);
|
|
707
|
+
return `siwx-v1-${encoded}`;
|
|
708
|
+
}
|
|
709
|
+
function addExtensionsToPayload(payload, extensions) {
|
|
710
|
+
if (!extensions || Object.keys(extensions).length === 0) {
|
|
711
|
+
return payload;
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
...payload,
|
|
715
|
+
extensions
|
|
716
|
+
};
|
|
717
|
+
}
|
|
337
718
|
export {
|
|
338
719
|
PaymentError,
|
|
339
720
|
SigningError,
|
|
340
721
|
V2_HEADERS3 as V2_HEADERS,
|
|
341
722
|
X402ClientError,
|
|
723
|
+
addExtensionsToPayload,
|
|
724
|
+
armoryDelete,
|
|
342
725
|
armoryGet,
|
|
726
|
+
armoryPatch,
|
|
343
727
|
armoryPay,
|
|
344
728
|
armoryPost,
|
|
729
|
+
armoryPut,
|
|
730
|
+
createArmory,
|
|
731
|
+
createSIWxProof,
|
|
345
732
|
createX402Client,
|
|
346
733
|
createX402Transport,
|
|
347
734
|
detectX402Version,
|
|
735
|
+
executeHooks,
|
|
736
|
+
extractExtension,
|
|
348
737
|
getNetworks,
|
|
349
738
|
getTokens,
|
|
350
739
|
getWalletAddress2 as getWalletAddress,
|
|
740
|
+
mergeExtensions,
|
|
741
|
+
normalizeWallet,
|
|
742
|
+
parseExtensions,
|
|
351
743
|
parsePaymentRequired,
|
|
352
744
|
validateNetwork,
|
|
353
745
|
validateToken
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@armory-sh/client-viem",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"bun": "./src/index.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./extensions": {
|
|
16
|
+
"types": "./dist/extensions.d.ts",
|
|
17
|
+
"bun": "./src/extensions.ts",
|
|
18
|
+
"default": "./dist/extensions.js"
|
|
19
|
+
},
|
|
15
20
|
"./dist/*": "./dist/*.js"
|
|
16
21
|
},
|
|
17
22
|
"files": [
|
|
@@ -27,7 +32,8 @@
|
|
|
27
32
|
"directory": "packages/client-viem"
|
|
28
33
|
},
|
|
29
34
|
"dependencies": {
|
|
30
|
-
"@armory-sh/base": "^0.2.
|
|
35
|
+
"@armory-sh/base": "^0.2.20",
|
|
36
|
+
"@armory-sh/extensions": "0.1.1",
|
|
31
37
|
"viem": "2.45.0"
|
|
32
38
|
},
|
|
33
39
|
"devDependencies": {
|