@agether/sdk 1.8.0 → 1.9.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/README.md CHANGED
@@ -106,6 +106,90 @@ await vault.withdraw(parseUnits('5000', 6));
106
106
 
107
107
  ---
108
108
 
109
+ ## 🔐 Signing Modes
110
+
111
+ Both `MorphoClient` (ethers) and `X402Client` (viem) support two mutually exclusive signing modes: **private key** (SDK-managed wallet) and **external signer/wallet** (custody-agnostic).
112
+
113
+ ### MorphoClient — Private Key
114
+
115
+ ```typescript
116
+ import { MorphoClient } from '@agether/sdk';
117
+
118
+ const client = new MorphoClient({
119
+ privateKey: process.env.AGENT_PRIVATE_KEY!,
120
+ rpcUrl: 'https://mainnet.base.org',
121
+ agentId: '42',
122
+ });
123
+ ```
124
+
125
+ ### MorphoClient — External Signer (ethers.AbstractSigner)
126
+
127
+ ```typescript
128
+ import { MorphoClient, AgetherSigner } from '@agether/sdk';
129
+
130
+ // Any ethers.AbstractSigner works:
131
+ // - ethers.Wallet
132
+ // - Privy embedded wallet signer
133
+ // - Bankr custodial signer
134
+ // - Turnkey / MPC wallet signer
135
+ // - MetaMask via BrowserProvider.getSigner()
136
+ const signer: AgetherSigner = getSignerFromCustodyProvider();
137
+
138
+ const client = new MorphoClient({
139
+ signer,
140
+ rpcUrl: 'https://mainnet.base.org',
141
+ agentId: '42',
142
+ });
143
+
144
+ // All operations work identically regardless of signing mode
145
+ const status = await client.getStatus();
146
+ await client.supplyCollateral('WETH', '0.1');
147
+ await client.borrow('100');
148
+ ```
149
+
150
+ > **Note:** When using an external signer, the SDK treats it as immutable — it will never
151
+ > recreate or reconnect the signer. Nonce management is the caller's responsibility.
152
+
153
+ ### X402Client — Private Key
154
+
155
+ ```typescript
156
+ import { X402Client } from '@agether/sdk';
157
+
158
+ const client = new X402Client({
159
+ privateKey: process.env.AGENT_PRIVATE_KEY!,
160
+ rpcUrl: 'https://mainnet.base.org',
161
+ backendUrl: 'https://api.agether.ai',
162
+ agentId: '42',
163
+ });
164
+ ```
165
+
166
+ ### X402Client — viem WalletClient (Privy, Turnkey, etc.)
167
+
168
+ ```typescript
169
+ import { X402Client, AgetherViemWallet } from '@agether/sdk';
170
+
171
+ // Any viem WalletClient with an attached account works:
172
+ // - Privy embedded wallet → createWalletClient(...)
173
+ // - Turnkey signer → turnkeyToViemAccount(...)
174
+ // - MetaMask → createWalletClient({ transport: custom(window.ethereum) })
175
+ const walletClient: AgetherViemWallet = privyWalletClient;
176
+
177
+ const client = new X402Client({
178
+ walletClient,
179
+ rpcUrl: 'https://mainnet.base.org',
180
+ backendUrl: 'https://api.agether.ai',
181
+ agentId: '42',
182
+ });
183
+
184
+ // All x402 operations work identically regardless of signing mode
185
+ const result = await client.get('https://paid-api.example.com/data');
186
+ ```
187
+
188
+ > **Note:** The `walletClient` must have an `account` property set (i.e. created with
189
+ > `createWalletClient({ account: ... })`). The SDK will never recreate or reconnect the client.
190
+
191
+ ---
192
+
109
193
  ## 📚 API Reference
110
194
 
111
195
  ### AgetherClient
@@ -215,6 +299,29 @@ Client for ERC-8004 agent identity management via ag0 SDK.
215
299
 
216
300
  ---
217
301
 
