@agether/sdk 1.0.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.
Files changed (42) hide show
  1. package/README.md +480 -0
  2. package/dist/cli.d.mts +2 -0
  3. package/dist/cli.d.ts +19 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +2149 -0
  6. package/dist/cli.mjs +0 -0
  7. package/dist/clients/AgentIdentityClient.d.ts +163 -0
  8. package/dist/clients/AgentIdentityClient.d.ts.map +1 -0
  9. package/dist/clients/AgentIdentityClient.js +293 -0
  10. package/dist/clients/AgetherClient.d.ts +101 -0
  11. package/dist/clients/AgetherClient.d.ts.map +1 -0
  12. package/dist/clients/AgetherClient.js +272 -0
  13. package/dist/clients/ScoringClient.d.ts +138 -0
  14. package/dist/clients/ScoringClient.d.ts.map +1 -0
  15. package/dist/clients/ScoringClient.js +135 -0
  16. package/dist/clients/VaultClient.d.ts +62 -0
  17. package/dist/clients/VaultClient.d.ts.map +1 -0
  18. package/dist/clients/VaultClient.js +157 -0
  19. package/dist/clients/WalletClient.d.ts +73 -0
  20. package/dist/clients/WalletClient.d.ts.map +1 -0
  21. package/dist/clients/WalletClient.js +174 -0
  22. package/dist/clients/X402Client.d.ts +61 -0
  23. package/dist/clients/X402Client.d.ts.map +1 -0
  24. package/dist/clients/X402Client.js +303 -0
  25. package/dist/index.d.mts +932 -0
  26. package/dist/index.d.ts +932 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +1680 -0
  29. package/dist/index.mjs +1610 -0
  30. package/dist/types/index.d.ts +220 -0
  31. package/dist/types/index.d.ts.map +1 -0
  32. package/dist/types/index.js +52 -0
  33. package/dist/utils/abis.d.ts +21 -0
  34. package/dist/utils/abis.d.ts.map +1 -0
  35. package/dist/utils/abis.js +134 -0
  36. package/dist/utils/config.d.ts +31 -0
  37. package/dist/utils/config.d.ts.map +1 -0
  38. package/dist/utils/config.js +117 -0
  39. package/dist/utils/format.d.ts +44 -0
  40. package/dist/utils/format.d.ts.map +1 -0
  41. package/dist/utils/format.js +75 -0
  42. package/package.json +57 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * x402 HTTP Client — Make paid API calls via the x402 protocol (v2)
