@armory-sh/client-ethers 0.2.9 → 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 +115 -36
- package/dist/index.js +313 -137
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { Address, BalanceOfParams, CAIP2ChainId, CAIPAssetId, EIP712_TYPES, ERC20_ABI, Extensions, NETWORKS, NetworkConfig, PayToV2, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, Signature, TransferWithAuthorizationParams, V1_HEADERS, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV1, decodePaymentV2, decodeSettlementLegacy, decodeSettlementV1, decodeSettlementV2, detectPaymentVersion, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash,
|
|
1
|
+
import { X402PaymentRequirementsV1, PaymentRequirementsV2, Address, X402PaymentPayloadV1, PaymentPayloadV2, CustomToken } from '@armory-sh/base';
|
|
2
|
+
export { Address, BalanceOfParams, CAIP2ChainId, CAIPAssetId, EIP712_TYPES, ERC20_ABI, Extensions, NETWORKS, NetworkConfig, PayToV2, PaymentPayload, PaymentPayloadV1, PaymentPayloadV2, PaymentRequirements, PaymentRequirementsV1, PaymentRequirementsV2, SettlementResponse, SettlementResponseV1, SettlementResponseV2, Signature, TransferWithAuthorizationParams, V1_HEADERS, V2_HEADERS, createEIP712Domain, createTransferWithAuthorization, decodePayment, decodePaymentV1, decodePaymentV2, decodeSettlementLegacy, decodeSettlementV1, decodeSettlementV2, detectPaymentVersion, encodePaymentV1, encodePaymentV2, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName as getPaymentHeaderNameBase, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersion, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash, isCAIP2ChainId, isCAIPAssetId, isPaymentV1, isPaymentV2, isSettlementSuccessful, isSettlementV1, isSettlementV2, isV1, isV2, isX402V1PaymentRequired, isX402V1Requirements, isX402V2PaymentRequired, safeBase64Decode, validateTransferWithAuthorization } from '@armory-sh/base';
|
|
3
3
|
import { Signer, Provider } from 'ethers';
|
|
4
|
-
export { Signer } from 'ethers';
|
|
4
|
+
export { Provider, Signer } from 'ethers';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
declare function
|
|
37
|
-
|
|
6
|
+
/**
|
|
7
|
+
* X402 Protocol Implementation for Ethers Client
|
|
8
|
+
*
|
|
9
|
+
* Handles parsing x402 V1 and V2 PAYMENT-REQUIRED headers
|
|
10
|
+
* and generating x402 V1 and V2 PAYMENT-SIGNATURE payloads
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect x402 protocol version from response headers
|
|
15
|
+
*/
|
|
16
|
+
declare function detectX402Version(response: Response): 1 | 2;
|
|
17
|
+
/**
|
|
18
|
+
* Parsed payment requirements with version info
|
|
19
|
+
*/
|
|
20
|
+
interface ParsedPaymentRequirements {
|
|
21
|
+
version: 1 | 2;
|
|
22
|
+
requirements: X402PaymentRequirementsV1 | PaymentRequirementsV2;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse x402 PAYMENT-REQUIRED header from response
|
|
26
|
+
* Automatically detects V1 or V2 format
|
|
27
|
+
*/
|
|
28
|
+
declare function parsePaymentRequired(response: Response): ParsedPaymentRequirements;
|
|
29
|
+
/**
|
|
30
|
+
* Create x402 V1 payment payload
|
|
31
|
+
*/
|
|
32
|
+
declare function createX402V1Payment(signer: Signer, requirements: X402PaymentRequirementsV1, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1>;
|
|
33
|
+
/**
|
|
34
|
+
* Create x402 V2 payment payload
|
|
35
|
+
*/
|
|
36
|
+
declare function createX402V2Payment(signer: Signer, requirements: PaymentRequirementsV2, fromAddress: Address, nonce: `0x${string}`, validBefore: number, domainName?: string, domainVersion?: string): Promise<PaymentPayloadV2>;
|
|
37
|
+
/**
|
|
38
|
+
* Create x402 payment payload (auto-detects version)
|
|
39
|
+
*/
|
|
40
|
+
declare function createX402Payment(signer: Signer, parsed: ParsedPaymentRequirements, fromAddress: Address, nonce?: `0x${string}`, validBefore?: number, domainName?: string, domainVersion?: string): Promise<X402PaymentPayloadV1 | PaymentPayloadV2>;
|
|
41
|
+
/**
|
|
42
|
+
* Encode x402 payment payload to Base64 for transport
|
|
43
|
+
*/
|
|
44
|
+
declare function encodeX402Payment(payload: X402PaymentPayloadV1 | PaymentPayloadV2): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get the correct header name for payment based on version
|
|
47
|
+
*/
|
|
48
|
+
declare function getPaymentHeaderName(version: 1 | 2): string;
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Use createX402Payment instead
|
|
51
|
+
*/
|
|
52
|
+
declare function createPaymentPayload(requirements: unknown, signer: Signer, from: string): Promise<[string, string]>;
|
|
53
|
+
/**
|
|
54
|
+
* @deprecated Use parsePaymentRequired instead
|
|
55
|
+
*/
|
|
56
|
+
declare function parsePaymentRequirements(response: Response): Promise<unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated Use detectX402Version instead
|
|
59
|
+
*/
|
|
60
|
+
declare function detectProtocolVersion(requirements: unknown): 1 | 2;
|
|
61
|
+
|
|
62
|
+
declare class X402ClientError extends Error {
|
|
63
|
+
readonly cause?: unknown;
|
|
64
|
+
constructor(message: string, cause?: unknown);
|
|
65
|
+
}
|
|
66
|
+
declare class SigningError extends X402ClientError {
|
|
67
|
+
constructor(message: string, cause?: unknown);
|
|
68
|
+
}
|
|
69
|
+
declare class PaymentError extends X402ClientError {
|
|
70
|
+
constructor(message: string, cause?: unknown);
|
|
71
|
+
}
|
|
72
|
+
declare class SignerRequiredError extends X402ClientError {
|
|
73
|
+
constructor(message?: string);
|
|
74
|
+
}
|
|
75
|
+
declare class AuthorizationError extends X402ClientError {
|
|
76
|
+
constructor(message: string, cause?: unknown);
|
|
77
|
+
}
|
|
78
|
+
declare class ProviderRequiredError extends X402ClientError {
|
|
79
|
+
constructor(message?: string);
|
|
80
|
+
}
|
|
38
81
|
|
|
39
82
|
/** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
|
|
40
83
|
type Token = CustomToken;
|
|
@@ -76,6 +119,20 @@ interface X402RequestInit extends Omit<RequestInit, "headers"> {
|
|
|
76
119
|
skipAutoPay?: boolean;
|
|
77
120
|
forceProtocolVersion?: 1 | 2;
|
|
78
121
|
}
|
|
122
|
+
interface TransferWithAuthorizationParams {
|
|
123
|
+
from: `0x${string}`;
|
|
124
|
+
to: `0x${string}`;
|
|
125
|
+
value: bigint | number;
|
|
126
|
+
validAfter: bigint | number;
|
|
127
|
+
validBefore: bigint | number;
|
|
128
|
+
nonce: bigint | number;
|
|
129
|
+
}
|
|
130
|
+
interface EIP712Domain {
|
|
131
|
+
name: string;
|
|
132
|
+
version: string;
|
|
133
|
+
chainId: number;
|
|
134
|
+
verifyingContract: `0x${string}`;
|
|
135
|
+
}
|
|
79
136
|
|
|
80
137
|
interface X402Transport {
|
|
81
138
|
fetch(url: string, init?: X402RequestInit): Promise<Response>;
|
|
@@ -89,4 +146,26 @@ interface X402Transport {
|
|
|
89
146
|
}
|
|
90
147
|
declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
|
|
91
148
|
|
|
92
|
-
|
|
149
|
+
declare function signEIP3009(signer: Signer, params: TransferWithAuthorizationParams, domain: EIP712Domain): Promise<{
|
|
150
|
+
v: number;
|
|
151
|
+
r: string;
|
|
152
|
+
s: string;
|
|
153
|
+
}>;
|
|
154
|
+
declare function signEIP3009WithDomain(signer: Signer, params: TransferWithAuthorizationParams, chainId: number, verifyingContract: `0x${string}`, domainName?: string, domainVersion?: string): Promise<{
|
|
155
|
+
v: number;
|
|
156
|
+
r: string;
|
|
157
|
+
s: string;
|
|
158
|
+
}>;
|
|
159
|
+
declare function signPayment(signer: Signer, from: `0x${string}`, to: `0x${string}`, value: bigint, chainId: number, verifyingContract: `0x${string}`, expirySeconds?: number, domainName?: string, domainVersion?: string): Promise<{
|
|
160
|
+
v: number;
|
|
161
|
+
r: string;
|
|
162
|
+
s: string;
|
|
163
|
+
nonce: bigint;
|
|
164
|
+
}>;
|
|
165
|
+
declare function recoverEIP3009Signer(params: TransferWithAuthorizationParams, signature: {
|
|
166
|
+
v: number;
|
|
167
|
+
r: string;
|
|
168
|
+
s: string;
|
|
169
|
+
}, domain: EIP712Domain): Promise<`0x${string}`>;
|
|
170
|
+
|
|
171
|
+
export { AuthorizationError, type ClientConfig, type ParsedPaymentRequirements, PaymentError, ProviderRequiredError, SignerRequiredError, SigningError, type X402ClientConfig, X402ClientError, type X402RequestInit, type X402Transport, type X402TransportConfig, createPaymentPayload, createX402Payment, createX402Transport, createX402V1Payment, createX402V2Payment, detectProtocolVersion, detectX402Version, encodeX402Payment, getPaymentHeaderName, parsePaymentRequired, parsePaymentRequirements, recoverEIP3009Signer, signEIP3009, signEIP3009WithDomain, signPayment };
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
V1_HEADERS as
|
|
4
|
-
V2_HEADERS as
|
|
3
|
+
V1_HEADERS as V1_HEADERS3,
|
|
4
|
+
V2_HEADERS as V2_HEADERS3,
|
|
5
5
|
isV1,
|
|
6
6
|
isV2,
|
|
7
7
|
getPaymentVersion,
|
|
8
8
|
getRequirementsVersion,
|
|
9
9
|
getSettlementVersion,
|
|
10
|
-
getPaymentHeaderName,
|
|
10
|
+
getPaymentHeaderName as getPaymentHeaderName2,
|
|
11
11
|
getPaymentResponseHeaderName,
|
|
12
12
|
getPaymentRequiredHeaderName,
|
|
13
|
-
isSettlementSuccessful,
|
|
13
|
+
isSettlementSuccessful as isSettlementSuccessful2,
|
|
14
14
|
getTxHash,
|
|
15
15
|
NETWORKS,
|
|
16
|
-
getNetworkConfig,
|
|
16
|
+
getNetworkConfig as getNetworkConfig2,
|
|
17
17
|
getNetworkByChainId as getNetworkByChainId2,
|
|
18
18
|
getMainnets,
|
|
19
19
|
getTestnets,
|
|
20
20
|
ERC20_ABI,
|
|
21
|
-
encodePaymentV1
|
|
21
|
+
encodePaymentV1,
|
|
22
22
|
decodePaymentV1,
|
|
23
23
|
encodeSettlementV1,
|
|
24
24
|
decodeSettlementV1,
|
|
25
|
-
encodePaymentV2
|
|
25
|
+
encodePaymentV2,
|
|
26
26
|
decodePaymentV2,
|
|
27
27
|
encodeSettlementV2,
|
|
28
28
|
decodeSettlementV2,
|
|
@@ -33,30 +33,39 @@ import {
|
|
|
33
33
|
isPaymentV2,
|
|
34
34
|
isSettlementV1,
|
|
35
35
|
isSettlementV2,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
isX402V1PaymentRequired as isX402V1PaymentRequired2,
|
|
37
|
+
isX402V2PaymentRequired as isX402V2PaymentRequired2,
|
|
38
|
+
isX402V1Requirements as isX402V1Requirements2,
|
|
39
|
+
EIP712_TYPES as EIP712_TYPES2,
|
|
40
|
+
createEIP712Domain as createEIP712Domain3,
|
|
41
|
+
createTransferWithAuthorization as createTransferWithAuthorization2,
|
|
39
42
|
validateTransferWithAuthorization,
|
|
40
43
|
isCAIP2ChainId,
|
|
41
44
|
isCAIPAssetId,
|
|
42
|
-
|
|
45
|
+
safeBase64Decode as safeBase64Decode2
|
|
43
46
|
} from "@armory-sh/base";
|
|
44
47
|
|
|
45
48
|
// src/protocol.ts
|
|
46
49
|
import {
|
|
47
|
-
encodePaymentV1,
|
|
48
|
-
encodePaymentV2,
|
|
49
50
|
V1_HEADERS,
|
|
50
51
|
V2_HEADERS,
|
|
51
|
-
|
|
52
|
+
safeBase64Decode,
|
|
53
|
+
isX402V1PaymentRequired,
|
|
54
|
+
isX402V2PaymentRequired,
|
|
55
|
+
isX402V1Requirements,
|
|
56
|
+
getNetworkByChainId,
|
|
57
|
+
getNetworkConfig,
|
|
58
|
+
createEIP712Domain as createEIP712Domain2,
|
|
59
|
+
normalizeNetworkName
|
|
52
60
|
} from "@armory-sh/base";
|
|
53
61
|
|
|
54
62
|
// src/eip3009.ts
|
|
63
|
+
import { ethers } from "ethers";
|
|
55
64
|
import {
|
|
56
65
|
createEIP712Domain
|
|
57
66
|
} from "@armory-sh/base";
|
|
58
67
|
|
|
59
|
-
// src/
|
|
68
|
+
// src/errors.ts
|
|
60
69
|
var X402ClientError = class extends Error {
|
|
61
70
|
cause;
|
|
62
71
|
constructor(message, cause) {
|
|
@@ -65,6 +74,18 @@ var X402ClientError = class extends Error {
|
|
|
65
74
|
this.cause = cause;
|
|
66
75
|
}
|
|
67
76
|
};
|
|
77
|
+
var SigningError = class extends X402ClientError {
|
|
78
|
+
constructor(message, cause) {
|
|
79
|
+
super(`Signing failed: ${message}`, cause);
|
|
80
|
+
this.name = "SigningError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var PaymentError = class extends X402ClientError {
|
|
84
|
+
constructor(message, cause) {
|
|
85
|
+
super(`Payment failed: ${message}`, cause);
|
|
86
|
+
this.name = "PaymentError";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
68
89
|
var SignerRequiredError = class extends X402ClientError {
|
|
69
90
|
constructor(message = "Signer is required for this operation") {
|
|
70
91
|
super(message);
|
|
@@ -77,6 +98,12 @@ var AuthorizationError = class extends X402ClientError {
|
|
|
77
98
|
this.name = "AuthorizationError";
|
|
78
99
|
}
|
|
79
100
|
};
|
|
101
|
+
var ProviderRequiredError = class extends X402ClientError {
|
|
102
|
+
constructor(message = "Provider is required for this operation") {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = "ProviderRequiredError";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
80
107
|
|
|
81
108
|
// src/eip3009.ts
|
|
82
109
|
var EIP712_TYPES_ETHERS = {
|
|
@@ -109,128 +136,256 @@ async function signEIP3009(signer, params, domain) {
|
|
|
109
136
|
);
|
|
110
137
|
}
|
|
111
138
|
}
|
|
139
|
+
async function signEIP3009WithDomain(signer, params, chainId, verifyingContract, domainName, domainVersion) {
|
|
140
|
+
const domain = createEIP712Domain(chainId, verifyingContract);
|
|
141
|
+
const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
|
|
142
|
+
return signEIP3009(signer, params, customDomain);
|
|
143
|
+
}
|
|
144
|
+
async function signPayment(signer, from, to, value, chainId, verifyingContract, expirySeconds = 3600, domainName, domainVersion) {
|
|
145
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
146
|
+
const nonce = BigInt(now * 1e3);
|
|
147
|
+
const signature = await signEIP3009WithDomain(
|
|
148
|
+
signer,
|
|
149
|
+
{ from, to, value, validAfter: 0n, validBefore: BigInt(now + expirySeconds), nonce },
|
|
150
|
+
chainId,
|
|
151
|
+
verifyingContract,
|
|
152
|
+
domainName,
|
|
153
|
+
domainVersion
|
|
154
|
+
);
|
|
155
|
+
return { ...signature, nonce };
|
|
156
|
+
}
|
|
157
|
+
async function recoverEIP3009Signer(params, signature, domain) {
|
|
158
|
+
const message = {
|
|
159
|
+
from: params.from,
|
|
160
|
+
to: params.to,
|
|
161
|
+
value: BigInt(params.value),
|
|
162
|
+
validAfter: BigInt(params.validAfter),
|
|
163
|
+
validBefore: BigInt(params.validBefore),
|
|
164
|
+
nonce: BigInt(params.nonce)
|
|
165
|
+
};
|
|
166
|
+
const sig = ethers.Signature.from({
|
|
167
|
+
v: signature.v,
|
|
168
|
+
r: signature.r,
|
|
169
|
+
s: signature.s
|
|
170
|
+
});
|
|
171
|
+
const address = ethers.verifyTypedData(domain, EIP712_TYPES_ETHERS, message, sig);
|
|
172
|
+
return address;
|
|
173
|
+
}
|
|
112
174
|
|
|
113
175
|
// src/protocol.ts
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
176
|
+
function detectX402Version(response) {
|
|
177
|
+
const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
|
|
178
|
+
if (v2Header) {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(v2Header);
|
|
181
|
+
if (parsed.x402Version === 2) return 2;
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
|
|
186
|
+
if (v1Header) {
|
|
187
|
+
try {
|
|
188
|
+
const decoded = safeBase64Decode(v1Header);
|
|
189
|
+
const parsed = JSON.parse(decoded);
|
|
190
|
+
if (parsed.x402Version === 1) return 1;
|
|
191
|
+
return 1;
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
function parsePaymentRequired(response) {
|
|
198
|
+
const version = detectX402Version(response);
|
|
199
|
+
if (version === 2) {
|
|
200
|
+
const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
|
|
201
|
+
if (!v2Header) {
|
|
202
|
+
throw new PaymentError("No PAYMENT-REQUIRED header found in 402 response");
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(v2Header);
|
|
206
|
+
if (!isX402V2PaymentRequired(parsed)) {
|
|
207
|
+
throw new PaymentError("Invalid x402 V2 payment required format");
|
|
208
|
+
}
|
|
209
|
+
if (!parsed.accepts || parsed.accepts.length === 0) {
|
|
210
|
+
throw new PaymentError("No payment requirements found in accepts array");
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
version: 2,
|
|
214
|
+
requirements: parsed.accepts[0]
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof PaymentError) throw error;
|
|
218
|
+
throw new PaymentError(`Failed to parse V2 PAYMENT-REQUIRED header: ${error}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const v1Header = response.headers.get(V1_HEADERS.PAYMENT_REQUIRED);
|
|
222
|
+
if (!v1Header) {
|
|
223
|
+
throw new PaymentError("No X-PAYMENT-REQUIRED header found in 402 response");
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const decoded = safeBase64Decode(v1Header);
|
|
227
|
+
const parsed = JSON.parse(decoded);
|
|
228
|
+
if (!isX402V1PaymentRequired(parsed)) {
|
|
229
|
+
throw new PaymentError("Invalid x402 V1 payment required format");
|
|
230
|
+
}
|
|
231
|
+
if (!parsed.accepts || parsed.accepts.length === 0) {
|
|
232
|
+
throw new PaymentError("No payment requirements found in accepts array");
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
version: 1,
|
|
236
|
+
requirements: parsed.accepts[0]
|
|
237
|
+
};
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error instanceof PaymentError) throw error;
|
|
240
|
+
throw new PaymentError(`Failed to parse V1 PAYMENT-REQUIRED header: ${error}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function toAtomicUnits(amount) {
|
|
244
|
+
return Math.floor(parseFloat(amount) * 1e6).toString();
|
|
245
|
+
}
|
|
246
|
+
function extractChainId(network) {
|
|
247
|
+
if (network.startsWith("eip155:")) {
|
|
248
|
+
return parseInt(network.split(":")[1], 10);
|
|
249
|
+
}
|
|
250
|
+
const net = getNetworkConfig(normalizeNetworkName(network));
|
|
251
|
+
if (net) {
|
|
252
|
+
return net.chainId;
|
|
253
|
+
}
|
|
254
|
+
throw new PaymentError(`Unsupported network: ${network}`);
|
|
255
|
+
}
|
|
256
|
+
function getNetworkSlug(network) {
|
|
257
|
+
if (network.startsWith("eip155:")) {
|
|
258
|
+
const chainId = parseInt(network.split(":")[1], 10);
|
|
259
|
+
const net = getNetworkByChainId(chainId);
|
|
260
|
+
if (!net) {
|
|
261
|
+
throw new PaymentError(`No network config found for chainId: ${chainId}`);
|
|
262
|
+
}
|
|
263
|
+
return normalizeNetworkName(net.name);
|
|
264
|
+
}
|
|
265
|
+
return normalizeNetworkName(network);
|
|
266
|
+
}
|
|
267
|
+
function createNonce() {
|
|
268
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
269
|
+
return `0x${(now * 1e3).toString(16).padStart(64, "0")}`;
|
|
270
|
+
}
|
|
271
|
+
async function createX402V1Payment(signer, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
|
|
272
|
+
const network = getNetworkSlug(requirements.network);
|
|
273
|
+
const contractAddress = requirements.asset;
|
|
274
|
+
const chainId = extractChainId(requirements.network);
|
|
275
|
+
const domain = createEIP712Domain2(chainId, contractAddress);
|
|
276
|
+
const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
|
|
277
|
+
const authorization = {
|
|
278
|
+
from: fromAddress,
|
|
279
|
+
to: requirements.payTo,
|
|
280
|
+
value: toAtomicUnits(requirements.maxAmountRequired),
|
|
281
|
+
validAfter: "0",
|
|
282
|
+
validBefore: validBefore.toString(),
|
|
283
|
+
nonce
|
|
123
284
|
};
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
285
|
+
const authParams = {
|
|
286
|
+
from: authorization.from,
|
|
287
|
+
to: authorization.to,
|
|
288
|
+
value: BigInt(authorization.value),
|
|
289
|
+
validAfter: BigInt(authorization.validAfter),
|
|
290
|
+
validBefore: BigInt(authorization.validBefore),
|
|
291
|
+
nonce: BigInt(authorization.nonce)
|
|
129
292
|
};
|
|
130
|
-
const signature = await signEIP3009(signer, authParams,
|
|
293
|
+
const signature = await signEIP3009(signer, authParams, customDomain);
|
|
294
|
+
const combinedSignature = `0x${signature.r.slice(2)}${signature.s.slice(2)}${signature.v.toString(16).padStart(2, "0")}`;
|
|
131
295
|
const payload = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
chainId,
|
|
141
|
-
contractAddress,
|
|
142
|
-
network
|
|
296
|
+
signature: combinedSignature,
|
|
297
|
+
authorization
|
|
298
|
+
};
|
|
299
|
+
return {
|
|
300
|
+
x402Version: 1,
|
|
301
|
+
scheme: "exact",
|
|
302
|
+
network,
|
|
303
|
+
payload
|
|
143
304
|
};
|
|
144
|
-
return encodePaymentV1(payload);
|
|
145
305
|
}
|
|
146
|
-
async function
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
306
|
+
async function createX402V2Payment(signer, requirements, fromAddress, nonce, validBefore, domainName, domainVersion) {
|
|
307
|
+
const contractAddress = requirements.asset;
|
|
308
|
+
const chainId = extractChainId(requirements.network);
|
|
309
|
+
const domain = createEIP712Domain2(chainId, contractAddress);
|
|
310
|
+
const customDomain = domainName || domainVersion ? { ...domain, name: domainName ?? domain.name, version: domainVersion ?? domain.version } : domain;
|
|
311
|
+
const authorization = {
|
|
312
|
+
from: fromAddress,
|
|
313
|
+
to: requirements.payTo,
|
|
314
|
+
value: toAtomicUnits(requirements.amount),
|
|
315
|
+
validAfter: "0",
|
|
316
|
+
validBefore: validBefore.toString(),
|
|
317
|
+
nonce
|
|
157
318
|
};
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
319
|
+
const authParams = {
|
|
320
|
+
from: authorization.from,
|
|
321
|
+
to: authorization.to,
|
|
322
|
+
value: BigInt(authorization.value),
|
|
323
|
+
validAfter: BigInt(authorization.validAfter),
|
|
324
|
+
validBefore: BigInt(authorization.validBefore),
|
|
325
|
+
nonce: BigInt(authorization.nonce)
|
|
163
326
|
};
|
|
164
|
-
const signature = await signEIP3009(signer, authParams,
|
|
327
|
+
const signature = await signEIP3009(signer, authParams, customDomain);
|
|
328
|
+
const combinedSignature = `0x${signature.r.slice(2)}${signature.s.slice(2)}${signature.v.toString(16).padStart(2, "0")}`;
|
|
165
329
|
const payload = {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
r: signature.r,
|
|
174
|
-
s: signature.s
|
|
175
|
-
},
|
|
176
|
-
chainId,
|
|
177
|
-
assetId,
|
|
178
|
-
extensions
|
|
330
|
+
signature: combinedSignature,
|
|
331
|
+
authorization
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
x402Version: 2,
|
|
335
|
+
accepted: requirements,
|
|
336
|
+
payload
|
|
179
337
|
};
|
|
180
|
-
return encodePaymentV2(payload);
|
|
181
|
-
}
|
|
182
|
-
function detectProtocolVersion(requirements) {
|
|
183
|
-
if ("contractAddress" in requirements && "network" in requirements) {
|
|
184
|
-
return 1;
|
|
185
|
-
}
|
|
186
|
-
return 2;
|
|
187
338
|
}
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (version === 1) {
|
|
194
|
-
const req2 = requirements;
|
|
195
|
-
const networkConfig = getNetworkByChainId(
|
|
196
|
-
parseInt(req2.contractAddress.slice(0, 10), 16)
|
|
197
|
-
);
|
|
198
|
-
const payload2 = await createPaymentPayloadV1({
|
|
339
|
+
async function createX402Payment(signer, parsed, fromAddress, nonce, validBefore, domainName, domainVersion) {
|
|
340
|
+
const effectiveNonce = nonce ?? createNonce();
|
|
341
|
+
const effectiveValidBefore = validBefore ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
342
|
+
if (parsed.version === 1) {
|
|
343
|
+
return createX402V1Payment(
|
|
199
344
|
signer,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
network: req2.network
|
|
208
|
-
});
|
|
209
|
-
return [payload2, V1_HEADERS.PAYMENT];
|
|
345
|
+
parsed.requirements,
|
|
346
|
+
fromAddress,
|
|
347
|
+
effectiveNonce,
|
|
348
|
+
effectiveValidBefore,
|
|
349
|
+
domainName,
|
|
350
|
+
domainVersion
|
|
351
|
+
);
|
|
210
352
|
}
|
|
211
|
-
|
|
212
|
-
const payload = await createPaymentPayloadV2({
|
|
353
|
+
return createX402V2Payment(
|
|
213
354
|
signer,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
355
|
+
parsed.requirements,
|
|
356
|
+
fromAddress,
|
|
357
|
+
effectiveNonce,
|
|
358
|
+
effectiveValidBefore,
|
|
359
|
+
domainName,
|
|
360
|
+
domainVersion
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function encodeX402Payment(payload) {
|
|
364
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
365
|
+
}
|
|
366
|
+
function getPaymentHeaderName(version) {
|
|
367
|
+
return version === 1 ? V1_HEADERS.PAYMENT : V2_HEADERS.PAYMENT_SIGNATURE;
|
|
368
|
+
}
|
|
369
|
+
async function createPaymentPayload(requirements, signer, from) {
|
|
370
|
+
const parsed = isX402V1Requirements(requirements) ? { version: 1, requirements } : { version: 2, requirements };
|
|
371
|
+
const payload = await createX402Payment(signer, parsed, from);
|
|
372
|
+
const encoded = encodeX402Payment(payload);
|
|
373
|
+
return [encoded, getPaymentHeaderName(parsed.version)];
|
|
223
374
|
}
|
|
224
375
|
async function parsePaymentRequirements(response) {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
376
|
+
const parsed = parsePaymentRequired(response);
|
|
377
|
+
return parsed.requirements;
|
|
378
|
+
}
|
|
379
|
+
function detectProtocolVersion(requirements) {
|
|
380
|
+
if (typeof requirements === "object" && requirements !== null) {
|
|
381
|
+
if ("x402Version" in requirements && requirements.x402Version === 2) {
|
|
382
|
+
return 2;
|
|
383
|
+
}
|
|
384
|
+
if ("chainId" in requirements && "assetId" in requirements && !("contractAddress" in requirements)) {
|
|
385
|
+
return 2;
|
|
386
|
+
}
|
|
233
387
|
}
|
|
388
|
+
return 1;
|
|
234
389
|
}
|
|
235
390
|
|
|
236
391
|
// src/transport.ts
|
|
@@ -274,26 +429,29 @@ var handlePaymentRequired = async (state, response) => {
|
|
|
274
429
|
throw new SignerRequiredError("Cannot handle payment: no signer configured.");
|
|
275
430
|
}
|
|
276
431
|
try {
|
|
277
|
-
const
|
|
432
|
+
const parsed = parsePaymentRequired(response);
|
|
278
433
|
const from = await state.signer.getAddress();
|
|
279
|
-
const
|
|
434
|
+
const payload = await createX402Payment(state.signer, parsed, from);
|
|
435
|
+
const encoded = encodeX402Payment(payload);
|
|
436
|
+
const headerName = getPaymentHeaderName(parsed.version);
|
|
280
437
|
const paymentResponse = await fetchWithTimeout(
|
|
281
438
|
response.url,
|
|
282
439
|
{
|
|
283
440
|
method: "GET",
|
|
284
|
-
headers: { ...state.config.headers, [headerName]:
|
|
441
|
+
headers: { ...state.config.headers, [headerName]: encoded }
|
|
285
442
|
},
|
|
286
443
|
state.config.timeout
|
|
287
444
|
);
|
|
288
|
-
|
|
445
|
+
const settlement = decodeSettlementLegacy(paymentResponse.headers);
|
|
446
|
+
return { success: true, settlement };
|
|
289
447
|
} catch (error) {
|
|
290
448
|
return { success: false, error: error instanceof Error ? error : new Error(String(error)) };
|
|
291
449
|
}
|
|
292
450
|
};
|
|
293
451
|
var shouldRetryPayment = async (state, response) => {
|
|
294
452
|
if (!state.config.autoPay) return false;
|
|
295
|
-
const
|
|
296
|
-
return await state.config.onPaymentRequired(requirements);
|
|
453
|
+
const parsed = parsePaymentRequired(response);
|
|
454
|
+
return await state.config.onPaymentRequired(parsed.requirements);
|
|
297
455
|
};
|
|
298
456
|
var x402Fetch = async (state, url, init = {}) => {
|
|
299
457
|
const fullUrl = state.config.baseURL ? new URL(url, state.config.baseURL).toString() : url;
|
|
@@ -345,17 +503,24 @@ var createX402Transport = (config) => {
|
|
|
345
503
|
};
|
|
346
504
|
};
|
|
347
505
|
export {
|
|
348
|
-
|
|
506
|
+
AuthorizationError,
|
|
507
|
+
EIP712_TYPES2 as EIP712_TYPES,
|
|
349
508
|
ERC20_ABI,
|
|
350
509
|
NETWORKS,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
510
|
+
PaymentError,
|
|
511
|
+
ProviderRequiredError,
|
|
512
|
+
SignerRequiredError,
|
|
513
|
+
SigningError,
|
|
514
|
+
V1_HEADERS3 as V1_HEADERS,
|
|
515
|
+
V2_HEADERS3 as V2_HEADERS,
|
|
516
|
+
X402ClientError,
|
|
517
|
+
createEIP712Domain3 as createEIP712Domain,
|
|
354
518
|
createPaymentPayload,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
createTransferWithAuthorization,
|
|
519
|
+
createTransferWithAuthorization2 as createTransferWithAuthorization,
|
|
520
|
+
createX402Payment,
|
|
358
521
|
createX402Transport,
|
|
522
|
+
createX402V1Payment,
|
|
523
|
+
createX402V2Payment,
|
|
359
524
|
decodePayment,
|
|
360
525
|
decodePaymentV1,
|
|
361
526
|
decodePaymentV2,
|
|
@@ -364,14 +529,17 @@ export {
|
|
|
364
529
|
decodeSettlementV2,
|
|
365
530
|
detectPaymentVersion,
|
|
366
531
|
detectProtocolVersion,
|
|
367
|
-
|
|
368
|
-
|
|
532
|
+
detectX402Version,
|
|
533
|
+
encodePaymentV1,
|
|
534
|
+
encodePaymentV2,
|
|
369
535
|
encodeSettlementV1,
|
|
370
536
|
encodeSettlementV2,
|
|
537
|
+
encodeX402Payment,
|
|
371
538
|
getMainnets,
|
|
372
539
|
getNetworkByChainId2 as getNetworkByChainId,
|
|
373
|
-
getNetworkConfig,
|
|
540
|
+
getNetworkConfig2 as getNetworkConfig,
|
|
374
541
|
getPaymentHeaderName,
|
|
542
|
+
getPaymentHeaderName2 as getPaymentHeaderNameBase,
|
|
375
543
|
getPaymentRequiredHeaderName,
|
|
376
544
|
getPaymentResponseHeaderName,
|
|
377
545
|
getPaymentVersion,
|
|
@@ -379,16 +547,24 @@ export {
|
|
|
379
547
|
getSettlementVersion,
|
|
380
548
|
getTestnets,
|
|
381
549
|
getTxHash,
|
|
382
|
-
isAddress,
|
|
383
550
|
isCAIP2ChainId,
|
|
384
551
|
isCAIPAssetId,
|
|
385
552
|
isPaymentV1,
|
|
386
553
|
isPaymentV2,
|
|
387
|
-
isSettlementSuccessful,
|
|
554
|
+
isSettlementSuccessful2 as isSettlementSuccessful,
|
|
388
555
|
isSettlementV1,
|
|
389
556
|
isSettlementV2,
|
|
390
557
|
isV1,
|
|
391
558
|
isV2,
|
|
559
|
+
isX402V1PaymentRequired2 as isX402V1PaymentRequired,
|
|
560
|
+
isX402V1Requirements2 as isX402V1Requirements,
|
|
561
|
+
isX402V2PaymentRequired2 as isX402V2PaymentRequired,
|
|
562
|
+
parsePaymentRequired,
|
|
392
563
|
parsePaymentRequirements,
|
|
564
|
+
recoverEIP3009Signer,
|
|
565
|
+
safeBase64Decode2 as safeBase64Decode,
|
|
566
|
+
signEIP3009,
|
|
567
|
+
signEIP3009WithDomain,
|
|
568
|
+
signPayment,
|
|
393
569
|
validateTransferWithAuthorization
|
|
394
570
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@armory-sh/client-ethers",
|
|
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-ethers"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@armory-sh/base": "^0.2.
|
|
30
|
+
"@armory-sh/base": "^0.2.12",
|
|
31
31
|
"ethers": "6.16.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|