@x1scroll/agent-sdk 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +115 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x1scroll/agent-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Agent identity and on-chain memory protocol for X1 blockchain",
5
5
  "license": "BSL-1.1",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -52,7 +52,7 @@ const PROGRAM_ID = new PublicKey('ECgaMEwH4KLSz3awDo1vz84mSrx5n6h1ZCrbmunB5UxB')
52
52
  */
53
53
  const TREASURY = new PublicKey('GmvrL1ymC9ENuQCUqymC9robGa9t9L59AbFiwhDDd4Ld');
54
54
 
55
- const DEFAULT_RPC_URL = 'https://rpc.x1.xyz';
55
+ const DEFAULT_RPC_URL = 'https://x1scroll.io/rpc';
56
56
 
57
57
  // ── Anchor instruction discriminators (sha256("global:<name>")[0..8]) ─────────
58
58
  // Pre-computed from the IDL. These are fixed for the deployed program version.
@@ -326,25 +326,26 @@ class AgentClient {
326
326
  * X1 RPC endpoint (default: https://rpc.x1.xyz).
327
327
  * Use https://rpc.x1scroll.io for our dedicated node.
328
328
  */
329
- constructor({ wallet = null, rpcUrl = DEFAULT_RPC_URL } = {}) {
330
- // ── resolve wallet ──
331
- if (wallet === null || wallet === undefined) {
329
+ constructor({ wallet = null, keypair = null, rpcUrl = DEFAULT_RPC_URL } = {}) {
330
+ // ── resolve wallet — accept both `wallet` and `keypair` param names ──
331
+ const resolvedWallet = wallet || keypair;
332
+ if (resolvedWallet === null || resolvedWallet === undefined) {
332
333
  this.keypair = null;
333
334
  this.walletAddress = null;
334
- } else if (wallet instanceof Keypair) {
335
- this.keypair = wallet;
336
- this.walletAddress = wallet.publicKey.toBase58();
337
- } else if (typeof wallet === 'string') {
338
- const secretKey = bs58decode(wallet);
335
+ } else if (resolvedWallet instanceof Keypair) {
336
+ this.keypair = resolvedWallet;
337
+ this.walletAddress = resolvedWallet.publicKey.toBase58();
338
+ } else if (typeof resolvedWallet === 'string') {
339
+ const secretKey = bs58decode(resolvedWallet);
339
340
  this.keypair = Keypair.fromSecretKey(secretKey);
340
341
  this.walletAddress = this.keypair.publicKey.toBase58();
341
- } else if (wallet && wallet.secretKey && wallet.publicKey) {
342
+ } else if (resolvedWallet && resolvedWallet.secretKey && resolvedWallet.publicKey) {
342
343
  // Keypair-like object
343
- this.keypair = wallet;
344
- this.walletAddress = wallet.publicKey.toBase58();
344
+ this.keypair = resolvedWallet;
345
+ this.walletAddress = resolvedWallet.publicKey.toBase58();
345
346
  } else {
346
347
  throw new AgentSDKError(
347
- 'wallet must be a Keypair, a base58 secret key string, or null',
348
+ 'wallet (or keypair) must be a Keypair, a base58 secret key string, or null',
348
349
  'INVALID_WALLET'
349
350
  );
350
351
  }
@@ -355,6 +356,54 @@ class AgentClient {
355
356
  this._registryCacheExpiry = 0;
356
357
  }
357
358
 
359
+ /**
360
+ * Check RPC connectivity. Throws AgentSDKError with fallback suggestion if unreachable.
361
+ * @returns {Promise<{ ok: boolean, slot: number, rpcUrl: string }>}
362
+ */
363
+ async healthCheck() {
364
+ const conn = this._getConnection();
365
+ try {
366
+ const slot = await Promise.race([
367
+ conn.getSlot(),
368
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 8000)),
369
+ ]);
370
+ return { ok: true, slot, rpcUrl: this.rpcUrl };
371
+ } catch (err) {
372
+ throw new AgentSDKError(
373
+ `RPC endpoint unreachable: ${this.rpcUrl}. ` +
374
+ `Try the public fallback: https://x1scroll.io/rpc\n` +
375
+ `Original error: ${err.message}`,
376
+ 'RPC_UNREACHABLE',
377
+ err
378
+ );
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Check that a keypair has sufficient XNT balance for an operation.
384
+ * Throws AgentSDKError with current balance and required amount if insufficient.
385
+ * @param {Keypair} keypair
386
+ * @param {number} requiredLamports
387
+ * @param {string} [operationName]
388
+ */
389
+ async _assertSufficientBalance(keypair, requiredLamports, operationName = 'operation') {
390
+ const conn = this._getConnection();
391
+ const balance = await conn.getBalance(keypair.publicKey);
392
+ // Add 5000 lamports buffer for network tx fee
393
+ const totalRequired = requiredLamports + 5000;
394
+ if (balance < totalRequired) {
395
+ const balanceXNT = (balance / 1e9).toFixed(6);
396
+ const requiredXNT = (totalRequired / 1e9).toFixed(6);
397
+ throw new AgentSDKError(
398
+ `Insufficient balance for ${operationName}. ` +
399
+ `Have: ${balanceXNT} XNT, Need: ${requiredXNT} XNT. ` +
400
+ `Fund wallet: ${keypair.publicKey.toBase58()}`,
401
+ 'INSUFFICIENT_BALANCE'
402
+ );
403
+ }
404
+ return balance;
405
+ }
406
+
358
407
  // ── Internal ────────────────────────────────────────────────────────────────
359
408
 
360
409
  /** @returns {Connection} */
@@ -518,6 +567,10 @@ class AgentClient {
518
567
 
519
568
  const humanKeypair = this.keypair;
520
569
  const agentPubkey = agentKeypair.publicKey;
570
+
571
+ // ── Pre-flight balance check (0.05 XNT registration fee) ──
572
+ await this._assertSufficientBalance(humanKeypair, 50_000_000, 'register_agent (0.05 XNT)');
573
+
521
574
  const { pda: agentRecordPDA } = AgentClient.deriveAgentRecord(agentPubkey);
522
575
 
523
576
  // Borsh-encode instruction data: discriminator + name + metadata_uri
@@ -940,16 +993,42 @@ class AgentClient {
940
993
  * @param {Buffer|null} [parentHash] 32-byte parent decision hash; zeros for root
941
994
  * @returns {Promise<{ sig: string, decisionHash: string, pda: string }>}
942
995
  */
943
- async decisionWrite(agentKeypair, branchLabel, cid, outcome, confidence, parentHash = null) {
996
+ async decisionWrite(agentKeypair, branchLabelOrMessage, cidOrOpts, outcome, confidence, parentHash = null) {
944
997
  assertIsSigner(agentKeypair, 'agentKeypair');
998
+
999
+ // ── Simple overload: decisionWrite(keypair, type, message) ──
1000
+ // Allows: client.decisionWrite(kp, 'trade', 'bought XNT at 0.34')
1001
+ // Internally maps to branchLabel=type, cid=sha256(message), outcome=1, confidence=9000
1002
+ let branchLabel, cid;
1003
+ if (typeof cidOrOpts === 'string' && cidOrOpts.length < 64 && !cidOrOpts.startsWith('Qm') && outcome === undefined) {
1004
+ branchLabel = branchLabelOrMessage;
1005
+ // Hash the message string into a pseudo-CID for on-chain storage
1006
+ cid = 'msg:' + crypto.createHash('sha256').update(cidOrOpts).digest('hex').slice(0, 44);
1007
+ outcome = 1; // executed
1008
+ confidence = 9000; // 90%
1009
+ } else {
1010
+ branchLabel = branchLabelOrMessage;
1011
+ cid = cidOrOpts;
1012
+ }
1013
+
945
1014
  validateString(branchLabel, 'branchLabel', 64);
946
1015
  validateString(cid, 'cid', 64);
947
1016
 
1017
+ // ── Pre-flight balance check (0.001 XNT decision write fee) ──
1018
+ await this._assertSufficientBalance(agentKeypair, 1_000_000, 'decision_write (0.001 XNT)');
1019
+
948
1020
  if (typeof outcome !== 'number' || ![0, 1, 2].includes(outcome)) {
949
- throw new AgentSDKError('outcome must be 0 (pending), 1 (executed), or 2 (rejected)', 'INVALID_INPUT');
1021
+ throw new AgentSDKError(
1022
+ `outcome must be 0 (pending), 1 (executed), or 2 (rejected) — got: ${JSON.stringify(outcome)}. ` +
1023
+ 'Tip: use the simple form decisionWrite(keypair, type, message) to skip outcome/confidence.',
1024
+ 'INVALID_INPUT'
1025
+ );
950
1026
  }
951
1027
  if (typeof confidence !== 'number' || confidence < 0 || confidence > 10000) {
952
- throw new AgentSDKError('confidence must be 0-10000 basis points', 'INVALID_INPUT');
1028
+ throw new AgentSDKError(
1029
+ `confidence must be 0-10000 basis points — got: ${JSON.stringify(confidence)}`,
1030
+ 'INVALID_INPUT'
1031
+ );
953
1032
  }
954
1033
 
955
1034
  // Derive decision_hash: sha256(JSON.stringify({cid, branchLabel, timestamp}))
@@ -1124,8 +1203,17 @@ class AgentClient {
1124
1203
  throw new AgentSDKError('limit must be a positive integer', 'INVALID_INPUT');
1125
1204
  }
1126
1205
 
1127
- const agentPdaPubkey = new PublicKey(agentPda);
1128
- const connection = this._getConnection();
1206
+ let agentPdaPubkey;
1207
+ try {
1208
+ agentPdaPubkey = new PublicKey(agentPda);
1209
+ } catch (err) {
1210
+ throw new AgentSDKError(
1211
+ `contextGet: invalid agentPda — expected a base58 public key, got: ${JSON.stringify(agentPda)}`,
1212
+ 'INVALID_INPUT', err
1213
+ );
1214
+ }
1215
+
1216
+ const connection = this._getConnection();
1129
1217
 
1130
1218
  // Fetch recent signatures for this address
1131
1219
  const sigs = await connection.getSignaturesForAddress(agentPdaPubkey, { limit });
@@ -1167,8 +1255,15 @@ class AgentClient {
1167
1255
  });
1168
1256
  break; // one entry per tx
1169
1257
  }
1170
- } catch (_) {
1171
- // skip transactions we can't parse
1258
+ } catch (err) {
1259
+ // Log parse failures but don't block — one bad tx shouldn't kill the whole query
1260
+ entries.push({
1261
+ slot: sigInfo.slot,
1262
+ sig: sigInfo.signature,
1263
+ instruction: 'ParseError',
1264
+ error: err.message || String(err),
1265
+ blockTime: sigInfo.blockTime || null,
1266
+ });
1172
1267
  }
1173
1268
  }));
1174
1269