@armory-sh/client-viem 0.2.12 → 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 -60
- package/dist/index.js +90 -333
- package/package.json +3 -3
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,52 +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
|
-
/**
|
|
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
|
-
}
|
|
94
|
+
type X402Version = 2;
|
|
95
|
+
declare function detectX402Version(_response: Response): X402Version;
|
|
96
|
+
type ParsedPaymentRequirements = PaymentRequirementsV2;
|
|
107
97
|
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
98
|
|
|
129
99
|
/**
|
|
130
100
|
* Simple one-line payment API for Armory
|
|
@@ -166,8 +136,8 @@ declare const armoryPay: <T = unknown>(wallet: SimpleWallet, url: string, networ
|
|
|
166
136
|
body?: unknown;
|
|
167
137
|
/** Request headers */
|
|
168
138
|
headers?: Record<string, string>;
|
|
169
|
-
/** Protocol version (
|
|
170
|
-
version?:
|
|
139
|
+
/** Protocol version (V2 only) */
|
|
140
|
+
version?: 2;
|
|
171
141
|
/** Payment amount in token units (default: from 402 header) */
|
|
172
142
|
amount?: string;
|
|
173
143
|
/** Enable debug logging */
|
|
@@ -209,4 +179,4 @@ declare const getNetworks: () => string[];
|
|
|
209
179
|
*/
|
|
210
180
|
declare const getTokens: () => string[];
|
|
211
181
|
|
|
212
|
-
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,301 +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 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");
|
|
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");
|
|
97
45
|
}
|
|
98
46
|
try {
|
|
99
|
-
const decoded =
|
|
47
|
+
const decoded = Buffer.from(v2Header, "base64").toString("utf-8");
|
|
100
48
|
const parsed = JSON.parse(decoded);
|
|
101
|
-
if (!
|
|
102
|
-
throw new PaymentError("Invalid x402
|
|
49
|
+
if (!isX402V2PaymentRequired(parsed)) {
|
|
50
|
+
throw new PaymentError("Invalid x402 V2 payment required format");
|
|
103
51
|
}
|
|
104
|
-
|
|
105
|
-
throw new PaymentError("No payment requirements found in accepts array");
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
version: 1,
|
|
109
|
-
requirements: parsed.accepts[0]
|
|
110
|
-
};
|
|
52
|
+
return parsed;
|
|
111
53
|
} catch (error) {
|
|
112
54
|
if (error instanceof PaymentError) throw error;
|
|
113
|
-
throw new PaymentError(`Failed to parse
|
|
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) {
|
|
120
|
-
if (wallet.type === "account" && !wallet.account.signTypedData) {
|
|
121
|
-
throw new SigningError("Account does not support signTypedData");
|
|
55
|
+
throw new PaymentError(`Failed to parse V2 PAYMENT-REQUIRED header: ${error}`);
|
|
122
56
|
}
|
|
123
|
-
const params = {
|
|
124
|
-
domain,
|
|
125
|
-
types,
|
|
126
|
-
primaryType: "TransferWithAuthorization",
|
|
127
|
-
message
|
|
128
|
-
};
|
|
129
|
-
if (wallet.type === "account") {
|
|
130
|
-
return wallet.account.signTypedData(params);
|
|
131
|
-
}
|
|
132
|
-
return wallet.walletClient.signTypedData({
|
|
133
|
-
...params,
|
|
134
|
-
account: wallet.walletClient.account
|
|
135
|
-
});
|
|
136
57
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
v: parseInt(sig.slice(128, 130), 16) + 27,
|
|
141
|
-
r: `0x${sig.slice(0, 64)}`,
|
|
142
|
-
s: `0x${sig.slice(64, 128)}`
|
|
143
|
-
};
|
|
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);
|
|
151
|
-
}
|
|
152
|
-
const net = getNetworkConfig(normalizeNetworkName(network));
|
|
153
|
-
if (net) {
|
|
154
|
-
return net.chainId;
|
|
155
|
-
}
|
|
156
|
-
throw new PaymentError(`Unsupported network: ${network}`);
|
|
58
|
+
function encodeX402Payment(payload) {
|
|
59
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
157
60
|
}
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!net) {
|
|
163
|
-
throw new PaymentError(`No network config found for chainId: ${chainId}`);
|
|
164
|
-
}
|
|
165
|
-
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");
|
|
166
65
|
}
|
|
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
|
-
};
|
|
196
|
-
return {
|
|
197
|
-
x402Version: 1,
|
|
198
|
-
scheme: "exact",
|
|
199
|
-
network,
|
|
200
|
-
payload
|
|
201
|
-
};
|
|
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
66
|
const authorization = {
|
|
209
|
-
from
|
|
67
|
+
from,
|
|
210
68
|
to: requirements.payTo,
|
|
211
|
-
value:
|
|
69
|
+
value: requirements.amount,
|
|
212
70
|
validAfter: "0",
|
|
213
|
-
validBefore: validBefore.toString(),
|
|
71
|
+
validBefore: (validBefore ?? Math.floor(Date.now() / 1e3 + 3600)).toString(),
|
|
214
72
|
nonce
|
|
215
73
|
};
|
|
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
74
|
return {
|
|
231
75
|
x402Version: 2,
|
|
232
|
-
|
|
233
|
-
|
|
76
|
+
scheme: requirements.scheme,
|
|
77
|
+
network: requirements.network,
|
|
78
|
+
payload: {
|
|
79
|
+
signature: "0x",
|
|
80
|
+
authorization
|
|
81
|
+
}
|
|
234
82
|
};
|
|
235
83
|
}
|
|
236
|
-
async function createX402Payment(wallet, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
|
|
237
|
-
if (parsed.version === 1) {
|
|
238
|
-
return createX402V1Payment(
|
|
239
|
-
wallet,
|
|
240
|
-
parsed.requirements,
|
|
241
|
-
fromAddress,
|
|
242
|
-
nonce,
|
|
243
|
-
validBefore,
|
|
244
|
-
domainName,
|
|
245
|
-
domainVersion
|
|
246
|
-
);
|
|
247
|
-
}
|
|
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
84
|
|
|
265
85
|
// src/client.ts
|
|
266
86
|
var DEFAULT_EXPIRY = 3600;
|
|
87
|
+
var toCaip2Id = (chainId) => `eip155:${chainId}`;
|
|
267
88
|
var toProtocolWallet = (wallet) => wallet.type === "account" ? { type: "account", account: wallet.account } : { type: "walletClient", walletClient: wallet.walletClient };
|
|
268
89
|
var generateNonce = (nonceGenerator) => {
|
|
269
90
|
const nonce = nonceGenerator?.() ?? Date.now().toString();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
var checkSettlement = (response, version) => {
|
|
273
|
-
const v1Header = response.headers.get(V1_HEADERS2.PAYMENT_RESPONSE);
|
|
274
|
-
const v2Header = response.headers.get(V2_HEADERS2.PAYMENT_RESPONSE);
|
|
275
|
-
if (version === 1 && v1Header) {
|
|
276
|
-
try {
|
|
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;
|
|
283
|
-
}
|
|
284
|
-
} else if (version === 2 && v2Header) {
|
|
285
|
-
try {
|
|
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;
|
|
292
|
-
}
|
|
91
|
+
if (nonce.startsWith("0x")) {
|
|
92
|
+
return nonce;
|
|
293
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);
|
|
294
104
|
};
|
|
295
|
-
var
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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;
|
|
300
117
|
}
|
|
301
118
|
};
|
|
302
119
|
var createFetch = (wallet, config) => {
|
|
303
|
-
const { version = "auto", nonceGenerator, debug = false, domainName, domainVersion } = config;
|
|
304
120
|
const protocolWallet = toProtocolWallet(wallet);
|
|
305
121
|
const getAddress = () => getWalletAddress(protocolWallet);
|
|
122
|
+
const { defaultExpiry, nonceGenerator, debug, domainName, domainVersion } = config;
|
|
306
123
|
return async (input, init) => {
|
|
307
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
308
|
-
if (debug) {
|
|
309
|
-
console.log(`[X402] Fetching: ${url}`);
|
|
310
|
-
}
|
|
311
124
|
let response = await fetch(input, init);
|
|
312
125
|
if (response.status === 402) {
|
|
313
126
|
if (debug) {
|
|
314
|
-
console.log(
|
|
315
|
-
}
|
|
316
|
-
let detectedVersion;
|
|
317
|
-
if (version === "auto") {
|
|
318
|
-
detectedVersion = detectX402Version(response);
|
|
319
|
-
} else {
|
|
320
|
-
detectedVersion = version;
|
|
321
|
-
}
|
|
322
|
-
if (debug) {
|
|
323
|
-
console.log(`[X402] Detected protocol v${detectedVersion}`);
|
|
127
|
+
console.log("[X402] Payment required");
|
|
324
128
|
}
|
|
325
129
|
const parsed = parsePaymentRequired(response);
|
|
326
130
|
if (debug) {
|
|
327
|
-
console.log(
|
|
131
|
+
console.log("[X402] Payment requirements:", parsed);
|
|
328
132
|
}
|
|
329
133
|
const fromAddress = getAddress();
|
|
330
134
|
const nonce = generateNonce(nonceGenerator);
|
|
331
|
-
const validBefore = Math.floor(Date.now() / 1e3) +
|
|
135
|
+
const validBefore = Math.floor(Date.now() / 1e3) + defaultExpiry;
|
|
332
136
|
const payment = await createX402Payment(
|
|
333
137
|
protocolWallet,
|
|
334
138
|
parsed,
|
|
@@ -339,22 +143,21 @@ var createFetch = (wallet, config) => {
|
|
|
339
143
|
domainVersion
|
|
340
144
|
);
|
|
341
145
|
if (debug) {
|
|
342
|
-
console.log(
|
|
146
|
+
console.log("[X402] Created payment payload");
|
|
343
147
|
}
|
|
344
148
|
const headers = new Headers(init?.headers);
|
|
345
|
-
addPaymentHeader(headers, payment
|
|
149
|
+
addPaymentHeader(headers, payment);
|
|
346
150
|
response = await fetch(input, { ...init, headers });
|
|
347
151
|
if (debug) {
|
|
348
152
|
console.log(`[X402] Payment response status: ${response.status}`);
|
|
349
153
|
}
|
|
350
|
-
checkSettlement(response
|
|
154
|
+
checkSettlement(response);
|
|
351
155
|
}
|
|
352
156
|
return response;
|
|
353
157
|
};
|
|
354
158
|
};
|
|
355
|
-
var extractDomainConfig = (config) => config.token ? { domainName: config.token.name, domainVersion: config.token.version } : { domainName: config.domainName, domainVersion: config.domainVersion };
|
|
356
159
|
var createX402Client = (config) => {
|
|
357
|
-
const { wallet, version =
|
|
160
|
+
const { wallet, version = 2, defaultExpiry = DEFAULT_EXPIRY, nonceGenerator } = config;
|
|
358
161
|
const { domainName, domainVersion } = extractDomainConfig(config);
|
|
359
162
|
const protocolWallet = toProtocolWallet(wallet);
|
|
360
163
|
const getAddress = () => getWalletAddress(protocolWallet);
|
|
@@ -373,91 +176,51 @@ var createX402Client = (config) => {
|
|
|
373
176
|
const from = getAddress();
|
|
374
177
|
const nonce = generateNonce(nonceGenerator);
|
|
375
178
|
const validBefore = expiry ?? Math.floor(Date.now() / 1e3) + defaultExpiry;
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const networkSlug = networkConfig ? normalizeNetworkName2(networkConfig.name) : `eip155:${chainId}`;
|
|
379
|
-
if (targetVersion === 1) {
|
|
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);
|
|
392
|
-
}
|
|
179
|
+
const networkConfig = getNetworkByChainId(chainId);
|
|
180
|
+
const caip2Network = networkConfig?.caip2Id ?? toCaip2Id(chainId);
|
|
393
181
|
const requirements = {
|
|
394
182
|
scheme: "exact",
|
|
395
|
-
network:
|
|
183
|
+
network: caip2Network,
|
|
396
184
|
amount,
|
|
397
185
|
asset: contractAddress,
|
|
398
186
|
payTo: to,
|
|
399
|
-
maxTimeoutSeconds:
|
|
187
|
+
maxTimeoutSeconds: validBefore - Math.floor(Date.now() / 1e3)
|
|
400
188
|
};
|
|
401
|
-
|
|
402
|
-
return createX402Payment(protocolWallet, parsed, from, nonce, validBefore, domainName, domainVersion);
|
|
189
|
+
return createX402Payment(protocolWallet, requirements, from, nonce, validBefore, domainName, domainVersion);
|
|
403
190
|
},
|
|
404
191
|
async signPayment(payload) {
|
|
405
192
|
const from = getAddress();
|
|
406
|
-
const nonce =
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"chainId" in payload && typeof payload.chainId === "number" ? payload.chainId : 84532
|
|
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);
|
|
427
|
-
}
|
|
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;
|
|
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);
|
|
434
197
|
const requirements = {
|
|
435
198
|
scheme: "exact",
|
|
436
|
-
network,
|
|
437
|
-
amount,
|
|
438
|
-
asset,
|
|
439
|
-
payTo: to,
|
|
440
|
-
maxTimeoutSeconds:
|
|
199
|
+
network: caip2Network,
|
|
200
|
+
amount: payload.amount,
|
|
201
|
+
asset: payload.contractAddress,
|
|
202
|
+
payTo: payload.to,
|
|
203
|
+
maxTimeoutSeconds: validBefore - Math.floor(Date.now() / 1e3)
|
|
441
204
|
};
|
|
442
|
-
|
|
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);
|
|
205
|
+
return createX402Payment(protocolWallet, requirements, from, nonce, validBefore, domainName, domainVersion);
|
|
445
206
|
}
|
|
446
207
|
};
|
|
447
208
|
};
|
|
448
209
|
var createX402Transport = (config) => {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
version,
|
|
452
|
-
defaultExpiry,
|
|
453
|
-
nonceGenerator,
|
|
454
|
-
debug,
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
457
219
|
});
|
|
220
|
+
return client.fetch;
|
|
458
221
|
};
|
|
459
222
|
|
|
460
|
-
// src/
|
|
223
|
+
// src/payment-api.ts
|
|
461
224
|
import {
|
|
462
225
|
resolveNetwork,
|
|
463
226
|
resolveToken,
|
|
@@ -480,7 +243,7 @@ var armoryPay = async (wallet, url, network, token, options) => {
|
|
|
480
243
|
}
|
|
481
244
|
const client = createX402Client({
|
|
482
245
|
wallet: x402Wallet,
|
|
483
|
-
version: options?.version ??
|
|
246
|
+
version: options?.version ?? 2,
|
|
484
247
|
token: config.token.config,
|
|
485
248
|
debug: options?.debug ?? false
|
|
486
249
|
});
|
|
@@ -570,25 +333,19 @@ var getTokens = () => {
|
|
|
570
333
|
};
|
|
571
334
|
|
|
572
335
|
// src/index.ts
|
|
573
|
-
import {
|
|
336
|
+
import { V2_HEADERS as V2_HEADERS3 } from "@armory-sh/base";
|
|
574
337
|
export {
|
|
575
338
|
PaymentError,
|
|
576
339
|
SigningError,
|
|
577
|
-
V1_HEADERS3 as V1_HEADERS,
|
|
578
340
|
V2_HEADERS3 as V2_HEADERS,
|
|
579
341
|
X402ClientError,
|
|
580
342
|
armoryGet,
|
|
581
343
|
armoryPay,
|
|
582
344
|
armoryPost,
|
|
583
345
|
createX402Client,
|
|
584
|
-
createX402Payment,
|
|
585
346
|
createX402Transport,
|
|
586
|
-
createX402V1Payment,
|
|
587
|
-
createX402V2Payment,
|
|
588
347
|
detectX402Version,
|
|
589
|
-
encodeX402Payment,
|
|
590
348
|
getNetworks,
|
|
591
|
-
getPaymentHeaderName,
|
|
592
349
|
getTokens,
|
|
593
350
|
getWalletAddress2 as getWalletAddress,
|
|
594
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": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"bun-types": "latest"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"build": "tsup",
|
|
38
|
+
"build": "rm -rf dist && tsup",
|
|
39
39
|
"test": "bun test",
|
|
40
40
|
"example": "bun run examples/"
|
|
41
41
|
}
|