@armory-sh/client-ethers 0.2.0
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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +412 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sawyer Cutler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @armory-sh/client-ethers
|
|
2
|
+
|
|
3
|
+
Ethers v6 client for creating and signing Armory payments.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @armory-sh/client-ethers
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ethers } from 'ethers'
|
|
15
|
+
import { createArmoryPayment } from '@armory-sh/client-ethers'
|
|
16
|
+
|
|
17
|
+
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org')
|
|
18
|
+
const signer = await provider.getSigner()
|
|
19
|
+
|
|
20
|
+
const payment = await createArmoryPayment(signer, {
|
|
21
|
+
to: '0x...',
|
|
22
|
+
amount: 1000000n,
|
|
23
|
+
chainId: 'eip155:8453',
|
|
24
|
+
assetId: 'eip155:8453/erc20:0x...'
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
MIT License | Sawyer Cutler 2026 | Provided "AS IS" without warranty
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { PaymentRequirements, CAIP2ChainId, CAIPAssetId, 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, decodePaymentPayload, decodePaymentV1, decodePaymentV2, decodeSettlement, decodeSettlementResponse, decodeSettlementV1, decodeSettlementV2, detectPaymentVersion, encodePaymentPayload, encodePaymentV1, encodePaymentV2, encodeSettlementResponse, encodeSettlementV1, encodeSettlementV2, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getPaymentRequiredHeaderName, getPaymentResponseHeaderName, getPaymentVersionFromPayload, getRequirementsVersion, getSettlementVersion, getTestnets, getTxHash, isAddress, isCAIP2ChainId, isCAIPAssetId, isPaymentV1, isPaymentV2, isSettlementSuccessful, isSettlementV1, isSettlementV2, isV1, isV2, validateTransferWithAuthorization } from '@armory-sh/base';
|
|
3
|
+
import { Signer, Provider } from 'ethers';
|
|
4
|
+
export { Signer } from 'ethers';
|
|
5
|
+
|
|
6
|
+
declare function createPaymentPayloadV1(params: {
|
|
7
|
+
signer: Signer;
|
|
8
|
+
from: string;
|
|
9
|
+
to: string;
|
|
10
|
+
amount: string;
|
|
11
|
+
nonce: string;
|
|
12
|
+
expiry: number;
|
|
13
|
+
chainId: number;
|
|
14
|
+
contractAddress: string;
|
|
15
|
+
network: string;
|
|
16
|
+
domainName?: string;
|
|
17
|
+
domainVersion?: string;
|
|
18
|
+
}): Promise<string>;
|
|
19
|
+
declare function createPaymentPayloadV2(params: {
|
|
20
|
+
signer: Signer;
|
|
21
|
+
from: `0x${string}`;
|
|
22
|
+
to: `0x${string}` | {
|
|
23
|
+
role?: string;
|
|
24
|
+
callback?: string;
|
|
25
|
+
};
|
|
26
|
+
amount: string;
|
|
27
|
+
nonce: string;
|
|
28
|
+
expiry: number;
|
|
29
|
+
chainId: CAIP2ChainId;
|
|
30
|
+
assetId: CAIPAssetId;
|
|
31
|
+
extensions?: Record<string, unknown>;
|
|
32
|
+
domainName?: string;
|
|
33
|
+
domainVersion?: string;
|
|
34
|
+
}): Promise<string>;
|
|
35
|
+
declare function detectProtocolVersion(requirements: PaymentRequirements): 1 | 2;
|
|
36
|
+
declare function createPaymentPayload(requirements: PaymentRequirements, signer: Signer, from: string): Promise<[string, string]>;
|
|
37
|
+
declare function parsePaymentRequirements(response: Response): Promise<PaymentRequirements>;
|
|
38
|
+
|
|
39
|
+
/** Token configuration - can use pre-configured tokens from @armory-sh/tokens */
|
|
40
|
+
type Token = CustomToken;
|
|
41
|
+
interface X402ClientConfig {
|
|
42
|
+
protocolVersion?: 1 | 2 | "auto";
|
|
43
|
+
defaultExpiry?: number;
|
|
44
|
+
nonceGenerator?: () => string;
|
|
45
|
+
network?: string;
|
|
46
|
+
usdcAddress?: `0x${string}`;
|
|
47
|
+
rpcUrl?: string;
|
|
48
|
+
/** Pre-configured token object (overrides individual fields below) */
|
|
49
|
+
token?: Token;
|
|
50
|
+
/** Override EIP-712 domain name for custom tokens */
|
|
51
|
+
domainName?: string;
|
|
52
|
+
/** Override EIP-712 domain version for custom tokens */
|
|
53
|
+
domainVersion?: string;
|
|
54
|
+
}
|
|
55
|
+
interface SignerClientConfig extends X402ClientConfig {
|
|
56
|
+
signer: Signer;
|
|
57
|
+
}
|
|
58
|
+
interface ProviderClientConfig extends X402ClientConfig {
|
|
59
|
+
provider: Provider;
|
|
60
|
+
signer?: never;
|
|
61
|
+
}
|
|
62
|
+
type ClientConfig = SignerClientConfig | ProviderClientConfig;
|
|
63
|
+
interface X402TransportConfig {
|
|
64
|
+
baseURL?: string;
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
timeout?: number;
|
|
67
|
+
autoPay?: boolean;
|
|
68
|
+
maxRetries?: number;
|
|
69
|
+
retryDelay?: number;
|
|
70
|
+
onPaymentRequired?: (requirements: unknown) => boolean | Promise<boolean>;
|
|
71
|
+
onPaymentSuccess?: (settlement: unknown) => void;
|
|
72
|
+
onPaymentError?: (error: Error) => void;
|
|
73
|
+
}
|
|
74
|
+
interface X402RequestInit extends Omit<RequestInit, "headers"> {
|
|
75
|
+
headers?: Record<string, string> | Headers;
|
|
76
|
+
skipAutoPay?: boolean;
|
|
77
|
+
forceProtocolVersion?: 1 | 2;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface X402Transport {
|
|
81
|
+
fetch(url: string, init?: X402RequestInit): Promise<Response>;
|
|
82
|
+
get(url: string, init?: X402RequestInit): Promise<Response>;
|
|
83
|
+
post(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
|
|
84
|
+
put(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
|
|
85
|
+
del(url: string, init?: X402RequestInit): Promise<Response>;
|
|
86
|
+
patch(url: string, body?: unknown, init?: X402RequestInit): Promise<Response>;
|
|
87
|
+
setSigner(signer: Signer): void;
|
|
88
|
+
getSigner(): Signer | undefined;
|
|
89
|
+
}
|
|
90
|
+
declare const createX402Transport: (config?: X402TransportConfig) => X402Transport;
|
|
91
|
+
|
|
92
|
+
export { type ClientConfig, type X402ClientConfig, createPaymentPayload, createPaymentPayloadV1, createPaymentPayloadV2, createX402Transport, detectProtocolVersion, parsePaymentRequirements };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
V1_HEADERS as V1_HEADERS2,
|
|
4
|
+
encodePaymentPayload,
|
|
5
|
+
decodePaymentPayload,
|
|
6
|
+
encodeSettlementResponse,
|
|
7
|
+
decodeSettlementResponse
|
|
8
|
+
} from "@armory-sh/base";
|
|
9
|
+
import {
|
|
10
|
+
V2_HEADERS as V2_HEADERS2,
|
|
11
|
+
isCAIP2ChainId,
|
|
12
|
+
isCAIPAssetId,
|
|
13
|
+
isAddress
|
|
14
|
+
} from "@armory-sh/base";
|
|
15
|
+
import {
|
|
16
|
+
isV1,
|
|
17
|
+
isV2,
|
|
18
|
+
getPaymentVersionFromPayload,
|
|
19
|
+
getRequirementsVersion,
|
|
20
|
+
getSettlementVersion,
|
|
21
|
+
getPaymentHeaderName,
|
|
22
|
+
getPaymentResponseHeaderName,
|
|
23
|
+
getPaymentRequiredHeaderName,
|
|
24
|
+
isSettlementSuccessful,
|
|
25
|
+
getTxHash
|
|
26
|
+
} from "@armory-sh/base";
|
|
27
|
+
import {
|
|
28
|
+
NETWORKS,
|
|
29
|
+
getNetworkConfig,
|
|
30
|
+
getNetworkByChainId as getNetworkByChainId2,
|
|
31
|
+
getMainnets,
|
|
32
|
+
getTestnets
|
|
33
|
+
} from "@armory-sh/base";
|
|
34
|
+
import { ERC20_ABI } from "@armory-sh/base";
|
|
35
|
+
import {
|
|
36
|
+
encodePaymentV1 as encodePaymentV12,
|
|
37
|
+
decodePaymentV1,
|
|
38
|
+
encodeSettlementV1,
|
|
39
|
+
decodeSettlementV1,
|
|
40
|
+
encodePaymentV2 as encodePaymentV22,
|
|
41
|
+
decodePaymentV2,
|
|
42
|
+
encodeSettlementV2,
|
|
43
|
+
decodeSettlementV2,
|
|
44
|
+
detectPaymentVersion,
|
|
45
|
+
decodePayment,
|
|
46
|
+
decodeSettlement as decodeSettlement2,
|
|
47
|
+
isPaymentV1,
|
|
48
|
+
isPaymentV2,
|
|
49
|
+
isSettlementV1,
|
|
50
|
+
isSettlementV2
|
|
51
|
+
} from "@armory-sh/base";
|
|
52
|
+
import {
|
|
53
|
+
EIP712_TYPES,
|
|
54
|
+
createEIP712Domain as createEIP712Domain2,
|
|
55
|
+
createTransferWithAuthorization,
|
|
56
|
+
validateTransferWithAuthorization
|
|
57
|
+
} from "@armory-sh/base";
|
|
58
|
+
|
|
59
|
+
// src/protocol.ts
|
|
60
|
+
import {
|
|
61
|
+
encodePaymentV1,
|
|
62
|
+
encodePaymentV2,
|
|
63
|
+
V1_HEADERS,
|
|
64
|
+
V2_HEADERS,
|
|
65
|
+
getNetworkByChainId
|
|
66
|
+
} from "@armory-sh/base";
|
|
67
|
+
|
|
68
|
+
// src/eip3009.ts
|
|
69
|
+
import {
|
|
70
|
+
createEIP712Domain
|
|
71
|
+
} from "@armory-sh/base";
|
|
72
|
+
|
|
73
|
+
// src/types.ts
|
|
74
|
+
var X402ClientError = class extends Error {
|
|
75
|
+
cause;
|
|
76
|
+
constructor(message, cause) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.name = "X402ClientError";
|
|
79
|
+
this.cause = cause;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var SignerRequiredError = class extends X402ClientError {
|
|
83
|
+
constructor(message = "Signer is required for this operation") {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = "SignerRequiredError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var AuthorizationError = class extends X402ClientError {
|
|
89
|
+
constructor(message, cause) {
|
|
90
|
+
super(`Authorization failed: ${message}`, cause);
|
|
91
|
+
this.name = "AuthorizationError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/eip3009.ts
|
|
96
|
+
var EIP712_TYPES_ETHERS = {
|
|
97
|
+
TransferWithAuthorization: [
|
|
98
|
+
{ name: "from", type: "address" },
|
|
99
|
+
{ name: "to", type: "address" },
|
|
100
|
+
{ name: "value", type: "uint256" },
|
|
101
|
+
{ name: "validAfter", type: "uint256" },
|
|
102
|
+
{ name: "validBefore", type: "uint256" },
|
|
103
|
+
{ name: "nonce", type: "uint256" }
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
async function signEIP3009(signer, params, domain) {
|
|
107
|
+
try {
|
|
108
|
+
const message = {
|
|
109
|
+
from: params.from,
|
|
110
|
+
to: params.to,
|
|
111
|
+
value: BigInt(params.value),
|
|
112
|
+
validAfter: BigInt(params.validAfter),
|
|
113
|
+
validBefore: BigInt(params.validBefore),
|
|
114
|
+
nonce: BigInt(params.nonce)
|
|
115
|
+
};
|
|
116
|
+
const signature = await signer.signTypedData(domain, EIP712_TYPES_ETHERS, message);
|
|
117
|
+
const { v, r, s } = ethers.Signature.from(signature);
|
|
118
|
+
return { v: Number(v), r, s };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw new AuthorizationError(
|
|
121
|
+
`Failed to sign EIP-3009 authorization: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
122
|
+
error
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/protocol.ts
|
|
128
|
+
async function createPaymentPayloadV1(params) {
|
|
129
|
+
const { signer, from, to, amount, nonce, expiry, chainId, contractAddress, network, domainName, domainVersion } = params;
|
|
130
|
+
const authParams = {
|
|
131
|
+
from,
|
|
132
|
+
to,
|
|
133
|
+
value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
|
|
134
|
+
validAfter: 0n,
|
|
135
|
+
validBefore: BigInt(expiry),
|
|
136
|
+
nonce: BigInt(nonce)
|
|
137
|
+
};
|
|
138
|
+
const domain = {
|
|
139
|
+
name: domainName ?? "USD Coin",
|
|
140
|
+
version: domainVersion ?? "2",
|
|
141
|
+
chainId,
|
|
142
|
+
verifyingContract: contractAddress
|
|
143
|
+
};
|
|
144
|
+
const signature = await signEIP3009(signer, authParams, domain);
|
|
145
|
+
const payload = {
|
|
146
|
+
from,
|
|
147
|
+
to,
|
|
148
|
+
amount,
|
|
149
|
+
nonce,
|
|
150
|
+
expiry,
|
|
151
|
+
v: signature.v,
|
|
152
|
+
r: signature.r,
|
|
153
|
+
s: signature.s,
|
|
154
|
+
chainId,
|
|
155
|
+
contractAddress,
|
|
156
|
+
network
|
|
157
|
+
};
|
|
158
|
+
return encodePaymentV1(payload);
|
|
159
|
+
}
|
|
160
|
+
async function createPaymentPayloadV2(params) {
|
|
161
|
+
const { signer, from, to, amount, nonce, expiry, chainId, assetId, extensions, domainName, domainVersion } = params;
|
|
162
|
+
const chainIdNum = parseInt(chainId.split(":")[1], 10);
|
|
163
|
+
const contractAddress = assetId.split(":")[2];
|
|
164
|
+
const authParams = {
|
|
165
|
+
from,
|
|
166
|
+
to: typeof to === "string" ? to : "0x0000000000000000000000000000000000000000",
|
|
167
|
+
value: BigInt(Math.floor(parseFloat(amount) * 1e6)),
|
|
168
|
+
validAfter: 0n,
|
|
169
|
+
validBefore: BigInt(expiry),
|
|
170
|
+
nonce: BigInt(nonce)
|
|
171
|
+
};
|
|
172
|
+
const domain = {
|
|
173
|
+
name: domainName ?? "USD Coin",
|
|
174
|
+
version: domainVersion ?? "2",
|
|
175
|
+
chainId: chainIdNum,
|
|
176
|
+
verifyingContract: contractAddress
|
|
177
|
+
};
|
|
178
|
+
const signature = await signEIP3009(signer, authParams, domain);
|
|
179
|
+
const payload = {
|
|
180
|
+
from,
|
|
181
|
+
to,
|
|
182
|
+
amount,
|
|
183
|
+
nonce,
|
|
184
|
+
expiry,
|
|
185
|
+
signature: {
|
|
186
|
+
v: signature.v,
|
|
187
|
+
r: signature.r,
|
|
188
|
+
s: signature.s
|
|
189
|
+
},
|
|
190
|
+
chainId,
|
|
191
|
+
assetId,
|
|
192
|
+
extensions
|
|
193
|
+
};
|
|
194
|
+
return encodePaymentV2(payload);
|
|
195
|
+
}
|
|
196
|
+
function detectProtocolVersion(requirements) {
|
|
197
|
+
if ("contractAddress" in requirements && "network" in requirements) {
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
return 2;
|
|
201
|
+
}
|
|
202
|
+
async function createPaymentPayload(requirements, signer, from) {
|
|
203
|
+
const version = detectProtocolVersion(requirements);
|
|
204
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
205
|
+
const nonce = now.toString();
|
|
206
|
+
const expiry = now + 3600;
|
|
207
|
+
if (version === 1) {
|
|
208
|
+
const req2 = requirements;
|
|
209
|
+
const networkConfig = getNetworkByChainId(
|
|
210
|
+
parseInt(req2.contractAddress.slice(0, 10), 16)
|
|
211
|
+
);
|
|
212
|
+
const payload2 = await createPaymentPayloadV1({
|
|
213
|
+
signer,
|
|
214
|
+
from,
|
|
215
|
+
to: req2.payTo,
|
|
216
|
+
amount: req2.amount,
|
|
217
|
+
nonce,
|
|
218
|
+
expiry,
|
|
219
|
+
chainId: networkConfig?.chainId || 1,
|
|
220
|
+
contractAddress: req2.contractAddress,
|
|
221
|
+
network: req2.network
|
|
222
|
+
});
|
|
223
|
+
return [payload2, V1_HEADERS.PAYMENT];
|
|
224
|
+
}
|
|
225
|
+
const req = requirements;
|
|
226
|
+
const payload = await createPaymentPayloadV2({
|
|
227
|
+
signer,
|
|
228
|
+
from,
|
|
229
|
+
to: req.to,
|
|
230
|
+
amount: req.amount,
|
|
231
|
+
nonce,
|
|
232
|
+
expiry,
|
|
233
|
+
chainId: req.chainId,
|
|
234
|
+
assetId: req.assetId
|
|
235
|
+
});
|
|
236
|
+
return [payload, V2_HEADERS.PAYMENT_SIGNATURE];
|
|
237
|
+
}
|
|
238
|
+
async function parsePaymentRequirements(response) {
|
|
239
|
+
const v2Header = response.headers.get(V2_HEADERS.PAYMENT_REQUIRED);
|
|
240
|
+
if (v2Header) {
|
|
241
|
+
return JSON.parse(v2Header);
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
return await response.json();
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error("No payment requirements found in response");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/transport.ts
|
|
251
|
+
import {
|
|
252
|
+
decodeSettlement
|
|
253
|
+
} from "@armory-sh/base";
|
|
254
|
+
var defaultConfig = {
|
|
255
|
+
baseURL: "",
|
|
256
|
+
headers: {},
|
|
257
|
+
timeout: 3e4,
|
|
258
|
+
autoPay: true,
|
|
259
|
+
maxRetries: 3,
|
|
260
|
+
retryDelay: 1e3,
|
|
261
|
+
onPaymentRequired: () => true,
|
|
262
|
+
onPaymentSuccess: () => {
|
|
263
|
+
},
|
|
264
|
+
onPaymentError: () => {
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var createState = (config) => ({
|
|
268
|
+
config: { ...defaultConfig, ...config }
|
|
269
|
+
});
|
|
270
|
+
var fetchWithTimeout = async (url, init, timeout) => {
|
|
271
|
+
const controller = new AbortController();
|
|
272
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
275
|
+
clearTimeout(timeoutId);
|
|
276
|
+
return response;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
clearTimeout(timeoutId);
|
|
279
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
280
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
281
|
+
}
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
286
|
+
var handlePaymentRequired = async (state, response) => {
|
|
287
|
+
if (!state.signer) {
|
|
288
|
+
throw new SignerRequiredError("Cannot handle payment: no signer configured.");
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const requirements = await parsePaymentRequirements(response);
|
|
292
|
+
const from = await state.signer.getAddress();
|
|
293
|
+
const [payload, headerName] = await createPaymentPayload(requirements, state.signer, from);
|
|
294
|
+
const paymentResponse = await fetchWithTimeout(
|
|
295
|
+
response.url,
|
|
296
|
+
{
|
|
297
|
+
method: "GET",
|
|
298
|
+
headers: { ...state.config.headers, [headerName]: payload }
|
|
299
|
+
},
|
|
300
|
+
state.config.timeout
|
|
301
|
+
);
|
|
302
|
+
return { success: true, settlement: decodeSettlement(paymentResponse.headers) };
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return { success: false, error: error instanceof Error ? error : new Error(String(error)) };
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
var shouldRetryPayment = async (state, response) => {
|
|
308
|
+
if (!state.config.autoPay) return false;
|
|
309
|
+
const requirements = await parsePaymentRequirements(response);
|
|
310
|
+
return await state.config.onPaymentRequired(requirements);
|
|
311
|
+
};
|
|
312
|
+
var x402Fetch = async (state, url, init = {}) => {
|
|
313
|
+
const fullUrl = state.config.baseURL ? new URL(url, state.config.baseURL).toString() : url;
|
|
314
|
+
const headers = new Headers({ ...state.config.headers, ...init.headers });
|
|
315
|
+
for (let attempt = 1; attempt <= state.config.maxRetries; attempt++) {
|
|
316
|
+
try {
|
|
317
|
+
const response = await fetchWithTimeout(fullUrl, { ...init, headers }, state.config.timeout);
|
|
318
|
+
if (response.status === 402 && !init.skipAutoPay && await shouldRetryPayment(state, response)) {
|
|
319
|
+
const paymentResult = await handlePaymentRequired(state, response);
|
|
320
|
+
if (paymentResult.success) {
|
|
321
|
+
state.config.onPaymentSuccess(paymentResult.settlement);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
state.config.onPaymentError(paymentResult.error ?? new Error("Payment failed"));
|
|
325
|
+
}
|
|
326
|
+
return response;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const lastError = error instanceof Error ? error : new Error(String(error));
|
|
329
|
+
if (attempt === state.config.maxRetries) throw lastError;
|
|
330
|
+
await delay(state.config.retryDelay * attempt);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
throw new Error("Max retries exceeded");
|
|
334
|
+
};
|
|
335
|
+
var createMethod = (state, method) => async (url, bodyOrInit, init) => {
|
|
336
|
+
const bodyInit = typeof bodyOrInit === "object" && bodyOrInit !== null && "headers" in bodyOrInit ? bodyOrInit : void 0;
|
|
337
|
+
const body = typeof bodyOrInit === "object" && bodyOrInit !== null && !("headers" in bodyOrInit) ? bodyOrInit : void 0;
|
|
338
|
+
const finalInit = bodyInit ?? init;
|
|
339
|
+
return x402Fetch(state, url, {
|
|
340
|
+
...finalInit,
|
|
341
|
+
method,
|
|
342
|
+
headers: { ...finalInit?.headers, "Content-Type": "application/json" },
|
|
343
|
+
body: body ? JSON.stringify(body) : void 0
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
var createX402Transport = (config) => {
|
|
347
|
+
let state = createState(config);
|
|
348
|
+
return {
|
|
349
|
+
fetch: (url, init) => x402Fetch(state, url, init),
|
|
350
|
+
get: createMethod(state, "GET"),
|
|
351
|
+
post: createMethod(state, "POST"),
|
|
352
|
+
put: createMethod(state, "PUT"),
|
|
353
|
+
del: createMethod(state, "DELETE"),
|
|
354
|
+
patch: createMethod(state, "PATCH"),
|
|
355
|
+
setSigner: (signer) => {
|
|
356
|
+
state.signer = signer;
|
|
357
|
+
},
|
|
358
|
+
getSigner: () => state.signer
|
|
359
|
+
};
|
|
360
|
+
};
|
|
361
|
+
export {
|
|
362
|
+
EIP712_TYPES,
|
|
363
|
+
ERC20_ABI,
|
|
364
|
+
NETWORKS,
|
|
365
|
+
V1_HEADERS2 as V1_HEADERS,
|
|
366
|
+
V2_HEADERS2 as V2_HEADERS,
|
|
367
|
+
createEIP712Domain2 as createEIP712Domain,
|
|
368
|
+
createPaymentPayload,
|
|
369
|
+
createPaymentPayloadV1,
|
|
370
|
+
createPaymentPayloadV2,
|
|
371
|
+
createTransferWithAuthorization,
|
|
372
|
+
createX402Transport,
|
|
373
|
+
decodePayment,
|
|
374
|
+
decodePaymentPayload,
|
|
375
|
+
decodePaymentV1,
|
|
376
|
+
decodePaymentV2,
|
|
377
|
+
decodeSettlement2 as decodeSettlement,
|
|
378
|
+
decodeSettlementResponse,
|
|
379
|
+
decodeSettlementV1,
|
|
380
|
+
decodeSettlementV2,
|
|
381
|
+
detectPaymentVersion,
|
|
382
|
+
detectProtocolVersion,
|
|
383
|
+
encodePaymentPayload,
|
|
384
|
+
encodePaymentV12 as encodePaymentV1,
|
|
385
|
+
encodePaymentV22 as encodePaymentV2,
|
|
386
|
+
encodeSettlementResponse,
|
|
387
|
+
encodeSettlementV1,
|
|
388
|
+
encodeSettlementV2,
|
|
389
|
+
getMainnets,
|
|
390
|
+
getNetworkByChainId2 as getNetworkByChainId,
|
|
391
|
+
getNetworkConfig,
|
|
392
|
+
getPaymentHeaderName,
|
|
393
|
+
getPaymentRequiredHeaderName,
|
|
394
|
+
getPaymentResponseHeaderName,
|
|
395
|
+
getPaymentVersionFromPayload,
|
|
396
|
+
getRequirementsVersion,
|
|
397
|
+
getSettlementVersion,
|
|
398
|
+
getTestnets,
|
|
399
|
+
getTxHash,
|
|
400
|
+
isAddress,
|
|
401
|
+
isCAIP2ChainId,
|
|
402
|
+
isCAIPAssetId,
|
|
403
|
+
isPaymentV1,
|
|
404
|
+
isPaymentV2,
|
|
405
|
+
isSettlementSuccessful,
|
|
406
|
+
isSettlementV1,
|
|
407
|
+
isSettlementV2,
|
|
408
|
+
isV1,
|
|
409
|
+
isV2,
|
|
410
|
+
parsePaymentRequirements,
|
|
411
|
+
validateTransferWithAuthorization
|
|
412
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@armory-sh/client-ethers",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"bun": "./src/index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./dist/*": "./dist/*.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/thegreataxios/armory.git",
|
|
27
|
+
"directory": "packages/client-ethers"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@armory-sh/base": "workspace:*",
|
|
31
|
+
"ethers": "6.16.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
|
+
"bun-types": "latest"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"test": "bun test",
|
|
40
|
+
"example": "bun run examples/"
|
|
41
|
+
}
|
|
42
|
+
}
|