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