@armory-sh/client-viem 0.2.9 → 0.2.13
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 +65 -9
- package/dist/index.js +363 -185
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Address, Account, WalletClient, Transport } from 'viem';
|
|
2
|
-
import { CustomToken,
|
|
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
|
-
|
|
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<
|
|
32
|
-
signPayment
|
|
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) =>
|
|
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,50 @@ 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
|
+
interface ParsedPaymentRequirements {
|
|
100
|
+
version: 1 | 2;
|
|
101
|
+
requirements: X402PaymentRequirementsV1 | PaymentRequirementsV2;
|
|
102
|
+
}
|
|
103
|
+
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
|
+
|
|
69
125
|
/**
|
|
70
126
|
* Simple one-line payment API for Armory
|
|
71
127
|
* Focus on DX/UX - "everything just magically works"
|
|
@@ -149,4 +205,4 @@ declare const getNetworks: () => string[];
|
|
|
149
205
|
*/
|
|
150
206
|
declare const getTokens: () => string[];
|
|
151
207
|
|
|
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 };
|
|
208
|
+
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,92 @@ var PaymentError = class extends X402ClientError {
|
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 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");
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const decoded = safeBase64Decode(v1Header);
|
|
101
|
+
const parsed = JSON.parse(decoded);
|
|
102
|
+
if (!isX402V1PaymentRequired(parsed)) {
|
|
103
|
+
throw new PaymentError("Invalid x402 V1 payment required format");
|
|
104
|
+
}
|
|
105
|
+
if (!parsed.accepts || parsed.accepts.length === 0) {
|
|
106
|
+
throw new PaymentError("No payment requirements found in accepts array");
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
version: 1,
|
|
110
|
+
requirements: parsed.accepts[0]
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error instanceof PaymentError) throw error;
|
|
114
|
+
throw new PaymentError(`Failed to parse V1 PAYMENT-REQUIRED header: ${error}`);
|
|
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) {
|
|
47
121
|
if (wallet.type === "account" && !wallet.account.signTypedData) {
|
|
48
122
|
throw new SigningError("Account does not support signTypedData");
|
|
49
123
|
}
|
|
@@ -53,151 +127,184 @@ var signTypedData = (wallet, domain, types, message) => {
|
|
|
53
127
|
primaryType: "TransferWithAuthorization",
|
|
54
128
|
message
|
|
55
129
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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)
|
|
130
|
+
if (wallet.type === "account") {
|
|
131
|
+
return wallet.account.signTypedData(params);
|
|
132
|
+
}
|
|
133
|
+
return wallet.walletClient.signTypedData({
|
|
134
|
+
...params,
|
|
135
|
+
account: wallet.walletClient.account
|
|
84
136
|
});
|
|
85
|
-
|
|
86
|
-
|
|
137
|
+
}
|
|
138
|
+
function parseSignature(signature) {
|
|
139
|
+
const sig = signature.slice(2);
|
|
87
140
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
nonce,
|
|
92
|
-
expiry,
|
|
93
|
-
signature: { v, r, s },
|
|
94
|
-
chainId: `eip155:${chainId}`,
|
|
95
|
-
assetId
|
|
141
|
+
v: parseInt(sig.slice(128, 130), 16) + 27,
|
|
142
|
+
r: `0x${sig.slice(0, 64)}`,
|
|
143
|
+
s: `0x${sig.slice(64, 128)}`
|
|
96
144
|
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
return 1;
|
|
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);
|
|
104
152
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
};
|
|
153
|
+
const net = getNetworkConfig(normalizeNetworkName(network));
|
|
154
|
+
if (net) {
|
|
155
|
+
return net.chainId;
|
|
125
156
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
throw new PaymentError(`Unsupported network: ${network}`);
|
|
158
|
+
}
|
|
159
|
+
function getNetworkSlug(network) {
|
|
160
|
+
if (network.startsWith("eip155:")) {
|
|
161
|
+
const chainId = parseInt(network.split(":")[1], 10);
|
|
162
|
+
const net = getNetworkByChainId(chainId);
|
|
163
|
+
if (!net) {
|
|
164
|
+
throw new PaymentError(`No network config found for chainId: ${chainId}`);
|
|
132
165
|
}
|
|
166
|
+
return normalizeNetworkName(net.name);
|
|
133
167
|
}
|
|
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
|
+
};
|
|
134
197
|
return {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
nonce: `${Date.now()}`,
|
|
140
|
-
expiry: Math.floor(Date.now() / 1e3) + 3600
|
|
198
|
+
x402Version: 1,
|
|
199
|
+
scheme: "exact",
|
|
200
|
+
network,
|
|
201
|
+
payload
|
|
141
202
|
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
const authorization = {
|
|
210
|
+
from: fromAddress,
|
|
211
|
+
to: requirements.payTo,
|
|
212
|
+
value: toAtomicUnits(requirements.amount),
|
|
213
|
+
validAfter: "0",
|
|
214
|
+
validBefore: validBefore.toString(),
|
|
215
|
+
nonce
|
|
216
|
+
};
|
|
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
|
+
return {
|
|
232
|
+
x402Version: 2,
|
|
233
|
+
scheme: requirements.scheme,
|
|
234
|
+
network: requirements.network,
|
|
235
|
+
payload
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function createX402Payment(wallet, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
|
|
239
|
+
if (parsed.version === 1) {
|
|
240
|
+
return createX402V1Payment(
|
|
150
241
|
wallet,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
req2.amount,
|
|
154
|
-
req2.network === "base" ? 8453 : 1,
|
|
155
|
-
req2.contractAddress,
|
|
242
|
+
parsed.requirements,
|
|
243
|
+
fromAddress,
|
|
156
244
|
nonce,
|
|
157
|
-
|
|
245
|
+
validBefore,
|
|
158
246
|
domainName,
|
|
159
247
|
domainVersion
|
|
160
248
|
);
|
|
161
249
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
|
|
267
|
+
// src/client.ts
|
|
268
|
+
var DEFAULT_EXPIRY = 3600;
|
|
269
|
+
var toProtocolWallet = (wallet) => wallet.type === "account" ? { type: "account", account: wallet.account } : { type: "walletClient", walletClient: wallet.walletClient };
|
|
270
|
+
var generateNonce = (nonceGenerator) => {
|
|
271
|
+
const nonce = nonceGenerator?.() ?? Date.now().toString();
|
|
272
|
+
return nonce.startsWith("0x") ? nonce : `0x${nonce.padStart(64, "0")}`;
|
|
173
273
|
};
|
|
174
274
|
var checkSettlement = (response, version) => {
|
|
175
|
-
const v1Header = response.headers.get(
|
|
176
|
-
const v2Header = response.headers.get(
|
|
177
|
-
let settlement = null;
|
|
275
|
+
const v1Header = response.headers.get(V1_HEADERS2.PAYMENT_RESPONSE);
|
|
276
|
+
const v2Header = response.headers.get(V2_HEADERS2.PAYMENT_RESPONSE);
|
|
178
277
|
if (version === 1 && v1Header) {
|
|
179
278
|
try {
|
|
180
|
-
const
|
|
181
|
-
settlement
|
|
182
|
-
|
|
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;
|
|
183
285
|
}
|
|
184
286
|
} else if (version === 2 && v2Header) {
|
|
185
287
|
try {
|
|
186
|
-
settlement =
|
|
187
|
-
|
|
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;
|
|
188
294
|
}
|
|
189
295
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
};
|
|
297
|
+
var addPaymentHeader = (headers, payment, version) => {
|
|
298
|
+
if (version === 1) {
|
|
299
|
+
headers.set(V1_HEADERS2.PAYMENT, encodeX402Payment(payment));
|
|
300
|
+
} else {
|
|
301
|
+
headers.set(V2_HEADERS2.PAYMENT_SIGNATURE, JSON.stringify(payment));
|
|
196
302
|
}
|
|
197
303
|
};
|
|
198
304
|
var createFetch = (wallet, config) => {
|
|
199
|
-
const { version = "auto", nonceGenerator
|
|
200
|
-
const
|
|
305
|
+
const { version = "auto", nonceGenerator, debug = false, domainName, domainVersion } = config;
|
|
306
|
+
const protocolWallet = toProtocolWallet(wallet);
|
|
307
|
+
const getAddress = () => getWalletAddress(protocolWallet);
|
|
201
308
|
return async (input, init) => {
|
|
202
309
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
203
310
|
if (debug) {
|
|
@@ -208,89 +315,148 @@ var createFetch = (wallet, config) => {
|
|
|
208
315
|
if (debug) {
|
|
209
316
|
console.log(`[X402] Payment required for: ${url}`);
|
|
210
317
|
}
|
|
211
|
-
|
|
318
|
+
let detectedVersion;
|
|
319
|
+
if (version === "auto") {
|
|
320
|
+
detectedVersion = detectX402Version(response);
|
|
321
|
+
} else {
|
|
322
|
+
detectedVersion = version;
|
|
323
|
+
}
|
|
212
324
|
if (debug) {
|
|
213
325
|
console.log(`[X402] Detected protocol v${detectedVersion}`);
|
|
214
326
|
}
|
|
215
|
-
const
|
|
327
|
+
const parsed = parsePaymentRequired(response);
|
|
216
328
|
if (debug) {
|
|
217
|
-
console.log(`[X402] Payment requirements:`, requirements);
|
|
329
|
+
console.log(`[X402] Payment requirements:`, parsed.requirements);
|
|
218
330
|
}
|
|
219
|
-
const
|
|
331
|
+
const fromAddress = getAddress();
|
|
332
|
+
const nonce = generateNonce(nonceGenerator);
|
|
333
|
+
const validBefore = Math.floor(Date.now() / 1e3) + (config.defaultExpiry ?? DEFAULT_EXPIRY);
|
|
334
|
+
const payment = await createX402Payment(
|
|
335
|
+
protocolWallet,
|
|
336
|
+
parsed,
|
|
337
|
+
fromAddress,
|
|
338
|
+
nonce,
|
|
339
|
+
validBefore,
|
|
340
|
+
domainName,
|
|
341
|
+
domainVersion
|
|
342
|
+
);
|
|
220
343
|
if (debug) {
|
|
221
|
-
console.log(`[X402] Created payment
|
|
344
|
+
console.log(`[X402] Created payment payload`);
|
|
222
345
|
}
|
|
223
346
|
const headers = new Headers(init?.headers);
|
|
224
|
-
addPaymentHeader(headers, payment,
|
|
347
|
+
addPaymentHeader(headers, payment, parsed.version);
|
|
225
348
|
response = await fetch(input, { ...init, headers });
|
|
226
349
|
if (debug) {
|
|
227
350
|
console.log(`[X402] Payment response status: ${response.status}`);
|
|
228
351
|
}
|
|
229
|
-
checkSettlement(response,
|
|
352
|
+
checkSettlement(response, parsed.version);
|
|
230
353
|
}
|
|
231
354
|
return response;
|
|
232
355
|
};
|
|
233
356
|
};
|
|
234
357
|
var extractDomainConfig = (config) => config.token ? { domainName: config.token.name, domainVersion: config.token.version } : { domainName: config.domainName, domainVersion: config.domainVersion };
|
|
235
358
|
var createX402Client = (config) => {
|
|
236
|
-
const { wallet, version = "auto", defaultExpiry = DEFAULT_EXPIRY, nonceGenerator
|
|
359
|
+
const { wallet, version = "auto", defaultExpiry = DEFAULT_EXPIRY, nonceGenerator } = config;
|
|
237
360
|
const { domainName, domainVersion } = extractDomainConfig(config);
|
|
238
|
-
const
|
|
239
|
-
const
|
|
361
|
+
const protocolWallet = toProtocolWallet(wallet);
|
|
362
|
+
const getAddress = () => getWalletAddress(protocolWallet);
|
|
363
|
+
const fetchFn = createFetch(wallet, {
|
|
364
|
+
version,
|
|
365
|
+
defaultExpiry,
|
|
366
|
+
nonceGenerator,
|
|
367
|
+
debug: config.debug,
|
|
368
|
+
domainName,
|
|
369
|
+
domainVersion
|
|
370
|
+
});
|
|
240
371
|
return {
|
|
241
372
|
fetch: fetchFn,
|
|
242
373
|
getAddress,
|
|
243
374
|
async createPayment(amount, to, contractAddress, chainId, expiry) {
|
|
244
375
|
const from = getAddress();
|
|
245
|
-
const nonce = nonceGenerator
|
|
246
|
-
const
|
|
376
|
+
const nonce = generateNonce(nonceGenerator);
|
|
377
|
+
const validBefore = expiry ?? Math.floor(Date.now() / 1e3) + defaultExpiry;
|
|
247
378
|
const targetVersion = version === "auto" ? 1 : version;
|
|
379
|
+
const networkConfig = getNetworkByChainId2(chainId);
|
|
380
|
+
const networkSlug = networkConfig ? normalizeNetworkName2(networkConfig.name) : `eip155:${chainId}`;
|
|
248
381
|
if (targetVersion === 1) {
|
|
249
|
-
|
|
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);
|
|
250
394
|
}
|
|
251
|
-
const
|
|
252
|
-
|
|
395
|
+
const requirements = {
|
|
396
|
+
scheme: "exact",
|
|
397
|
+
network: `eip155:${chainId}`,
|
|
398
|
+
amount,
|
|
399
|
+
asset: contractAddress,
|
|
400
|
+
payTo: to,
|
|
401
|
+
maxTimeoutSeconds: defaultExpiry
|
|
402
|
+
};
|
|
403
|
+
const parsed = { version: 2, requirements };
|
|
404
|
+
return createX402Payment(protocolWallet, parsed, from, nonce, validBefore, domainName, domainVersion);
|
|
253
405
|
},
|
|
254
406
|
async signPayment(payload) {
|
|
255
407
|
const from = getAddress();
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
408
|
+
const nonce = generateNonce(nonceGenerator);
|
|
409
|
+
const hasContractAddress = (p) => typeof p === "object" && p !== null && "contractAddress" in p;
|
|
410
|
+
if (hasContractAddress(payload)) {
|
|
411
|
+
const networkConfig = getNetworkByChainId2(
|
|
412
|
+
"chainId" in payload && typeof payload.chainId === "number" ? payload.chainId : 84532
|
|
273
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);
|
|
274
429
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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;
|
|
436
|
+
const requirements = {
|
|
437
|
+
scheme: "exact",
|
|
438
|
+
network,
|
|
439
|
+
amount,
|
|
440
|
+
asset,
|
|
441
|
+
payTo: to,
|
|
442
|
+
maxTimeoutSeconds: defaultExpiry
|
|
443
|
+
};
|
|
444
|
+
const parsed = { version: 2, requirements };
|
|
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);
|
|
288
447
|
}
|
|
289
448
|
};
|
|
290
449
|
};
|
|
291
450
|
var createX402Transport = (config) => {
|
|
292
|
-
const { wallet,
|
|
293
|
-
return createFetch(wallet, {
|
|
451
|
+
const { wallet, version, defaultExpiry, nonceGenerator, debug, token, domainName, domainVersion } = config;
|
|
452
|
+
return createFetch(wallet, {
|
|
453
|
+
version,
|
|
454
|
+
defaultExpiry,
|
|
455
|
+
nonceGenerator,
|
|
456
|
+
debug,
|
|
457
|
+
domainName: domainName ?? token?.name,
|
|
458
|
+
domainVersion: domainVersion ?? token?.version
|
|
459
|
+
});
|
|
294
460
|
};
|
|
295
461
|
|
|
296
462
|
// src/simple.ts
|
|
@@ -404,18 +570,30 @@ var getNetworks = () => {
|
|
|
404
570
|
var getTokens = () => {
|
|
405
571
|
return getAvailableTokens();
|
|
406
572
|
};
|
|
573
|
+
|
|
574
|
+
// src/index.ts
|
|
575
|
+
import { V1_HEADERS as V1_HEADERS3, V2_HEADERS as V2_HEADERS3 } from "@armory-sh/base";
|
|
407
576
|
export {
|
|
408
577
|
PaymentError,
|
|
409
578
|
SigningError,
|
|
579
|
+
V1_HEADERS3 as V1_HEADERS,
|
|
580
|
+
V2_HEADERS3 as V2_HEADERS,
|
|
410
581
|
X402ClientError,
|
|
411
582
|
armoryGet,
|
|
412
583
|
armoryPay,
|
|
413
584
|
armoryPost,
|
|
414
585
|
createX402Client,
|
|
586
|
+
createX402Payment,
|
|
415
587
|
createX402Transport,
|
|
588
|
+
createX402V1Payment,
|
|
589
|
+
createX402V2Payment,
|
|
590
|
+
detectX402Version,
|
|
591
|
+
encodeX402Payment,
|
|
416
592
|
getNetworks,
|
|
593
|
+
getPaymentHeaderName,
|
|
417
594
|
getTokens,
|
|
418
595
|
getWalletAddress2 as getWalletAddress,
|
|
596
|
+
parsePaymentRequired,
|
|
419
597
|
validateNetwork,
|
|
420
598
|
validateToken
|
|
421
599
|
};
|
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.13",
|
|
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.13",
|
|
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
|
}
|