3
+ *
4
+ * Implements the Coinbase x402 spec:
5
+ * https://github.com/coinbase/x402
6
+ *
7
+ * Flow:
8
+ * 1. Client → Resource Server (normal request)
9
+ * 2. Resource Server → 402 with PaymentRequired JSON body
10
+ * Body: { x402Version, error, resource, accepts: [PaymentRequirements…] }
11
+ * 3. Client picks a PaymentRequirements from `accepts`,
12
+ * signs an EIP-3009 transferWithAuthorization (EIP-712 typed data),
13
+ * builds a PaymentPayload, base64-encodes it as PAYMENT-SIGNATURE header
14
+ * 4. Client → Resource Server (retries with PAYMENT-SIGNATURE)
15
+ * 5. Resource Server forwards to Facilitator /verify → /settle
16
+ * 6. Resource Server → 200 + data (or error)
17
+ *
18
+ * Chain support: Base (8453), Ethereum (1), and Hardhat fork (31337).
19
+ * USDC domain is resolved per-chain from USDC_DOMAINS map.
20
+ */
21
+ import { ethers } from 'ethers';
22
+ // ──────────────────── Well-known USDC metadata ────────────────────
23
+ // EIP-3009 domain per chain (name / version / verifyingContract)
24
+ const USDC_DOMAINS = {
25
+ 'eip155:1': { name: 'USD Coin', version: '2', address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' },
26
+ 'eip155:8453': { name: 'USD Coin', version: '2', address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
27
+ 'eip155:84532': { name: 'USD Coin', version: '2', address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e' },
28
+ 'eip155:42161': { name: 'USD Coin', version: '2', address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' },
29
+ 'eip155:10': { name: 'USD Coin', version: '2', address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' },
30
+ };
31
+ function chainIdFromNetwork(network) {
32
+ const m = network.match(/^eip155:(\d+)$/);
33
+ return m ? Number(m[1]) : 1;
34
+ }
35
+ // ──────────────────── Client ────────────────────
36
+ export class X402Client {
37
+ constructor(config) {
38
+ this.config = config;
39
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl);
40
+ this.wallet = new ethers.Wallet(config.privateKey, provider);
41
+ }
42
+ async get(url, opts) {
43
+ return this.request(url, { ...opts, method: 'GET' });
44
+ }
45
+ async post(url, body, opts) {
46
+ return this.request(url, {
47
+ ...opts,
48
+ method: 'POST',
49
+ body: body ? JSON.stringify(body) : undefined,
50
+ headers: { 'Content-Type': 'application/json', ...opts?.headers },
51
+ });
52
+ }
53
+ getAddress() {
54
+ return this.wallet.address;
55
+ }
56
+ // ──────────── Core request / 402-retry loop ────────────
57
+ async request(url, options) {
58
+ try {
59
+ // 1️⃣ First attempt — may get 402
60
+ console.log(' [1/4] Calling resource server…');
61
+ const response = await fetch(url, {
62
+ ...options,
63
+ headers: {
64
+ ...options?.headers,
65
+ 'X-Agent-Id': this.config.agentId || '',
66
+ },
67
+ });
68
+ if (response.ok) {
69
+ const data = await response.json();
70
+ return { success: true, data };
71
+ }
72
+ if (response.status !== 402) {
73
+ return { success: false, error: `HTTP ${response.status}: ${await response.text()}` };
74
+ }
75
+ // 2️⃣ Parse PaymentRequired
76
+ console.log(' [2/4] 402 received — parsing payment requirements…');
77
+ const parsed = await this.parsePaymentRequired(response);
78
+ if (!parsed) {
79
+ return { success: false, error: 'Could not parse payment requirements from 402 response' };
80
+ }
81
+ const { requirements, resource } = parsed;
82
+ console.log(` scheme : ${requirements.scheme}`);
83
+ console.log(` network : ${requirements.network}`);
84
+ console.log(` amount : ${requirements.amount} (atomic)`);
85
+ console.log(` asset : ${requirements.asset}`);
86
+ console.log(` payTo : ${requirements.payTo}`);
87
+ // 3️⃣ Sign EIP-3009 authorization & build PaymentPayload
88
+ console.log(' [3/4] Signing EIP-3009 transferWithAuthorization…');
89
+ const paymentPayload = await this.buildPaymentPayload(requirements, resource, url);
90
+ const paymentB64 = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
91
+ // Optional: risk-check via our backend
92
+ await this.riskCheck(paymentPayload, requirements);
93
+ // 4️⃣ Retry with PAYMENT-SIGNATURE header
94
+ console.log(' [4/4] Retrying with PAYMENT-SIGNATURE header…');
95
+ const paidResponse = await fetch(url, {
96
+ ...options,
97
+ headers: {
98
+ ...options?.headers,
99
+ 'X-Agent-Id': this.config.agentId || '',
100
+ // v2 header
101
+ 'PAYMENT-SIGNATURE': paymentB64,
102
+ // v1 compat header (some servers still use this)
103
+ 'X-PAYMENT': paymentB64,
104
+ },
105
+ });
106
+ if (paidResponse.ok) {
107
+ const data = await paidResponse.json();
108
+ // Check for settlement response
109
+ const settlementHeader = paidResponse.headers.get('PAYMENT-RESPONSE')
110
+ || paidResponse.headers.get('X-PAYMENT-RESPONSE');
111
+ let txHash;
112
+ if (settlementHeader) {
113
+ try {
114
+ const settlement = JSON.parse(Buffer.from(settlementHeader, 'base64').toString('utf-8'));
115
+ txHash = settlement.transaction;
116
+ }
117
+ catch { }
118
+ }
119
+ return {
120
+ success: true,
121
+ data,
122
+ paymentInfo: {
123
+ amount: requirements.amount,
124
+ asset: requirements.extra?.name || 'USDC',
125
+ network: requirements.network,
126
+ txHash,
127
+ },
128
+ };
129
+ }
130
+ const errBody = await paidResponse.text();
131
+ return { success: false, error: `Payment rejected (HTTP ${paidResponse.status}): ${errBody}` };
132
+ }
133
+ catch (error) {
134
+ return { success: false, error: `Request failed: ${error instanceof Error ? error.message : String(error)}` };
135
+ }
136
+ }
137
+ // ──────────── Parse 402 response ────────────
138
+ async parsePaymentRequired(response) {
139
+ // Strategy 1: PAYMENT-REQUIRED header (base64 JSON — some servers)
140
+ const prHeader = response.headers.get('PAYMENT-REQUIRED') || response.headers.get('x-payment-required');
141
+ if (prHeader) {
142
+ try {
143
+ const decoded = JSON.parse(Buffer.from(prHeader, 'base64').toString('utf-8'));
144
+ if (decoded.accepts?.length) {
145
+ return { requirements: decoded.accepts[0], resource: decoded.resource };
146
+ }
147
+ }
148
+ catch { /* fall through */ }
149
+ }
150
+ // Strategy 2: Response body (primary method per v2 spec)
151
+ try {
152
+ const body = await response.json();
153
+ // v2 format: { accepts: [...] }
154
+ if (body.accepts && Array.isArray(body.accepts) && body.accepts.length > 0) {
155
+ return { requirements: body.accepts[0], resource: body.resource };
156
+ }
157
+ // v1 compat: { paymentRequirements: {...} } or { paymentRequirements: [...] }
158
+ if (body.paymentRequirements) {
159
+ const pr = Array.isArray(body.paymentRequirements) ? body.paymentRequirements[0] : body.paymentRequirements;
160
+ return { requirements: pr, resource: body.resource };
161
+ }
162
+ // Ultra-legacy: flat fields
163
+ if (body.scheme && body.network) {
164
+ return { requirements: body, resource: body.resource };
165
+ }
166
+ }
167
+ catch { /* fall through */ }
168
+ // Strategy 3: WWW-Authenticate header (old implementations)
169
+ const wwwAuth = response.headers.get('WWW-Authenticate');
170
+ if (wwwAuth) {
171
+ const m = wwwAuth.match(/x402[^"]*"([^"]+)"/);
172
+ if (m) {
173
+ try {
174
+ const decoded = JSON.parse(Buffer.from(m[1], 'base64').toString('utf-8'));
175
+ const reqs = Array.isArray(decoded) ? decoded[0] : decoded;
176
+ return { requirements: reqs };
177
+ }
178
+ catch { /* fall through */ }
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ // ──────────── Build x402 v2 PaymentPayload with EIP-3009 ────────────
184
+ //
185
+ // If an AgentAccount is configured, we use it as the `from` address
186
+ // (smart wallet pays directly). The AgentAccount implements EIP-1271
187
+ // so USDC's transferWithAuthorization will call isValidSignature()
188
+ // to verify the owner's ECDSA signature. The facilitator detects
189
+ // the >65-byte or smart-wallet case and uses the bytes overload.
190
+ async buildPaymentPayload(reqs, resource, url) {
191
+ const now = Math.floor(Date.now() / 1000);
192
+ const validAfter = String(now - 60);
193
+ const validBefore = String(now + (reqs.maxTimeoutSeconds || 300));
194
+ const nonce = ethers.hexlify(ethers.randomBytes(32));
195
+ const chainId = chainIdFromNetwork(reqs.network);
196
+ const usdcDomain = USDC_DOMAINS[reqs.network] || USDC_DOMAINS['eip155:1'];
197
+ // Determine payer: AgentAccount (smart wallet) if available, else EOA
198
+ const payerAddress = this.config.accountAddress || this.wallet.address;
199
+ const isSmartWallet = !!this.config.accountAddress;
200
+ // EIP-712 domain for USDC's transferWithAuthorization (EIP-3009)
201
+ const domain = {
202
+ name: usdcDomain.name,
203
+ version: usdcDomain.version,
204
+ chainId,
205
+ verifyingContract: reqs.asset || usdcDomain.address,
206
+ };
207
+ const types = {
208
+ TransferWithAuthorization: [
209
+ { name: 'from', type: 'address' },
210
+ { name: 'to', type: 'address' },
211
+ { name: 'value', type: 'uint256' },
212
+ { name: 'validAfter', type: 'uint256' },
213
+ { name: 'validBefore', type: 'uint256' },
214
+ { name: 'nonce', type: 'bytes32' },
215
+ ],
216
+ };
217
+ const value = {
218
+ from: payerAddress, // AgentAccount or EOA
219
+ to: reqs.payTo,
220
+ value: reqs.amount,
221
+ validAfter,
222
+ validBefore,
223
+ nonce,
224
+ };
225
+ // EOA signs the typed data — for smart wallets, EIP-1271 validation
226
+ // will verify this signature against the AgentAccount's owner()
227
+ let signature = await this.wallet.signTypedData(domain, types, value);
228
+ // For smart wallets: pad the signature to >65 bytes (>130 hex chars).
229
+ // The Coinbase x402 facilitator uses signature length to choose the
230
+ // settlement path:
231
+ // - 65 bytes (130 hex) → v,r,s overload (EOA) — ecrecover
232
+ // - >65 bytes (>130 hex) → bytes overload (smart wallet) — EIP-1271
233
+ // Without padding, the facilitator would use the EOA path and
234
+ // ecrecover would return the EOA address, not AgentAccount → fail.
235
+ // With padding, USDC calls AgentAccount.isValidSignature() which
236
+ // extracts r,s,v from the first 65 bytes and verifies the owner.
237
+ if (isSmartWallet) {
238
+ signature = (signature + '00');
239
+ }
240
+ if (isSmartWallet) {
241
+ console.log(` ✓ Signed for AgentAccount ${payerAddress.slice(0, 10)}… (EIP-1271, chain=${chainId})`);
242
+ }
243
+ else {
244
+ console.log(` ✓ Signed (from=${payerAddress.slice(0, 10)}…, chain=${chainId})`);
245
+ }
246
+ return {
247
+ x402Version: 2,
248
+ resource: resource || { url, description: '', mimeType: 'application/json' },
249
+ accepted: {
250
+ scheme: reqs.scheme,
251
+ network: reqs.network,
252
+ amount: reqs.amount,
253
+ asset: reqs.asset,
254
+ payTo: reqs.payTo,
255
+ maxTimeoutSeconds: reqs.maxTimeoutSeconds,
256
+ extra: reqs.extra || {},
257
+ },
258
+ payload: {
259
+ signature,
260
+ authorization: {
261
+ from: payerAddress, // AgentAccount address — facilitator checks balance here
262
+ to: reqs.payTo,
263
+ value: reqs.amount,
264
+ validAfter,
265
+ validBefore,
266
+ nonce,
267
+ },
268
+ },
269
+ };
270
+ }
271
+ // ──────────── Risk check via our backend ────────────
272
+ async riskCheck(paymentPayload, reqs) {
273
+ try {
274
+ const verifyUrl = `${this.config.backendUrl}/x402/verify`;
275
+ const resp = await fetch(verifyUrl, {
276
+ method: 'POST',
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ 'X-Agent-Id': this.config.agentId || '',
280
+ ...(this.config.accountAddress ? { 'X-Agent-Account': this.config.accountAddress } : {}),
281
+ },
282
+ body: JSON.stringify({
283
+ x402Version: 2,
284
+ paymentPayload,
285
+ paymentRequirements: reqs,
286
+ }),
287
+ signal: AbortSignal.timeout(5000),
288
+ });
289
+ if (resp.ok) {
290
+ const result = await resp.json();
291
+ const decision = resp.headers.get('X-Risk-Decision') || (result.isValid ? 'allow' : 'unknown');
292
+ const score = resp.headers.get('X-Risk-Score') || '?';
293
+ console.log(` ✓ Risk check: ${decision} (score=${score})`);
294
+ }
295
+ else {
296
+ console.log(` ⚠ Risk check failed (HTTP ${resp.status}) — continuing anyway`);
297
+ }
298
+ }
299
+ catch {
300
+ console.log(' ⚠ Risk check unavailable — continuing');
301
+ }
302
+ }
303
+ }