@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.
- package/package.json +1 -1
- package/src/index.js +115 -20
package/package.json
CHANGED
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
|
|
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
|
-
|
|
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 (
|
|
335
|
-
this.keypair =
|
|
336
|
-
this.walletAddress =
|
|
337
|
-
} else if (typeof
|
|
338
|
-
const secretKey = bs58decode(
|
|
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 (
|
|
342
|
+
} else if (resolvedWallet && resolvedWallet.secretKey && resolvedWallet.publicKey) {
|
|
342
343
|
// Keypair-like object
|
|
343
|
-
this.keypair =
|
|
344
|
-
this.walletAddress =
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1128
|
-
|
|
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
|
-
//
|
|
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
|
|