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