302
+ ## 🔒 KYA (Know Your Agent) Gate
303
+
304
+ The protocol includes an optional **KYA code verification gate**. When enabled, agents must have their code approved by a validator in the `ValidationRegistry` before they can call `execute()` or `executeBatch()` on their `AgentAccount`.
305
+
306
+ **When is it active?**
307
+ - If the `AccountFactory` was deployed with a non-zero `validationRegistry` address, KYA is **enabled**
308
+ - If `validationRegistry = address(0)`, KYA is **disabled** — agents can transact immediately after registration
309
+
310
+ **Checking from the SDK:**
311
+
312
+ ```typescript
313
+ const kyaRequired = await client.isKyaRequired();
314
+ if (kyaRequired) {
315
+ console.log('KYA gate is active — agent code must be approved before executing');
316
+ } else {
317
+ console.log('KYA gate is disabled — agents can execute immediately');
318
+ }
319
+ ```
320
+
321
+ The `register()` method also returns `kyaRequired: boolean` so consumers know the state at registration time.
322
+
323
+ ---
324
+
218
325
  ## 📝 Types
219
326
 
220
327
  ### CreditStatus
package/dist/cli.js CHANGED
@@ -66,7 +66,10 @@ var init_abis = __esm({
66
66
  "function totalAccounts() view returns (uint256)",
67
67
  "function getAgentId(address account) view returns (uint256)",
68
68
  "function createAccount(uint256 agentId) returns (address account)",
69
- "event AccountCreated(uint256 indexed agentId, address indexed account, address indexed owner)"
69
+ "function validationRegistry() view returns (address)",
70
+ "function setValidationRegistry(address newRegistry)",
71
+ "event AccountCreated(uint256 indexed agentId, address indexed account, address indexed owner)",
72
+ "event ValidationRegistryUpdated(address indexed oldRegistry, address indexed newRegistry)"
70
73
  ];
71
74
  AGENT_ACCOUNT_ABI = [
72
75
  "function agentId() view returns (uint256)",
@@ -228,14 +231,44 @@ var init_MorphoClient = __esm({
228
231
  this.config = defaultCfg;
229
232
  this.agentId = config.agentId;
230
233
  this._rpcUrl = config.rpcUrl || defaultCfg.rpcUrl;
231
- this._privateKey = config.privateKey;
232
- this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
233
- this.wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
234
+ if ("signer" in config && config.signer) {
235
+ this._useExternalSigner = true;
236
+ const signerProvider = config.signer.provider;
237
+ if (signerProvider) {
238
+ this.provider = signerProvider;
239
+ this._signer = config.signer;
240
+ } else {
241
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
242
+ this._signer = config.signer.connect(this.provider);
243
+ }
244
+ if ("address" in config.signer && typeof config.signer.address === "string") {
245
+ this._eoaAddress = config.signer.address;
246
+ }
247
+ } else {
248
+ this._privateKey = config.privateKey;
249
+ this._useExternalSigner = false;
250
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
251
+ const wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
252
+ this._signer = wallet;
253
+ this._eoaAddress = wallet.address;
254
+ }
234
255
  const addrs = { ...defaultCfg.contracts, ...config.contracts };
235
- this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this.wallet);
256
+ this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this._signer);
236
257
  this.morphoBlue = new import_ethers.Contract(addrs.morphoBlue, MORPHO_BLUE_ABI, this.provider);
237
- this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this.wallet);
238
- this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this.wallet);
258
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
259
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
260
+ }
261
+ // ════════════════════════════════════════════════════════
262
+ // KYA Gate Check
263
+ // ════════════════════════════════════════════════════════
264
+ /**
265
+ * Check whether the KYA (Know Your Agent) code verification gate is active.
266
+ * When validationRegistry is address(0), KYA is disabled — agents can use
267
+ * execute/executeBatch without prior code approval.
268
+ */
269
+ async isKyaRequired() {
270
+ const registryAddr = await this.accountFactory.validationRegistry();
271
+ return registryAddr !== import_ethers.ethers.ZeroAddress;
239
272
  }
240
273
  // ════════════════════════════════════════════════════════
241
274
  // Account Management
