@armory-sh/client-viem 0.2.4 → 0.2.12

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
@@ -1,8 +1,12 @@
1
1
  import { Address, Account, WalletClient, Transport } from 'viem';
2
- import { CustomToken, PaymentPayloadV1, PaymentPayloadV2, NetworkId, TokenId, ArmoryPaymentResult, ValidationError } from '@armory-sh/base';
3
- export { ArmoryPaymentResult, FacilitatorConfig, NetworkId, TokenId } from '@armory-sh/base';
2
+ import { CustomToken, X402PaymentPayloadV1, PaymentPayloadV2, X402PaymentRequirementsV1, PaymentRequirementsV2, NetworkId, TokenId, ArmoryPaymentResult, ValidationError } from '@armory-sh/base';
3
+ export { ArmoryPaymentResult, EIP3009Authorization, EIP3009AuthorizationV1, FacilitatorConfig, NetworkId, PaymentPayloadV2, PaymentRequiredV2, PaymentRequirementsV2, ResourceInfo, SchemePayloadV2, SettlementResponseV2, TokenId, V1_HEADERS, V2_HEADERS, X402PaymentPayloadV1, X402PaymentRequiredV1, X402PaymentRequirementsV1, X402SchemePayloadV1, X402SettlementResponseV1 } from '@armory-sh/base';
4
4
 
