@armory-sh/client-web3 0.2.24 → 0.2.26
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/README.md +22 -0
- package/dist/index.d.ts +31 -30
- package/dist/index.js +244 -139
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Armory x402 SDK — Payment client for Web3.js. Make payments from any Web3.js w
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
bun add @armory-sh/client-web3
|
|
11
|
+
bun add @armory-sh/client-hooks # optional preference/logger hooks
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
## Why Armory?
|
|
@@ -60,9 +61,30 @@ const response = await client.fetch('https://api.example.com/protected')
|
|
|
60
61
|
const data = await response.json()
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
## Hook Pipeline
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createX402Client } from '@armory-sh/client-web3'
|
|
68
|
+
import { PaymentPreference, Logger } from '@armory-sh/client-hooks'
|
|
69
|
+
|
|
70
|
+
const client = createX402Client({
|
|
71
|
+
account,
|
|
72
|
+
hooks: [
|
|
73
|
+
PaymentPreference.chain(['base', 'polygon', 'skale']),
|
|
74
|
+
PaymentPreference.token(['USDT', 'USDC', 'WBTC']),
|
|
75
|
+
PaymentPreference.cheapest(),
|
|
76
|
+
Logger.console(),
|
|
77
|
+
],
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`parsePaymentRequired` returns `accepts[]` (x402 v2 challenge options). Clients select from this list.
|
|
82
|
+
`hooks` are lifecycle callbacks. `extensions` are protocol payload fields. Hooks can drive selection and payload behavior, but they are not extensions.
|
|
83
|
+
|
|
63
84
|
## Features
|
|
64
85
|
|
|
65
86
|
- **Auto 402 Handling**: Automatically intercepts and pays for 402 responses
|
|
87
|
+
- **Detailed Verification Errors**: 402 retry failures include server details (for example `insufficient_funds`)
|
|
66
88
|
- **EIP-3009 Signing**: Full support for EIP-3009 TransferWithAuthorization
|
|
67
89
|
- **Multi-Network**: Ethereum, Base, SKALE support
|
|
68
90
|
- **Multi-Token**: USDC, EURC, USDT, WBTC, WETH, SKL
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Web3BaseWalletAccount, Web3BaseWallet } from 'web3-types';
|
|
2
1
|
import { NetworkConfig, CustomToken, PaymentPayloadV2, PaymentRequirementsV2, SettlementResponseV2, NetworkId, TokenId, ArmoryPaymentResult, ValidationError } from '@armory-sh/base';
|
|
3
|
-
|
|
2
|
+
import { ClientHook } from '@armory-sh/base/types/hooks';
|
|
3
|
+
import { Web3BaseWalletAccount, Web3BaseWallet } from 'web3-types';
|
|
4
4
|
|
|
5
5
|
type Web3Account = Web3BaseWalletAccount | Web3BaseWallet<Web3BaseWalletAccount>;
|
|
6
6
|
/** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
|
|
@@ -16,6 +16,7 @@ interface Web3ClientConfig {
|
|
|
16
16
|
domainName?: string;
|
|
17
17
|
/** Override EIP-712 domain version for custom tokens */
|
|
18
18
|
domainVersion?: string;
|
|
19
|
+
hooks?: ClientHook<Web3Account>[];
|
|
19
20
|
}
|
|
20
21
|
interface PaymentSignatureResult {
|
|
21
22
|
signature?: {
|
|
@@ -67,34 +68,6 @@ interface Web3EIP712Domain {
|
|
|
67
68
|
[key: string]: string | number;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
declare const createX402Client: (config: Web3ClientConfig) => Web3X402Client;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Create an x402 transport layer for handling payment-required responses
|
|
74
|
-
*/
|
|
75
|
-
declare const createX402Transport: (options: X402TransportOptions) => X402Transport;
|
|
76
|
-
|
|
77
|
-
declare const createEIP712Domain: (chainId: number | string, contractAddress: string, domainName?: string, domainVersion?: string) => Web3EIP712Domain;
|
|
78
|
-
declare const createTransferWithAuthorization: (params: Web3TransferWithAuthorization) => Record<string, string>;
|
|
79
|
-
declare const validateTransferWithAuthorization: (message: Web3TransferWithAuthorization) => boolean;
|
|
80
|
-
declare const parseSignature: (signature: string) => {
|
|
81
|
-
v: number;
|
|
82
|
-
r: string;
|
|
83
|
-
s: string;
|
|
84
|
-
};
|
|
85
|
-
declare const concatenateSignature: (v: number, r: string, s: string) => string;
|
|
86
|
-
declare const adjustVForChainId: (v: number, chainId: number) => number;
|
|
87
|
-
declare const signTypedData: (_account: Web3BaseWalletAccount | Web3BaseWallet<Web3BaseWalletAccount>, _domain: Web3EIP712Domain, _message: Record<string, string>) => Promise<{
|
|
88
|
-
v: number;
|
|
89
|
-
r: string;
|
|
90
|
-
s: string;
|
|
91
|
-
}>;
|
|
92
|
-
declare const signWithPrivateKey: (_privateKey: string, _domain: Web3EIP712Domain, _message: Record<string, string>) => Promise<{
|
|
93
|
-
v: number;
|
|
94
|
-
r: string;
|
|
95
|
-
s: string;
|
|
96
|
-
}>;
|
|
97
|
-
|
|
98
71
|
/**
|
|
99
72
|
* Simple one-line payment API for Armory (Web3)
|
|
100
73
|
* Focus on DX/UX - "everything just magically works"
|
|
@@ -205,4 +178,32 @@ interface ArmoryInstance {
|
|
|
205
178
|
}
|
|
206
179
|
declare const createArmory: (config: ArmoryConfig) => ArmoryInstance;
|
|
207
180
|
|
|
181
|
+
declare const createX402Client: (config: Web3ClientConfig) => Web3X402Client;
|
|
182
|
+
|
|
183
|
+
declare const createEIP712Domain: (chainId: number | string, contractAddress: string, domainName?: string, domainVersion?: string) => Web3EIP712Domain;
|
|
184
|
+
declare const createTransferWithAuthorization: (params: Web3TransferWithAuthorization) => Record<string, string>;
|
|
185
|
+
declare const validateTransferWithAuthorization: (message: Web3TransferWithAuthorization) => boolean;
|
|
186
|
+
declare const parseSignature: (signature: string) => {
|
|
187
|
+
v: number;
|
|
188
|
+
r: string;
|
|
189
|
+
s: string;
|
|
190
|
+
};
|
|
191
|
+
declare const concatenateSignature: (v: number, r: string, s: string) => string;
|
|
192
|
+
declare const adjustVForChainId: (v: number, chainId: number) => number;
|
|
193
|
+
declare const signTypedData: (_account: Web3BaseWalletAccount | Web3BaseWallet<Web3BaseWalletAccount>, _domain: Web3EIP712Domain, _message: Record<string, string>) => Promise<{
|
|
194
|
+
v: number;
|
|
195
|
+
r: string;
|
|
196
|
+
s: string;
|
|
197
|
+
}>;
|
|
198
|
+
declare const signWithPrivateKey: (_privateKey: string, _domain: Web3EIP712Domain, _message: Record<string, string>) => Promise<{
|
|
199
|
+
v: number;
|
|
200
|
+
r: string;
|
|
201
|
+
s: string;
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Create an x402 transport layer for handling payment-required responses
|
|
206
|
+
*/
|
|
207
|
+
declare const createX402Transport: (options: X402TransportOptions) => X402Transport;
|
|
208
|
+
|
|
208
209
|
export { type ArmoryConfig, type ArmoryInstance, type HttpMethod, type NormalizedWallet, type PaymentOptions, type SimpleWalletInput, adjustVForChainId, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, concatenateSignature, createArmory, createEIP712Domain, createTransferWithAuthorization, createX402Client, createX402Transport, getNetworks, getTokens, getWalletAddress, normalizeWallet, parseSignature, signTypedData, signWithPrivateKey, validateNetwork, validateToken, validateTransferWithAuthorization };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { encodePaymentV2, validatePaymentConfig, isValidationError, resolveNetwork, resolveToken, V2_HEADERS, getNetworkConfig, getNetworkByChainId, networkToCaip2, combineSignatureV2, normalizeBase64Url, decodeBase64ToUtf8, isX402V2PaymentRequired } from '@armory-sh/base';
|
|
2
|
+
import { runOnPaymentRequiredHooks, getRequirementAttemptOrderWithHooks, runBeforeSignPaymentHooks, runAfterPaymentResponseHooks } from '@armory-sh/base/client-hooks-runtime';
|
|
1
3
|
import { Web3 } from 'web3';
|
|
2
|
-
import { encodePaymentV2, validatePaymentConfig, isValidationError, resolveNetwork, resolveToken, V2_HEADERS, getNetworkConfig, getNetworkByChainId, combineSignatureV2, networkToCaip2, isX402V2PaymentRequired } from '@armory-sh/base';
|
|
3
4
|
|
|
4
5
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
6
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -74,7 +75,9 @@ var validateTransferWithAuthorization = (message) => {
|
|
|
74
75
|
var parseSignature = (signature) => {
|
|
75
76
|
const hexSig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
76
77
|
if (hexSig.length !== 130) {
|
|
77
|
-
throw new Error(
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Invalid signature length: ${hexSig.length} (expected 130)`
|
|
80
|
+
);
|
|
78
81
|
}
|
|
79
82
|
return {
|
|
80
83
|
r: `0x${hexSig.slice(0, 64)}`,
|
|
@@ -103,40 +106,6 @@ var signWithPrivateKey = async (_privateKey, _domain, _message) => {
|
|
|
103
106
|
"Direct private key signing not implemented. Use wallet provider's signTypedData method instead."
|
|
104
107
|
);
|
|
105
108
|
};
|
|
106
|
-
|
|
107
|
-
// src/bytes.ts
|
|
108
|
-
new TextEncoder();
|
|
109
|
-
var textDecoder = new TextDecoder();
|
|
110
|
-
function getNodeBuffer() {
|
|
111
|
-
if ("Buffer" in globalThis) {
|
|
112
|
-
return globalThis.Buffer;
|
|
113
|
-
}
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
function fromBase64(base64) {
|
|
117
|
-
if (typeof atob === "function") {
|
|
118
|
-
const binary = atob(base64);
|
|
119
|
-
const bytes = new Uint8Array(binary.length);
|
|
120
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
121
|
-
bytes[index] = binary.charCodeAt(index);
|
|
122
|
-
}
|
|
123
|
-
return bytes;
|
|
124
|
-
}
|
|
125
|
-
const nodeBuffer = getNodeBuffer();
|
|
126
|
-
if (nodeBuffer) {
|
|
127
|
-
return Uint8Array.from(nodeBuffer.from(base64, "base64"));
|
|
128
|
-
}
|
|
129
|
-
throw new Error("No base64 decoder available in this runtime");
|
|
130
|
-
}
|
|
131
|
-
function decodeBase64ToUtf8(value) {
|
|
132
|
-
const bytes = fromBase64(value);
|
|
133
|
-
return textDecoder.decode(bytes);
|
|
134
|
-
}
|
|
135
|
-
function normalizeBase64Url(value) {
|
|
136
|
-
return value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/protocol.ts
|
|
140
109
|
var detectX402Version = (_response, _fallbackVersion = 2) => {
|
|
141
110
|
return 2;
|
|
142
111
|
};
|
|
@@ -230,7 +199,8 @@ var getAddress = (account) => {
|
|
|
230
199
|
};
|
|
231
200
|
var parseSignature2 = (signature) => {
|
|
232
201
|
const hexSig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
233
|
-
if (hexSig.length !== 130)
|
|
202
|
+
if (hexSig.length !== 130)
|
|
203
|
+
throw new Error(`Invalid signature length: ${hexSig.length}`);
|
|
234
204
|
return {
|
|
235
205
|
r: `0x${hexSig.slice(0, 64)}`,
|
|
236
206
|
s: `0x${hexSig.slice(64, 128)}`,
|
|
@@ -256,38 +226,52 @@ var signTypedDataWrapper = async (account, domain, message) => {
|
|
|
256
226
|
return parseSignature2(sig);
|
|
257
227
|
}
|
|
258
228
|
if ("privateKey" in account && typeof account.privateKey === "string") {
|
|
259
|
-
throw new Error(
|
|
229
|
+
throw new Error(
|
|
230
|
+
"Direct private key signing not implemented. Use wallet provider with signTypedData."
|
|
231
|
+
);
|
|
260
232
|
}
|
|
261
233
|
throw new Error("Account does not support EIP-712 signing.");
|
|
262
234
|
};
|
|
263
235
|
var signPaymentV2 = async (state, network, params) => {
|
|
264
236
|
const { from, to, amount, nonce, expiry, accepted } = params;
|
|
265
|
-
const
|
|
266
|
-
|
|
237
|
+
const defaultAccepted = accepted ?? {
|
|
238
|
+
scheme: "exact",
|
|
239
|
+
network: networkToCaip2(network.name),
|
|
240
|
+
amount,
|
|
241
|
+
asset: network.usdcAddress,
|
|
242
|
+
payTo: to,
|
|
243
|
+
maxTimeoutSeconds: expiry - Math.floor(Date.now() / 1e3)
|
|
244
|
+
};
|
|
245
|
+
const domainExtra = defaultAccepted.extra;
|
|
246
|
+
const requirementDomainName = defaultAccepted.name ?? (domainExtra && typeof domainExtra === "object" && typeof domainExtra.name === "string" ? domainExtra.name : void 0);
|
|
247
|
+
const requirementDomainVersion = defaultAccepted.version ?? (domainExtra && typeof domainExtra === "object" && typeof domainExtra.version === "string" ? domainExtra.version : void 0);
|
|
248
|
+
const effectiveDomainName = state.domainName ?? requirementDomainName;
|
|
249
|
+
const effectiveDomainVersion = state.domainVersion ?? requirementDomainVersion;
|
|
250
|
+
const chainId = parseInt(defaultAccepted.network.split(":")[1], 10);
|
|
251
|
+
const domain = createEIP712Domain(
|
|
252
|
+
chainId,
|
|
253
|
+
defaultAccepted.asset,
|
|
254
|
+
effectiveDomainName,
|
|
255
|
+
effectiveDomainVersion
|
|
256
|
+
);
|
|
257
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
258
|
+
const validAfterHex = `0x${(nowSeconds - 600).toString(16)}`;
|
|
267
259
|
const message = createTransferWithAuthorization({
|
|
268
260
|
from,
|
|
269
261
|
to,
|
|
270
262
|
value: amount,
|
|
271
|
-
validAfter:
|
|
263
|
+
validAfter: validAfterHex,
|
|
272
264
|
validBefore: `0x${expiry.toString(16)}`,
|
|
273
265
|
nonce: `0x${nonce}`
|
|
274
266
|
});
|
|
275
267
|
const signature = await signTypedDataWrapper(state.account, domain, message);
|
|
276
268
|
const combinedSig = combineSignatureV2(signature.v, signature.r, signature.s);
|
|
277
|
-
const defaultAccepted = accepted ?? {
|
|
278
|
-
scheme: "exact",
|
|
279
|
-
network: networkToCaip2(network.name),
|
|
280
|
-
amount,
|
|
281
|
-
asset: network.usdcAddress,
|
|
282
|
-
payTo: to,
|
|
283
|
-
maxTimeoutSeconds: expiry - Math.floor(Date.now() / 1e3)
|
|
284
|
-
};
|
|
285
269
|
const x402Payload = createX402V2Payment({
|
|
286
270
|
from,
|
|
287
271
|
to,
|
|
288
272
|
value: amount,
|
|
289
273
|
nonce: `0x${nonce.padStart(64, "0")}`,
|
|
290
|
-
validAfter:
|
|
274
|
+
validAfter: validAfterHex,
|
|
291
275
|
validBefore: `0x${expiry.toString(16)}`,
|
|
292
276
|
signature: combinedSig,
|
|
293
277
|
network: defaultAccepted.network,
|
|
@@ -315,31 +299,111 @@ var extractNetworkFromRequirements = (requirements) => {
|
|
|
315
299
|
}
|
|
316
300
|
return network;
|
|
317
301
|
};
|
|
302
|
+
var getPaymentFailureDetail = async (response) => {
|
|
303
|
+
const text = (await response.clone().text()).trim();
|
|
304
|
+
if (!text) {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const parsed = JSON.parse(text);
|
|
309
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) {
|
|
310
|
+
return parsed.message.trim();
|
|
311
|
+
}
|
|
312
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
313
|
+
return parsed.error.trim();
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
return text;
|
|
318
|
+
};
|
|
319
|
+
var createPaymentVerificationError = async (response) => {
|
|
320
|
+
const detail = await getPaymentFailureDetail(response);
|
|
321
|
+
return new Error(
|
|
322
|
+
detail ? `Payment verification failed: ${detail}` : "Payment verification failed"
|
|
323
|
+
);
|
|
324
|
+
};
|
|
318
325
|
var createX402Client = (config) => {
|
|
319
326
|
const state = createClientState(config);
|
|
327
|
+
const hooks = config.hooks;
|
|
320
328
|
const fetch2 = async (url, init) => {
|
|
321
|
-
let response = await
|
|
329
|
+
let response = await globalThis.fetch(url, init);
|
|
322
330
|
if (response.status === 402) {
|
|
323
331
|
const parsed = await parsePaymentRequired(response);
|
|
324
|
-
const selectedRequirements = parsed.requirements
|
|
332
|
+
const selectedRequirements = parsed.requirements.find(
|
|
333
|
+
(requirement) => requirement.network === state.network?.caip2Id
|
|
334
|
+
) ?? parsed.requirements[0];
|
|
325
335
|
if (!selectedRequirements) {
|
|
326
336
|
throw new Error("No supported payment scheme found in requirements");
|
|
327
337
|
}
|
|
328
338
|
const from = getAddress(state.account);
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
339
|
+
const paymentRequiredContext = {
|
|
340
|
+
url,
|
|
341
|
+
requestInit: init,
|
|
342
|
+
accepts: parsed.requirements,
|
|
343
|
+
requirements: selectedRequirements,
|
|
344
|
+
selectedRequirement: selectedRequirements,
|
|
345
|
+
serverExtensions: void 0,
|
|
346
|
+
fromAddress: from,
|
|
347
|
+
nonce: `0x${Date.now().toString(16).padStart(64, "0")}`,
|
|
348
|
+
validBefore: Math.floor(Date.now() / 1e3) + selectedRequirements.maxTimeoutSeconds
|
|
349
|
+
};
|
|
350
|
+
await runOnPaymentRequiredHooks(hooks, paymentRequiredContext);
|
|
351
|
+
const attemptRequirements = await getRequirementAttemptOrderWithHooks(
|
|
352
|
+
hooks,
|
|
353
|
+
paymentRequiredContext
|
|
354
|
+
);
|
|
355
|
+
let lastError;
|
|
356
|
+
for (const req of attemptRequirements) {
|
|
357
|
+
try {
|
|
358
|
+
const to = typeof req.payTo === "string" ? req.payTo : "0x0000000000000000000000000000000000000000";
|
|
359
|
+
const attemptValidBefore = Math.floor(Date.now() / 1e3) + req.maxTimeoutSeconds;
|
|
360
|
+
const attemptContext = {
|
|
361
|
+
...paymentRequiredContext,
|
|
362
|
+
requirements: req,
|
|
363
|
+
selectedRequirement: req,
|
|
364
|
+
validBefore: attemptValidBefore
|
|
365
|
+
};
|
|
366
|
+
const network = extractNetworkFromRequirements(req);
|
|
367
|
+
const result = await signPaymentV2(state, network, {
|
|
368
|
+
from,
|
|
369
|
+
to,
|
|
370
|
+
amount: req.amount,
|
|
371
|
+
nonce: crypto.randomUUID(),
|
|
372
|
+
expiry: attemptValidBefore,
|
|
373
|
+
accepted: req
|
|
374
|
+
});
|
|
375
|
+
await runBeforeSignPaymentHooks(hooks, {
|
|
376
|
+
payload: result.payload,
|
|
377
|
+
requirements: req,
|
|
378
|
+
wallet: state.account,
|
|
379
|
+
paymentContext: attemptContext
|
|
380
|
+
});
|
|
381
|
+
const paymentHeaders = new Headers(init?.headers);
|
|
382
|
+
paymentHeaders.set(
|
|
383
|
+
V2_HEADERS.PAYMENT_SIGNATURE,
|
|
384
|
+
encodePaymentV2(result.payload)
|
|
385
|
+
);
|
|
386
|
+
response = await globalThis.fetch(url, {
|
|
387
|
+
...init,
|
|
388
|
+
headers: paymentHeaders
|
|
389
|
+
});
|
|
390
|
+
await runAfterPaymentResponseHooks(hooks, {
|
|
391
|
+
payload: result.payload,
|
|
392
|
+
requirements: req,
|
|
393
|
+
wallet: state.account,
|
|
394
|
+
paymentContext: attemptContext,
|
|
395
|
+
response
|
|
396
|
+
});
|
|
397
|
+
if (response.status === 402) {
|
|
398
|
+
lastError = await createPaymentVerificationError(response);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
return response;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
throw lastError ?? new Error("Payment verification failed");
|
|
343
407
|
}
|
|
344
408
|
return response;
|
|
345
409
|
};
|
|
@@ -350,7 +414,9 @@ var createX402Client = (config) => {
|
|
|
350
414
|
getVersion: () => state.version,
|
|
351
415
|
signPayment: async (options) => {
|
|
352
416
|
const network = state.network ?? (() => {
|
|
353
|
-
throw new Error(
|
|
417
|
+
throw new Error(
|
|
418
|
+
"Network must be configured for manual payment signing"
|
|
419
|
+
);
|
|
354
420
|
})();
|
|
355
421
|
const from = getAddress(state.account);
|
|
356
422
|
const to = options.to;
|
|
@@ -361,14 +427,22 @@ var createX402Client = (config) => {
|
|
|
361
427
|
},
|
|
362
428
|
createPaymentHeaders: async (options) => {
|
|
363
429
|
const network = state.network ?? (() => {
|
|
364
|
-
throw new Error(
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Network must be configured for manual payment signing"
|
|
432
|
+
);
|
|
365
433
|
})();
|
|
366
434
|
const from = getAddress(state.account);
|
|
367
435
|
const to = options.to;
|
|
368
436
|
const amount = options.amount.toString();
|
|
369
437
|
const nonce = options.nonce ?? crypto.randomUUID();
|
|
370
438
|
const expiry = options.expiry ?? Math.floor(Date.now() / 1e3) + DEFAULT_EXPIRY_SECONDS;
|
|
371
|
-
const result = await signPaymentV2(state, network, {
|
|
439
|
+
const result = await signPaymentV2(state, network, {
|
|
440
|
+
from,
|
|
441
|
+
to,
|
|
442
|
+
amount,
|
|
443
|
+
nonce,
|
|
444
|
+
expiry
|
|
445
|
+
});
|
|
372
446
|
const headers = new Headers();
|
|
373
447
|
headers.set("PAYMENT-SIGNATURE", encodePaymentV2(result.payload));
|
|
374
448
|
return headers;
|
|
@@ -391,69 +465,8 @@ var createX402Client = (config) => {
|
|
|
391
465
|
}
|
|
392
466
|
};
|
|
393
467
|
};
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const headers = new Headers();
|
|
397
|
-
headers.set(V2_HEADERS.PAYMENT_SIGNATURE, encodePaymentV2(payload));
|
|
398
|
-
return headers;
|
|
399
|
-
};
|
|
400
|
-
var isPaymentRelatedError = (error) => error.message.includes("402") || error.message.includes("payment") || error.message.includes("signature") || error.message.includes("Payment");
|
|
401
|
-
var backoff = (attempt) => {
|
|
402
|
-
const baseDelay = Math.min(1e3 * Math.pow(2, attempt - 1), 1e4);
|
|
403
|
-
const jitter = Math.random() * 100;
|
|
404
|
-
return new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
|
|
405
|
-
};
|
|
406
|
-
var handlePaymentRequired = async (response, client) => {
|
|
407
|
-
detectX402Version(response, client.getVersion());
|
|
408
|
-
const parsed = await parsePaymentRequired(response);
|
|
409
|
-
const selectedRequirements = parsed.requirements[0];
|
|
410
|
-
if (!selectedRequirements) {
|
|
411
|
-
throw new Error("No supported payment scheme found in requirements");
|
|
412
|
-
}
|
|
413
|
-
const req = selectedRequirements;
|
|
414
|
-
const result = await client.handlePaymentRequired(req);
|
|
415
|
-
return createPaymentHeaders(result.payload);
|
|
416
|
-
};
|
|
417
|
-
var mergePaymentHeaders = (init = {}, paymentHeaders) => {
|
|
418
|
-
const existingHeaders = new Headers(init.headers);
|
|
419
|
-
for (const [key, value] of paymentHeaders.entries()) {
|
|
420
|
-
existingHeaders.set(key, value);
|
|
421
|
-
}
|
|
422
|
-
return { ...init, headers: existingHeaders };
|
|
423
|
-
};
|
|
424
|
-
var createX402Transport = (options) => {
|
|
425
|
-
const client = options.client;
|
|
426
|
-
const autoSign = options.autoSign ?? true;
|
|
427
|
-
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
428
|
-
return {
|
|
429
|
-
getClient: () => client,
|
|
430
|
-
fetch: async (url, init) => {
|
|
431
|
-
let attempt = 0;
|
|
432
|
-
let lastError;
|
|
433
|
-
while (attempt < maxRetries) {
|
|
434
|
-
attempt++;
|
|
435
|
-
try {
|
|
436
|
-
const response = await fetch(url, init);
|
|
437
|
-
if (isPaymentRequiredResponse(response) && autoSign) {
|
|
438
|
-
const paymentHeaders = await handlePaymentRequired(response, client);
|
|
439
|
-
const newInit = mergePaymentHeaders(init, paymentHeaders);
|
|
440
|
-
return await fetch(url, newInit);
|
|
441
|
-
}
|
|
442
|
-
return response;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
445
|
-
if (!isPaymentRelatedError(lastError)) {
|
|
446
|
-
throw lastError;
|
|
447
|
-
}
|
|
448
|
-
if (attempt < maxRetries) {
|
|
449
|
-
await backoff(attempt);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
throw lastError ?? new Error("Max retries exceeded");
|
|
454
|
-
}
|
|
455
|
-
};
|
|
456
|
-
};
|
|
468
|
+
|
|
469
|
+
// src/payment-api.ts
|
|
457
470
|
var normalizeWallet = (wallet) => {
|
|
458
471
|
if (typeof wallet === "object" && wallet !== null && "account" in wallet) {
|
|
459
472
|
return wallet.account;
|
|
@@ -522,19 +535,37 @@ var armoryPay = async (wallet, url, network, token, options) => {
|
|
|
522
535
|
}
|
|
523
536
|
};
|
|
524
537
|
var armoryGet = (wallet, url, network, token, options) => {
|
|
525
|
-
return armoryPay(wallet, url, network, token, {
|
|
538
|
+
return armoryPay(wallet, url, network, token, {
|
|
539
|
+
...options,
|
|
540
|
+
method: "GET"
|
|
541
|
+
});
|
|
526
542
|
};
|
|
527
543
|
var armoryPost = (wallet, url, network, token, body, options) => {
|
|
528
|
-
return armoryPay(wallet, url, network, token, {
|
|
544
|
+
return armoryPay(wallet, url, network, token, {
|
|
545
|
+
...options,
|
|
546
|
+
method: "POST",
|
|
547
|
+
body
|
|
548
|
+
});
|
|
529
549
|
};
|
|
530
550
|
var armoryPut = (wallet, url, network, token, body, options) => {
|
|
531
|
-
return armoryPay(wallet, url, network, token, {
|
|
551
|
+
return armoryPay(wallet, url, network, token, {
|
|
552
|
+
...options,
|
|
553
|
+
method: "PUT",
|
|
554
|
+
body
|
|
555
|
+
});
|
|
532
556
|
};
|
|
533
557
|
var armoryDelete = (wallet, url, network, token, options) => {
|
|
534
|
-
return armoryPay(wallet, url, network, token, {
|
|
558
|
+
return armoryPay(wallet, url, network, token, {
|
|
559
|
+
...options,
|
|
560
|
+
method: "DELETE"
|
|
561
|
+
});
|
|
535
562
|
};
|
|
536
563
|
var armoryPatch = (wallet, url, network, token, body, options) => {
|
|
537
|
-
return armoryPay(wallet, url, network, token, {
|
|
564
|
+
return armoryPay(wallet, url, network, token, {
|
|
565
|
+
...options,
|
|
566
|
+
method: "PATCH",
|
|
567
|
+
body
|
|
568
|
+
});
|
|
538
569
|
};
|
|
539
570
|
var getWalletAddress = (wallet) => {
|
|
540
571
|
const account = normalizeWallet(wallet);
|
|
@@ -550,7 +581,7 @@ var validateNetwork = (network) => {
|
|
|
550
581
|
return { success: true, network: resolved.config.name };
|
|
551
582
|
};
|
|
552
583
|
var validateToken = (token, network) => {
|
|
553
|
-
let resolvedNetwork
|
|
584
|
+
let resolvedNetwork;
|
|
554
585
|
if (network) {
|
|
555
586
|
const networkResult = resolveNetwork(network);
|
|
556
587
|
if (isValidationError(networkResult)) {
|
|
@@ -578,7 +609,13 @@ var getTokens = () => {
|
|
|
578
609
|
};
|
|
579
610
|
|
|
580
611
|
// src/armory-api.ts
|
|
581
|
-
var ALL_METHODS = /* @__PURE__ */ new Set([
|
|
612
|
+
var ALL_METHODS = /* @__PURE__ */ new Set([
|
|
613
|
+
"GET",
|
|
614
|
+
"POST",
|
|
615
|
+
"PUT",
|
|
616
|
+
"DELETE",
|
|
617
|
+
"PATCH"
|
|
618
|
+
]);
|
|
582
619
|
var arrayify = (value) => {
|
|
583
620
|
if (value === void 0) return void 0;
|
|
584
621
|
return Array.isArray(value) ? value : [value];
|
|
@@ -634,5 +671,73 @@ var createArmory = (config) => {
|
|
|
634
671
|
call: (url) => makeRequest(url, "GET")
|
|
635
672
|
};
|
|
636
673
|
};
|
|
674
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
675
|
+
var createPaymentHeaders = (payload, _version) => {
|
|
676
|
+
const headers = new Headers();
|
|
677
|
+
headers.set(V2_HEADERS.PAYMENT_SIGNATURE, encodePaymentV2(payload));
|
|
678
|
+
return headers;
|
|
679
|
+
};
|
|
680
|
+
var isPaymentRelatedError = (error) => error.message.includes("402") || error.message.includes("payment") || error.message.includes("signature") || error.message.includes("Payment");
|
|
681
|
+
var backoff = (attempt) => {
|
|
682
|
+
const baseDelay = Math.min(1e3 * 2 ** (attempt - 1), 1e4);
|
|
683
|
+
const jitter = Math.random() * 100;
|
|
684
|
+
return new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
|
|
685
|
+
};
|
|
686
|
+
var handlePaymentRequired = async (response, client) => {
|
|
687
|
+
detectX402Version(response, client.getVersion());
|
|
688
|
+
const parsed = await parsePaymentRequired(response);
|
|
689
|
+
const selectedRequirements = parsed.requirements.find(
|
|
690
|
+
(requirement) => requirement.network === client.getNetwork()?.caip2Id
|
|
691
|
+
) ?? parsed.requirements[0];
|
|
692
|
+
if (!selectedRequirements) {
|
|
693
|
+
throw new Error("No supported payment scheme found in requirements");
|
|
694
|
+
}
|
|
695
|
+
const req = selectedRequirements;
|
|
696
|
+
const result = await client.handlePaymentRequired(req);
|
|
697
|
+
return createPaymentHeaders(result.payload);
|
|
698
|
+
};
|
|
699
|
+
var mergePaymentHeaders = (init = {}, paymentHeaders) => {
|
|
700
|
+
const existingHeaders = new Headers(init.headers);
|
|
701
|
+
for (const [key, value] of paymentHeaders.entries()) {
|
|
702
|
+
existingHeaders.set(key, value);
|
|
703
|
+
}
|
|
704
|
+
return { ...init, headers: existingHeaders };
|
|
705
|
+
};
|
|
706
|
+
var createX402Transport = (options) => {
|
|
707
|
+
const client = options.client;
|
|
708
|
+
const autoSign = options.autoSign ?? true;
|
|
709
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
710
|
+
return {
|
|
711
|
+
getClient: () => client,
|
|
712
|
+
fetch: async (url, init) => {
|
|
713
|
+
let attempt = 0;
|
|
714
|
+
let lastError;
|
|
715
|
+
while (attempt < maxRetries) {
|
|
716
|
+
attempt++;
|
|
717
|
+
try {
|
|
718
|
+
const response = await fetch(url, init);
|
|
719
|
+
if (isPaymentRequiredResponse(response) && autoSign) {
|
|
720
|
+
const paymentHeaders = await handlePaymentRequired(
|
|
721
|
+
response,
|
|
722
|
+
client
|
|
723
|
+
);
|
|
724
|
+
const newInit = mergePaymentHeaders(init, paymentHeaders);
|
|
725
|
+
return await fetch(url, newInit);
|
|
726
|
+
}
|
|
727
|
+
return response;
|
|
728
|
+
} catch (error) {
|
|
729
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
730
|
+
if (!isPaymentRelatedError(lastError)) {
|
|
731
|
+
throw lastError;
|
|
732
|
+
}
|
|
733
|
+
if (attempt < maxRetries) {
|
|
734
|
+
await backoff(attempt);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
};
|
|
637
742
|
|
|
638
743
|
export { adjustVForChainId, armoryDelete, armoryGet, armoryPatch, armoryPay, armoryPost, armoryPut, concatenateSignature, createArmory, createEIP712Domain, createTransferWithAuthorization, createX402Client, createX402Transport, getNetworks, getTokens, getWalletAddress, normalizeWallet, parseSignature, signTypedData, signWithPrivateKey, validateNetwork, validateToken, validateTransferWithAuthorization };
|
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.26",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"directory": "packages/client-web3"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@armory-sh/base": "0.2.
|
|
49
|
+
"@armory-sh/base": "0.2.30",
|
|
50
50
|
"web3": "4.16.0",
|
|
51
51
|
"web3-types": "1.10.0"
|
|
52
52
|
},
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rm -rf dist && tsup",
|
|
59
|
+
"lint": "bun run build",
|
|
60
|
+
"format": "bun run lint",
|
|
59
61
|
"test": "bun test",
|
|
60
62
|
"example": "bun run examples/"
|
|
61
63
|
}
|