@armory-sh/client-web3 0.2.6 → 0.2.10
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 +98 -3
- package/dist/index.js +327 -78
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Web3BaseWalletAccount, Web3BaseWallet } from 'web3-types';
|
|
2
|
-
import { PaymentPayloadV1, PaymentPayloadV2, CustomToken, NetworkConfig, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponseV1, SettlementResponseV2 } from '@armory-sh/base';
|
|
3
|
-
export { EIP712_TYPES as CORE_EIP712_TYPES, USDC_DOMAIN as CORE_USDC_DOMAIN, NETWORKS, NetworkConfig, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, V1_HEADERS, V2_HEADERS, createEIP712Domain as createCoreEIP712Domain, createTransferWithAuthorization as createCoreTransferWithAuthorization, decodePaymentV1, decodePaymentV2, decodeSettlementV1, decodeSettlementV2, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash,
|
|
2
|
+
import { PaymentPayloadV1, PaymentPayloadV2, CustomToken, NetworkConfig, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponseV1, SettlementResponseV2, X402PaymentRequiredV1, PaymentRequiredV2, X402PaymentPayloadV1 } from '@armory-sh/base';
|
|
3
|
+
export { EIP712_TYPES as CORE_EIP712_TYPES, USDC_DOMAIN as CORE_USDC_DOMAIN, NETWORKS, NetworkConfig, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, V1_HEADERS, V2_HEADERS, combineSignatureV2, createEIP712Domain as createCoreEIP712Domain, createTransferWithAuthorization as createCoreTransferWithAuthorization, createNonce, decodePaymentV1, decodePaymentV2, decodeSettlementV1, decodeSettlementV2, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash, isSettlementSuccessful, isV1, isV2, isX402V1Payload, isX402V1Requirements, isX402V1Settlement, isX402V2Payload, isX402V2Requirements, isX402V2Settlement, parseSignatureV2, validateTransferWithAuthorization as validateCoreTransferWithAuthorization } from '@armory-sh/base';
|
|
4
4
|
|
|
5
5
|
type Web3Account = Web3BaseWalletAccount | Web3BaseWallet<Web3BaseWalletAccount>;
|
|
6
6
|
/** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
|
|
@@ -48,6 +48,7 @@ interface X402TransportOptions {
|
|
|
48
48
|
maxRetries?: number;
|
|
49
49
|
}
|
|
50
50
|
interface Web3X402Client {
|
|
51
|
+
fetch(url: string | Request, init?: RequestInit): Promise<Response>;
|
|
51
52
|
getAccount(): Web3Account;
|
|
52
53
|
getNetwork(): NetworkConfig;
|
|
53
54
|
getVersion(): 1 | 2;
|
|
@@ -82,9 +83,103 @@ declare const isV2Settlement: (response: SettlementResponseV1 | SettlementRespon
|
|
|
82
83
|
|
|
83
84
|
declare const createX402Client: (config: Web3ClientConfig) => Web3X402Client;
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Create an x402 transport layer for handling payment-required responses
|
|
88
|
+
*/
|
|
85
89
|
declare const createX402Transport: (options: X402TransportOptions) => X402Transport;
|
|
90
|
+
/**
|
|
91
|
+
* Create a fetch function bound to an x402 transport
|
|
92
|
+
*/
|
|
86
93
|
declare const createFetchWithX402: (transport: X402Transport) => (url: string | Request, init?: RequestInit) => Promise<Response>;
|
|
87
94
|
|
|
95
|
+
/**
|
|
96
|
+
* X402 Protocol Detection and Parsing Functions
|
|
97
|
+
*
|
|
98
|
+
* Handles both x402 V1 and V2 protocol detection and parsing from HTTP responses.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
type X402Version = 1 | 2;
|
|
102
|
+
interface ParsedPaymentRequired {
|
|
103
|
+
version: X402Version;
|
|
104
|
+
requirements: PaymentRequirementsV1[] | PaymentRequirementsV2[];
|
|
105
|
+
raw: X402PaymentRequiredV1 | PaymentRequiredV2;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Detect x402 protocol version from response headers
|
|
109
|
+
* Returns V2 if PAYMENT-REQUIRED header exists, V1 for X-PAYMENT-REQUIRED
|
|
110
|
+
* Falls back to body detection if no headers present
|
|
111
|
+
*/
|
|
112
|
+
declare const detectX402Version: (response: Response, fallbackVersion?: X402Version) => X402Version;
|
|
113
|
+
/**
|
|
114
|
+
* Detect version from a parsed payment required object
|
|
115
|
+
*/
|
|
116
|
+
declare const detectVersionFromObject: (obj: unknown) => X402Version | null;
|
|
117
|
+
/**
|
|
118
|
+
* Parse PAYMENT-REQUIRED header or response body
|
|
119
|
+
* Handles both V1 (base64 encoded) and V2 (JSON) formats
|
|
120
|
+
*/
|
|
121
|
+
declare const parsePaymentRequired: (response: Response, version?: X402Version) => Promise<ParsedPaymentRequired>;
|
|
122
|
+
/**
|
|
123
|
+
* Parse payment required from header value
|
|
124
|
+
*/
|
|
125
|
+
declare const parsePaymentRequiredFromHeader: (header: string, version: X402Version) => ParsedPaymentRequired;
|
|
126
|
+
/**
|
|
127
|
+
* Parse payment required from response body
|
|
128
|
+
*/
|
|
129
|
+
declare const parsePaymentRequiredFromBody: (body: string, version: X402Version) => ParsedPaymentRequired;
|
|
130
|
+
/**
|
|
131
|
+
* Create x402 V1 payment payload
|
|
132
|
+
*/
|
|
133
|
+
declare const createX402V1Payment: (params: {
|
|
134
|
+
from: string;
|
|
135
|
+
to: string;
|
|
136
|
+
value: string;
|
|
137
|
+
nonce: `0x${string}`;
|
|
138
|
+
validAfter: string;
|
|
139
|
+
validBefore: string;
|
|
140
|
+
signature: `0x${string}`;
|
|
141
|
+
network: string;
|
|
142
|
+
}) => X402PaymentPayloadV1;
|
|
143
|
+
/**
|
|
144
|
+
* Create x402 V2 payment payload
|
|
145
|
+
*/
|
|
146
|
+
declare const createX402V2Payment: (params: {
|
|
147
|
+
from: string;
|
|
148
|
+
to: string;
|
|
149
|
+
value: string;
|
|
150
|
+
nonce: `0x${string}`;
|
|
151
|
+
validAfter: string;
|
|
152
|
+
validBefore: string;
|
|
153
|
+
signature: `0x${string}`;
|
|
154
|
+
accepted: PaymentRequirementsV2;
|
|
155
|
+
resource?: {
|
|
156
|
+
url: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
mimeType?: string;
|
|
159
|
+
};
|
|
160
|
+
}) => PaymentPayloadV2;
|
|
161
|
+
/**
|
|
162
|
+
* Create payment header for request
|
|
163
|
+
*/
|
|
164
|
+
declare const createPaymentHeader: (payload: X402PaymentPayloadV1 | PaymentPayloadV2, version: X402Version) => string;
|
|
165
|
+
/**
|
|
166
|
+
* Get the payment header name for a version
|
|
167
|
+
*/
|
|
168
|
+
declare const getPaymentHeader: (version: X402Version) => string;
|
|
169
|
+
/**
|
|
170
|
+
* Get the payment required header name for a version
|
|
171
|
+
*/
|
|
172
|
+
declare const getPaymentRequiredHeader: (version: X402Version) => string;
|
|
173
|
+
/**
|
|
174
|
+
* Check if response indicates payment is required
|
|
175
|
+
*/
|
|
176
|
+
declare const isPaymentRequiredResponse: (response: Response) => boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Extract requirements for a specific scheme from accepts array
|
|
179
|
+
* Uses type assertion to handle union types
|
|
180
|
+
*/
|
|
181
|
+
declare const selectSchemeRequirements: (requirements: PaymentRequirementsV1[] | PaymentRequirementsV2[], scheme?: string) => PaymentRequirementsV1 | PaymentRequirementsV2 | undefined;
|
|
182
|
+
|
|
88
183
|
declare const EIP712_TYPES: {
|
|
89
184
|
readonly EIP712Domain: readonly [{
|
|
90
185
|
readonly name: "name";
|
|
@@ -144,4 +239,4 @@ declare const signWithPrivateKey: (_privateKey: string, _domain: Web3EIP712Domai
|
|
|
144
239
|
s: string;
|
|
145
240
|
}>;
|
|
146
241
|
|
|
147
|
-
export { EIP712_TYPES, type PaymentSignOptions, type PaymentSignatureResult, type Token, USDC_DOMAIN, type Web3Account, type Web3ClientConfig, type Web3EIP712Domain, type Web3TransferWithAuthorization, type Web3X402Client, type X402RequestContext, type X402Transport, type X402TransportOptions, adjustVForChainId, concatenateSignature, createEIP712Domain, createFetchWithX402, createTransferWithAuthorization, createX402Client, createX402Transport, isV1Requirements, isV1Settlement, isV2Requirements, isV2Settlement, parseSignature, signTypedData, signWithPrivateKey, validateTransferWithAuthorization };
|
|
242
|
+
export { EIP712_TYPES, type ParsedPaymentRequired, type PaymentSignOptions, type PaymentSignatureResult, type Token, USDC_DOMAIN, type Web3Account, type Web3ClientConfig, type Web3EIP712Domain, type Web3TransferWithAuthorization, type Web3X402Client, type X402RequestContext, type X402Transport, type X402TransportOptions, type X402Version, adjustVForChainId, concatenateSignature, createEIP712Domain, createFetchWithX402, createPaymentHeader, createTransferWithAuthorization, createX402Client, createX402Transport, createX402V1Payment, createX402V2Payment, detectVersionFromObject, detectX402Version, getPaymentHeader, getPaymentRequiredHeader, isPaymentRequiredResponse, isV1Requirements, isV1Settlement, isV2Requirements, isV2Settlement, parsePaymentRequired, parsePaymentRequiredFromBody, parsePaymentRequiredFromHeader, parseSignature, selectSchemeRequirements, signTypedData, signWithPrivateKey, validateTransferWithAuthorization };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,12 @@ import { Web3 } from "web3";
|
|
|
3
3
|
import {
|
|
4
4
|
getNetworkConfig,
|
|
5
5
|
encodePaymentV1,
|
|
6
|
-
encodePaymentV2
|
|
6
|
+
encodePaymentV2,
|
|
7
|
+
isX402V1Requirements,
|
|
8
|
+
isX402V2Requirements,
|
|
9
|
+
combineSignatureV2,
|
|
10
|
+
V1_HEADERS as V1_HEADERS2,
|
|
11
|
+
V2_HEADERS as V2_HEADERS2
|
|
7
12
|
} from "@armory-sh/base";
|
|
8
13
|
|
|
9
14
|
// src/eip3009.ts
|
|
@@ -118,6 +123,151 @@ var signWithPrivateKey = async (_privateKey, _domain, _message) => {
|
|
|
118
123
|
);
|
|
119
124
|
};
|
|
120
125
|
|
|
126
|
+
// src/protocol.ts
|
|
127
|
+
import {
|
|
128
|
+
V1_HEADERS,
|
|
129
|
+
V2_HEADERS,
|
|
130
|
+
decodeX402PaymentRequiredV1,
|
|
131
|
+
isX402V1PaymentRequired,
|
|
132
|
+
isX402V2PaymentRequired,
|
|
133
|
+
getPaymentRequiredHeaderName,
|
|
134
|
+
getPaymentHeaderName
|
|
135
|
+
} from "@armory-sh/base";
|
|
136
|
+
var detectX402Version = (response, fallbackVersion = 2) => {
|
|
137
|
+
if (response.headers.has(V2_HEADERS.PAYMENT_REQUIRED)) {
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
if (response.headers.has(V1_HEADERS.PAYMENT_REQUIRED)) {
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
if (response.headers.has("Payment-Required")) {
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
146
|
+
return fallbackVersion;
|
|
147
|
+
};
|
|
148
|
+
var detectVersionFromObject = (obj) => {
|
|
149
|
+
if (isX402V2PaymentRequired(obj)) return 2;
|
|
150
|
+
if (isX402V1PaymentRequired(obj)) return 1;
|
|
151
|
+
return null;
|
|
152
|
+
};
|
|
153
|
+
var parsePaymentRequired = async (response, version) => {
|
|
154
|
+
const detectedVersion = version ?? detectX402Version(response);
|
|
155
|
+
const headerName = getPaymentRequiredHeaderName(detectedVersion);
|
|
156
|
+
const header = response.headers.get(headerName);
|
|
157
|
+
if (header) {
|
|
158
|
+
return parsePaymentRequiredFromHeader(header, detectedVersion);
|
|
159
|
+
}
|
|
160
|
+
const body = await response.clone().text();
|
|
161
|
+
return parsePaymentRequiredFromBody(body, detectedVersion);
|
|
162
|
+
};
|
|
163
|
+
var parsePaymentRequiredFromHeader = (header, version) => {
|
|
164
|
+
if (version === 1) {
|
|
165
|
+
const decoded = decodeX402PaymentRequiredV1(header);
|
|
166
|
+
return {
|
|
167
|
+
version: 1,
|
|
168
|
+
requirements: decoded.accepts,
|
|
169
|
+
raw: decoded
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(header);
|
|
175
|
+
} catch {
|
|
176
|
+
const decoded = Buffer.from(header, "base64").toString("utf-8");
|
|
177
|
+
parsed = JSON.parse(decoded);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
version: 2,
|
|
181
|
+
requirements: parsed.accepts,
|
|
182
|
+
raw: parsed
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
var parsePaymentRequiredFromBody = (body, version) => {
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = JSON.parse(body);
|
|
189
|
+
} catch {
|
|
190
|
+
const decoded = Buffer.from(body, "base64").toString("utf-8");
|
|
191
|
+
parsed = JSON.parse(decoded);
|
|
192
|
+
}
|
|
193
|
+
const detectedVersion = detectVersionFromObject(parsed) ?? version;
|
|
194
|
+
if (detectedVersion === 1 && isX402V1PaymentRequired(parsed)) {
|
|
195
|
+
return {
|
|
196
|
+
version: 1,
|
|
197
|
+
requirements: parsed.accepts,
|
|
198
|
+
raw: parsed
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (detectedVersion === 2 && isX402V2PaymentRequired(parsed)) {
|
|
202
|
+
const v2Parsed = parsed;
|
|
203
|
+
return {
|
|
204
|
+
version: 2,
|
|
205
|
+
requirements: v2Parsed.accepts,
|
|
206
|
+
raw: v2Parsed
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
throw new Error("Unable to parse payment required response");
|
|
210
|
+
};
|
|
211
|
+
var createX402V1Payment = (params) => {
|
|
212
|
+
const authorization = {
|
|
213
|
+
from: params.from,
|
|
214
|
+
to: params.to,
|
|
215
|
+
value: params.value,
|
|
216
|
+
validAfter: params.validAfter,
|
|
217
|
+
validBefore: params.validBefore,
|
|
218
|
+
nonce: params.nonce
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
x402Version: 1,
|
|
222
|
+
scheme: "exact",
|
|
223
|
+
network: params.network,
|
|
224
|
+
payload: {
|
|
225
|
+
signature: params.signature,
|
|
226
|
+
authorization
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
var createX402V2Payment = (params) => {
|
|
231
|
+
const authorization = {
|
|
232
|
+
from: params.from,
|
|
233
|
+
to: params.to,
|
|
234
|
+
value: params.value,
|
|
235
|
+
validAfter: params.validAfter,
|
|
236
|
+
validBefore: params.validBefore,
|
|
237
|
+
nonce: params.nonce
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
x402Version: 2,
|
|
241
|
+
scheme: params.accepted.scheme,
|
|
242
|
+
network: params.accepted.network,
|
|
243
|
+
resource: params.resource,
|
|
244
|
+
payload: {
|
|
245
|
+
signature: params.signature,
|
|
246
|
+
authorization
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
var createPaymentHeader = (payload, version) => {
|
|
251
|
+
const headerName = getPaymentHeaderName(version);
|
|
252
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
253
|
+
return encoded;
|
|
254
|
+
};
|
|
255
|
+
var getPaymentHeader = (version) => {
|
|
256
|
+
return getPaymentHeaderName(version);
|
|
257
|
+
};
|
|
258
|
+
var getPaymentRequiredHeader = (version) => {
|
|
259
|
+
return getPaymentRequiredHeaderName(version);
|
|
260
|
+
};
|
|
261
|
+
var isPaymentRequiredResponse = (response) => {
|
|
262
|
+
if (response.status === 402) return true;
|
|
263
|
+
return response.headers.has(V1_HEADERS.PAYMENT_REQUIRED) || response.headers.has(V2_HEADERS.PAYMENT_REQUIRED) || response.headers.has("Payment-Required");
|
|
264
|
+
};
|
|
265
|
+
var selectSchemeRequirements = (requirements, scheme = "exact") => {
|
|
266
|
+
return requirements.find(
|
|
267
|
+
(r) => "scheme" in r && r.scheme === scheme
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
121
271
|
// src/client.ts
|
|
122
272
|
var DEFAULT_EXPIRY_SECONDS = 3600;
|
|
123
273
|
var DEFAULT_VALID_AFTER = 0;
|
|
@@ -167,7 +317,7 @@ var signTypedDataWrapper = async (account, domain, message) => {
|
|
|
167
317
|
const sig = await acc.signTypedData(domain, message);
|
|
168
318
|
return parseSignature2(sig);
|
|
169
319
|
}
|
|
170
|
-
const
|
|
320
|
+
const getAddressLocal = () => {
|
|
171
321
|
if ("address" in account) return account.address;
|
|
172
322
|
if (Array.isArray(account) && account[0]) return account[0].address;
|
|
173
323
|
throw new Error("Unable to get address from account");
|
|
@@ -175,7 +325,7 @@ var signTypedDataWrapper = async (account, domain, message) => {
|
|
|
175
325
|
if (typeof acc.request === "function") {
|
|
176
326
|
const sig = await acc.request({
|
|
177
327
|
method: "eth_signTypedData_v4",
|
|
178
|
-
params: [
|
|
328
|
+
params: [getAddressLocal(), JSON.stringify({ domain, message })]
|
|
179
329
|
});
|
|
180
330
|
return parseSignature2(sig);
|
|
181
331
|
}
|
|
@@ -197,7 +347,7 @@ var signPaymentV1 = async (state, params) => {
|
|
|
197
347
|
nonce: `0x${nonce}`
|
|
198
348
|
});
|
|
199
349
|
const signature = await signTypedDataWrapper(state.account, domain, message);
|
|
200
|
-
const
|
|
350
|
+
const legacyPayload = {
|
|
201
351
|
from,
|
|
202
352
|
to,
|
|
203
353
|
amount,
|
|
@@ -210,10 +360,40 @@ var signPaymentV1 = async (state, params) => {
|
|
|
210
360
|
contractAddress: network.usdcAddress,
|
|
211
361
|
network: network.name.toLowerCase().replace(" ", "-")
|
|
212
362
|
};
|
|
213
|
-
return { v: signature.v, r: signature.r, s: signature.s, payload };
|
|
363
|
+
return { v: signature.v, r: signature.r, s: signature.s, payload: legacyPayload };
|
|
364
|
+
};
|
|
365
|
+
var handlePaymentRequirements = async (requirements, state) => {
|
|
366
|
+
const version = detectVersionFromRequirements(requirements);
|
|
367
|
+
if (version === 1) {
|
|
368
|
+
const req2 = requirements;
|
|
369
|
+
if (isX402V1Requirements(req2)) {
|
|
370
|
+
const x402Req = req2;
|
|
371
|
+
return signPayment({
|
|
372
|
+
amount: x402Req.maxAmountRequired,
|
|
373
|
+
to: x402Req.payTo
|
|
374
|
+
}, state);
|
|
375
|
+
}
|
|
376
|
+
const legacyReq = req2;
|
|
377
|
+
return signPayment({
|
|
378
|
+
amount: legacyReq.amount,
|
|
379
|
+
to: legacyReq.payTo,
|
|
380
|
+
expiry: legacyReq.expiry
|
|
381
|
+
}, state);
|
|
382
|
+
}
|
|
383
|
+
const req = requirements;
|
|
384
|
+
const to = typeof req.payTo === "string" ? req.payTo : "0x0000000000000000000000000000000000000000";
|
|
385
|
+
const from = getAddress(state.account);
|
|
386
|
+
return signPaymentV2(state, {
|
|
387
|
+
from,
|
|
388
|
+
to,
|
|
389
|
+
amount: req.amount,
|
|
390
|
+
nonce: crypto.randomUUID(),
|
|
391
|
+
expiry: Math.floor(Date.now() / 1e3) + DEFAULT_EXPIRY_SECONDS,
|
|
392
|
+
accepted: req
|
|
393
|
+
});
|
|
214
394
|
};
|
|
215
395
|
var signPaymentV2 = async (state, params) => {
|
|
216
|
-
const { from, to, amount, nonce, expiry } = params;
|
|
396
|
+
const { from, to, amount, nonce, expiry, accepted } = params;
|
|
217
397
|
const { network, domainName, domainVersion } = state;
|
|
218
398
|
const domain = createEIP712Domain(network.chainId, network.usdcAddress, domainName, domainVersion);
|
|
219
399
|
const message = createTransferWithAuthorization({
|
|
@@ -225,25 +405,73 @@ var signPaymentV2 = async (state, params) => {
|
|
|
225
405
|
nonce: `0x${nonce}`
|
|
226
406
|
});
|
|
227
407
|
const signature = await signTypedDataWrapper(state.account, domain, message);
|
|
228
|
-
const
|
|
408
|
+
const combinedSig = combineSignatureV2(signature.v, signature.r, signature.s);
|
|
409
|
+
const defaultAccepted = accepted ?? {
|
|
410
|
+
scheme: "exact",
|
|
411
|
+
network: network.caip2Id,
|
|
412
|
+
amount,
|
|
413
|
+
asset: network.usdcAddress,
|
|
414
|
+
payTo: to,
|
|
415
|
+
maxTimeoutSeconds: expiry - Math.floor(Date.now() / 1e3)
|
|
416
|
+
};
|
|
417
|
+
const x402Payload = createX402V2Payment({
|
|
229
418
|
from,
|
|
230
419
|
to,
|
|
231
|
-
amount,
|
|
232
|
-
nonce,
|
|
233
|
-
|
|
420
|
+
value: amount,
|
|
421
|
+
nonce: `0x${nonce.padStart(64, "0")}`,
|
|
422
|
+
validAfter: "0x0",
|
|
423
|
+
validBefore: `0x${expiry.toString(16)}`,
|
|
424
|
+
signature: combinedSig,
|
|
425
|
+
accepted: defaultAccepted
|
|
426
|
+
});
|
|
427
|
+
return {
|
|
234
428
|
signature: {
|
|
235
429
|
v: signature.v,
|
|
236
430
|
r: signature.r,
|
|
237
431
|
s: signature.s
|
|
238
432
|
},
|
|
239
|
-
|
|
240
|
-
assetId: network.caipAssetId
|
|
433
|
+
payload: x402Payload
|
|
241
434
|
};
|
|
242
|
-
return { signature: payload.signature, payload };
|
|
243
435
|
};
|
|
244
436
|
var createX402Client = (config) => {
|
|
245
437
|
const state = createClientState(config);
|
|
438
|
+
const fetch2 = async (url, init) => {
|
|
439
|
+
let response = await fetch2(url, init);
|
|
440
|
+
if (response.status === 402) {
|
|
441
|
+
const version = detectX402Version(response, state.version);
|
|
442
|
+
const parsed = await parsePaymentRequired(response, version);
|
|
443
|
+
const selectedRequirements = selectSchemeRequirements(parsed.requirements, "exact");
|
|
444
|
+
if (!selectedRequirements) {
|
|
445
|
+
throw new Error("No supported payment scheme found in requirements");
|
|
446
|
+
}
|
|
447
|
+
const from = getAddress(state.account);
|
|
448
|
+
let result;
|
|
449
|
+
if (version === 1) {
|
|
450
|
+
result = await handlePaymentRequirements(selectedRequirements, state);
|
|
451
|
+
} else {
|
|
452
|
+
const req = selectedRequirements;
|
|
453
|
+
const to = typeof req.payTo === "string" ? req.payTo : "0x0000000000000000000000000000000000000000";
|
|
454
|
+
result = await signPaymentV2(state, {
|
|
455
|
+
from,
|
|
456
|
+
to,
|
|
457
|
+
amount: req.amount,
|
|
458
|
+
nonce: crypto.randomUUID(),
|
|
459
|
+
expiry: Math.floor(Date.now() / 1e3) + DEFAULT_EXPIRY_SECONDS,
|
|
460
|
+
accepted: req
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
const paymentHeaders = new Headers(init?.headers);
|
|
464
|
+
if (version === 1) {
|
|
465
|
+
paymentHeaders.set(V1_HEADERS2.PAYMENT, encodePaymentV1(result.payload));
|
|
466
|
+
} else {
|
|
467
|
+
paymentHeaders.set(V2_HEADERS2.PAYMENT_SIGNATURE, encodePaymentV2(result.payload));
|
|
468
|
+
}
|
|
469
|
+
response = await fetch2(url, { ...init, headers: paymentHeaders });
|
|
470
|
+
}
|
|
471
|
+
return response;
|
|
472
|
+
};
|
|
246
473
|
return {
|
|
474
|
+
fetch: fetch2,
|
|
247
475
|
getAccount: () => state.account,
|
|
248
476
|
getNetwork: () => state.network,
|
|
249
477
|
getVersion: () => state.version,
|
|
@@ -276,28 +504,28 @@ var createX402Client = (config) => {
|
|
|
276
504
|
return headers;
|
|
277
505
|
},
|
|
278
506
|
handlePaymentRequired: async (requirements) => {
|
|
279
|
-
|
|
280
|
-
const req2 = requirements;
|
|
281
|
-
return signPayment({
|
|
282
|
-
amount: req2.amount,
|
|
283
|
-
to: req2.payTo,
|
|
284
|
-
expiry: req2.expiry
|
|
285
|
-
}, state);
|
|
286
|
-
}
|
|
287
|
-
const req = requirements;
|
|
288
|
-
const to = typeof req.to === "string" ? req.to : "0x0000000000000000000000000000000000000000";
|
|
289
|
-
return signPayment({
|
|
290
|
-
amount: req.amount,
|
|
291
|
-
to,
|
|
292
|
-
nonce: req.nonce,
|
|
293
|
-
expiry: req.expiry
|
|
294
|
-
}, state);
|
|
507
|
+
return handlePaymentRequirements(requirements, state);
|
|
295
508
|
},
|
|
296
509
|
verifySettlement: (response) => {
|
|
297
|
-
|
|
510
|
+
if ("success" in response) {
|
|
511
|
+
return response.success;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
298
514
|
}
|
|
299
515
|
};
|
|
300
516
|
};
|
|
517
|
+
var detectVersionFromRequirements = (requirements) => {
|
|
518
|
+
if (isX402V2Requirements(requirements)) {
|
|
519
|
+
return 2;
|
|
520
|
+
}
|
|
521
|
+
if (isX402V1Requirements(requirements)) {
|
|
522
|
+
return 1;
|
|
523
|
+
}
|
|
524
|
+
if ("contractAddress" in requirements) {
|
|
525
|
+
return 1;
|
|
526
|
+
}
|
|
527
|
+
return 2;
|
|
528
|
+
};
|
|
301
529
|
var signPayment = async (options, state) => {
|
|
302
530
|
const from = getAddress(state.account);
|
|
303
531
|
if (state.version === 1) {
|
|
@@ -320,50 +548,47 @@ var signPayment = async (options, state) => {
|
|
|
320
548
|
};
|
|
321
549
|
|
|
322
550
|
// src/transport.ts
|
|
323
|
-
import {
|
|
551
|
+
import {
|
|
552
|
+
V1_HEADERS as V1_HEADERS3,
|
|
553
|
+
V2_HEADERS as V2_HEADERS3,
|
|
554
|
+
encodePaymentV1 as encodePaymentV12,
|
|
555
|
+
encodePaymentV2 as encodePaymentV22,
|
|
556
|
+
isX402V1Requirements as isX402V1Requirements2,
|
|
557
|
+
isX402V2Requirements as isX402V2Requirements2
|
|
558
|
+
} from "@armory-sh/base";
|
|
324
559
|
var DEFAULT_MAX_RETRIES = 3;
|
|
325
|
-
var detectProtocolVersion = (response, client) => {
|
|
326
|
-
if (response.headers.has(V2_HEADERS.PAYMENT_REQUIRED) || response.headers.has("PAYMENT-REQUIRED")) {
|
|
327
|
-
return 2;
|
|
328
|
-
}
|
|
329
|
-
if (response.headers.has("X-PAYMENT-REQUIRED")) {
|
|
330
|
-
return 1;
|
|
331
|
-
}
|
|
332
|
-
return client.getVersion();
|
|
333
|
-
};
|
|
334
|
-
var parsePaymentRequirements = async (response, version) => {
|
|
335
|
-
if (version === 2) {
|
|
336
|
-
const header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
|
|
337
|
-
if (header) return JSON.parse(header);
|
|
338
|
-
} else {
|
|
339
|
-
const header = response.headers.get("X-PAYMENT-REQUIRED");
|
|
340
|
-
if (header) {
|
|
341
|
-
const json = Buffer.from(header, "base64").toString("utf-8");
|
|
342
|
-
return JSON.parse(json);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return JSON.parse(await response.clone().text());
|
|
346
|
-
};
|
|
347
560
|
var createPaymentHeaders = (payload, version) => {
|
|
348
561
|
const headers = new Headers();
|
|
349
562
|
if (version === 1) {
|
|
350
|
-
headers.set(
|
|
563
|
+
headers.set(V1_HEADERS3.PAYMENT, encodePaymentV12(payload));
|
|
351
564
|
} else {
|
|
352
|
-
headers.set(
|
|
565
|
+
headers.set(V2_HEADERS3.PAYMENT_SIGNATURE, encodePaymentV22(payload));
|
|
353
566
|
}
|
|
354
567
|
return headers;
|
|
355
568
|
};
|
|
356
|
-
var isPaymentRelatedError = (error) => error.message.includes("402") || error.message.includes("payment") || error.message.includes("signature");
|
|
569
|
+
var isPaymentRelatedError = (error) => error.message.includes("402") || error.message.includes("payment") || error.message.includes("signature") || error.message.includes("Payment");
|
|
357
570
|
var backoff = (attempt) => {
|
|
358
|
-
const
|
|
359
|
-
|
|
571
|
+
const baseDelay = Math.min(1e3 * Math.pow(2, attempt - 1), 1e4);
|
|
572
|
+
const jitter = Math.random() * 100;
|
|
573
|
+
return new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
|
|
360
574
|
};
|
|
361
575
|
var handlePaymentRequired = async (response, client) => {
|
|
362
|
-
const version =
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
576
|
+
const version = detectX402Version(response, client.getVersion());
|
|
577
|
+
const parsed = await parsePaymentRequired(response, version);
|
|
578
|
+
const selectedRequirements = selectSchemeRequirements(parsed.requirements, "exact");
|
|
579
|
+
if (!selectedRequirements) {
|
|
580
|
+
throw new Error("No supported payment scheme found in requirements");
|
|
581
|
+
}
|
|
582
|
+
let result;
|
|
583
|
+
if (version === 1 && isX402V1Requirements2(selectedRequirements)) {
|
|
584
|
+
const req = selectedRequirements;
|
|
585
|
+
result = await client.handlePaymentRequired(req);
|
|
586
|
+
} else if (version === 2 && isX402V2Requirements2(selectedRequirements)) {
|
|
587
|
+
const req = selectedRequirements;
|
|
588
|
+
result = await client.handlePaymentRequired(req);
|
|
589
|
+
} else {
|
|
590
|
+
result = await client.handlePaymentRequired(selectedRequirements);
|
|
591
|
+
}
|
|
367
592
|
return createPaymentHeaders(result.payload, version);
|
|
368
593
|
};
|
|
369
594
|
var mergePaymentHeaders = (init = {}, paymentHeaders) => {
|
|
@@ -386,7 +611,7 @@ var createX402Transport = (options) => {
|
|
|
386
611
|
attempt++;
|
|
387
612
|
try {
|
|
388
613
|
const response = await fetch(url, init);
|
|
389
|
-
if (response
|
|
614
|
+
if (isPaymentRequiredResponse(response) && autoSign) {
|
|
390
615
|
const paymentHeaders = await handlePaymentRequired(response, client);
|
|
391
616
|
const newInit = mergePaymentHeaders(init, paymentHeaders);
|
|
392
617
|
return await fetch(url, newInit);
|
|
@@ -416,15 +641,12 @@ var isV2Settlement = (response) => "status" in response;
|
|
|
416
641
|
|
|
417
642
|
// src/index.ts
|
|
418
643
|
import {
|
|
419
|
-
V1_HEADERS as
|
|
644
|
+
V1_HEADERS as V1_HEADERS4,
|
|
645
|
+
V2_HEADERS as V2_HEADERS4,
|
|
420
646
|
encodePaymentV1 as encodePaymentV13,
|
|
421
647
|
decodePaymentV1,
|
|
422
648
|
encodeSettlementV1,
|
|
423
649
|
decodeSettlementV1,
|
|
424
|
-
V2_HEADERS as V2_HEADERS2,
|
|
425
|
-
isCAIP2ChainId,
|
|
426
|
-
isCAIPAssetId,
|
|
427
|
-
isAddress,
|
|
428
650
|
encodePaymentV2 as encodePaymentV23,
|
|
429
651
|
decodePaymentV2,
|
|
430
652
|
encodeSettlementV2,
|
|
@@ -434,9 +656,9 @@ import {
|
|
|
434
656
|
getPaymentVersion,
|
|
435
657
|
getRequirementsVersion,
|
|
436
658
|
getSettlementVersion,
|
|
437
|
-
getPaymentHeaderName,
|
|
659
|
+
getPaymentHeaderName as getPaymentHeaderName2,
|
|
438
660
|
getPaymentResponseHeaderName,
|
|
439
|
-
getPaymentRequiredHeaderName,
|
|
661
|
+
getPaymentRequiredHeaderName as getPaymentRequiredHeaderName2,
|
|
440
662
|
isSettlementSuccessful,
|
|
441
663
|
getTxHash,
|
|
442
664
|
NETWORKS,
|
|
@@ -444,6 +666,15 @@ import {
|
|
|
444
666
|
getNetworkByChainId,
|
|
445
667
|
getMainnets,
|
|
446
668
|
getTestnets,
|
|
669
|
+
isX402V1Payload,
|
|
670
|
+
isX402V2Payload,
|
|
671
|
+
isX402V1Requirements as isX402V1Requirements3,
|
|
672
|
+
isX402V2Requirements as isX402V2Requirements3,
|
|
673
|
+
isX402V1Settlement,
|
|
674
|
+
isX402V2Settlement,
|
|
675
|
+
combineSignatureV2 as combineSignatureV22,
|
|
676
|
+
parseSignatureV2,
|
|
677
|
+
createNonce as createNonce2,
|
|
447
678
|
EIP712_TYPES as EIP712_TYPES2,
|
|
448
679
|
USDC_DOMAIN as USDC_DOMAIN2,
|
|
449
680
|
createEIP712Domain as createEIP712Domain2,
|
|
@@ -456,21 +687,28 @@ export {
|
|
|
456
687
|
EIP712_TYPES,
|
|
457
688
|
NETWORKS,
|
|
458
689
|
USDC_DOMAIN,
|
|
459
|
-
|
|
460
|
-
|
|
690
|
+
V1_HEADERS4 as V1_HEADERS,
|
|
691
|
+
V2_HEADERS4 as V2_HEADERS,
|
|
461
692
|
adjustVForChainId,
|
|
693
|
+
combineSignatureV22 as combineSignatureV2,
|
|
462
694
|
concatenateSignature,
|
|
463
695
|
createEIP712Domain2 as createCoreEIP712Domain,
|
|
464
696
|
createTransferWithAuthorization2 as createCoreTransferWithAuthorization,
|
|
465
697
|
createEIP712Domain,
|
|
466
698
|
createFetchWithX402,
|
|
699
|
+
createNonce2 as createNonce,
|
|
700
|
+
createPaymentHeader,
|
|
467
701
|
createTransferWithAuthorization,
|
|
468
702
|
createX402Client,
|
|
469
703
|
createX402Transport,
|
|
704
|
+
createX402V1Payment,
|
|
705
|
+
createX402V2Payment,
|
|
470
706
|
decodePaymentV1,
|
|
471
707
|
decodePaymentV2,
|
|
472
708
|
decodeSettlementV1,
|
|
473
709
|
decodeSettlementV2,
|
|
710
|
+
detectVersionFromObject,
|
|
711
|
+
detectX402Version,
|
|
474
712
|
encodePaymentV13 as encodePaymentV1,
|
|
475
713
|
encodePaymentV23 as encodePaymentV2,
|
|
476
714
|
encodeSettlementV1,
|
|
@@ -478,17 +716,17 @@ export {
|
|
|
478
716
|
getMainnets,
|
|
479
717
|
getNetworkByChainId,
|
|
480
718
|
getNetworkConfig2 as getNetworkConfig,
|
|
481
|
-
|
|
482
|
-
|
|
719
|
+
getPaymentHeader,
|
|
720
|
+
getPaymentHeaderName2 as getPaymentHeaderName,
|
|
721
|
+
getPaymentRequiredHeader,
|
|
722
|
+
getPaymentRequiredHeaderName2 as getPaymentRequiredHeaderName,
|
|
483
723
|
getPaymentResponseHeaderName,
|
|
484
724
|
getPaymentVersion,
|
|
485
725
|
getRequirementsVersion,
|
|
486
726
|
getSettlementVersion,
|
|
487
727
|
getTestnets,
|
|
488
728
|
getTxHash,
|
|
489
|
-
|
|
490
|
-
isCAIP2ChainId,
|
|
491
|
-
isCAIPAssetId,
|
|
729
|
+
isPaymentRequiredResponse,
|
|
492
730
|
isSettlementSuccessful,
|
|
493
731
|
isV1,
|
|
494
732
|
isV1Requirements,
|
|
@@ -496,7 +734,18 @@ export {
|
|
|
496
734
|
isV2,
|
|
497
735
|
isV2Requirements,
|
|
498
736
|
isV2Settlement,
|
|
737
|
+
isX402V1Payload,
|
|
738
|
+
isX402V1Requirements3 as isX402V1Requirements,
|
|
739
|
+
isX402V1Settlement,
|
|
740
|
+
isX402V2Payload,
|
|
741
|
+
isX402V2Requirements3 as isX402V2Requirements,
|
|
742
|
+
isX402V2Settlement,
|
|
743
|
+
parsePaymentRequired,
|
|
744
|
+
parsePaymentRequiredFromBody,
|
|
745
|
+
parsePaymentRequiredFromHeader,
|
|
499
746
|
parseSignature,
|
|
747
|
+
parseSignatureV2,
|
|
748
|
+
selectSchemeRequirements,
|
|
500
749
|
signTypedData,
|
|
501
750
|
signWithPrivateKey,
|
|
502
751
|
validateTransferWithAuthorization2 as validateCoreTransferWithAuthorization,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@armory-sh/client-web3",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
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-web3"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@armory-sh/base": "^0.2.
|
|
30
|
+
"@armory-sh/base": "^0.2.13",
|
|
31
31
|
"web3": "4.16.0",
|
|
32
32
|
"web3-types": "1.10.0"
|
|
33
33
|
},
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"bun-types": "latest"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"build": "tsup",
|
|
39
|
+
"build": "rm -rf dist && tsup",
|
|
40
40
|
"test": "bun test",
|
|
41
41
|
"example": "bun run examples/"
|
|
42
42
|
}
|