5
- type X402Wallet = {
5
+ /**
6
+ * X402 Client Types - V1 and V2 Compatible
7
+ */
8
+
9
+ type X402Wallet$1 = {
6
10
  type: "account";
7
11
  account: Account;
8
12
  } | {
@@ -13,7 +17,7 @@ type X402ProtocolVersion = 1 | 2 | "auto";
13
17
  /** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
14
18
  type Token = CustomToken;
15
19
  interface X402ClientConfig {
16
- wallet: X402Wallet;
20
+ wallet: X402Wallet$1;
17
21
  version?: X402ProtocolVersion;
18
22
  defaultExpiry?: number;
19
23
  nonceGenerator?: () => string;
@@ -28,11 +32,11 @@ interface X402ClientConfig {
28
32
  interface X402Client {
29
33
  fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
30
34
  getAddress(): Address;
31
- createPayment(amount: string, to: Address, contractAddress: Address, chainId: number, expiry?: number): Promise<PaymentPayloadV1 | PaymentPayloadV2>;
32
- signPayment<T extends PaymentPayloadV1 | PaymentPayloadV2>(payload: Omit<T, "signature" | "v" | "r" | "s">): Promise<T>;
35
+ createPayment(amount: string, to: Address, contractAddress: Address, chainId: number, expiry?: number): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
36
+ signPayment(payload: Omit<X402PaymentPayloadV1 | PaymentPayloadV2, "signature" | "v" | "r" | "s">): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
33
37
  }
34
38
  interface X402TransportConfig {
35
- wallet: X402Wallet;
39
+ wallet: X402Wallet$1;
36
40
  transport?: Transport;
37
41
  version?: X402ProtocolVersion;
38
42
  defaultExpiry?: number;
@@ -52,8 +56,16 @@ interface PaymentResult {
52
56
  timestamp: number;
53
57
  }
54
58
 
59
+ /**
60
+ * X402 Client for Viem - Full V1 and V2 Support
61
+ *
62
+ * Handles x402 V1 (Base64 encoded) and V2 (JSON) protocols.
63
+ * Uses protocol.ts for parsing and creating payment payloads.
64
+ */
65
+
66
+ declare const createFetch: (wallet: X402Wallet$1, config: Omit<X402ClientConfig, "wallet">) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
55
67
  declare const createX402Client: (config: X402ClientConfig) => X402Client;
56
- declare const createX402Transport: (config: X402TransportConfig) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
68
+ declare const createX402Transport: (config: X402TransportConfig) => ReturnType<typeof createFetch>;
57
69
 
58
70
  declare class X402ClientError extends Error {
59
71
  readonly cause?: unknown;
@@ -66,6 +78,54 @@ declare class PaymentError extends X402ClientError {
66
78
  constructor(message: string, cause?: unknown);
67
79
  }
68
80
 
81
+ /**
82
+ * X402 Protocol Implementation for Viem Client
83
+ *
84
+ * Handles parsing x402 V1 and V2 PAYMENT-REQUIRED headers
85
+ * and generating x402 V1 and V2 PAYMENT-SIGNATURE payloads
86
+ */
87
+
88
+ type X402Wallet = {
89
+ type: "account";
90
+ account: Account;
91
+ } | {
92
+ type: "walletClient";
93
+ walletClient: WalletClient;
94
+ };
95
+ /**
96
+ * Detect x402 protocol version from response headers
97
+ */
98
+ declare function detectX402Version(response: Response): 1 | 2;
99
+ /**
100
+ * Parse x402 PAYMENT-REQUIRED header from response
101
+ * Automatically detects V1 or V2 format
102
+ */
103
+ interface ParsedPaymentRequirements {
104
+ version: 1 | 2;
105
+ requirements: X402PaymentRequirementsV1 | PaymentRequirementsV2;
106
+ }
107
+ declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
108
+ /**
109
+ * Create x402 V1 payment payload
110
+ */
111
+ declare function createX402V1Payment(wallet: X402Wallet, requirements: X402PaymentRequirementsV1, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1>;
112
+ /**
113
+ * Create x402 V2 payment payload
114
+ */
115
+ declare function createX402V2Payment(wallet: X402Wallet, requirements: PaymentRequirementsV2, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
116
+ /**
117
+ * Create x402 payment payload (auto-detects version)
118
+ */
119
+ declare function createX402Payment(wallet: X402Wallet, parsed: ParsedPaymentRequirements, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
120
+ /**
121
+ * Encode x402 payment payload to Base64 for transport
122
+ */
123
+ declare function encodeX402Payment(payload: X402PaymentPayloadV1 | PaymentPayloadV2): string;
124
+ /**
125
+ * Get the correct header name for payment based on version
126
+ */
127
+ declare function getPaymentHeaderName(version: 1 | 2): string;
128
+
69
129
  /**
70
130
  * Simple one-line payment API for Armory
71
131
  * Focus on DX/UX - "everything just magically works"
@@ -149,4 +209,4 @@ declare const getNetworks: () => string[];
149
209
  */
150
210
  declare const getTokens: () => string[];
151
211
 
152
- export { PaymentError, type PaymentResult, SigningError, type SimpleWallet, type Token, type X402Client, type X402ClientConfig, X402ClientError, type X402ProtocolVersion, type X402TransportConfig, type X402Wallet, armoryGet, armoryPay, armoryPost, createX402Client, createX402Transport, getNetworks, getTokens, getWalletAddress, validateNetwork, validateToken };
212
+ export { type ParsedPaymentRequirements, PaymentError, type PaymentResult, type X402Wallet as ProtocolWallet, SigningError, type SimpleWallet, type Token, type X402Client, type X402ClientConfig, X402ClientError, type X402ProtocolVersion, type X402TransportConfig, type X402Wallet$1 as X402Wallet, armoryGet, armoryPay, armoryPost, createX402Client, createX402Payment, createX402Transport, createX402V1Payment, createX402V2Payment, detectX402Version, encodeX402Payment, getNetworks, getPaymentHeaderName, getTokens, getWalletAddress, parsePaymentRequired, validateNetwork, validateToken };
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  // src/client.ts
2
2
  import {
3
- encodePaymentV1,
4
- encodePaymentV2,
5
- V1_HEADERS,
6
- V2_HEADERS,
7
- createEIP712Domain,
8
- createTransferWithAuthorization,
9
- EIP712_TYPES
3
+ V1_HEADERS as V1_HEADERS2,
4
+ V2_HEADERS as V2_HEADERS2,
5
+ decodeSettlementV1,
6
+ decodeSettlementV2,
7
+ isX402V1Settlement,
8
+ isX402V2Settlement,
9
+ getNetworkByChainId as getNetworkByChainId2,
10
+ normalizeNetworkName as normalizeNetworkName2
10
11
  } from "@armory-sh/base";
11
12
 
12
13
  // src/errors.ts
@@ -31,19 +32,91 @@ var PaymentError = class extends X402ClientError {
31
32
  }
32
33
  };
33
34
 
34
- // src/client.ts
35
- var DEFAULT_EXPIRY = 3600;
36
- var DEFAULT_NONCE = () => `${Date.now()}`;
37
- var parseSignature = (signature) => {
38
- const sig = signature.slice(2);
39
- return {
40
- v: parseInt(sig.slice(128, 130), 16) + 27,
41
- r: `0x${sig.slice(0, 64)}`,
42
- s: `0x${sig.slice(64, 128)}`
43
- };
44
- };
45
- var getWalletAddress = (wallet) => wallet.type === "account" ? wallet.account.address : wallet.walletClient.account.address;
46
- var signTypedData = (wallet, domain, types, message) => {
35
+ // src/protocol.ts
36
+ import {
37
+ V1_HEADERS,
38
+ V2_HEADERS,
39
+ safeBase64Decode,
40
+ isX402V1PaymentRequired,
41
+ isX402V2PaymentRequired,
42
+ getNetworkByChainId,
43
+ getNetworkConfig,
44
+ createEIP712Domain,
45
+ createTransferWithAuthorization,
46
+ EIP712_TYPES,
47
+ normalizeNetworkName
48
+ } from "@armory-sh/base";
49
+ function detectX402Version(response) {
50
+ const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
51
+ if (v2Header) {
52
+ try {
53
+ const parsed = JSON.parse(v2Header);
54
+ if (parsed.x402Version === 2) return 2;
55
+ } catch {
56
+ }
57
+ }
58
+ const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
59
+ if (v1Header) {
60
+ try {
61
+ const decoded = safeBase64Decode(v1Header);
62
+ const parsed = JSON.parse(decoded);
63
+ if (parsed.x402Version === 1) return 1;
64
+ return 1;
65
+ } catch {
66
+ }
67
+ }
68
+ return 2;
69
+ }
70
+ function parsePaymentRequired(response) {
71
+ const version = detectX402Version(response);
72
+ if (version === 2) {
73
+ const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
74
+ if (!v2Header) {
75
+ throw new PaymentError("No PAYMENT-REQUIRED header found in 402 response");
76
+ }
77
+ try {
78
+ const parsed = JSON.parse(v2Header);
79
+ if (!isX402V2PaymentRequired(parsed)) {
80
+ throw new PaymentError("Invalid x402 V2 payment required format");
81
+ }
82
+ if (!parsed.accepts || parsed.accepts.length === 0) {
83
+ throw new PaymentError("No payment requirements found in accepts array");
84
+ }
85
+ return {
86
+ version: 2,
87
+ requirements: parsed.accepts[0]
88
+ };
89
+ } catch (error) {
90
+ if (error instanceof PaymentError) throw error;
91
+ throw new PaymentError(`Failed to parse V2 PAYMENT-REQUIRED header: ${error}`);
92
+ }
93
+ }
94
+ const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
95
+ if (!v1Header) {
96
+ throw new PaymentError("No X-PAYMENT-REQUIRED header found in 402 response");
97
+ }
98
+ try {
99
+ const decoded = safeBase64Decode(v1Header);
100
+ const parsed = JSON.parse(decoded);
101
+ if (!isX402V1PaymentRequired(parsed)) {
102
+ throw new PaymentError("Invalid x402 V1 payment required format");
103
+ }
104
+ if (!parsed.accepts || parsed.accepts.length === 0) {
105
+ throw new PaymentError("No payment requirements found in accepts array");
106
+ }
107
+ return {
108
+ version: 1,
109
+ requirements: parsed.accepts[0]
110
+ };
111
+ } catch (error) {
112
+ if (error instanceof PaymentError) throw error;
113
+ throw new PaymentError(`Failed to parse V1 PAYMENT-REQUIRED header: ${error}`);
114
+ }
115
+ }
116
+ function getWalletAddress(wallet) {
117
+ return wallet.type === "account" ? wallet.account.address : wallet.walletClient.account.address;
118
+ }
119
+ async function signTypedData(wallet, domain, types, message) {
47
120
  if (wallet.type === "account" && !wallet.account.signTypedData) {
48
121
  throw new SigningError("Account does not support signTypedData");
49
122
  }
@@ -53,151 +126,183 @@ var signTypedData = (wallet, domain, types, message) => {
53
126
  primaryType: "TransferWithAuthorization",
54
127
  message
55
128
  };
56
- return wallet.type === "account" ? wallet.account.signTypedData(params) : wallet.walletClient.signTypedData({ ...params, account: wallet.walletClient.account });
57
- };
58
- var withCustomDomain = (domain, domainName, domainVersion) => domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
59
- var toNetworkName = (chainId) => chainId === 1 ? "ethereum" : chainId === 8453 ? "base" : "evm";
60
- var createPaymentV1 = async (wallet, from, to, amount, chainId, contractAddress, nonce, expiry, domainName, domainVersion) => {
61
- const domain = createEIP712Domain(chainId, contractAddress);
62
- const value = createTransferWithAuthorization({
63
- from,
64
- to,
65
- value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
66
- validAfter: 0n,
67
- validBefore: BigInt(expiry),
68
- nonce: BigInt(nonce)
69
- });
70
- const signature = await signTypedData(wallet, withCustomDomain(domain, domainName, domainVersion), EIP712_TYPES, value);
71
- const { v, r, s } = parseSignature(signature);
72
- return { from, to, amount, nonce, expiry, v, r, s, chainId, contractAddress, network: toNetworkName(chainId) };
73
- };
74
- var createPaymentV2 = async (wallet, from, to, amount, chainId, assetId, nonce, expiry, domainName, domainVersion) => {
75
- const contractAddress = assetId.split(":")[2];
76
- const domain = createEIP712Domain(chainId, contractAddress);
77
- const value = createTransferWithAuthorization({
78
- from,
79
- to,
80
- value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
81
- validAfter: 0n,
82
- validBefore: BigInt(expiry),
83
- nonce: BigInt(nonce)
129
+ if (wallet.type === "account") {
130
+ return wallet.account.signTypedData(params);
131
+ }
132
+ return wallet.walletClient.signTypedData({
133
+ ...params,
134
+ account: wallet.walletClient.account
84
135
  });
85
- const signature = await signTypedData(wallet, withCustomDomain(domain, domainName, domainVersion), EIP712_TYPES, value);
86
- const { v, r, s } = parseSignature(signature);
136
+ }
137
+ function parseSignature(signature) {
138
+ const sig = signature.slice(2);
87
139
  return {
88
- from,
89
- to,
90
- amount,
91
- nonce,
92
- expiry,
93
- signature: { v, r, s },
94
- chainId: `eip155:${chainId}`,
95
- assetId
140
+ v: parseInt(sig.slice(128, 130), 16) + 27,
141
+ r: `0x${sig.slice(0, 64)}`,
142
+ s: `0x${sig.slice(64, 128)}`
96
143
  };
97
- };
98
- var detectVersion = (response, version) => {
99
- if (version === 1) return 1;
100
- if (version === 2) return 2;
101
- const headers = response.headers;
102
- if (headers.has(V1_HEADERS.PAYMENT_RESPONSE) || headers.has("X-PAYMENT-REQUIRED")) {
103
- return 1;
144
+ }
145
+ function toAtomicUnits(amount) {
146
+ return Math.floor(parseFloat(amount) * 1e6).toString();
147
+ }
148
+ function extractChainId(network) {
149
+ if (network.startsWith("eip155:")) {
150
+ return parseInt(network.split(":")[1], 10);
104
151
  }
105
- return 2;
106
- };
107
- var parseRequirements = (response, version) => {
108
- if (version === 1) {
109
- const text = response.headers.get("X-PAYMENT-REQUIRED");
110
- if (text) {
111
- try {
112
- const json = atob(text);
113
- return JSON.parse(json);
114
- } catch {
115
- throw new PaymentError("Failed to decode v1 payment requirements");
116
- }
117
- }
118
- return {
119
- amount: "1.0",
120
- network: "base",
121
- contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
122
- payTo: "0x0000000000000000000000000000000000000000",
123
- expiry: Math.floor(Date.now() / 1e3) + 3600
124
- };
152
+ const net = getNetworkConfig(normalizeNetworkName(network));
153
+ if (net) {
154
+ return net.chainId;
125
155
  }
126
- const v2Text = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
127
- if (v2Text) {
128
- try {
129
- return JSON.parse(v2Text);
130
- } catch {
131
- throw new PaymentError("Failed to decode v2 payment requirements");
156
+ throw new PaymentError(`Unsupported network: ${network}`);
157
+ }
158
+ function getNetworkSlug(network) {
159
+ if (network.startsWith("eip155:")) {
160
+ const chainId = parseInt(network.split(":")[1], 10);
161
+ const net = getNetworkByChainId(chainId);
162
+ if (!net) {
163
+ throw new PaymentError(`No network config found for chainId: ${chainId}`);
132
164
  }
165
+ return normalizeNetworkName(net.name);
133
166
  }
167
+ return normalizeNetworkName(network);
168
+ }
169
+ async function createX402V1Payment(wallet, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
170
+ const network = getNetworkSlug(requirements.network);
171
+ const contractAddress = requirements.asset;
172
+ const domain = createEIP712Domain(extractChainId(requirements.network), contractAddress);
173
+ const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
174
+ const authorization = {
175
+ from: fromAddress,
176
+ to: requirements.payTo,
177
+ value: toAtomicUnits(requirements.maxAmountRequired),
178
+ validAfter: "0",
179
+ validBefore: validBefore.toString(),
180
+ nonce
181
+ };
182
+ const value = createTransferWithAuthorization({
183
+ from: authorization.from,
184
+ to: authorization.to,
185
+ value: BigInt(authorization.value),
186
+ validAfter: BigInt(authorization.validAfter),
187
+ validBefore: BigInt(authorization.validBefore),
188
+ nonce: BigInt(authorization.nonce)
189
+ });
190
+ const signature = await signTypedData(wallet, customDomain, EIP712_TYPES, value);
191
+ const { v, r, s } = parseSignature(signature);
192
+ const payload = {
193
+ signature: `0x${r.slice(2)}${s.slice(2)}${v.toString(16).padStart(2, "0")}`,
194
+ authorization
195
+ };
134
196
  return {
135
- amount: "1.0",
136
- to: "0x0000000000000000000000000000000000000000",
137
- chainId: "eip155:8453",
138
- assetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
139
- nonce: `${Date.now()}`,
140
- expiry: Math.floor(Date.now() / 1e3) + 3600
197
+ x402Version: 1,
198
+ scheme: "exact",
199
+ network,
200
+ payload
141
201
  };
142
- };
143
- var createPaymentFromRequirements = async (wallet, requirements, version, getAddress, nonceGenerator, domainName, domainVersion) => {
144
- const from = getAddress();
145
- const nonce = nonceGenerator();
146
- const expiry = requirements.expiry;
147
- if (version === 1) {
148
- const req2 = requirements;
149
- return createPaymentV1(
202
+ }
203
+ async function createX402V2Payment(wallet, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
204
+ const contractAddress = requirements.asset;
205
+ const network = requirements.network;
206
+ const domain = createEIP712Domain(extractChainId(network), contractAddress);
207
+ const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
208
+ const authorization = {
209
+ from: fromAddress,
210
+ to: requirements.payTo,
211
+ value: toAtomicUnits(requirements.amount),
212
+ validAfter: "0",
213
+ validBefore: validBefore.toString(),
214
+ nonce
215
+ };
216
+ const value = createTransferWithAuthorization({
217
+ from: authorization.from,
218
+ to: authorization.to,
219
+ value: BigInt(authorization.value),
220
+ validAfter: BigInt(authorization.validAfter),
221
+ validBefore: BigInt(authorization.validBefore),
222
+ nonce: BigInt(authorization.nonce)
223
+ });
224
+ const signature = await signTypedData(wallet, customDomain, EIP712_TYPES, value);
225
+ const { v, r, s } = parseSignature(signature);
226
+ const payload = {
227
+ signature: `0x${r.slice(2)}${s.slice(2)}${v.toString(16).padStart(2, "0")}`,
228
+ authorization
229
+ };
230
+ return {
231
+ x402Version: 2,
232
+ accepted: requirements,
233
+ payload
234
+ };
235
+ }
236
+ async function createX402Payment(wallet, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
237
+ if (parsed.version === 1) {
238
+ return createX402V1Payment(
150
239
  wallet,
151
- from,
152
- req2.payTo,
153
- req2.amount,
154
- req2.network === "base" ? 8453 : 1,
155
- req2.contractAddress,
240
+ parsed.requirements,
241
+ fromAddress,
156
242
  nonce,
157
- expiry,
243
+ validBefore,
158
244
  domainName,
159
245
  domainVersion
160
246
  );
161
247
  }
162
- const req = requirements;
163
- const chainId = parseInt(req.chainId.split(":")[1], 10);
164
- const to = typeof req.to === "string" ? req.to : "0x0000000000000000000000000000000000000000";
165
- return createPaymentV2(wallet, from, to, req.amount, chainId, req.assetId, nonce, expiry, domainName, domainVersion);
166
- };
167
- var addPaymentHeader = (headers, payment, version) => {
168
- if (version === 1) {
169
- headers.set(V1_HEADERS.PAYMENT, encodePaymentV1(payment));
170
- } else {
171
- headers.set(V2_HEADERS.PAYMENT_SIGNATURE, encodePaymentV2(payment));
172
- }
248
+ return createX402V2Payment(
249
+ wallet,
250
+ parsed.requirements,
251
+ fromAddress,
252
+ nonce,
253
+ validBefore,
254
+ domainName,
255
+ domainVersion
256
+ );
257
+ }
258
+ function encodeX402Payment(payload) {
259
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
260
+ }
261
+ function getPaymentHeaderName(version) {
262
+ return version === 1 ? V1_HEADERS.PAYMENT : V2_HEADERS.PAYMENT_SIGNATURE;
263
+ }
264
+
265
+ // src/client.ts
266
+ var DEFAULT_EXPIRY = 3600;
267
+ var toProtocolWallet = (wallet) => wallet.type === "account" ? { type: "account", account: wallet.account } : { type: "walletClient", walletClient: wallet.walletClient };
268
+ var generateNonce = (nonceGenerator) => {
269
+ const nonce = nonceGenerator?.() ?? Date.now().toString();
270
+ return nonce.startsWith("0x") ? nonce : `0x${nonce.padStart(64, "0")}`;
173
271
  };
174
272
  var checkSettlement = (response, version) => {
175
- const v1Header = response.headers.get(V1_HEADERS.PAYMENT_RESPONSE);
176
- const v2Header = response.headers.get(V2_HEADERS.PAYMENT_RESPONSE);
177
- let settlement = null;
273
+ const v1Header = response.headers.get(V1_HEADERS2.PAYMENT_RESPONSE);
274
+ const v2Header = response.headers.get(V2_HEADERS2.PAYMENT_RESPONSE);
178
275
  if (version === 1 && v1Header) {
179
276
  try {
180
- const json = atob(v1Header);
181
- settlement = JSON.parse(json);
182
- } catch {
277
+ const settlement = decodeSettlementV1(v1Header);
278
+ if (isX402V1Settlement(settlement) && !settlement.success) {
279
+ throw new PaymentError(settlement.errorReason ?? "Payment settlement failed");
280
+ }
281
+ } catch (error) {
282
+ if (error instanceof PaymentError) throw error;
183
283
  }
184
284
  } else if (version === 2 && v2Header) {
185
285
  try {
186
- settlement = JSON.parse(v2Header);
187
- } catch {
286
+ const settlement = decodeSettlementV2(v2Header);
287
+ if (isX402V2Settlement(settlement) && !settlement.success) {
288
+ throw new PaymentError(settlement.errorReason ?? "Payment settlement failed");
289
+ }
290
+ } catch (error) {
291
+ if (error instanceof PaymentError) throw error;
188
292
  }
189
293
  }
190
- if (settlement) {
191
- const success = "success" in settlement ? settlement.success : settlement.status === "success";
192
- if (!success) {
193
- const error = "error" in settlement ? settlement.error : "Payment settlement failed";
194
- throw new PaymentError(error);
195
- }
294
+ };
295
+ var addPaymentHeader = (headers, payment, version) => {
296
+ if (version === 1) {
297
+ headers.set(V1_HEADERS2.PAYMENT, encodeX402Payment(payment));
298
+ } else {
299
+ headers.set(V2_HEADERS2.PAYMENT_SIGNATURE, JSON.stringify(payment));
196
300
  }
197
301
  };
198
302
  var createFetch = (wallet, config) => {
199
- const { version = "auto", nonceGenerator = DEFAULT_NONCE, debug = false, domainName, domainVersion } = config;
200
- const getAddress = () => getWalletAddress(wallet);
303
+ const { version = "auto", nonceGenerator, debug = false, domainName, domainVersion } = config;
304
+ const protocolWallet = toProtocolWallet(wallet);
305
+ const getAddress = () => getWalletAddress(protocolWallet);
201
306
  return async (input, init) => {
202
307
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
203
308
  if (debug) {
@@ -208,89 +313,148 @@ var createFetch = (wallet, config) => {
208
313
  if (debug) {
209
314
  console.log(`[X402] Payment required for: ${url}`);
210
315
  }
211
- const detectedVersion = detectVersion(response, version);
316
+ let detectedVersion;
317
+ if (version === "auto") {
318
+ detectedVersion = detectX402Version(response);
319
+ } else {
320
+ detectedVersion = version;
321
+ }
212
322
  if (debug) {
213
323
  console.log(`[X402] Detected protocol v${detectedVersion}`);
214
324
  }
215
- const requirements = parseRequirements(response, detectedVersion);
325
+ const parsed = parsePaymentRequired(response);
216
326
  if (debug) {
217
- console.log(`[X402] Payment requirements:`, requirements);
327
+ console.log(`[X402] Payment requirements:`, parsed.requirements);
218
328
  }
219
- const payment = await createPaymentFromRequirements(wallet, requirements, detectedVersion, getAddress, nonceGenerator, domainName, domainVersion);
329
+ const fromAddress = getAddress();
330
+ const nonce = generateNonce(nonceGenerator);
331
+ const validBefore = Math.floor(Date.now() / 1e3) + (config.defaultExpiry ?? DEFAULT_EXPIRY);
332
+ const payment = await createX402Payment(
333
+ protocolWallet,
334
+ parsed,
335
+ fromAddress,
336
+ nonce,
337
+ validBefore,
338
+ domainName,
339
+ domainVersion
340
+ );
220
341
  if (debug) {
221
- console.log(`[X402] Created payment:`, payment);
342
+ console.log(`[X402] Created payment payload`);
222
343
  }
223
344
  const headers = new Headers(init?.headers);
224
- addPaymentHeader(headers, payment, detectedVersion);
345
+ addPaymentHeader(headers, payment, parsed.version);
225
346
  response = await fetch(input, { ...init, headers });
226
347
  if (debug) {
227
348
  console.log(`[X402] Payment response status: ${response.status}`);
228
349
  }
229
- checkSettlement(response, detectedVersion);
350
+ checkSettlement(response, parsed.version);
230
351
  }
231
352
  return response;
232
353
  };
233
354
  };
234
355
  var extractDomainConfig = (config) => config.token ? { domainName: config.token.name, domainVersion: config.token.version } : { domainName: config.domainName, domainVersion: config.domainVersion };
235
356
  var createX402Client = (config) => {
236
- const { wallet, version = "auto", defaultExpiry = DEFAULT_EXPIRY, nonceGenerator = DEFAULT_NONCE } = config;
357
+ const { wallet, version = "auto", defaultExpiry = DEFAULT_EXPIRY, nonceGenerator } = config;
237
358
  const { domainName, domainVersion } = extractDomainConfig(config);
238
- const getAddress = () => getWalletAddress(wallet);
239
- const fetchFn = createFetch(wallet, { version, defaultExpiry, nonceGenerator, debug: config.debug, domainName, domainVersion });
359
+ const protocolWallet = toProtocolWallet(wallet);
360
+ const getAddress = () => getWalletAddress(protocolWallet);
361
+ const fetchFn = createFetch(wallet, {
362
+ version,
363
+ defaultExpiry,
364
+ nonceGenerator,
365
+ debug: config.debug,
366
+ domainName,
367
+ domainVersion
368
+ });
240
369
  return {
241
370
  fetch: fetchFn,
242
371
  getAddress,
243
372
  async createPayment(amount, to, contractAddress, chainId, expiry) {
244
373
  const from = getAddress();
245
- const nonce = nonceGenerator();
246
- const validExpiry = expiry ?? Math.floor(Date.now() / 1e3) + defaultExpiry;
374
+ const nonce = generateNonce(nonceGenerator);
375
+ const validBefore = expiry ?? Math.floor(Date.now() / 1e3) + defaultExpiry;
247
376
  const targetVersion = version === "auto" ? 1 : version;
377
+ const networkConfig = getNetworkByChainId2(chainId);
378
+ const networkSlug = networkConfig ? normalizeNetworkName2(networkConfig.name) : `eip155:${chainId}`;
248
379
  if (targetVersion === 1) {
249
- return createPaymentV1(wallet, from, to, amount, chainId, contractAddress, nonce, validExpiry, domainName, domainVersion);
380
+ const requirements2 = {
381
+ scheme: "exact",
382
+ network: networkSlug,
383
+ maxAmountRequired: amount,
384
+ asset: contractAddress,
385
+ payTo: to,
386
+ resource: "",
387
+ description: "",
388
+ maxTimeoutSeconds: defaultExpiry
389
+ };
390
+ const parsed2 = { version: 1, requirements: requirements2 };
391
+ return createX402Payment(protocolWallet, parsed2, from, nonce, validBefore, domainName, domainVersion);
250
392
  }
251
- const assetId = `eip155:${chainId}/erc20:${contractAddress}`;
252
- return createPaymentV2(wallet, from, to, amount, chainId, assetId, nonce, validExpiry, domainName, domainVersion);
393
+ const requirements = {
394
+ scheme: "exact",
395
+ network: `eip155:${chainId}`,
396
+ amount,
397
+ asset: contractAddress,
398
+ payTo: to,
399
+ maxTimeoutSeconds: defaultExpiry
400
+ };
401
+ const parsed = { version: 2, requirements };
402
+ return createX402Payment(protocolWallet, parsed, from, nonce, validBefore, domainName, domainVersion);
253
403
  },
254
404
  async signPayment(payload) {
255
405
  const from = getAddress();
256
- const chainId = typeof payload.chainId === "number" ? payload.chainId : parseInt(payload.chainId.split(":")[1], 10);
257
- const to = payload.to;
258
- const expiry = payload.expiry;
259
- const nonce = nonceGenerator();
260
- if ("contractAddress" in payload) {
261
- const input2 = payload;
262
- return createPaymentV1(
263
- wallet,
264
- from,
265
- to,
266
- input2.amount,
267
- chainId,
268
- input2.contractAddress,
269
- nonce,
270
- expiry,
271
- domainName,
272
- domainVersion
406
+ const nonce = generateNonce(nonceGenerator);
407
+ const hasContractAddress = (p) => typeof p === "object" && p !== null && "contractAddress" in p;
408
+ if (hasContractAddress(payload)) {
409
+ const networkConfig = getNetworkByChainId2(
410
+ "chainId" in payload && typeof payload.chainId === "number" ? payload.chainId : 84532
273
411
  );
412
+ const networkSlug = networkConfig ? normalizeNetworkName2(networkConfig.name) : "base";
413
+ const to2 = "to" in payload && typeof payload.to === "string" ? payload.to : "0x0000000000000000000000000000000000000000";
414
+ const requirements2 = {
415
+ scheme: "exact",
416
+ network: networkSlug,
417
+ maxAmountRequired: payload.amount,
418
+ asset: payload.contractAddress,
419
+ payTo: to2,
420
+ resource: "",
421
+ description: "",
422
+ maxTimeoutSeconds: defaultExpiry
423
+ };
424
+ const parsed2 = { version: 1, requirements: requirements2 };
425
+ const validBefore2 = payload.expiry ?? Math.floor(Date.now() / 1e3) + defaultExpiry;
426
+ return createX402Payment(protocolWallet, parsed2, from, nonce, validBefore2, domainName, domainVersion);
274
427
  }
275
- const input = payload;
276
- return createPaymentV2(
277
- wallet,
278
- from,
279
- to,
280
- input.amount,
281
- chainId,
282
- input.assetId,
283
- nonce,
284
- expiry,
285
- domainName,
286
- domainVersion
287
- );
428
+ const to = "payTo" in payload && typeof payload.payTo === "string" ? payload.payTo : "0x0000000000000000000000000000000000000000";
429
+ const amount = "amount" in payload && typeof payload.amount === "string" ? payload.amount : "1000000";
430
+ const network = "network" in payload && typeof payload.network === "string" ? payload.network : "eip155:8453";
431
+ const chainId = parseInt(network.split(":")[1], 10);
432
+ const defaultAsset = getNetworkByChainId2(chainId)?.usdcAddress ?? "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
433
+ const asset = "asset" in payload && typeof payload.asset === "string" ? payload.asset : defaultAsset;
434
+ const requirements = {
435
+ scheme: "exact",
436
+ network,
437
+ amount,
438
+ asset,
439
+ payTo: to,
440
+ maxTimeoutSeconds: defaultExpiry
441
+ };
442
+ const parsed = { version: 2, requirements };
443
+ const validBefore = "expiry" in payload && typeof payload.expiry === "number" ? payload.expiry : Math.floor(Date.now() / 1e3) + defaultExpiry;
444
+ return createX402Payment(protocolWallet, parsed, from, nonce, validBefore, domainName, domainVersion);
288
445
  }
289
446
  };
290
447
  };
291
448
  var createX402Transport = (config) => {
292
- const { wallet, transport, version, defaultExpiry, nonceGenerator, debug, token, domainName, domainVersion } = config;
293
- return createFetch(wallet, { version, defaultExpiry, nonceGenerator, debug, token, domainName, domainVersion });
449
+ const { wallet, version, defaultExpiry, nonceGenerator, debug, token, domainName, domainVersion } = config;
450
+ return createFetch(wallet, {
451
+ version,
452
+ defaultExpiry,
453
+ nonceGenerator,
454
+ debug,
455
+ domainName: domainName ?? token?.name,
456
+ domainVersion: domainVersion ?? token?.version
457
+ });
294
458
  };
295
459
 
296
460
  // src/simple.ts
@@ -404,18 +568,30 @@ var getNetworks = () => {
404
568
  var getTokens = () => {
405
569
  return getAvailableTokens();
406
570
  };
571
+
572
+ // src/index.ts
573
+ import { V1_HEADERS as V1_HEADERS3, V2_HEADERS as V2_HEADERS3 } from "@armory-sh/base";
407
574
  export {
408
575
  PaymentError,
409
576
  SigningError,
577
+ V1_HEADERS3 as V1_HEADERS,
578
+ V2_HEADERS3 as V2_HEADERS,
410
579
  X402ClientError,
411
580
  armoryGet,
412
581
  armoryPay,
413
582
  armoryPost,
414
583
  createX402Client,
584
+ createX402Payment,
415
585
  createX402Transport,
586
+ createX402V1Payment,
587
+ createX402V2Payment,
588
+ detectX402Version,
589
+ encodeX402Payment,
416
590
  getNetworks,
591
+ getPaymentHeaderName,
417
592
  getTokens,
418
593
  getWalletAddress2 as getWalletAddress,
594
+ parsePaymentRequired,
419
595
  validateNetwork,
420
596
  validateToken
421
597
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/client-viem",
3
- "version": "0.2.4",
3
+ "version": "0.2.12",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "directory": "packages/client-viem"
28
28
  },
29
29
  "dependencies": {
30
- "@armory-sh/base": "^0.2.4",
30
+ "@armory-sh/base": "^0.2.12",
31
31
  "viem": "2.45.0"
32
32
  },
33
33
  "devDependencies": {