@agether/sdk 1.5.1 → 1.5.3

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.
@@ -0,0 +1,257 @@
1
+ // src/clients/X402Client.ts
2
+ import { ethers } from "ethers";
3
+ var USDC_DOMAINS = {
4
+ "eip155:1": { name: "USD Coin", version: "2", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
5
+ "eip155:8453": { name: "USD Coin", version: "2", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
6
+ "eip155:84532": { name: "USD Coin", version: "2", address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" },
7
+ "eip155:42161": { name: "USD Coin", version: "2", address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" },
8
+ "eip155:10": { name: "USD Coin", version: "2", address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" }
9
+ };
10
+ function chainIdFromNetwork(network) {
11
+ const m = network.match(/^eip155:(\d+)$/);
12
+ return m ? Number(m[1]) : 1;
13
+ }
14
+ var X402Client = class {
15
+ constructor(config) {
16
+ this.config = config;
17
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl);
18
+ this.wallet = new ethers.Wallet(config.privateKey, provider);
19
+ }
20
+ async get(url, opts) {
21
+ return this.request(url, { ...opts, method: "GET" });
22
+ }
23
+ async post(url, body, opts) {
24
+ return this.request(url, {
25
+ ...opts,
26
+ method: "POST",
27
+ body: body ? JSON.stringify(body) : void 0,
28
+ headers: { "Content-Type": "application/json", ...opts?.headers }
29
+ });
30
+ }
31
+ getAddress() {
32
+ return this.wallet.address;
33
+ }
34
+ // ──────────── Core request / 402-retry loop ────────────
35
+ async request(url, options) {
36
+ try {
37
+ console.log(" [1/4] Calling resource server\u2026");
38
+ const response = await fetch(url, {
39
+ ...options,
40
+ headers: {
41
+ ...options?.headers,
42
+ "X-Agent-Id": this.config.agentId || ""
43
+ }
44
+ });
45
+ if (response.ok) {
46
+ const data = await response.json();
47
+ return { success: true, data };
48
+ }
49
+ if (response.status !== 402) {
50
+ return { success: false, error: `HTTP ${response.status}: ${await response.text()}` };
51
+ }
52
+ console.log(" [2/4] 402 received \u2014 parsing payment requirements\u2026");
53
+ const parsed = await this.parsePaymentRequired(response);
54
+ if (!parsed) {
55
+ return { success: false, error: "Could not parse payment requirements from 402 response" };
56
+ }
57
+ const { requirements, resource } = parsed;
58
+ console.log(` scheme : ${requirements.scheme}`);
59
+ console.log(` network : ${requirements.network}`);
60
+ console.log(` amount : ${requirements.amount} (atomic)`);
61
+ console.log(` asset : ${requirements.asset}`);
62
+ console.log(` payTo : ${requirements.payTo}`);
63
+ console.log(" [3/4] Signing EIP-3009 transferWithAuthorization\u2026");
64
+ const paymentPayload = await this.buildPaymentPayload(requirements, resource, url);
65
+ const paymentB64 = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
66
+ await this.riskCheck(paymentPayload, requirements);
67
+ console.log(" [4/4] Retrying with PAYMENT-SIGNATURE header\u2026");
68
+ const paidResponse = await fetch(url, {
69
+ ...options,
70
+ headers: {
71
+ ...options?.headers,
72
+ "X-Agent-Id": this.config.agentId || "",
73
+ // v2 header
74
+ "PAYMENT-SIGNATURE": paymentB64,
75
+ // v1 compat header (some servers still use this)
76
+ "X-PAYMENT": paymentB64
77
+ }
78
+ });
79
+ if (paidResponse.ok) {
80
+ const data = await paidResponse.json();
81
+ const settlementHeader = paidResponse.headers.get("PAYMENT-RESPONSE") || paidResponse.headers.get("X-PAYMENT-RESPONSE");
82
+ let txHash;
83
+ if (settlementHeader) {
84
+ try {
85
+ const settlement = JSON.parse(Buffer.from(settlementHeader, "base64").toString("utf-8"));
86
+ txHash = settlement.transaction;
87
+ } catch {
88
+ }
89
+ }
90
+ return {
91
+ success: true,
92
+ data,
93
+ paymentInfo: {
94
+ amount: requirements.amount,
95
+ asset: requirements.extra?.name || "USDC",
96
+ network: requirements.network,
97
+ txHash
98
+ }
99
+ };
100
+ }
101
+ const errBody = await paidResponse.text();
102
+ return { success: false, error: `Payment rejected (HTTP ${paidResponse.status}): ${errBody}` };
103
+ } catch (error) {
104
+ return { success: false, error: `Request failed: ${error instanceof Error ? error.message : String(error)}` };
105
+ }
106
+ }
107
+ // ──────────── Parse 402 response ────────────
108
+ async parsePaymentRequired(response) {
109
+ const prHeader = response.headers.get("PAYMENT-REQUIRED") || response.headers.get("x-payment-required");
110
+ if (prHeader) {
111
+ try {
112
+ const decoded = JSON.parse(Buffer.from(prHeader, "base64").toString("utf-8"));
113
+ if (decoded.accepts?.length) {
114
+ return { requirements: decoded.accepts[0], resource: decoded.resource };
115
+ }
116
+ } catch {
117
+ }
118
+ }
119
+ try {
120
+ const body = await response.json();
121
+ if (body.accepts && Array.isArray(body.accepts) && body.accepts.length > 0) {
122
+ return { requirements: body.accepts[0], resource: body.resource };
123
+ }
124
+ if (body.paymentRequirements) {
125
+ const pr = Array.isArray(body.paymentRequirements) ? body.paymentRequirements[0] : body.paymentRequirements;
126
+ return { requirements: pr, resource: body.resource };
127
+ }
128
+ if (body.scheme && body.network) {
129
+ return { requirements: body, resource: body.resource };
130
+ }
131
+ } catch {
132
+ }
133
+ const wwwAuth = response.headers.get("WWW-Authenticate");
134
+ if (wwwAuth) {
135
+ const m = wwwAuth.match(/x402[^"]*"([^"]+)"/);
136
+ if (m) {
137
+ try {
138
+ const decoded = JSON.parse(Buffer.from(m[1], "base64").toString("utf-8"));
139
+ const reqs = Array.isArray(decoded) ? decoded[0] : decoded;
140
+ return { requirements: reqs };
141
+ } catch {
142
+ }
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ // ──────────── Build x402 v2 PaymentPayload with EIP-3009 ────────────
148
+ //
149
+ // If an AgentAccount is configured, we use it as the `from` address
150
+ // (smart wallet pays directly). The AgentAccount implements EIP-1271
151
+ // so USDC's transferWithAuthorization will call isValidSignature()
152
+ // to verify the owner's ECDSA signature. The facilitator detects
153
+ // the >65-byte or smart-wallet case and uses the bytes overload.
154
+ async buildPaymentPayload(reqs, resource, url) {
155
+ const now = Math.floor(Date.now() / 1e3);
156
+ const validAfter = String(now - 60);
157
+ const validBefore = String(now + (reqs.maxTimeoutSeconds || 300));
158
+ const nonce = ethers.hexlify(ethers.randomBytes(32));
159
+ const chainId = chainIdFromNetwork(reqs.network);
160
+ const usdcDomain = USDC_DOMAINS[reqs.network] || USDC_DOMAINS["eip155:1"];
161
+ const payerAddress = this.config.accountAddress || this.wallet.address;
162
+ const isSmartWallet = !!this.config.accountAddress;
163
+ const domain = {
164
+ name: usdcDomain.name,
165
+ version: usdcDomain.version,
166
+ chainId,
167
+ verifyingContract: reqs.asset || usdcDomain.address
168
+ };
169
+ const types = {
170
+ TransferWithAuthorization: [
171
+ { name: "from", type: "address" },
172
+ { name: "to", type: "address" },
173
+ { name: "value", type: "uint256" },
174
+ { name: "validAfter", type: "uint256" },
175
+ { name: "validBefore", type: "uint256" },
176
+ { name: "nonce", type: "bytes32" }
177
+ ]
178
+ };
179
+ const value = {
180
+ from: payerAddress,
181
+ // AgentAccount or EOA
182
+ to: reqs.payTo,
183
+ value: reqs.amount,
184
+ validAfter,
185
+ validBefore,
186
+ nonce
187
+ };
188
+ let signature = await this.wallet.signTypedData(domain, types, value);
189
+ if (isSmartWallet) {
190
+ signature = signature + "00";
191
+ }
192
+ if (isSmartWallet) {
193
+ console.log(` \u2713 Signed for AgentAccount ${payerAddress.slice(0, 10)}\u2026 (EIP-1271, chain=${chainId})`);
194
+ } else {
195
+ console.log(` \u2713 Signed (from=${payerAddress.slice(0, 10)}\u2026, chain=${chainId})`);
196
+ }
197
+ return {
198
+ x402Version: 2,
199
+ resource: resource || { url, description: "", mimeType: "application/json" },
200
+ accepted: {
201
+ scheme: reqs.scheme,
202
+ network: reqs.network,
203
+ amount: reqs.amount,
204
+ asset: reqs.asset,
205
+ payTo: reqs.payTo,
206
+ maxTimeoutSeconds: reqs.maxTimeoutSeconds,
207
+ extra: reqs.extra || {}
208
+ },
209
+ payload: {
210
+ signature,
211
+ authorization: {
212
+ from: payerAddress,
213
+ // AgentAccount address — facilitator checks balance here
214
+ to: reqs.payTo,
215
+ value: reqs.amount,
216
+ validAfter,
217
+ validBefore,
218
+ nonce
219
+ }
220
+ }
221
+ };
222
+ }
223
+ // ──────────── Risk check via our backend ────────────
224
+ async riskCheck(paymentPayload, reqs) {
225
+ try {
226
+ const verifyUrl = `${this.config.backendUrl}/x402/verify`;
227
+ const resp = await fetch(verifyUrl, {
228
+ method: "POST",
229
+ headers: {
230
+ "Content-Type": "application/json",
231
+ "X-Agent-Id": this.config.agentId || "",
232
+ ...this.config.accountAddress ? { "X-Agent-Account": this.config.accountAddress } : {}
233
+ },
234
+ body: JSON.stringify({
235
+ x402Version: 2,
236
+ paymentPayload,
237
+ paymentRequirements: reqs
238
+ }),
239
+ signal: AbortSignal.timeout(5e3)
240
+ });
241
+ if (resp.ok) {
242
+ const result = await resp.json();
243
+ const decision = resp.headers.get("X-Risk-Decision") || (result.isValid ? "allow" : "unknown");
244
+ const score = resp.headers.get("X-Risk-Score") || "?";
245
+ console.log(` \u2713 Risk check: ${decision} (score=${score})`);
246
+ } else {
247
+ console.log(` \u26A0 Risk check failed (HTTP ${resp.status}) \u2014 continuing anyway`);
248
+ }
249
+ } catch {
250
+ console.log(" \u26A0 Risk check unavailable \u2014 continuing");
251
+ }
252
+ }
253
+ };
254
+
255
+ export {
256
+ X402Client
257
+ };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js CHANGED
@@ -232,8 +232,8 @@ var init_MorphoClient = __esm({
232
232
  const addrs = { ...defaultCfg.contracts, ...config.contracts };
233
233
  this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this.wallet);
234
234
  this.morphoBlue = new import_ethers.Contract(addrs.morphoBlue, MORPHO_BLUE_ABI, this.provider);
235
- this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this.provider);
236
- this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this.provider);
235
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this.wallet);
236
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this.wallet);
237
237
  }
238
238
  // ════════════════════════════════════════════════════════
239
239
  // Account Management