@@ -255,8 +288,35 @@ var init_MorphoClient = __esm({
255
288
  if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
256
289
  return this.agentId;
257
290
  }
291
+ /**
292
+ * Get the EOA wallet address (synchronous, best-effort).
293
+ *
294
+ * For the `privateKey` path this always works. For the `signer` path
295
+ * it works if the signer exposes `.address` synchronously (e.g. ethers.Wallet).
296
+ * If the address has not been resolved yet, throws — call `getSignerAddress()` first.
297
+ */
258
298
  getWalletAddress() {
259
- return this.wallet.address;
299
+ if (this._eoaAddress) return this._eoaAddress;
300
+ const signer = this._signer;
301
+ if (signer.address && typeof signer.address === "string") {
302
+ const addr = signer.address;
303
+ this._eoaAddress = addr;
304
+ return addr;
305
+ }
306
+ throw new AgetherError(
307
+ "EOA address not yet resolved. Call getSignerAddress() (async) first, or use a signer that exposes .address synchronously.",
308
+ "ADDRESS_NOT_RESOLVED"
309
+ );
310
+ }
311
+ /**
312
+ * Resolve the EOA signer address (async, works with all signer types).
313
+ * Result is cached after the first call.
314
+ */
315
+ async getSignerAddress() {
316
+ if (!this._eoaAddress) {
317
+ this._eoaAddress = await this._signer.getAddress();
318
+ }
319
+ return this._eoaAddress;
260
320
  }
261
321
  /** Mint a new ERC-8004 identity and return the agentId. */
262
322
  async _mintNewIdentity() {
@@ -283,13 +343,14 @@ var init_MorphoClient = __esm({
283
343
  * If already registered, returns existing state.
284
344
  */
285
345
  async register(_name) {
286
- const eoaAddr = this.wallet.address;
346
+ const eoaAddr = await this.getSignerAddress();
287
347
  if (this.agentId) {
288
348
  const exists = await this.accountFactory.accountExists(BigInt(this.agentId));
289
349
  if (exists) {
290
350
  const acct = await this.accountFactory.getAccount(BigInt(this.agentId));
291
351
  this._accountAddress = acct;
292
- return { agentId: this.agentId, address: eoaAddr, agentAccount: acct, alreadyRegistered: true };
352
+ const kyaRequired2 = await this.isKyaRequired();
353
+ return { agentId: this.agentId, address: eoaAddr, agentAccount: acct, alreadyRegistered: true, kyaRequired: kyaRequired2 };
293
354
  }
294
355
  }
295
356
  let agentId;
@@ -314,17 +375,19 @@ var init_MorphoClient = __esm({
314
375
  }
315
376
  const acctAddr = await this.accountFactory.getAccount(agentId);
316
377
  this._accountAddress = acctAddr;
378
+ const kyaRequired = await this.isKyaRequired();
317
379
  return {
318
380
  agentId: this.agentId,
319
381
  address: eoaAddr,
320
382
  agentAccount: acctAddr,
321
383
  alreadyRegistered: acctExists,
384
+ kyaRequired,
322
385
  tx: txHash
323
386
  };
324
387
  }
325
388
  /** Get ETH / USDC / collateral balances for EOA and AgentAccount. */
326
389
  async getBalances() {
327
- const eoaAddr = this.wallet.address;
390
+ const eoaAddr = await this.getSignerAddress();
328
391
  const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
329
392
  const ethBal = await this.provider.getBalance(eoaAddr);
330
393
  const usdcBal = await usdc.balanceOf(eoaAddr);
@@ -372,7 +435,7 @@ var init_MorphoClient = __esm({
372
435
  /** Transfer USDC from EOA to AgentAccount. */
373
436
  async fundAccount(usdcAmount) {
374
437
  const acctAddr = await this.getAccountAddress();
375
- const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.wallet);
438
+ const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this._signer);
376
439
  const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
377
440
  const tx = await usdc.transfer(acctAddr, amount);
378
441
  const receipt = await tx.wait();
@@ -720,7 +783,7 @@ var init_MorphoClient = __esm({
720
783
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
721
784
  const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
722
785
  const morphoAddr = this.config.contracts.morphoBlue;
723
- const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this.wallet);
786
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
724
787
  const transferTx = await colToken.transfer(acctAddr, weiAmount);
725
788
  await transferTx.wait();
726
789
  this._refreshSigner();
@@ -797,7 +860,7 @@ var init_MorphoClient = __esm({
797
860
  const colWei = import_ethers.ethers.parseUnits(collateralAmount, colInfo.decimals);
798
861
  const borrowWei = import_ethers.ethers.parseUnits(borrowUsdcAmount, 6);
799
862
  const morphoAddr = this.config.contracts.morphoBlue;
800
- const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this.wallet);
863
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
801
864
  const transferTx = await colToken.transfer(acctAddr, colWei);
802
865
  await transferTx.wait();
803
866
  this._refreshSigner();
@@ -927,7 +990,7 @@ var init_MorphoClient = __esm({
927
990
  if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
928
991
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
929
992
  const morphoAddr = this.config.contracts.morphoBlue;
930
- const dest = receiver || this.wallet.address;
993
+ const dest = receiver || await this.getSignerAddress();
931
994
  let weiAmount;
932
995
  if (amount === "all") {
933
996
  const markets = await this.getMarkets();
@@ -985,7 +1048,7 @@ var init_MorphoClient = __esm({
985
1048
  throw new AgetherError("Provide agentId or address", "INVALID_TARGET");
986
1049
  }
987
1050
  const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
988
- const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this.wallet);
1051
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
989
1052
  const tx = await colToken.transfer(targetAddr, weiAmount);
990
1053
  const receipt = await tx.wait();
991
1054
  this._refreshSigner();
@@ -1017,24 +1080,41 @@ var init_MorphoClient = __esm({
1017
1080
  // Internal Helpers
1018
1081
  // ════════════════════════════════════════════════════════
1019
1082
  /**
1020
- * Recreate provider + wallet so the next tx fetches a fresh nonce from chain.
1021
- * Anvil (and some RPC providers) return a stale `eth_getTransactionCount`
1022
- * right after a block is mined, causing "nonce too low" on the follow-up tx.
1083
+ * Refresh the signer and re-bind contract instances.
1084
+ *
1085
+ * For the **privateKey** path: recreates provider + wallet so the next tx
1086
+ * fetches a fresh nonce from chain. Anvil (and some RPC providers) return a
1087
+ * stale `eth_getTransactionCount` right after a block is mined, causing
1088
+ * "nonce too low" on the follow-up tx.
1089
+ *
1090
+ * For the **external signer** path: the signer is immutable and owned by the
1091
+ * caller (e.g. custody provider). We only re-bind contract instances to
1092
+ * ensure they reference the current signer. Nonce management is the caller's
1093
+ * responsibility.
1023
1094
  */
1024
1095
  _refreshSigner() {
1025
- this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
1026
- this.wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
1027
- const addrs = this.config.contracts;
1028
- this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this.wallet);
1029
- this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this.wallet);
1030
- this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this.wallet);
1096
+ if (this._useExternalSigner) {
1097
+ const addrs = this.config.contracts;
1098
+ this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this._signer);
1099
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
1100
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
1101
+ } else {
1102
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
1103
+ const wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
1104
+ this._signer = wallet;
1105
+ this._eoaAddress = wallet.address;
1106
+ const addrs = this.config.contracts;
1107
+ this.accountFactory = new import_ethers.Contract(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this._signer);
1108
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
1109
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
1110
+ }
1031
1111
  }
1032
1112
  /**
1033
1113
  * Execute a single call via AgentAccount.execute.
1034
1114
  */
1035
1115
  async exec(target, data, value = 0n) {
1036
1116
  const acctAddr = await this.getAccountAddress();
1037
- const account = new import_ethers.Contract(acctAddr, AGENT_ACCOUNT_ABI, this.wallet);
1117
+ const account = new import_ethers.Contract(acctAddr, AGENT_ACCOUNT_ABI, this._signer);
1038
1118
  let gasLimit;
1039
1119
  try {
1040
1120
  const estimate = await account.execute.estimateGas(target, value, data);
@@ -1052,7 +1132,7 @@ var init_MorphoClient = __esm({
1052
1132
  */
1053
1133
  async batch(targets, values, datas) {
1054
1134
  const acctAddr = await this.getAccountAddress();
1055
- const account = new import_ethers.Contract(acctAddr, AGENT_ACCOUNT_ABI, this.wallet);
1135
+ const account = new import_ethers.Contract(acctAddr, AGENT_ACCOUNT_ABI, this._signer);
1056
1136
  let gasLimit;
1057
1137
  try {
1058
1138
  const estimate = await account.executeBatch.estimateGas(targets, values, datas);
@@ -1116,28 +1196,62 @@ var init_X402Client = __esm({
1116
1196
  X402Client = class {
1117
1197
  constructor(config) {
1118
1198
  this.config = config;
1119
- const privateKey = config.privateKey.startsWith("0x") ? config.privateKey : `0x${config.privateKey}`;
1120
- const eoaSigner = (0, import_accounts.privateKeyToAccount)(privateKey);
1199
+ let baseSigner;
1200
+ if ("walletClient" in config && config.walletClient) {
1201
+ const wc = config.walletClient;
1202
+ const account = wc.account;
1203
+ if (!account) {
1204
+ throw new Error(
1205
+ "X402Client: walletClient must have an attached account. Pass an account when creating the WalletClient or use privateKey mode instead."
1206
+ );
1207
+ }
1208
+ this.address = account.address;
1209
+ baseSigner = {
1210
+ address: account.address,
1211
+ signTypedData: (msg) => wc.signTypedData({
1212
+ account,
1213
+ domain: msg.domain,
1214
+ types: msg.types,
1215
+ primaryType: msg.primaryType,
1216
+ message: msg.message
1217
+ })
1218
+ };
1219
+ } else if ("privateKey" in config && config.privateKey) {
1220
+ const privateKey = config.privateKey.startsWith("0x") ? config.privateKey : `0x${config.privateKey}`;
1221
+ const account = (0, import_accounts.privateKeyToAccount)(privateKey);
1222
+ this.address = account.address;
1223
+ baseSigner = account;
1224
+ } else {
1225
+ throw new Error(
1226
+ "X402Client: provide either privateKey or walletClient in config."
1227
+ );
1228
+ }
1121
1229
  let signer;
1122
1230
  if (config.accountAddress) {
1123
1231
  const accountAddr = config.accountAddress;
1232
+ const inner = baseSigner;
1124
1233
  signer = (0, import_accounts.toAccount)({
1125
1234
  address: accountAddr,
1126
1235
  async signMessage({ message }) {
1127
- return eoaSigner.signMessage({ message });
1236
+ if ("signMessage" in inner && typeof inner.signMessage === "function") {
1237
+ return inner.signMessage({ message });
1238
+ }
1239
+ throw new Error("signMessage not supported by underlying signer");
1128
1240
  },
1129
1241
  async signTransaction(tx) {
1130
- return eoaSigner.signTransaction(tx);
1242
+ if ("signTransaction" in inner && typeof inner.signTransaction === "function") {
1243
+ return inner.signTransaction(tx);
1244
+ }
1245
+ throw new Error("signTransaction not supported by underlying signer");
1131
1246
  },
1132
1247
  async signTypedData(typedData) {
1133
- const sig = await eoaSigner.signTypedData(typedData);
1248
+ const sig = await inner.signTypedData(typedData);
1134
1249
  return `${sig}00`;
1135
1250
  }
1136
1251
  });
1137
1252
  this.address = accountAddr;
1138
1253
  } else {
1139
- signer = eoaSigner;
1140
- this.address = eoaSigner.address;
1254
+ signer = baseSigner;
1141
1255
  }
1142
1256
  const client = new import_client.x402Client();
1143
1257
  (0, import_client2.registerExactEvmScheme)(client, { signer });
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { Signer } from 'ethers';
1
+ import { Signer, ethers } from 'ethers';
2
+ import { WalletClient } from 'viem';
2
3
 
3
4
  /**
4
5
  * Agether SDK Types
@@ -186,10 +187,20 @@ declare class AgetherClient {
186
187
  * - supplyCollateral: [ERC20.approve, Morpho.supplyCollateral]
187
188
  *
188
189
  * Market discovery via Morpho GraphQL API (https://api.morpho.org/graphql)
190
+ *
191
+ * Supports two signing modes:
192
+ * - `privateKey`: SDK manages wallet lifecycle (existing behavior)
193
+ * - `signer`: external signer (Bankr, Privy, Turnkey, MetaMask, etc.)
189
194
  */
190
195
 
191
- interface MorphoClientConfig {
192
- privateKey: string;
196
+ /**
197
+ * Convenience type alias for any ethers-compatible signer.
198
+ * Use this when declaring variables or parameters that accept
199
+ * ethers.Wallet, Privy signer, Bankr signer, Turnkey signer, etc.
200
+ */
201
+ type AgetherSigner = ethers.AbstractSigner;
202
+ /** Base configuration fields shared by both signing modes. */
203
+ interface MorphoClientBaseConfig {
193
204
  rpcUrl: string;
194
205
  agentId?: string;
195
206
  chainId?: ChainId;
@@ -201,6 +212,44 @@ interface MorphoClientConfig {
201
212
  identityRegistry: string;
202
213
  }>;
203
214
  }
215
+ /**
216
+ * MorphoClient configuration.
217
+ *
218
+ * Provide **either** `privateKey` (SDK manages wallet) **or** `signer`
219
+ * (external custody provider). The two fields are mutually exclusive.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * // Private key (existing behavior — fully backward compatible):
224
+ * new MorphoClient({ privateKey: '0x...', rpcUrl, agentId: '42' })
225
+ *
226
+ * // External signer (Bankr, Privy, Turnkey, MetaMask, etc.):
227
+ * new MorphoClient({ signer: privySigner, rpcUrl, agentId: '42' })
228
+ * ```
229
+ */
230
+ type MorphoClientConfig = MorphoClientBaseConfig & ({
231
+ /** Raw private key hex string. SDK creates an ethers.Wallet internally. */
232
+ privateKey: string;
233
+ signer?: never;
234
+ } | {
235
+ /**
236
+ * Any `ethers.AbstractSigner` instance for transaction signing.
237
+ *
238
+ * Accepts:
239
+ * - `ethers.Wallet` (local key)
240
+ * - Privy embedded wallet signer
241
+ * - Bankr custodial signer
242
+ * - Turnkey / MPC wallet signer
243
+ * - Hardware wallet adapter (Ledger, Trezor)
244
+ * - MetaMask `BrowserProvider.getSigner()`
245
+ * - Any object implementing `ethers.AbstractSigner`
246
+ *
247
+ * The signer is treated as **immutable** by the SDK — it will never be
248
+ * recreated or reconnected. Nonce management is the caller's responsibility.
249
+ */
250
+ signer: ethers.AbstractSigner;
251
+ privateKey?: never;
252
+ });
204
253
  interface BalancesResult {
205
254
  agentId: string;
206
255
  address: string;
@@ -219,6 +268,8 @@ interface RegisterResult {
219
268
  address: string;
220
269
  agentAccount: string;
221
270
  alreadyRegistered: boolean;
271
+ /** Whether the KYA (code verification) gate is active on this factory */
272
+ kyaRequired: boolean;
222
273
  tx?: string;
223
274
  }
224
275
  interface PositionResult {
@@ -272,12 +323,14 @@ interface FundResult {
272
323
  agentAccount: string;
273
324
  }
274
325
  declare class MorphoClient {
275
- private wallet;
326
+ private _signer;
276
327
  private provider;
277
328
  private config;
278
329
  private agentId;
279
330
  private _rpcUrl;
280
- private _privateKey;
331
+ private _privateKey?;
332
+ private _useExternalSigner;
333
+ private _eoaAddress?;
281
334
  private accountFactory;
282
335
  private morphoBlue;
283
336
  private agentReputation;
@@ -287,10 +340,28 @@ declare class MorphoClient {
287
340
  private _discoveredMarkets?;
288
341
  private _discoveredAt;
289
342
  constructor(config: MorphoClientConfig);
343
+ /**
344
+ * Check whether the KYA (Know Your Agent) code verification gate is active.
345
+ * When validationRegistry is address(0), KYA is disabled — agents can use
346
+ * execute/executeBatch without prior code approval.
347
+ */
348
+ isKyaRequired(): Promise<boolean>;
290
349
  /** Resolve the AgentAccount address (cached). */
291
350
  getAccountAddress(): Promise<string>;
292
351
  getAgentId(): string;
352
+ /**
353
+ * Get the EOA wallet address (synchronous, best-effort).
354
+ *
355
+ * For the `privateKey` path this always works. For the `signer` path
356
+ * it works if the signer exposes `.address` synchronously (e.g. ethers.Wallet).
357
+ * If the address has not been resolved yet, throws — call `getSignerAddress()` first.
358
+ */
293
359
  getWalletAddress(): string;
360
+ /**
361
+ * Resolve the EOA signer address (async, works with all signer types).
362
+ * Result is cached after the first call.
363
+ */
364
+ getSignerAddress(): Promise<string>;
294
365
  /** Mint a new ERC-8004 identity and return the agentId. */
295
366
  private _mintNewIdentity;
296
367
  /**
@@ -450,9 +521,17 @@ declare class MorphoClient {
450
521
  age: bigint;
451
522
  }>;
452
523
  /**
453
- * Recreate provider + wallet so the next tx fetches a fresh nonce from chain.
454
- * Anvil (and some RPC providers) return a stale `eth_getTransactionCount`
455
- * right after a block is mined, causing "nonce too low" on the follow-up tx.
524
+ * Refresh the signer and re-bind contract instances.
525
+ *
526
+ * For the **privateKey** path: recreates provider + wallet so the next tx
527
+ * fetches a fresh nonce from chain. Anvil (and some RPC providers) return a
528
+ * stale `eth_getTransactionCount` right after a block is mined, causing
529
+ * "nonce too low" on the follow-up tx.
530
+ *
531
+ * For the **external signer** path: the signer is immutable and owned by the
532
+ * caller (e.g. custody provider). We only re-bind contract instances to
533
+ * ensure they reference the current signer. Nonce management is the caller's
534
+ * responsibility.
456
535
  */
457
536
  private _refreshSigner;
458
537
  /**
@@ -491,8 +570,15 @@ declare class MorphoClient {
491
570
  *
492
571
  * Chain support: Base (8453), Base Sepolia (84532), Ethereum (1).
493
572
  */
494
- interface X402Config {
495
- privateKey: string;
573
+
574
+ /**
575
+ * Convenience type alias for any viem-compatible WalletClient.
576
+ * Use this when declaring variables or parameters that accept
577
+ * viem WalletClient instances from Privy, Turnkey, MetaMask, etc.
578
+ */
579
+ type AgetherViemWallet = WalletClient;
580
+ /** Base configuration fields shared by both signing modes. */
581
+ interface X402BaseConfig {
496
582
  rpcUrl: string;
497
583
  backendUrl: string;
498
584
  agentId?: string;
@@ -519,6 +605,29 @@ interface X402Config {
519
605
  */
520
606
  autoDrawBuffer?: string;
521
607
  }
608
+ /** Config with SDK-managed private key (existing behavior). */
609
+ interface X402PrivateKeyConfig extends X402BaseConfig {
610
+ /** Raw hex private key (with or without '0x' prefix). SDK creates the wallet internally. */
611
+ privateKey: string;
612
+ walletClient?: never;
613
+ }
614
+ /** Config with caller-provided viem WalletClient (custody-agnostic). */
615
+ interface X402WalletClientConfig extends X402BaseConfig {
616
+ /**
617
+ * A viem WalletClient with an attached account (e.g. from Privy, Turnkey, MetaMask).
618
+ * The client **must** have an `account` property set. The SDK will use it for
619
+ * EIP-712 typed-data signing and will never recreate or reconnect the client.
620
+ */
621
+ walletClient: WalletClient;
622
+ privateKey?: never;
623
+ }
624
+ /**
625
+ * X402Client configuration.
626
+ *
627
+ * Provide **either** `privateKey` (SDK manages wallet) **or** `walletClient`
628
+ * (external custody provider). The two fields are mutually exclusive.
629
+ */
630
+ type X402Config = X402PrivateKeyConfig | X402WalletClientConfig;
522
631
  interface X402Response<T = unknown> {
523
632
  success: boolean;
524
633
  data?: T;
@@ -954,4 +1063,4 @@ declare function getDefaultConfig(chainId: ChainId): AgetherConfig;
954
1063
  */
955
1064
  declare function createConfig(chainId: ChainId, options?: Partial<AgetherConfig>): AgetherConfig;
956
1065
 
957
- export { ACCOUNT_FACTORY_ABI, AGENT_ACCOUNT_ABI, AGENT_REPUTATION_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ERC20_ABI, type FundResult, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };
1066
+ export { ACCOUNT_FACTORY_ABI, AGENT_ACCOUNT_ABI, AGENT_REPUTATION_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ERC20_ABI, type FundResult, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getDefaultConfig, parseUnits, rateToBps };