@agether/sdk 2.8.1 → 2.11.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.js +763 -379
- package/dist/index.d.mts +165 -113
- package/dist/index.d.ts +165 -113
- package/dist/index.js +522 -337
- package/dist/index.mjs +522 -337
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { ethers, Contract } from "ethers";
|
|
3
3
|
|
|
4
4
|
// src/types/index.ts
|
|
5
|
-
var ChainId = /* @__PURE__ */ ((
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
5
|
+
var ChainId = /* @__PURE__ */ ((ChainId3) => {
|
|
6
|
+
ChainId3[ChainId3["Ethereum"] = 1] = "Ethereum";
|
|
7
|
+
ChainId3[ChainId3["Base"] = 8453] = "Base";
|
|
8
|
+
ChainId3[ChainId3["BaseSepolia"] = 84532] = "BaseSepolia";
|
|
9
|
+
ChainId3[ChainId3["Sepolia"] = 11155111] = "Sepolia";
|
|
10
|
+
ChainId3[ChainId3["Hardhat"] = 31337] = "Hardhat";
|
|
11
|
+
return ChainId3;
|
|
12
12
|
})(ChainId || {});
|
|
13
13
|
var AgetherError = class extends Error {
|
|
14
14
|
constructor(message, code, details) {
|
|
@@ -172,13 +172,13 @@ var CONTRACT_ADDRESSES = {
|
|
|
172
172
|
safeProxyFactory: SAFE_PROXY_FACTORY,
|
|
173
173
|
safe7579: SAFE7579,
|
|
174
174
|
entryPoint: ENTRYPOINT_V07,
|
|
175
|
-
agether4337Factory: "
|
|
176
|
-
agether7579Bootstrap: "
|
|
177
|
-
erc8004ValidationModule: "
|
|
178
|
-
agetherHookMultiplexer: "
|
|
175
|
+
agether4337Factory: "0xb6363c2B5C72C14D3fC4261e3dd836D8966bE072",
|
|
176
|
+
agether7579Bootstrap: "0x055C2e70dd011C4ADEEfB795Ab77D74437be6D33",
|
|
177
|
+
erc8004ValidationModule: "0xE282fB8615abb8bA53F07b8BAB2937C78fE3867D",
|
|
178
|
+
agetherHookMultiplexer: "0xeD62ac874F58CEc9F065aB8e6872752Eb0F6eA14",
|
|
179
179
|
validationRegistry: ZERO,
|
|
180
|
-
agether8004Scorer: "
|
|
181
|
-
timelockController: "
|
|
180
|
+
agether8004Scorer: "0x960853769d52B14aA0daeab7E1E59f5c9299cb65",
|
|
181
|
+
timelockController: "0x78e0227f9DE577e583B8149C73F0bA1E7200AD01",
|
|
182
182
|
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
183
183
|
identityRegistry: ERC8004_IDENTITY_REGISTRY,
|
|
184
184
|
morphoBlue: MORPHO_BLUE
|
|
@@ -188,13 +188,13 @@ var CONTRACT_ADDRESSES = {
|
|
|
188
188
|
safeProxyFactory: SAFE_PROXY_FACTORY,
|
|
189
189
|
safe7579: SAFE7579,
|
|
190
190
|
entryPoint: ENTRYPOINT_V07,
|
|
191
|
-
agether4337Factory: "
|
|
192
|
-
agether7579Bootstrap: "
|
|
193
|
-
erc8004ValidationModule: "
|
|
194
|
-
agetherHookMultiplexer: "
|
|
191
|
+
agether4337Factory: "0x73f4153bf1d46dB203Db27fc8FC942f6279D8d38",
|
|
192
|
+
agether7579Bootstrap: "0xbD0BDFE70fDB88fc03F2Ea22B81A2dfc99298E42",
|
|
193
|
+
erc8004ValidationModule: "0x85C8C97cE5AE540a4408D6A77a6D3aFcA9BCdB71",
|
|
194
|
+
agetherHookMultiplexer: "0x688cab46ce5A7450D706e9E3C8e0F31BaEa6c8BE",
|
|
195
195
|
validationRegistry: ZERO,
|
|
196
|
-
agether8004Scorer: "
|
|
197
|
-
timelockController: "
|
|
196
|
+
agether8004Scorer: "0x33eB904fe9975e2D8c577aD7e5B14CefBD4A65E1",
|
|
197
|
+
timelockController: "0xB3FD04f0B7c9DeC7f7B52d5c2CdfdCB3Fc9eE111",
|
|
198
198
|
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
199
199
|
identityRegistry: ERC8004_IDENTITY_REGISTRY,
|
|
200
200
|
morphoBlue: MORPHO_BLUE
|
|
@@ -292,93 +292,260 @@ function getContractAddresses(chainId) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/clients/AgetherClient.ts
|
|
295
|
+
var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
296
|
+
var erc20Iface = new ethers.Interface(ERC20_ABI);
|
|
297
|
+
var KNOWN_TOKENS = {
|
|
298
|
+
[8453 /* Base */]: {
|
|
299
|
+
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
300
|
+
wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
|
|
301
|
+
cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
|
|
302
|
+
},
|
|
303
|
+
[1 /* Ethereum */]: {
|
|
304
|
+
WETH: { address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", symbol: "WETH", decimals: 18 },
|
|
305
|
+
wstETH: { address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", symbol: "wstETH", decimals: 18 },
|
|
306
|
+
cbETH: { address: "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", symbol: "cbETH", decimals: 18 }
|
|
307
|
+
}
|
|
308
|
+
};
|
|
295
309
|
var AgetherClient = class _AgetherClient {
|
|
296
310
|
constructor(options) {
|
|
297
311
|
this.config = options.config;
|
|
298
312
|
this.signer = options.signer;
|
|
299
313
|
this.agentId = options.agentId;
|
|
314
|
+
this._rpcUrl = options.config.rpcUrl;
|
|
315
|
+
if (options._privateKey) {
|
|
316
|
+
this._privateKey = options._privateKey;
|
|
317
|
+
this._useExternalSigner = false;
|
|
318
|
+
} else {
|
|
319
|
+
this._useExternalSigner = true;
|
|
320
|
+
}
|
|
300
321
|
const provider = options.signer.provider;
|
|
301
322
|
if (!provider) throw new AgetherError("Signer must have a provider", "NO_PROVIDER");
|
|
302
|
-
|
|
303
|
-
options.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
);
|
|
307
|
-
this.identityRegistry = new Contract(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
312
|
-
this.agether8004Scorer = new Contract(
|
|
313
|
-
options.config.contracts.agether8004Scorer,
|
|
314
|
-
AGETHER_8004_SCORER_ABI,
|
|
315
|
-
provider
|
|
316
|
-
);
|
|
317
|
-
this.validationModule = new Contract(
|
|
318
|
-
options.config.contracts.erc8004ValidationModule,
|
|
319
|
-
AGETHER_8004_VALIDATION_MODULE_ABI,
|
|
320
|
-
provider
|
|
321
|
-
);
|
|
323
|
+
if ("address" in options.signer && typeof options.signer.address === "string") {
|
|
324
|
+
this._eoaAddress = options.signer.address;
|
|
325
|
+
}
|
|
326
|
+
const c = options.config.contracts;
|
|
327
|
+
this.agether4337Factory = new Contract(c.agether4337Factory, AGETHER_4337_FACTORY_ABI, options.signer);
|
|
328
|
+
this.identityRegistry = new Contract(c.identityRegistry, IDENTITY_REGISTRY_ABI, options.signer);
|
|
329
|
+
this.agether8004Scorer = new Contract(c.agether8004Scorer, AGETHER_8004_SCORER_ABI, provider);
|
|
330
|
+
this.validationModule = new Contract(c.erc8004ValidationModule, AGETHER_8004_VALIDATION_MODULE_ABI, provider);
|
|
331
|
+
this.entryPoint = new Contract(c.entryPoint, ENTRYPOINT_V07_ABI, options.signer);
|
|
322
332
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
333
|
+
static fromPrivateKey(privateKey, agentIdOrChain, chainIdOrConfig) {
|
|
334
|
+
let agentId;
|
|
335
|
+
let config;
|
|
336
|
+
if (typeof agentIdOrChain === "bigint") {
|
|
337
|
+
agentId = agentIdOrChain;
|
|
338
|
+
config = typeof chainIdOrConfig === "number" ? getDefaultConfig(chainIdOrConfig) : chainIdOrConfig;
|
|
339
|
+
} else {
|
|
340
|
+
config = typeof agentIdOrChain === "number" ? getDefaultConfig(agentIdOrChain) : agentIdOrChain;
|
|
341
|
+
}
|
|
326
342
|
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
327
343
|
const signer = new ethers.Wallet(privateKey, provider);
|
|
344
|
+
return new _AgetherClient({ config, signer, agentId, _privateKey: privateKey });
|
|
345
|
+
}
|
|
346
|
+
static fromSigner(signer, agentIdOrChain, chainIdOrConfig) {
|
|
347
|
+
if (!signer.provider) {
|
|
348
|
+
throw new AgetherError("Signer must be connected to a provider", "NO_PROVIDER");
|
|
349
|
+
}
|
|
350
|
+
let agentId;
|
|
351
|
+
let config;
|
|
352
|
+
if (typeof agentIdOrChain === "bigint") {
|
|
353
|
+
agentId = agentIdOrChain;
|
|
354
|
+
config = typeof chainIdOrConfig === "number" ? getDefaultConfig(chainIdOrConfig) : chainIdOrConfig;
|
|
355
|
+
} else {
|
|
356
|
+
config = typeof agentIdOrChain === "number" ? getDefaultConfig(agentIdOrChain) : agentIdOrChain;
|
|
357
|
+
}
|
|
328
358
|
return new _AgetherClient({ config, signer, agentId });
|
|
329
359
|
}
|
|
330
|
-
//
|
|
360
|
+
// ════════════════════════════════════════════════════════
|
|
361
|
+
// Registration
|
|
362
|
+
// ════════════════════════════════════════════════════════
|
|
363
|
+
/**
|
|
364
|
+
* Register: create ERC-8004 identity + Safe account in one flow.
|
|
365
|
+
* If already registered, returns existing state.
|
|
366
|
+
*
|
|
367
|
+
* Sets `this.agentId` on success so subsequent operations work immediately.
|
|
368
|
+
*/
|
|
369
|
+
async register() {
|
|
370
|
+
const eoaAddr = await this._getSignerAddress();
|
|
371
|
+
if (this.agentId !== void 0) {
|
|
372
|
+
const exists = await this.agether4337Factory.accountExists(this.agentId);
|
|
373
|
+
if (exists) {
|
|
374
|
+
const acct = await this.agether4337Factory.getAccount(this.agentId);
|
|
375
|
+
this.accountAddress = acct;
|
|
376
|
+
const kyaRequired2 = await this.isKyaRequired();
|
|
377
|
+
return {
|
|
378
|
+
agentId: this.agentId.toString(),
|
|
379
|
+
address: eoaAddr,
|
|
380
|
+
agentAccount: acct,
|
|
381
|
+
alreadyRegistered: true,
|
|
382
|
+
kyaRequired: kyaRequired2
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
let agentId;
|
|
387
|
+
if (this.agentId !== void 0) {
|
|
388
|
+
const balance = await this.identityRegistry.balanceOf(eoaAddr);
|
|
389
|
+
if (balance > 0n) {
|
|
390
|
+
agentId = this.agentId;
|
|
391
|
+
} else {
|
|
392
|
+
agentId = await this._mintNewIdentity();
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
agentId = await this._mintNewIdentity();
|
|
396
|
+
}
|
|
397
|
+
this.agentId = agentId;
|
|
398
|
+
const acctExists = await this.agether4337Factory.accountExists(agentId);
|
|
399
|
+
let txHash;
|
|
400
|
+
if (!acctExists) {
|
|
401
|
+
const tx = await this.agether4337Factory.createAccount(agentId);
|
|
402
|
+
const receipt = await tx.wait();
|
|
403
|
+
this._refreshSigner();
|
|
404
|
+
txHash = receipt.hash;
|
|
405
|
+
}
|
|
406
|
+
const acctAddr = await this.agether4337Factory.getAccount(agentId);
|
|
407
|
+
this.accountAddress = acctAddr;
|
|
408
|
+
const kyaRequired = await this.isKyaRequired();
|
|
409
|
+
return {
|
|
410
|
+
agentId: agentId.toString(),
|
|
411
|
+
address: eoaAddr,
|
|
412
|
+
agentAccount: acctAddr,
|
|
413
|
+
alreadyRegistered: acctExists,
|
|
414
|
+
kyaRequired,
|
|
415
|
+
tx: txHash
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/** Mint a new ERC-8004 identity and return the agentId. */
|
|
419
|
+
async _mintNewIdentity() {
|
|
420
|
+
const regTx = await this.identityRegistry.register();
|
|
421
|
+
const regReceipt = await regTx.wait();
|
|
422
|
+
this._refreshSigner();
|
|
423
|
+
let agentId = 0n;
|
|
424
|
+
for (const log of regReceipt.logs) {
|
|
425
|
+
try {
|
|
426
|
+
const parsed = this.identityRegistry.interface.parseLog({
|
|
427
|
+
topics: log.topics,
|
|
428
|
+
data: log.data
|
|
429
|
+
});
|
|
430
|
+
if (parsed?.name === "Transfer") {
|
|
431
|
+
agentId = parsed.args[2];
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
} catch (e) {
|
|
435
|
+
console.warn("[agether] parseLog skip:", e instanceof Error ? e.message : e);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (agentId === 0n) {
|
|
440
|
+
throw new AgetherError("Failed to parse agentId from registration", "PARSE_ERROR");
|
|
441
|
+
}
|
|
442
|
+
return agentId;
|
|
443
|
+
}
|
|
444
|
+
// ════════════════════════════════════════════════════════
|
|
445
|
+
// Account Management
|
|
446
|
+
// ════════════════════════════════════════════════════════
|
|
447
|
+
/**
|
|
448
|
+
* Deploy a Safe account smart wallet for the agent.
|
|
449
|
+
* The caller must own the ERC-8004 NFT.
|
|
450
|
+
*/
|
|
331
451
|
async createAccount() {
|
|
332
|
-
const
|
|
452
|
+
const id = this._requireAgentId();
|
|
453
|
+
const tx = await this.agether4337Factory.createAccount(id);
|
|
333
454
|
const receipt = await tx.wait();
|
|
455
|
+
this._refreshSigner();
|
|
334
456
|
const event = receipt.logs.map((log) => {
|
|
335
457
|
try {
|
|
336
458
|
return this.agether4337Factory.interface.parseLog(log);
|
|
337
|
-
} catch
|
|
338
|
-
console.warn("[agether] createAccount parseLog skip:", e instanceof Error ? e.message : e);
|
|
459
|
+
} catch {
|
|
339
460
|
return null;
|
|
340
461
|
}
|
|
341
462
|
}).find((e) => e?.name === "AccountCreated");
|
|
342
463
|
if (event) {
|
|
343
464
|
this.accountAddress = event.args.safeAccount;
|
|
344
465
|
} else {
|
|
345
|
-
this.accountAddress = await this.agether4337Factory.getAccount(
|
|
466
|
+
this.accountAddress = await this.agether4337Factory.getAccount(id);
|
|
346
467
|
}
|
|
347
468
|
return this.accountAddress;
|
|
348
469
|
}
|
|
470
|
+
/** Get the Safe account address for the current agent. Cached after first call. */
|
|
349
471
|
async getAccountAddress() {
|
|
350
472
|
if (this.accountAddress) return this.accountAddress;
|
|
351
|
-
const
|
|
473
|
+
const id = this._requireAgentId();
|
|
474
|
+
const addr = await this.agether4337Factory.getAccount(id);
|
|
352
475
|
if (addr === ethers.ZeroAddress) {
|
|
353
|
-
throw new AgetherError(
|
|
476
|
+
throw new AgetherError(
|
|
477
|
+
"No account found. Create one with createAccount() or register().",
|
|
478
|
+
"NO_ACCOUNT"
|
|
479
|
+
);
|
|
354
480
|
}
|
|
355
481
|
this.accountAddress = addr;
|
|
356
482
|
return addr;
|
|
357
483
|
}
|
|
484
|
+
/** Check whether the Safe account has been deployed. */
|
|
358
485
|
async accountExists() {
|
|
359
|
-
|
|
486
|
+
const id = this._requireAgentId();
|
|
487
|
+
return this.agether4337Factory.accountExists(id);
|
|
360
488
|
}
|
|
361
|
-
//
|
|
489
|
+
// ════════════════════════════════════════════════════════
|
|
490
|
+
// Balances
|
|
491
|
+
// ════════════════════════════════════════════════════════
|
|
492
|
+
/**
|
|
493
|
+
* Get ETH, USDC, and collateral token balances for EOA and Safe account.
|
|
494
|
+
*
|
|
495
|
+
* Collateral tokens are resolved from a built-in registry of well-known
|
|
496
|
+
* tokens per chain (WETH, wstETH, cbETH).
|
|
497
|
+
*/
|
|
362
498
|
async getBalances() {
|
|
363
499
|
const provider = this.signer.provider;
|
|
364
|
-
const eoaAddr = await this.
|
|
500
|
+
const eoaAddr = await this._getSignerAddress();
|
|
365
501
|
const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, provider);
|
|
366
502
|
const ethBal = await provider.getBalance(eoaAddr);
|
|
367
503
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
504
|
+
const knownTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
|
|
505
|
+
const eoaCollateral = {};
|
|
506
|
+
for (const [symbol, info] of Object.entries(knownTokens)) {
|
|
507
|
+
try {
|
|
508
|
+
const token = new Contract(info.address, ERC20_ABI, provider);
|
|
509
|
+
const bal = await token.balanceOf(eoaAddr);
|
|
510
|
+
eoaCollateral[symbol] = ethers.formatUnits(bal, info.decimals);
|
|
511
|
+
} catch (e) {
|
|
512
|
+
console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
513
|
+
eoaCollateral[symbol] = "0";
|
|
514
|
+
}
|
|
515
|
+
}
|
|
368
516
|
const result = {
|
|
369
|
-
|
|
517
|
+
agentId: this.agentId !== void 0 ? this.agentId.toString() : "?",
|
|
518
|
+
address: eoaAddr,
|
|
519
|
+
eth: ethers.formatEther(ethBal),
|
|
520
|
+
usdc: ethers.formatUnits(usdcBal, 6),
|
|
521
|
+
collateral: eoaCollateral
|
|
370
522
|
};
|
|
371
523
|
try {
|
|
372
524
|
const acctAddr = await this.getAccountAddress();
|
|
373
525
|
const acctEth = await provider.getBalance(acctAddr);
|
|
374
526
|
const acctUsdc = await usdc.balanceOf(acctAddr);
|
|
375
|
-
|
|
527
|
+
const acctCollateral = {};
|
|
528
|
+
for (const [symbol, info] of Object.entries(knownTokens)) {
|
|
529
|
+
try {
|
|
530
|
+
const token = new Contract(info.address, ERC20_ABI, provider);
|
|
531
|
+
const bal = await token.balanceOf(acctAddr);
|
|
532
|
+
acctCollateral[symbol] = ethers.formatUnits(bal, info.decimals);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
console.warn(`[agether] Account collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
535
|
+
acctCollateral[symbol] = "0";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
result.agentAccount = {
|
|
376
539
|
address: acctAddr,
|
|
377
540
|
eth: ethers.formatEther(acctEth),
|
|
378
|
-
usdc: ethers.formatUnits(acctUsdc, 6)
|
|
541
|
+
usdc: ethers.formatUnits(acctUsdc, 6),
|
|
542
|
+
collateral: acctCollateral
|
|
379
543
|
};
|
|
380
544
|
} catch (e) {
|
|
381
|
-
|
|
545
|
+
if (e instanceof AgetherError && (e.code === "NO_ACCOUNT" || e.code === "NO_AGENT_ID")) {
|
|
546
|
+
} else {
|
|
547
|
+
console.warn("[agether] getBalances: failed to fetch Safe account data:", e.message ?? e);
|
|
548
|
+
}
|
|
382
549
|
}
|
|
383
550
|
return result;
|
|
384
551
|
}
|
|
@@ -392,6 +559,7 @@ var AgetherClient = class _AgetherClient {
|
|
|
392
559
|
const amount = ethers.parseUnits(usdcAmount, 6);
|
|
393
560
|
const tx = await usdc.transfer(acctAddr, amount);
|
|
394
561
|
const receipt = await tx.wait();
|
|
562
|
+
this._refreshSigner();
|
|
395
563
|
return {
|
|
396
564
|
txHash: receipt.hash,
|
|
397
565
|
blockNumber: receipt.blockNumber,
|
|
@@ -399,7 +567,89 @@ var AgetherClient = class _AgetherClient {
|
|
|
399
567
|
gasUsed: receipt.gasUsed
|
|
400
568
|
};
|
|
401
569
|
}
|
|
402
|
-
//
|
|
570
|
+
// ════════════════════════════════════════════════════════
|
|
571
|
+
// Withdrawals (Safe → EOA via UserOps)
|
|
572
|
+
// ════════════════════════════════════════════════════════
|
|
573
|
+
/**
|
|
574
|
+
* Withdraw an ERC-20 token from Safe account to EOA.
|
|
575
|
+
* Executes a transfer via Safe UserOp.
|
|
576
|
+
*
|
|
577
|
+
* @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH') or 0x address
|
|
578
|
+
* @param amount - Amount to withdraw (human-readable, e.g. '100', or 'all')
|
|
579
|
+
*/
|
|
580
|
+
async withdrawToken(tokenSymbol, amount) {
|
|
581
|
+
const acctAddr = await this.getAccountAddress();
|
|
582
|
+
const eoaAddr = await this._getSignerAddress();
|
|
583
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
584
|
+
const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
|
|
585
|
+
let weiAmount;
|
|
586
|
+
if (amount === "all") {
|
|
587
|
+
weiAmount = await tokenContract.balanceOf(acctAddr);
|
|
588
|
+
if (weiAmount === 0n) {
|
|
589
|
+
throw new AgetherError(`No ${tokenInfo.symbol} in Safe account`, "INSUFFICIENT_BALANCE");
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
|
|
593
|
+
}
|
|
594
|
+
const data = erc20Iface.encodeFunctionData("transfer", [eoaAddr, weiAmount]);
|
|
595
|
+
const receipt = await this._exec(tokenInfo.address, data);
|
|
596
|
+
const actualAmount = amount === "all" ? ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
|
|
597
|
+
return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: eoaAddr };
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Withdraw ETH from Safe account to EOA.
|
|
601
|
+
* Executes a native ETH transfer via Safe UserOp.
|
|
602
|
+
*
|
|
603
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
604
|
+
*/
|
|
605
|
+
async withdrawEth(amount) {
|
|
606
|
+
const acctAddr = await this.getAccountAddress();
|
|
607
|
+
const eoaAddr = await this._getSignerAddress();
|
|
608
|
+
let weiAmount;
|
|
609
|
+
if (amount === "all") {
|
|
610
|
+
weiAmount = await this.signer.provider.getBalance(acctAddr);
|
|
611
|
+
if (weiAmount === 0n) throw new AgetherError("No ETH in Safe account", "INSUFFICIENT_BALANCE");
|
|
612
|
+
} else {
|
|
613
|
+
weiAmount = ethers.parseEther(amount);
|
|
614
|
+
}
|
|
615
|
+
const receipt = await this._exec(eoaAddr, "0x", weiAmount);
|
|
616
|
+
const actualAmount = amount === "all" ? ethers.formatEther(weiAmount) : amount;
|
|
617
|
+
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
|
|
618
|
+
}
|
|
619
|
+
// ════════════════════════════════════════════════════════
|
|
620
|
+
// Sponsorship
|
|
621
|
+
// ════════════════════════════════════════════════════════
|
|
622
|
+
/**
|
|
623
|
+
* Send tokens to another agent's Safe account (or any address).
|
|
624
|
+
* Transfers from EOA (does NOT require a UserOp).
|
|
625
|
+
*
|
|
626
|
+
* @param target - `{ agentId: '42' }` or `{ address: '0x...' }`
|
|
627
|
+
* @param tokenSymbol - Token to send (e.g. 'WETH', 'USDC') or 0x address
|
|
628
|
+
* @param amount - Amount to send (human-readable)
|
|
629
|
+
*/
|
|
630
|
+
async sponsor(target, tokenSymbol, amount) {
|
|
631
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
632
|
+
let targetAddr;
|
|
633
|
+
if (target.address) {
|
|
634
|
+
targetAddr = target.address;
|
|
635
|
+
} else if (target.agentId) {
|
|
636
|
+
targetAddr = await this.agether4337Factory.getAccount(BigInt(target.agentId));
|
|
637
|
+
if (targetAddr === ethers.ZeroAddress) {
|
|
638
|
+
throw new AgetherError("Target agent has no account", "NO_ACCOUNT");
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
throw new AgetherError("Provide agentId or address", "INVALID_TARGET");
|
|
642
|
+
}
|
|
643
|
+
const weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
|
|
644
|
+
const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer);
|
|
645
|
+
const tx = await tokenContract.transfer(targetAddr, weiAmount);
|
|
646
|
+
const receipt = await tx.wait();
|
|
647
|
+
this._refreshSigner();
|
|
648
|
+
return { tx: receipt.hash, targetAccount: targetAddr, targetAgentId: target.agentId };
|
|
649
|
+
}
|
|
650
|
+
// ════════════════════════════════════════════════════════
|
|
651
|
+
// Identity & Validation
|
|
652
|
+
// ════════════════════════════════════════════════════════
|
|
403
653
|
/**
|
|
404
654
|
* Check if the KYA gate is active on the validation module.
|
|
405
655
|
* If validationRegistry is not set, all txs pass (KYA disabled).
|
|
@@ -424,31 +674,45 @@ var AgetherClient = class _AgetherClient {
|
|
|
424
674
|
return false;
|
|
425
675
|
}
|
|
426
676
|
}
|
|
677
|
+
/** Check whether the agent's ERC-8004 NFT exists. */
|
|
427
678
|
async identityExists() {
|
|
679
|
+
const id = this._requireAgentId();
|
|
428
680
|
try {
|
|
429
|
-
await this.identityRegistry.ownerOf(
|
|
681
|
+
await this.identityRegistry.ownerOf(id);
|
|
430
682
|
return true;
|
|
431
683
|
} catch (e) {
|
|
432
684
|
console.warn("[agether] identityExists check failed:", e instanceof Error ? e.message : e);
|
|
433
685
|
return false;
|
|
434
686
|
}
|
|
435
687
|
}
|
|
688
|
+
/** Get the owner address of the ERC-8004 NFT. */
|
|
436
689
|
async getIdentityOwner() {
|
|
437
|
-
|
|
690
|
+
const id = this._requireAgentId();
|
|
691
|
+
return this.identityRegistry.ownerOf(id);
|
|
438
692
|
}
|
|
439
|
-
//
|
|
693
|
+
// ════════════════════════════════════════════════════════
|
|
694
|
+
// Reputation
|
|
695
|
+
// ════════════════════════════════════════════════════════
|
|
696
|
+
/** Read the agent's current credit score from the Agether8004Scorer contract. */
|
|
440
697
|
async getCreditScore() {
|
|
441
|
-
|
|
698
|
+
const id = this._requireAgentId();
|
|
699
|
+
return this.agether8004Scorer.getCreditScore(id);
|
|
442
700
|
}
|
|
701
|
+
/** Check if the score is fresh (within MAX_ORACLE_AGE). */
|
|
443
702
|
async isScoreFresh() {
|
|
444
|
-
const
|
|
703
|
+
const id = this._requireAgentId();
|
|
704
|
+
const [fresh, age] = await this.agether8004Scorer.isScoreFresh(id);
|
|
445
705
|
return { fresh, age };
|
|
446
706
|
}
|
|
707
|
+
/** Check if the agent meets a minimum score threshold. */
|
|
447
708
|
async isEligible(minScore = 500n) {
|
|
448
|
-
const
|
|
709
|
+
const id = this._requireAgentId();
|
|
710
|
+
const [eligible, currentScore] = await this.agether8004Scorer.isEligible(id, minScore);
|
|
449
711
|
return { eligible, currentScore };
|
|
450
712
|
}
|
|
451
|
-
//
|
|
713
|
+
// ════════════════════════════════════════════════════════
|
|
714
|
+
// Getters
|
|
715
|
+
// ════════════════════════════════════════════════════════
|
|
452
716
|
get chainId() {
|
|
453
717
|
return this.config.chainId;
|
|
454
718
|
}
|
|
@@ -462,24 +726,209 @@ var AgetherClient = class _AgetherClient {
|
|
|
462
726
|
return this.signer;
|
|
463
727
|
}
|
|
464
728
|
getAgentId() {
|
|
729
|
+
return this._requireAgentId();
|
|
730
|
+
}
|
|
731
|
+
// ════════════════════════════════════════════════════════
|
|
732
|
+
// Private Helpers
|
|
733
|
+
// ════════════════════════════════════════════════════════
|
|
734
|
+
/** Require agentId to be set, throw a helpful error otherwise. */
|
|
735
|
+
_requireAgentId() {
|
|
736
|
+
if (this.agentId === void 0) {
|
|
737
|
+
throw new AgetherError("agentId not set. Call register() first.", "NO_AGENT_ID");
|
|
738
|
+
}
|
|
465
739
|
return this.agentId;
|
|
466
740
|
}
|
|
741
|
+
/** Resolve EOA signer address (async, cached). */
|
|
742
|
+
async _getSignerAddress() {
|
|
743
|
+
if (!this._eoaAddress) {
|
|
744
|
+
this._eoaAddress = await this.signer.getAddress();
|
|
745
|
+
}
|
|
746
|
+
return this._eoaAddress;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
750
|
+
*
|
|
751
|
+
* Supports:
|
|
752
|
+
* - `'USDC'` → from chain config
|
|
753
|
+
* - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
|
|
754
|
+
* - `'0x...'` address → reads decimals and symbol onchain
|
|
755
|
+
*/
|
|
756
|
+
async _resolveToken(symbolOrAddress) {
|
|
757
|
+
if (symbolOrAddress.toUpperCase() === "USDC") {
|
|
758
|
+
return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
|
|
759
|
+
}
|
|
760
|
+
const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
|
|
761
|
+
const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
|
|
762
|
+
if (bySymbol) return bySymbol;
|
|
763
|
+
if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
|
|
764
|
+
try {
|
|
765
|
+
const token = new Contract(
|
|
766
|
+
symbolOrAddress,
|
|
767
|
+
["function decimals() view returns (uint8)", "function symbol() view returns (string)"],
|
|
768
|
+
this.signer.provider
|
|
769
|
+
);
|
|
770
|
+
const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
|
|
771
|
+
return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
|
|
772
|
+
} catch (e) {
|
|
773
|
+
throw new AgetherError(
|
|
774
|
+
`Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
|
|
775
|
+
"UNKNOWN_TOKEN"
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
throw new AgetherError(
|
|
780
|
+
`Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
|
|
781
|
+
"UNKNOWN_TOKEN"
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Refresh signer and rebind contracts for fresh nonce.
|
|
786
|
+
*
|
|
787
|
+
* For the privateKey path: recreates provider + wallet.
|
|
788
|
+
* For external signers: just rebinds contract instances.
|
|
789
|
+
*/
|
|
790
|
+
_refreshSigner() {
|
|
791
|
+
if (!this._useExternalSigner && this._privateKey) {
|
|
792
|
+
const provider = new ethers.JsonRpcProvider(this._rpcUrl);
|
|
793
|
+
const wallet = new ethers.Wallet(this._privateKey, provider);
|
|
794
|
+
this.signer = wallet;
|
|
795
|
+
this._eoaAddress = wallet.address;
|
|
796
|
+
}
|
|
797
|
+
const c = this.config.contracts;
|
|
798
|
+
this.agether4337Factory = new Contract(c.agether4337Factory, AGETHER_4337_FACTORY_ABI, this.signer);
|
|
799
|
+
this.identityRegistry = new Contract(c.identityRegistry, IDENTITY_REGISTRY_ABI, this.signer);
|
|
800
|
+
this.agether8004Scorer = new Contract(c.agether8004Scorer, AGETHER_8004_SCORER_ABI, this.signer.provider);
|
|
801
|
+
this.validationModule = new Contract(c.erc8004ValidationModule, AGETHER_8004_VALIDATION_MODULE_ABI, this.signer.provider);
|
|
802
|
+
this.entryPoint = new Contract(c.entryPoint, ENTRYPOINT_V07_ABI, this.signer);
|
|
803
|
+
}
|
|
804
|
+
// ────────────────────────────────────────────────────────
|
|
805
|
+
// ERC-4337 UserOp helpers (Safe + Safe7579 + EntryPoint v0.7)
|
|
806
|
+
// ────────────────────────────────────────────────────────
|
|
807
|
+
/**
|
|
808
|
+
* Pack two uint128 values into a single bytes32:
|
|
809
|
+
* bytes32 = (hi << 128) | lo
|
|
810
|
+
*/
|
|
811
|
+
_packUint128(hi, lo) {
|
|
812
|
+
return ethers.zeroPadValue(ethers.toBeHex(hi << 128n | lo), 32);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Build, sign and submit a PackedUserOperation through EntryPoint.handleOps.
|
|
816
|
+
*/
|
|
817
|
+
async _submitUserOp(callData) {
|
|
818
|
+
const sender = await this.getAccountAddress();
|
|
819
|
+
const eoaAddr = await this._getSignerAddress();
|
|
820
|
+
const validatorAddr = this.config.contracts.erc8004ValidationModule;
|
|
821
|
+
const nonceKey = BigInt(validatorAddr) << 32n;
|
|
822
|
+
const nonce = await this.entryPoint.getNonce(sender, nonceKey);
|
|
823
|
+
const feeData = await this.signer.provider.getFeeData();
|
|
824
|
+
const maxFeePerGas = feeData.maxFeePerGas ?? ethers.parseUnits("0.5", "gwei");
|
|
825
|
+
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? ethers.parseUnits("0.1", "gwei");
|
|
826
|
+
const verificationGasLimit = 500000n;
|
|
827
|
+
const callGasLimit = 800000n;
|
|
828
|
+
const preVerificationGas = 100000n;
|
|
829
|
+
const accountGasLimits = this._packUint128(verificationGasLimit, callGasLimit);
|
|
830
|
+
const gasFees = this._packUint128(maxPriorityFeePerGas, maxFeePerGas);
|
|
831
|
+
const requiredPrefund = (verificationGasLimit + callGasLimit + preVerificationGas) * maxFeePerGas;
|
|
832
|
+
const accountBalance = await this.signer.provider.getBalance(sender);
|
|
833
|
+
if (accountBalance < requiredPrefund) {
|
|
834
|
+
const topUp = requiredPrefund - accountBalance;
|
|
835
|
+
const topUpWithBuffer = topUp * 120n / 100n;
|
|
836
|
+
const fundTx = await this.signer.sendTransaction({
|
|
837
|
+
to: sender,
|
|
838
|
+
value: topUpWithBuffer
|
|
839
|
+
});
|
|
840
|
+
await fundTx.wait();
|
|
841
|
+
this._refreshSigner();
|
|
842
|
+
}
|
|
843
|
+
const userOp = {
|
|
844
|
+
sender,
|
|
845
|
+
nonce,
|
|
846
|
+
initCode: "0x",
|
|
847
|
+
callData,
|
|
848
|
+
accountGasLimits,
|
|
849
|
+
preVerificationGas,
|
|
850
|
+
gasFees,
|
|
851
|
+
paymasterAndData: "0x",
|
|
852
|
+
signature: "0x"
|
|
853
|
+
};
|
|
854
|
+
const userOpHash = await this.entryPoint.getUserOpHash(userOp);
|
|
855
|
+
const signature = await this.signer.signMessage(ethers.getBytes(userOpHash));
|
|
856
|
+
userOp.signature = signature;
|
|
857
|
+
const tx = await this.entryPoint.handleOps([userOp], eoaAddr);
|
|
858
|
+
const receipt = await tx.wait();
|
|
859
|
+
this._refreshSigner();
|
|
860
|
+
const epIface = new ethers.Interface(ENTRYPOINT_V07_ABI);
|
|
861
|
+
for (const log of receipt.logs) {
|
|
862
|
+
try {
|
|
863
|
+
const parsed = epIface.parseLog({ topics: log.topics, data: log.data });
|
|
864
|
+
if (parsed?.name === "UserOperationEvent" && !parsed.args.success) {
|
|
865
|
+
let revertMsg = "UserOp inner execution reverted";
|
|
866
|
+
for (const rLog of receipt.logs) {
|
|
867
|
+
try {
|
|
868
|
+
const rParsed = epIface.parseLog({ topics: rLog.topics, data: rLog.data });
|
|
869
|
+
if (rParsed?.name === "UserOperationRevertReason") {
|
|
870
|
+
const reason = rParsed.args.revertReason;
|
|
871
|
+
try {
|
|
872
|
+
if (reason.length >= 10 && reason.slice(0, 10) === "0x08c379a0") {
|
|
873
|
+
const decoded = ethers.AbiCoder.defaultAbiCoder().decode(
|
|
874
|
+
["string"],
|
|
875
|
+
"0x" + reason.slice(10)
|
|
876
|
+
);
|
|
877
|
+
revertMsg = `UserOp reverted: ${decoded[0]}`;
|
|
878
|
+
} else {
|
|
879
|
+
revertMsg = `UserOp reverted with data: ${reason}`;
|
|
880
|
+
}
|
|
881
|
+
} catch {
|
|
882
|
+
revertMsg = `UserOp reverted with data: ${reason}`;
|
|
883
|
+
}
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
} catch {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
throw new AgetherError(revertMsg, "USEROP_EXECUTION_FAILED");
|
|
891
|
+
}
|
|
892
|
+
} catch (e) {
|
|
893
|
+
if (e instanceof AgetherError) throw e;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return receipt;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Execute a single call via Safe7579 account (ERC-7579 single mode)
|
|
901
|
+
* through an ERC-4337 UserOperation.
|
|
902
|
+
*/
|
|
903
|
+
async _exec(target, data, value = 0n) {
|
|
904
|
+
const valueHex = ethers.zeroPadValue(ethers.toBeHex(value), 32);
|
|
905
|
+
const executionCalldata = ethers.concat([target, valueHex, data]);
|
|
906
|
+
const safe7579Iface = new ethers.Interface(SAFE7579_ACCOUNT_ABI);
|
|
907
|
+
const callData = safe7579Iface.encodeFunctionData("execute", [MODE_SINGLE, executionCalldata]);
|
|
908
|
+
return this._submitUserOp(callData);
|
|
909
|
+
}
|
|
467
910
|
};
|
|
468
911
|
|
|
469
912
|
// src/clients/MorphoClient.ts
|
|
470
913
|
import { ethers as ethers2, Contract as Contract2 } from "ethers";
|
|
471
914
|
import axios from "axios";
|
|
472
915
|
var MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
473
|
-
var
|
|
916
|
+
var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
474
917
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
475
918
|
var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
|
|
476
|
-
var
|
|
919
|
+
var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
|
|
477
920
|
var MorphoClient = class {
|
|
478
921
|
constructor(config) {
|
|
479
922
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
480
923
|
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
481
924
|
this._tokenCache = /* @__PURE__ */ new Map();
|
|
482
925
|
this._discoveredAt = 0;
|
|
926
|
+
if (!config.agentId) {
|
|
927
|
+
throw new AgetherError(
|
|
928
|
+
"agentId is required. Use AgetherClient.register() first to get an agentId.",
|
|
929
|
+
"NO_AGENT_ID"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
483
932
|
const chainId = config.chainId ?? 1 /* Ethereum */;
|
|
484
933
|
const defaultCfg = getDefaultConfig(chainId);
|
|
485
934
|
this.config = defaultCfg;
|
|
@@ -509,26 +958,7 @@ var MorphoClient = class {
|
|
|
509
958
|
const addrs = { ...defaultCfg.contracts, ...config.contracts };
|
|
510
959
|
this.agether4337Factory = new Contract2(addrs.agether4337Factory, ACCOUNT_FACTORY_ABI, this._signer);
|
|
511
960
|
this.morphoBlue = new Contract2(addrs.morphoBlue, MORPHO_BLUE_ABI, this.provider);
|
|
512
|
-
this.agether8004Scorer = new Contract2(addrs.agether8004Scorer, AGENT_REPUTATION_ABI, this._signer);
|
|
513
|
-
this.identityRegistry = new Contract2(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
|
|
514
961
|
this.entryPoint = new Contract2(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
|
|
515
|
-
this.validationModule = new Contract2(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
|
|
516
|
-
}
|
|
517
|
-
// ════════════════════════════════════════════════════════
|
|
518
|
-
// KYA Gate Check
|
|
519
|
-
// ════════════════════════════════════════════════════════
|
|
520
|
-
/**
|
|
521
|
-
* Check whether the KYA (Know Your Agent) code verification gate is active.
|
|
522
|
-
* Reads the ERC8004ValidationModule's validationRegistry — when set to
|
|
523
|
-
* a non-zero address, the module enforces KYA code approval.
|
|
524
|
-
*/
|
|
525
|
-
async isKyaRequired() {
|
|
526
|
-
try {
|
|
527
|
-
const registryAddr = await this.validationModule.validationRegistry();
|
|
528
|
-
return registryAddr !== ethers2.ZeroAddress;
|
|
529
|
-
} catch {
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
962
|
}
|
|
533
963
|
// ════════════════════════════════════════════════════════
|
|
534
964
|
// Account Management
|
|
@@ -536,7 +966,6 @@ var MorphoClient = class {
|
|
|
536
966
|
/** Resolve the AgentAccount address (cached, with retry for flaky RPCs). */
|
|
537
967
|
async getAccountAddress() {
|
|
538
968
|
if (this._accountAddress) return this._accountAddress;
|
|
539
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
540
969
|
const MAX_RETRIES = 3;
|
|
541
970
|
let lastErr;
|
|
542
971
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -558,7 +987,6 @@ var MorphoClient = class {
|
|
|
558
987
|
throw lastErr;
|
|
559
988
|
}
|
|
560
989
|
getAgentId() {
|
|
561
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
562
990
|
return this.agentId;
|
|
563
991
|
}
|
|
564
992
|
/**
|
|
@@ -591,182 +1019,6 @@ var MorphoClient = class {
|
|
|
591
1019
|
}
|
|
592
1020
|
return this._eoaAddress;
|
|
593
1021
|
}
|
|
594
|
-
/** Mint a new ERC-8004 identity and return the agentId. */
|
|
595
|
-
async _mintNewIdentity() {
|
|
596
|
-
const regTx = await this.identityRegistry.register();
|
|
597
|
-
const regReceipt = await regTx.wait();
|
|
598
|
-
this._refreshSigner();
|
|
599
|
-
let agentId = 0n;
|
|
600
|
-
for (const log of regReceipt.logs) {
|
|
601
|
-
try {
|
|
602
|
-
const parsed = this.identityRegistry.interface.parseLog({ topics: log.topics, data: log.data });
|
|
603
|
-
if (parsed?.name === "Transfer") {
|
|
604
|
-
agentId = parsed.args[2];
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
} catch (e) {
|
|
608
|
-
console.warn("[agether] parseLog skip:", e instanceof Error ? e.message : e);
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (agentId === 0n) throw new AgetherError("Failed to parse agentId from registration", "PARSE_ERROR");
|
|
613
|
-
return agentId;
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Register: create ERC-8004 identity + AgentAccount in one flow.
|
|
617
|
-
* If already registered, returns existing state.
|
|
618
|
-
*/
|
|
619
|
-
async register(_name) {
|
|
620
|
-
const eoaAddr = await this.getSignerAddress();
|
|
621
|
-
if (this.agentId) {
|
|
622
|
-
const exists = await this.agether4337Factory.accountExists(BigInt(this.agentId));
|
|
623
|
-
if (exists) {
|
|
624
|
-
const acct = await this.agether4337Factory.getAccount(BigInt(this.agentId));
|
|
625
|
-
this._accountAddress = acct;
|
|
626
|
-
const kyaRequired2 = await this.isKyaRequired();
|
|
627
|
-
return { agentId: this.agentId, address: eoaAddr, agentAccount: acct, alreadyRegistered: true, kyaRequired: kyaRequired2 };
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
let agentId;
|
|
631
|
-
if (this.agentId) {
|
|
632
|
-
const balance = await this.identityRegistry.balanceOf(eoaAddr);
|
|
633
|
-
if (balance > 0n) {
|
|
634
|
-
agentId = BigInt(this.agentId);
|
|
635
|
-
} else {
|
|
636
|
-
agentId = await this._mintNewIdentity();
|
|
637
|
-
}
|
|
638
|
-
} else {
|
|
639
|
-
agentId = await this._mintNewIdentity();
|
|
640
|
-
}
|
|
641
|
-
this.agentId = agentId.toString();
|
|
642
|
-
const acctExists = await this.agether4337Factory.accountExists(agentId);
|
|
643
|
-
let txHash;
|
|
644
|
-
if (!acctExists) {
|
|
645
|
-
const tx = await this.agether4337Factory.createAccount(agentId);
|
|
646
|
-
const receipt = await tx.wait();
|
|
647
|
-
this._refreshSigner();
|
|
648
|
-
txHash = receipt.hash;
|
|
649
|
-
}
|
|
650
|
-
const acctAddr = await this.agether4337Factory.getAccount(agentId);
|
|
651
|
-
this._accountAddress = acctAddr;
|
|
652
|
-
const kyaRequired = await this.isKyaRequired();
|
|
653
|
-
return {
|
|
654
|
-
agentId: this.agentId,
|
|
655
|
-
address: eoaAddr,
|
|
656
|
-
agentAccount: acctAddr,
|
|
657
|
-
alreadyRegistered: acctExists,
|
|
658
|
-
kyaRequired,
|
|
659
|
-
tx: txHash
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
/** Get ETH / USDC / collateral balances for EOA and AgentAccount. */
|
|
663
|
-
async getBalances() {
|
|
664
|
-
const eoaAddr = await this.getSignerAddress();
|
|
665
|
-
const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.provider);
|
|
666
|
-
const ethBal = await this.provider.getBalance(eoaAddr);
|
|
667
|
-
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
668
|
-
const discoveredTokens = await this._getDiscoveredTokens();
|
|
669
|
-
const eoaCollateral = {};
|
|
670
|
-
for (const info of discoveredTokens) {
|
|
671
|
-
try {
|
|
672
|
-
const token = new Contract2(info.address, ERC20_ABI, this.provider);
|
|
673
|
-
const bal = await token.balanceOf(eoaAddr);
|
|
674
|
-
eoaCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
675
|
-
} catch (e) {
|
|
676
|
-
console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
677
|
-
eoaCollateral[info.symbol] = "0";
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
const result = {
|
|
681
|
-
agentId: this.agentId || "?",
|
|
682
|
-
address: eoaAddr,
|
|
683
|
-
eth: ethers2.formatEther(ethBal),
|
|
684
|
-
usdc: ethers2.formatUnits(usdcBal, 6),
|
|
685
|
-
collateral: eoaCollateral
|
|
686
|
-
};
|
|
687
|
-
try {
|
|
688
|
-
const acctAddr = await this.getAccountAddress();
|
|
689
|
-
const acctEth = await this.provider.getBalance(acctAddr);
|
|
690
|
-
const acctUsdc = await usdc.balanceOf(acctAddr);
|
|
691
|
-
const acctCollateral = {};
|
|
692
|
-
for (const info of discoveredTokens) {
|
|
693
|
-
try {
|
|
694
|
-
const token = new Contract2(info.address, ERC20_ABI, this.provider);
|
|
695
|
-
const bal = await token.balanceOf(acctAddr);
|
|
696
|
-
acctCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
697
|
-
} catch (e) {
|
|
698
|
-
console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
699
|
-
acctCollateral[info.symbol] = "0";
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
result.agentAccount = {
|
|
703
|
-
address: acctAddr,
|
|
704
|
-
eth: ethers2.formatEther(acctEth),
|
|
705
|
-
usdc: ethers2.formatUnits(acctUsdc, 6),
|
|
706
|
-
collateral: acctCollateral
|
|
707
|
-
};
|
|
708
|
-
} catch (err) {
|
|
709
|
-
if (err instanceof AgetherError && err.code === "NO_ACCOUNT") {
|
|
710
|
-
} else {
|
|
711
|
-
console.warn("[agether] getBalances: failed to fetch AgentAccount data:", err.message ?? err);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
return result;
|
|
715
|
-
}
|
|
716
|
-
/** Transfer USDC from EOA to AgentAccount. */
|
|
717
|
-
async fundAccount(usdcAmount) {
|
|
718
|
-
const acctAddr = await this.getAccountAddress();
|
|
719
|
-
const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this._signer);
|
|
720
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
721
|
-
const tx = await usdc.transfer(acctAddr, amount);
|
|
722
|
-
const receipt = await tx.wait();
|
|
723
|
-
this._refreshSigner();
|
|
724
|
-
return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Withdraw (transfer) a token from AgentAccount to EOA.
|
|
728
|
-
* Executes an ERC-20 transfer via Safe UserOp.
|
|
729
|
-
*
|
|
730
|
-
* @param tokenSymbol - Token to withdraw (e.g. 'USDC', 'WETH', 'wstETH')
|
|
731
|
-
* @param amount - Amount to withdraw (human-readable, e.g. '100' for 100 USDC, or 'all')
|
|
732
|
-
*/
|
|
733
|
-
async withdrawToken(tokenSymbol, amount) {
|
|
734
|
-
const acctAddr = await this.getAccountAddress();
|
|
735
|
-
const eoaAddr = await this.getSignerAddress();
|
|
736
|
-
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
737
|
-
const tokenContract = new Contract2(tokenInfo.address, ERC20_ABI, this.provider);
|
|
738
|
-
let weiAmount;
|
|
739
|
-
if (amount === "all") {
|
|
740
|
-
weiAmount = await tokenContract.balanceOf(acctAddr);
|
|
741
|
-
if (weiAmount === 0n) throw new AgetherError(`No ${tokenSymbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
|
|
742
|
-
} else {
|
|
743
|
-
weiAmount = ethers2.parseUnits(amount, tokenInfo.decimals);
|
|
744
|
-
}
|
|
745
|
-
const data = erc20Iface.encodeFunctionData("transfer", [eoaAddr, weiAmount]);
|
|
746
|
-
const receipt = await this.exec(tokenInfo.address, data);
|
|
747
|
-
const actualAmount = amount === "all" ? ethers2.formatUnits(weiAmount, tokenInfo.decimals) : amount;
|
|
748
|
-
return { tx: receipt.hash, token: tokenSymbol, amount: actualAmount, destination: eoaAddr };
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Withdraw ETH from AgentAccount to EOA.
|
|
752
|
-
* Executes a native ETH transfer via Safe UserOp.
|
|
753
|
-
*
|
|
754
|
-
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
755
|
-
*/
|
|
756
|
-
async withdrawEth(amount) {
|
|
757
|
-
const acctAddr = await this.getAccountAddress();
|
|
758
|
-
const eoaAddr = await this.getSignerAddress();
|
|
759
|
-
let weiAmount;
|
|
760
|
-
if (amount === "all") {
|
|
761
|
-
weiAmount = await this.provider.getBalance(acctAddr);
|
|
762
|
-
if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
|
|
763
|
-
} else {
|
|
764
|
-
weiAmount = ethers2.parseEther(amount);
|
|
765
|
-
}
|
|
766
|
-
const receipt = await this.exec(eoaAddr, "0x", weiAmount);
|
|
767
|
-
const actualAmount = amount === "all" ? ethers2.formatEther(weiAmount) : amount;
|
|
768
|
-
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
|
|
769
|
-
}
|
|
770
1022
|
// ════════════════════════════════════════════════════════
|
|
771
1023
|
// Market Discovery (Morpho GraphQL API)
|
|
772
1024
|
// ════════════════════════════════════════════════════════
|
|
@@ -945,7 +1197,7 @@ var MorphoClient = class {
|
|
|
945
1197
|
}
|
|
946
1198
|
}
|
|
947
1199
|
return {
|
|
948
|
-
agentId: this.agentId
|
|
1200
|
+
agentId: this.agentId,
|
|
949
1201
|
agentAccount: acctAddr,
|
|
950
1202
|
totalDebt: ethers2.formatUnits(totalDebt, 6),
|
|
951
1203
|
positions
|
|
@@ -1191,7 +1443,7 @@ var MorphoClient = class {
|
|
|
1191
1443
|
const targets = [usdcAddr, morphoAddr];
|
|
1192
1444
|
const values = [0n, 0n];
|
|
1193
1445
|
const datas = [
|
|
1194
|
-
|
|
1446
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, amount]),
|
|
1195
1447
|
morphoIface.encodeFunctionData("supply", [
|
|
1196
1448
|
this._toTuple(params),
|
|
1197
1449
|
amount,
|
|
@@ -1423,7 +1675,7 @@ var MorphoClient = class {
|
|
|
1423
1675
|
const targets = [colInfo.address, morphoAddr];
|
|
1424
1676
|
const values = [0n, 0n];
|
|
1425
1677
|
const datas = [
|
|
1426
|
-
|
|
1678
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, weiAmount]),
|
|
1427
1679
|
morphoIface.encodeFunctionData("supplyCollateral", [
|
|
1428
1680
|
this._toTuple(params),
|
|
1429
1681
|
weiAmount,
|
|
@@ -1574,7 +1826,7 @@ var MorphoClient = class {
|
|
|
1574
1826
|
const targets = [colInfo.address, morphoAddr, morphoAddr];
|
|
1575
1827
|
const values = [0n, 0n, 0n];
|
|
1576
1828
|
const datas = [
|
|
1577
|
-
|
|
1829
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, colWei]),
|
|
1578
1830
|
morphoIface.encodeFunctionData("supplyCollateral", [
|
|
1579
1831
|
this._toTuple(params),
|
|
1580
1832
|
colWei,
|
|
@@ -1662,7 +1914,7 @@ var MorphoClient = class {
|
|
|
1662
1914
|
const targets = [usdcAddr, morphoAddr];
|
|
1663
1915
|
const values = [0n, 0n];
|
|
1664
1916
|
const datas = [
|
|
1665
|
-
|
|
1917
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
|
|
1666
1918
|
morphoIface.encodeFunctionData("repay", [
|
|
1667
1919
|
this._toTuple(params),
|
|
1668
1920
|
repayAssets,
|
|
@@ -1751,7 +2003,7 @@ var MorphoClient = class {
|
|
|
1751
2003
|
const targets = [usdcAddr, morphoAddr, morphoAddr];
|
|
1752
2004
|
const values = [0n, 0n, 0n];
|
|
1753
2005
|
const datas = [
|
|
1754
|
-
|
|
2006
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
|
|
1755
2007
|
morphoIface.encodeFunctionData("repay", [
|
|
1756
2008
|
this._toTuple(params),
|
|
1757
2009
|
0n,
|
|
@@ -1782,50 +2034,6 @@ var MorphoClient = class {
|
|
|
1782
2034
|
destination: dest
|
|
1783
2035
|
};
|
|
1784
2036
|
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Sponsor: transfer collateral to another agent's AgentAccount.
|
|
1787
|
-
* (The agent must then supplyCollateral themselves via their own account.)
|
|
1788
|
-
*/
|
|
1789
|
-
async sponsor(target, tokenSymbol, amount) {
|
|
1790
|
-
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1791
|
-
let targetAddr;
|
|
1792
|
-
if (target.address) {
|
|
1793
|
-
targetAddr = target.address;
|
|
1794
|
-
} else if (target.agentId) {
|
|
1795
|
-
targetAddr = await this.agether4337Factory.getAccount(BigInt(target.agentId));
|
|
1796
|
-
if (targetAddr === ethers2.ZeroAddress) throw new AgetherError("Target agent has no account", "NO_ACCOUNT");
|
|
1797
|
-
} else {
|
|
1798
|
-
throw new AgetherError("Provide agentId or address", "INVALID_TARGET");
|
|
1799
|
-
}
|
|
1800
|
-
const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
|
|
1801
|
-
const colToken = new Contract2(colInfo.address, ERC20_ABI, this._signer);
|
|
1802
|
-
const tx = await colToken.transfer(targetAddr, weiAmount);
|
|
1803
|
-
const receipt = await tx.wait();
|
|
1804
|
-
this._refreshSigner();
|
|
1805
|
-
return { tx: receipt.hash, targetAccount: targetAddr, targetAgentId: target.agentId };
|
|
1806
|
-
}
|
|
1807
|
-
// ════════════════════════════════════════════════════════
|
|
1808
|
-
// Reputation (Agether8004Scorer contract)
|
|
1809
|
-
// ════════════════════════════════════════════════════════
|
|
1810
|
-
async getCreditScore() {
|
|
1811
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
1812
|
-
return this.agether8004Scorer.getCreditScore(BigInt(this.agentId));
|
|
1813
|
-
}
|
|
1814
|
-
async getAttestation() {
|
|
1815
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
1816
|
-
const att = await this.agether8004Scorer.getAttestation(BigInt(this.agentId));
|
|
1817
|
-
return { score: att.score, timestamp: att.timestamp, signer: att.signer };
|
|
1818
|
-
}
|
|
1819
|
-
async isEligible(minScore = 500n) {
|
|
1820
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
1821
|
-
const [eligible, currentScore] = await this.agether8004Scorer.isEligible(BigInt(this.agentId), minScore);
|
|
1822
|
-
return { eligible, currentScore };
|
|
1823
|
-
}
|
|
1824
|
-
async isScoreFresh() {
|
|
1825
|
-
if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
|
|
1826
|
-
const [fresh, age] = await this.agether8004Scorer.isScoreFresh(BigInt(this.agentId));
|
|
1827
|
-
return { fresh, age };
|
|
1828
|
-
}
|
|
1829
2037
|
// ════════════════════════════════════════════════════════
|
|
1830
2038
|
// Internal Helpers
|
|
1831
2039
|
// ════════════════════════════════════════════════════════
|
|
@@ -1847,9 +2055,6 @@ var MorphoClient = class {
|
|
|
1847
2055
|
const addrs = this.config.contracts;
|
|
1848
2056
|
this.agether4337Factory = new Contract2(addrs.agether4337Factory, ACCOUNT_FACTORY_ABI, this._signer);
|
|
1849
2057
|
this.entryPoint = new Contract2(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
|
|
1850
|
-
this.validationModule = new Contract2(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
|
|
1851
|
-
this.agether8004Scorer = new Contract2(addrs.agether8004Scorer, AGENT_REPUTATION_ABI, this._signer);
|
|
1852
|
-
this.identityRegistry = new Contract2(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
|
|
1853
2058
|
} else {
|
|
1854
2059
|
this.provider = new ethers2.JsonRpcProvider(this._rpcUrl);
|
|
1855
2060
|
const wallet = new ethers2.Wallet(this._privateKey, this.provider);
|
|
@@ -1858,9 +2063,6 @@ var MorphoClient = class {
|
|
|
1858
2063
|
const addrs = this.config.contracts;
|
|
1859
2064
|
this.agether4337Factory = new Contract2(addrs.agether4337Factory, ACCOUNT_FACTORY_ABI, this._signer);
|
|
1860
2065
|
this.entryPoint = new Contract2(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
|
|
1861
|
-
this.validationModule = new Contract2(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
|
|
1862
|
-
this.agether8004Scorer = new Contract2(addrs.agether8004Scorer, AGENT_REPUTATION_ABI, this._signer);
|
|
1863
|
-
this.identityRegistry = new Contract2(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
|
|
1864
2066
|
}
|
|
1865
2067
|
}
|
|
1866
2068
|
// ────────────────────────────────────────────────────────────
|
|
@@ -1967,7 +2169,7 @@ var MorphoClient = class {
|
|
|
1967
2169
|
const valueHex = ethers2.zeroPadValue(ethers2.toBeHex(value), 32);
|
|
1968
2170
|
const executionCalldata = ethers2.concat([target, valueHex, data]);
|
|
1969
2171
|
const safe7579Iface = new ethers2.Interface(SAFE7579_ACCOUNT_ABI);
|
|
1970
|
-
const callData = safe7579Iface.encodeFunctionData("execute", [
|
|
2172
|
+
const callData = safe7579Iface.encodeFunctionData("execute", [MODE_SINGLE2, executionCalldata]);
|
|
1971
2173
|
return this._submitUserOp(callData);
|
|
1972
2174
|
}
|
|
1973
2175
|
/**
|
|
@@ -2063,23 +2265,6 @@ var MorphoClient = class {
|
|
|
2063
2265
|
"UNKNOWN_COLLATERAL"
|
|
2064
2266
|
);
|
|
2065
2267
|
}
|
|
2066
|
-
/**
|
|
2067
|
-
* Get all discovered collateral tokens (for balance iteration, etc.).
|
|
2068
|
-
* Returns unique tokens from the Morpho API market discovery.
|
|
2069
|
-
*/
|
|
2070
|
-
async _getDiscoveredTokens() {
|
|
2071
|
-
await this.getMarkets();
|
|
2072
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2073
|
-
const tokens = [];
|
|
2074
|
-
for (const [key, info] of this._tokenCache) {
|
|
2075
|
-
if (key.startsWith("0x")) continue;
|
|
2076
|
-
if (seen.has(info.address.toLowerCase())) continue;
|
|
2077
|
-
seen.add(info.address.toLowerCase());
|
|
2078
|
-
if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
|
|
2079
|
-
tokens.push(info);
|
|
2080
|
-
}
|
|
2081
|
-
return tokens;
|
|
2082
|
-
}
|
|
2083
2268
|
/**
|
|
2084
2269
|
* Compute net deposited amounts per market using Morpho GraphQL API.
|
|
2085
2270
|
*
|