@agether/sdk 2.13.0 → 2.14.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/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +252 -21
- package/dist/clients/AgentIdentityClient.d.ts +200 -0
- package/dist/clients/AgentIdentityClient.d.ts.map +1 -0
- package/dist/clients/AgentIdentityClient.js +351 -0
- package/dist/clients/AgetherClient.d.ts +242 -0
- package/dist/clients/AgetherClient.d.ts.map +1 -0
- package/dist/clients/AgetherClient.js +736 -0
- package/dist/clients/MorphoClient.d.ts +572 -0
- package/dist/clients/MorphoClient.d.ts.map +1 -0
- package/dist/clients/MorphoClient.js +1974 -0
- package/dist/clients/ScoringClient.d.ts +103 -0
- package/dist/clients/ScoringClient.d.ts.map +1 -0
- package/dist/clients/ScoringClient.js +112 -0
- package/dist/clients/X402Client.d.ts +198 -0
- package/dist/clients/X402Client.d.ts.map +1 -0
- package/dist/clients/X402Client.js +438 -0
- package/dist/index.d.mts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +252 -21
- package/dist/index.mjs +252 -21
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +46 -0
- package/dist/utils/abis.d.ts +29 -0
- package/dist/utils/abis.d.ts.map +1 -0
- package/dist/utils/abis.js +138 -0
- package/dist/utils/config.d.ts +36 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +168 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +75 -0
- package/package.json +1 -1
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 HTTP Client — Make paid API calls via the x402 protocol (v2)
|
|
3
|
+
*
|
|
4
|
+
* Built on top of the official @x402/fetch + @x402/evm SDK.
|
|
5
|
+
* https://docs.x402.org/getting-started/quickstart-for-buyers
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Client → Resource Server (normal request)
|
|
9
|
+
* 2. Resource Server → 402 with PAYMENT-REQUIRED header (base64 JSON)
|
|
10
|
+
* 3. @x402/fetch auto-picks PaymentRequirements, signs EIP-3009 via EVM scheme
|
|
11
|
+
* 4. Client → Resource Server (retries with PAYMENT-SIGNATURE header)
|
|
12
|
+
* 5. Resource Server → verifies via facilitator → settles → 200 + data
|
|
13
|
+
*
|
|
14
|
+
* Auto-Draw: When autoDraw is enabled and USDC balance is insufficient,
|
|
15
|
+
* the client automatically borrows from Morpho Blue before paying.
|
|
16
|
+
*
|
|
17
|
+
* Auto-Yield: When autoYield is enabled, the client first tries to cover
|
|
18
|
+
* the deficit from earned supply yield (principal untouched) before borrowing.
|
|
19
|
+
*
|
|
20
|
+
* Waterfall when both enabled: balance → yield → borrow
|
|
21
|
+
*
|
|
22
|
+
* Spending Limits: Optional daily spending cap (dailySpendLimitUsdc) with
|
|
23
|
+
* persistent state via onSpendingUpdate callback. The caller (e.g. plugin)
|
|
24
|
+
* is responsible for persisting and restoring state via initialSpendingState.
|
|
25
|
+
*
|
|
26
|
+
* Chain support: Base (8453), Base Sepolia (84532), Ethereum (1).
|
|
27
|
+
*/
|
|
28
|
+
import { wrapFetchWithPayment } from '@x402/fetch';
|
|
29
|
+
import { x402Client } from '@x402/core/client';
|
|
30
|
+
import { registerExactEvmScheme } from '@x402/evm/exact/client';
|
|
31
|
+
import { privateKeyToAccount, toAccount } from 'viem/accounts';
|
|
32
|
+
// ──────────────────── Client ────────────────────
|
|
33
|
+
export class X402Client {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
// ── Step 1: Resolve base signer from privateKey or walletClient ──
|
|
37
|
+
let baseSigner;
|
|
38
|
+
if ('walletClient' in config && config.walletClient) {
|
|
39
|
+
// External WalletClient mode (Privy, Turnkey, MetaMask, etc.)
|
|
40
|
+
const wc = config.walletClient;
|
|
41
|
+
const account = wc.account;
|
|
42
|
+
if (!account) {
|
|
43
|
+
throw new Error('X402Client: walletClient must have an attached account. ' +
|
|
44
|
+
'Pass an account when creating the WalletClient or use privateKey mode instead.');
|
|
45
|
+
}
|
|
46
|
+
this.address = account.address;
|
|
47
|
+
// Adapt WalletClient to the signer interface expected by @x402/evm
|
|
48
|
+
baseSigner = {
|
|
49
|
+
address: account.address,
|
|
50
|
+
signTypedData: (msg) => wc.signTypedData({
|
|
51
|
+
account,
|
|
52
|
+
domain: msg.domain,
|
|
53
|
+
types: msg.types,
|
|
54
|
+
primaryType: msg.primaryType,
|
|
55
|
+
message: msg.message,
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else if ('privateKey' in config && config.privateKey) {
|
|
60
|
+
// Private key mode (existing behavior — SDK manages wallet)
|
|
61
|
+
const privateKey = config.privateKey.startsWith('0x')
|
|
62
|
+
? config.privateKey
|
|
63
|
+
: `0x${config.privateKey}`;
|
|
64
|
+
const account = privateKeyToAccount(privateKey);
|
|
65
|
+
this.address = account.address;
|
|
66
|
+
baseSigner = account;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
throw new Error('X402Client: provide either privateKey or walletClient in config.');
|
|
70
|
+
}
|
|
71
|
+
// ── Step 2: Wrap signer for smart-wallet (AgentAccount) if accountAddress is set ──
|
|
72
|
+
// When accountAddress is provided, EIP-3009 `from` must be the contract, and
|
|
73
|
+
// the signature must be >65 bytes so the x402 facilitator routes through USDC's
|
|
74
|
+
// `bytes signature` overload of transferWithAuthorization — triggering EIP-1271
|
|
75
|
+
// `isValidSignature` on the AgentAccount contract.
|
|
76
|
+
let signer;
|
|
77
|
+
if (config.accountAddress) {
|
|
78
|
+
const accountAddr = config.accountAddress;
|
|
79
|
+
const inner = baseSigner;
|
|
80
|
+
signer = toAccount({
|
|
81
|
+
address: accountAddr,
|
|
82
|
+
async signMessage({ message }) {
|
|
83
|
+
if ('signMessage' in inner && typeof inner.signMessage === 'function') {
|
|
84
|
+
return inner.signMessage({ message });
|
|
85
|
+
}
|
|
86
|
+
throw new Error('signMessage not supported by underlying signer');
|
|
87
|
+
},
|
|
88
|
+
async signTransaction(tx) {
|
|
89
|
+
if ('signTransaction' in inner && typeof inner.signTransaction === 'function') {
|
|
90
|
+
return inner.signTransaction(tx);
|
|
91
|
+
}
|
|
92
|
+
throw new Error('signTransaction not supported by underlying signer');
|
|
93
|
+
},
|
|
94
|
+
async signTypedData(typedData) {
|
|
95
|
+
const sig = await inner.signTypedData(typedData);
|
|
96
|
+
// For Safe7579: prepend validator module address so isValidSignature
|
|
97
|
+
// routes through Safe7579 → validator.isValidSignatureWithSender()
|
|
98
|
+
if (config.validatorModule) {
|
|
99
|
+
const validatorHex = config.validatorModule.replace('0x', '').toLowerCase();
|
|
100
|
+
const sigHex = sig.replace('0x', '');
|
|
101
|
+
return `0x${validatorHex}${sigHex}`;
|
|
102
|
+
}
|
|
103
|
+
// Fallback: append 00 to trigger EIP-1271 via >65 byte sig
|
|
104
|
+
return `${sig}00`;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
this.address = accountAddr;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
signer = baseSigner;
|
|
111
|
+
}
|
|
112
|
+
// Create x402 client and register EVM scheme (handles EIP-3009 signing)
|
|
113
|
+
const client = new x402Client();
|
|
114
|
+
registerExactEvmScheme(client, { signer });
|
|
115
|
+
// Wrap native fetch with automatic 402 payment handling
|
|
116
|
+
this.paidFetch = wrapFetchWithPayment(fetch, client);
|
|
117
|
+
// Initialize spending tracker (restore from cache if provided)
|
|
118
|
+
const today = new Date().toISOString().split('T')[0];
|
|
119
|
+
const dailyLimit = config.dailySpendLimitUsdc
|
|
120
|
+
? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6))
|
|
121
|
+
: 0n;
|
|
122
|
+
const restored = config.initialSpendingState;
|
|
123
|
+
const restoredBorrowed = restored && restored.date === today
|
|
124
|
+
? BigInt(restored.totalBorrowed)
|
|
125
|
+
: 0n;
|
|
126
|
+
this._spendingTracker = { date: today, totalBorrowed: restoredBorrowed, dailyLimit };
|
|
127
|
+
}
|
|
128
|
+
async get(url, opts) {
|
|
129
|
+
return this.request(url, { ...opts, method: 'GET' });
|
|
130
|
+
}
|
|
131
|
+
async post(url, body, opts) {
|
|
132
|
+
return this.request(url, {
|
|
133
|
+
...opts,
|
|
134
|
+
method: 'POST',
|
|
135
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
136
|
+
headers: { 'Content-Type': 'application/json', ...opts?.headers },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
getAddress() {
|
|
140
|
+
return this.address;
|
|
141
|
+
}
|
|
142
|
+
/** Get the current spending tracker state */
|
|
143
|
+
getSpendingTracker() {
|
|
144
|
+
this._resetTrackerIfNewDay();
|
|
145
|
+
return { ...this._spendingTracker };
|
|
146
|
+
}
|
|
147
|
+
/** Get remaining daily spending allowance in USDC (human-readable) */
|
|
148
|
+
getRemainingDailyAllowance() {
|
|
149
|
+
this._resetTrackerIfNewDay();
|
|
150
|
+
if (this._spendingTracker.dailyLimit === 0n)
|
|
151
|
+
return 'unlimited';
|
|
152
|
+
const remaining = this._spendingTracker.dailyLimit - this._spendingTracker.totalBorrowed;
|
|
153
|
+
return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Pay with auto-funding: Make an x402 request with automatic USDC sourcing.
|
|
157
|
+
*
|
|
158
|
+
* Uses a **plan-then-execute** approach: all reads happen first, and if the
|
|
159
|
+
* full deficit can't be covered the method fails with NO side-effects
|
|
160
|
+
* (no yield withdrawn, no USDC borrowed).
|
|
161
|
+
*
|
|
162
|
+
* Waterfall (when both autoYield + autoDraw enabled):
|
|
163
|
+
* 1. Check USDC balance on AgentAccount
|
|
164
|
+
* 2. Probe the URL to discover payment amount (if 402)
|
|
165
|
+
* 3. If insufficient USDC — PLANNING PHASE (read-only):
|
|
166
|
+
* a. Calculate total available yield across supply positions
|
|
167
|
+
* b. Calculate max borrowable from Morpho markets
|
|
168
|
+
* c. Verify yield + borrow can cover full deficit — if not, fail immediately
|
|
169
|
+
* 4. EXECUTION PHASE (only if plan is feasible):
|
|
170
|
+
* a. Withdraw needed yield from supply positions
|
|
171
|
+
* b. Borrow remaining deficit from Morpho
|
|
172
|
+
* 5. Proceed with x402 payment
|
|
173
|
+
*/
|
|
174
|
+
async payWithAutoDraw(url, opts) {
|
|
175
|
+
const { morphoClient, ...fetchOpts } = opts || {};
|
|
176
|
+
// If neither auto mode enabled or no morphoClient, fall back to normal request
|
|
177
|
+
if ((!this.config.autoDraw && !this.config.autoYield) || !morphoClient) {
|
|
178
|
+
return this.request(url, fetchOpts);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
// ── Step 1: Check current USDC balance on AgentAccount ──
|
|
182
|
+
const usdcBalance = await morphoClient.getUsdcBalance();
|
|
183
|
+
console.log(` [auto-fund] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
|
|
184
|
+
// ── Step 2: Probe the URL to discover payment amount ──
|
|
185
|
+
const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
|
|
186
|
+
if (paymentAmount !== null) {
|
|
187
|
+
console.log(` [auto-fund] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
|
|
188
|
+
const needed = paymentAmount;
|
|
189
|
+
if (usdcBalance < needed) {
|
|
190
|
+
const totalDeficit = needed - usdcBalance;
|
|
191
|
+
console.log(` [auto-fund] Insufficient balance. Deficit: ${(Number(totalDeficit) / 1e6).toFixed(2)} USDC`);
|
|
192
|
+
const yieldPlan = [];
|
|
193
|
+
let totalYieldAvailable = 0n;
|
|
194
|
+
if (this.config.autoYield) {
|
|
195
|
+
try {
|
|
196
|
+
const positions = await morphoClient.getSupplyPositions();
|
|
197
|
+
const withYield = positions
|
|
198
|
+
.filter((p) => parseFloat(p.earnedYield) > 0.000001)
|
|
199
|
+
.sort((a, b) => parseFloat(b.earnedYield) - parseFloat(a.earnedYield));
|
|
200
|
+
for (const pos of withYield) {
|
|
201
|
+
const yieldRaw = BigInt(Math.floor(parseFloat(pos.earnedYield) * 1e6));
|
|
202
|
+
if (yieldRaw > 0n) {
|
|
203
|
+
yieldPlan.push({ collateralToken: pos.collateralToken, amount: yieldRaw });
|
|
204
|
+
totalYieldAvailable += yieldRaw;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
console.log(` [auto-fund] Plan: ${(Number(totalYieldAvailable) / 1e6).toFixed(6)} USDC available from yield`);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.warn(` [auto-fund] Plan: yield check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Plan: how much yield do we actually need to withdraw?
|
|
214
|
+
const yieldToUse = totalYieldAvailable < totalDeficit ? totalYieldAvailable : totalDeficit;
|
|
215
|
+
const deficitAfterYield = totalDeficit - yieldToUse;
|
|
216
|
+
// Plan: how much do we need to borrow?
|
|
217
|
+
let canBorrow = 0n;
|
|
218
|
+
if (this.config.autoDraw && deficitAfterYield > 0n) {
|
|
219
|
+
// Check spending limit first
|
|
220
|
+
const limitCheck = await this._checkSpendingLimit(deficitAfterYield, morphoClient);
|
|
221
|
+
if (!limitCheck.allowed) {
|
|
222
|
+
return { success: false, error: `Auto-fund blocked: ${limitCheck.reason}` };
|
|
223
|
+
}
|
|
224
|
+
const maxBorrowable = await morphoClient.getMaxBorrowable();
|
|
225
|
+
canBorrow = maxBorrowable.total;
|
|
226
|
+
console.log(` [auto-fund] Plan: ${(Number(canBorrow) / 1e6).toFixed(2)} USDC borrowable from Morpho`);
|
|
227
|
+
}
|
|
228
|
+
// ── Feasibility check: can yield + borrow cover full deficit? ──
|
|
229
|
+
const totalAvailable = yieldToUse + canBorrow;
|
|
230
|
+
if (totalAvailable < totalDeficit) {
|
|
231
|
+
const parts = [];
|
|
232
|
+
if (totalYieldAvailable > 0n)
|
|
233
|
+
parts.push(`yield: $${(Number(totalYieldAvailable) / 1e6).toFixed(2)}`);
|
|
234
|
+
if (canBorrow > 0n)
|
|
235
|
+
parts.push(`borrowable: $${(Number(canBorrow) / 1e6).toFixed(2)}`);
|
|
236
|
+
if (!this.config.autoYield)
|
|
237
|
+
parts.push('autoYield: off');
|
|
238
|
+
if (!this.config.autoDraw)
|
|
239
|
+
parts.push('autoDraw: off');
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Cannot cover deficit of $${(Number(totalDeficit) / 1e6).toFixed(2)} USDC. ` +
|
|
243
|
+
`Available: ${parts.join(', ')}. ` +
|
|
244
|
+
`Balance: $${(Number(usdcBalance) / 1e6).toFixed(2)}, needed: $${(Number(needed) / 1e6).toFixed(2)}.`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// ═══════════════════════════════════════════
|
|
248
|
+
// EXECUTION PHASE — plan is feasible, execute
|
|
249
|
+
// ═══════════════════════════════════════════
|
|
250
|
+
const drawInfo = {
|
|
251
|
+
reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`,
|
|
252
|
+
};
|
|
253
|
+
// Execute: withdraw yield (only the amount we planned)
|
|
254
|
+
if (yieldToUse > 0n) {
|
|
255
|
+
let remaining = yieldToUse;
|
|
256
|
+
let totalWithdrawn = 0n;
|
|
257
|
+
let lastTx = '';
|
|
258
|
+
for (const plan of yieldPlan) {
|
|
259
|
+
if (remaining <= 0n)
|
|
260
|
+
break;
|
|
261
|
+
const toWithdraw = plan.amount < remaining ? plan.amount : remaining;
|
|
262
|
+
const withdrawStr = (Number(toWithdraw) / 1e6).toFixed(6);
|
|
263
|
+
console.log(` [auto-yield] Withdrawing $${withdrawStr} yield from ${plan.collateralToken} market`);
|
|
264
|
+
const wr = await morphoClient.withdrawSupply(withdrawStr, plan.collateralToken);
|
|
265
|
+
lastTx = wr.tx;
|
|
266
|
+
totalWithdrawn += toWithdraw;
|
|
267
|
+
remaining -= toWithdraw;
|
|
268
|
+
}
|
|
269
|
+
if (totalWithdrawn > 0n) {
|
|
270
|
+
drawInfo.yieldWithdrawn = (Number(totalWithdrawn) / 1e6).toFixed(6);
|
|
271
|
+
drawInfo.yieldTx = lastTx;
|
|
272
|
+
console.log(` [auto-yield] Withdrawn: $${drawInfo.yieldWithdrawn}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Execute: borrow remaining deficit
|
|
276
|
+
if (deficitAfterYield > 0n && this.config.autoDraw) {
|
|
277
|
+
const borrowAmount = (Number(deficitAfterYield) / 1e6).toFixed(6);
|
|
278
|
+
console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
|
|
279
|
+
const borrowResult = await morphoClient.borrow(borrowAmount);
|
|
280
|
+
console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
|
|
281
|
+
drawInfo.borrowed = borrowAmount;
|
|
282
|
+
drawInfo.borrowTx = borrowResult.tx;
|
|
283
|
+
this._trackSpending(deficitAfterYield);
|
|
284
|
+
}
|
|
285
|
+
// Execute: proceed with payment
|
|
286
|
+
const result = await this.request(url, fetchOpts);
|
|
287
|
+
return { ...result, autoDrawInfo: drawInfo };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Balance sufficient or couldn't determine amount — proceed normally
|
|
291
|
+
return this.request(url, fetchOpts);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
console.log(` [auto-fund] Failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
|
|
295
|
+
return this.request(url, fetchOpts);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// ──────────── Core request — @x402/fetch handles 402 automatically ────────────
|
|
299
|
+
async request(url, options) {
|
|
300
|
+
try {
|
|
301
|
+
console.log(` [x402] ${options?.method || 'GET'} ${url}`);
|
|
302
|
+
const response = await this.paidFetch(url, {
|
|
303
|
+
...options,
|
|
304
|
+
headers: {
|
|
305
|
+
...options?.headers,
|
|
306
|
+
'X-Agent-Id': this.config.agentId || '',
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
if (response.ok) {
|
|
310
|
+
const data = await response.json();
|
|
311
|
+
// Check for settlement response header
|
|
312
|
+
const paymentResponse = response.headers.get('PAYMENT-RESPONSE');
|
|
313
|
+
let txHash;
|
|
314
|
+
let settlementNetwork;
|
|
315
|
+
if (paymentResponse) {
|
|
316
|
+
try {
|
|
317
|
+
const settlement = JSON.parse(Buffer.from(paymentResponse, 'base64').toString('utf-8'));
|
|
318
|
+
txHash = settlement.transaction;
|
|
319
|
+
settlementNetwork = settlement.network;
|
|
320
|
+
}
|
|
321
|
+
catch (e) {
|
|
322
|
+
console.warn('[agether] x402 payment response parse failed:', e instanceof Error ? e.message : e);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
success: true,
|
|
327
|
+
data,
|
|
328
|
+
...(txHash ? {
|
|
329
|
+
paymentInfo: {
|
|
330
|
+
amount: '',
|
|
331
|
+
asset: 'USDC',
|
|
332
|
+
network: settlementNetwork || 'eip155',
|
|
333
|
+
txHash,
|
|
334
|
+
},
|
|
335
|
+
} : {}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// If we get here, @x402/fetch already tried to pay but the server rejected
|
|
339
|
+
const errBody = await response.text();
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: `HTTP ${response.status}: ${errBody}`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
return {
|
|
347
|
+
success: false,
|
|
348
|
+
error: `Request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ──────────── Auto-Draw Helpers ────────────
|
|
353
|
+
/**
|
|
354
|
+
* Probe a URL to discover payment requirements without paying.
|
|
355
|
+
* Makes a request and parses the 402 PAYMENT-REQUIRED header.
|
|
356
|
+
* @returns Payment amount in raw USDC units (6 decimals), or null if not a 402.
|
|
357
|
+
*/
|
|
358
|
+
async _probePaymentAmount(url, options) {
|
|
359
|
+
try {
|
|
360
|
+
const response = await fetch(url, {
|
|
361
|
+
...options,
|
|
362
|
+
headers: {
|
|
363
|
+
...options?.headers,
|
|
364
|
+
'X-Agent-Id': this.config.agentId || '',
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
if (response.status !== 402)
|
|
368
|
+
return null;
|
|
369
|
+
// Parse PAYMENT-REQUIRED header (base64 JSON)
|
|
370
|
+
const paymentHeader = response.headers.get('X-PAYMENT') || response.headers.get('PAYMENT-REQUIRED');
|
|
371
|
+
if (!paymentHeader)
|
|
372
|
+
return null;
|
|
373
|
+
try {
|
|
374
|
+
const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString('utf-8'));
|
|
375
|
+
const requirements = Array.isArray(decoded) ? decoded : decoded.accepts || [decoded];
|
|
376
|
+
if (requirements.length > 0) {
|
|
377
|
+
const amount = requirements[0].maxAmountRequired || requirements[0].amount;
|
|
378
|
+
if (amount) {
|
|
379
|
+
return BigInt(amount);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
console.warn('[agether] x402 payment header parse failed:', e instanceof Error ? e.message : e);
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
catch (e) {
|
|
389
|
+
console.warn('[agether] x402 getPaymentRequired failed:', e instanceof Error ? e.message : e);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** Reset spending tracker if it's a new day */
|
|
394
|
+
_resetTrackerIfNewDay() {
|
|
395
|
+
const today = new Date().toISOString().split('T')[0];
|
|
396
|
+
if (this._spendingTracker.date !== today) {
|
|
397
|
+
this._spendingTracker = {
|
|
398
|
+
date: today,
|
|
399
|
+
totalBorrowed: 0n,
|
|
400
|
+
dailyLimit: this._spendingTracker.dailyLimit,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Track a new spending amount and notify the caller for persistence */
|
|
405
|
+
_trackSpending(amount) {
|
|
406
|
+
this._resetTrackerIfNewDay();
|
|
407
|
+
this._spendingTracker.totalBorrowed += amount;
|
|
408
|
+
// Notify caller so they can persist the updated state
|
|
409
|
+
if (this.config.onSpendingUpdate) {
|
|
410
|
+
try {
|
|
411
|
+
this.config.onSpendingUpdate({
|
|
412
|
+
date: this._spendingTracker.date,
|
|
413
|
+
totalBorrowed: this._spendingTracker.totalBorrowed.toString(),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
catch (e) {
|
|
417
|
+
console.warn('[agether] onSpendingUpdate callback failed:', e instanceof Error ? e.message : e);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Check if a borrow amount is within the fixed daily spending limit.
|
|
423
|
+
*/
|
|
424
|
+
async _checkSpendingLimit(amount, _morphoClient) {
|
|
425
|
+
this._resetTrackerIfNewDay();
|
|
426
|
+
// Check fixed daily limit
|
|
427
|
+
if (this._spendingTracker.dailyLimit > 0n) {
|
|
428
|
+
const newTotal = this._spendingTracker.totalBorrowed + amount;
|
|
429
|
+
if (newTotal > this._spendingTracker.dailyLimit) {
|
|
430
|
+
return {
|
|
431
|
+
allowed: false,
|
|
432
|
+
reason: `Daily spending limit exceeded. Limit: $${(Number(this._spendingTracker.dailyLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { allowed: true };
|
|
437
|
+
}
|
|
438
|
+
}
|
package/dist/index.d.mts
CHANGED
|
@@ -633,6 +633,89 @@ declare class MorphoClient {
|
|
|
633
633
|
lltv: string;
|
|
634
634
|
marketId: string;
|
|
635
635
|
}>>;
|
|
636
|
+
/**
|
|
637
|
+
* Search Morpho markets by token name using the Morpho GraphQL API `search` field.
|
|
638
|
+
* No hardcoded token lists — uses Morpho's built-in fuzzy search.
|
|
639
|
+
*
|
|
640
|
+
* @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
|
|
641
|
+
* @param options.asCollateral - only return markets where the searched token is collateral
|
|
642
|
+
* @param options.asLoanToken - only return markets where the searched token is the loan asset
|
|
643
|
+
*/
|
|
644
|
+
searchMarkets(search: string, options?: {
|
|
645
|
+
asCollateral?: boolean;
|
|
646
|
+
asLoanToken?: boolean;
|
|
647
|
+
}): Promise<Array<{
|
|
648
|
+
collateralToken: string;
|
|
649
|
+
loanToken: string;
|
|
650
|
+
loanDecimals: number;
|
|
651
|
+
collateralDecimals: number;
|
|
652
|
+
supplyApy: number;
|
|
653
|
+
borrowApy: number;
|
|
654
|
+
utilization: number;
|
|
655
|
+
totalSupplyUsd: number;
|
|
656
|
+
totalBorrowUsd: number;
|
|
657
|
+
lltv: string;
|
|
658
|
+
marketId: string;
|
|
659
|
+
collateralAddress: string;
|
|
660
|
+
loanAddress: string;
|
|
661
|
+
}>>;
|
|
662
|
+
/**
|
|
663
|
+
* Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
|
|
664
|
+
* markets on the current chain. Returns tokens where balance > 0.
|
|
665
|
+
*
|
|
666
|
+
* Uses the full market list (500 markets) to discover all relevant tokens,
|
|
667
|
+
* then checks on-chain balance for each unique token address.
|
|
668
|
+
*
|
|
669
|
+
* @returns Array of tokens with non-zero balance, sorted by balance descending.
|
|
670
|
+
*/
|
|
671
|
+
getWalletTokenBalances(): Promise<Array<{
|
|
672
|
+
symbol: string;
|
|
673
|
+
address: string;
|
|
674
|
+
decimals: number;
|
|
675
|
+
balance: bigint;
|
|
676
|
+
balanceFormatted: string;
|
|
677
|
+
}>>;
|
|
678
|
+
/**
|
|
679
|
+
* Find borrowing opportunities for the agent.
|
|
680
|
+
*
|
|
681
|
+
* - If `collateralSymbol` is provided: find all markets where that token is collateral.
|
|
682
|
+
* - If omitted: scan wallet balances and find markets for each token the agent holds.
|
|
683
|
+
*
|
|
684
|
+
* Returns markets grouped by collateral token with APY, liquidity, and balance info.
|
|
685
|
+
*
|
|
686
|
+
* @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
|
|
687
|
+
*/
|
|
688
|
+
findBorrowingOptions(collateralSymbol?: string): Promise<Array<{
|
|
689
|
+
collateralToken: string;
|
|
690
|
+
collateralBalance: string;
|
|
691
|
+
markets: Array<{
|
|
692
|
+
loanToken: string;
|
|
693
|
+
borrowApy: string;
|
|
694
|
+
supplyApy: string;
|
|
695
|
+
lltv: string;
|
|
696
|
+
utilization: string;
|
|
697
|
+
availableLiquidity: string;
|
|
698
|
+
marketId: string;
|
|
699
|
+
}>;
|
|
700
|
+
}>>;
|
|
701
|
+
/**
|
|
702
|
+
* Find supply/lending opportunities for a specific loan token.
|
|
703
|
+
*
|
|
704
|
+
* "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
|
|
705
|
+
* (user supplies WETH to earn yield from borrowers).
|
|
706
|
+
*
|
|
707
|
+
* @param loanTokenSymbol - e.g. 'USDC', 'WETH'
|
|
708
|
+
*/
|
|
709
|
+
findSupplyOptions(loanTokenSymbol: string): Promise<Array<{
|
|
710
|
+
collateralToken: string;
|
|
711
|
+
loanToken: string;
|
|
712
|
+
supplyApy: string;
|
|
713
|
+
borrowApy: string;
|
|
714
|
+
lltv: string;
|
|
715
|
+
utilization: string;
|
|
716
|
+
totalSupply: string;
|
|
717
|
+
marketId: string;
|
|
718
|
+
}>>;
|
|
636
719
|
/**
|
|
637
720
|
* Estimate theoretical yield for a given collateral amount over a period.
|
|
638
721
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -633,6 +633,89 @@ declare class MorphoClient {
|
|
|
633
633
|
lltv: string;
|
|
634
634
|
marketId: string;
|
|
635
635
|
}>>;
|
|
636
|
+
/**
|
|
637
|
+
* Search Morpho markets by token name using the Morpho GraphQL API `search` field.
|
|
638
|
+
* No hardcoded token lists — uses Morpho's built-in fuzzy search.
|
|
639
|
+
*
|
|
640
|
+
* @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
|
|
641
|
+
* @param options.asCollateral - only return markets where the searched token is collateral
|
|
642
|
+
* @param options.asLoanToken - only return markets where the searched token is the loan asset
|
|
643
|
+
*/
|
|
644
|
+
searchMarkets(search: string, options?: {
|
|
645
|
+
asCollateral?: boolean;
|
|
646
|
+
asLoanToken?: boolean;
|
|
647
|
+
}): Promise<Array<{
|
|
648
|
+
collateralToken: string;
|
|
649
|
+
loanToken: string;
|
|
650
|
+
loanDecimals: number;
|
|
651
|
+
collateralDecimals: number;
|
|
652
|
+
supplyApy: number;
|
|
653
|
+
borrowApy: number;
|
|
654
|
+
utilization: number;
|
|
655
|
+
totalSupplyUsd: number;
|
|
656
|
+
totalBorrowUsd: number;
|
|
657
|
+
lltv: string;
|
|
658
|
+
marketId: string;
|
|
659
|
+
collateralAddress: string;
|
|
660
|
+
loanAddress: string;
|
|
661
|
+
}>>;
|
|
662
|
+
/**
|
|
663
|
+
* Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
|
|
664
|
+
* markets on the current chain. Returns tokens where balance > 0.
|
|
665
|
+
*
|
|
666
|
+
* Uses the full market list (500 markets) to discover all relevant tokens,
|
|
667
|
+
* then checks on-chain balance for each unique token address.
|
|
668
|
+
*
|
|
669
|
+
* @returns Array of tokens with non-zero balance, sorted by balance descending.
|
|
670
|
+
*/
|
|
671
|
+
getWalletTokenBalances(): Promise<Array<{
|
|
672
|
+
symbol: string;
|
|
673
|
+
address: string;
|
|
674
|
+
decimals: number;
|
|
675
|
+
balance: bigint;
|
|
676
|
+
balanceFormatted: string;
|
|
677
|
+
}>>;
|
|
678
|
+
/**
|
|
679
|
+
* Find borrowing opportunities for the agent.
|
|
680
|
+
*
|
|
681
|
+
* - If `collateralSymbol` is provided: find all markets where that token is collateral.
|
|
682
|
+
* - If omitted: scan wallet balances and find markets for each token the agent holds.
|
|
683
|
+
*
|
|
684
|
+
* Returns markets grouped by collateral token with APY, liquidity, and balance info.
|
|
685
|
+
*
|
|
686
|
+
* @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
|
|
687
|
+
*/
|
|
688
|
+
findBorrowingOptions(collateralSymbol?: string): Promise<Array<{
|
|
689
|
+
collateralToken: string;
|
|
690
|
+
collateralBalance: string;
|
|
691
|
+
markets: Array<{
|
|
692
|
+
loanToken: string;
|
|
693
|
+
borrowApy: string;
|
|
694
|
+
supplyApy: string;
|
|
695
|
+
lltv: string;
|
|
696
|
+
utilization: string;
|
|
697
|
+
availableLiquidity: string;
|
|
698
|
+
marketId: string;
|
|
699
|
+
}>;
|
|
700
|
+
}>>;
|
|
701
|
+
/**
|
|
702
|
+
* Find supply/lending opportunities for a specific loan token.
|
|
703
|
+
*
|
|
704
|
+
* "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
|
|
705
|
+
* (user supplies WETH to earn yield from borrowers).
|
|
706
|
+
*
|
|
707
|
+
* @param loanTokenSymbol - e.g. 'USDC', 'WETH'
|
|
708
|
+
*/
|
|
709
|
+
findSupplyOptions(loanTokenSymbol: string): Promise<Array<{
|
|
710
|
+
collateralToken: string;
|
|
711
|
+
loanToken: string;
|
|
712
|
+
supplyApy: string;
|
|
713
|
+
borrowApy: string;
|
|
714
|
+
lltv: string;
|
|
715
|
+
utilization: string;
|
|
716
|
+
totalSupply: string;
|
|
717
|
+
marketId: string;
|
|
718
|
+
}>>;
|
|
636
719
|
/**
|
|
637
720
|
* Estimate theoretical yield for a given collateral amount over a period.
|
|
638
721
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,yBAAyB,EACzB,cAAc,GACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,YAAY,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAI9H,cAAc,SAAS,CAAC;AAIxB,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,SAAS,EACT,SAAS,GACV,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EACL,qBAAqB,EAErB,wBAAwB,EACxB,kCAAkC,EAClC,4BAA4B,EAC5B,uBAAuB,EAEvB,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,oBAAoB,EACpB,oBAAoB,EAEpB,uBAAuB,EACvB,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
|