@x1scroll/agent-sdk 1.1.2 → 1.2.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/package.json +1 -1
- package/src/index.js +312 -5
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -21,7 +21,8 @@ const {
|
|
|
21
21
|
SystemProgram,
|
|
22
22
|
LAMPORTS_PER_SOL,
|
|
23
23
|
} = require('@solana/web3.js');
|
|
24
|
-
const bs58
|
|
24
|
+
const bs58 = require('bs58');
|
|
25
|
+
const crypto = require('crypto');
|
|
25
26
|
|
|
26
27
|
// ── bs58 compat (v4 vs v5 API shape) ─────────────────────────────────────────
|
|
27
28
|
const bs58encode = (typeof bs58.encode === 'function') ? bs58.encode : bs58.default.encode;
|
|
@@ -56,10 +57,14 @@ const DEFAULT_RPC_URL = 'https://rpc.x1.xyz';
|
|
|
56
57
|
// ── Anchor instruction discriminators (sha256("global:<name>")[0..8]) ─────────
|
|
57
58
|
// Pre-computed from the IDL. These are fixed for the deployed program version.
|
|
58
59
|
const DISCRIMINATORS = {
|
|
59
|
-
register_agent:
|
|
60
|
-
store_memory:
|
|
61
|
-
update_agent:
|
|
62
|
-
transfer_agent:
|
|
60
|
+
register_agent: Buffer.from([135, 157, 66, 55, 116, 253, 50, 45]),
|
|
61
|
+
store_memory: Buffer.from([31, 139, 69, 89, 102, 57, 218, 246]),
|
|
62
|
+
update_agent: Buffer.from([220, 76, 168, 212, 224, 211, 185, 76]),
|
|
63
|
+
transfer_agent: Buffer.from([39, 202, 189, 195, 254, 40, 59, 198]),
|
|
64
|
+
// v2 discriminators — sha256("global:<name>")[0:8]
|
|
65
|
+
decision_write: crypto.createHash('sha256').update('global:decision_write').digest().slice(0, 8),
|
|
66
|
+
strategy_branch_open: crypto.createHash('sha256').update('global:strategy_branch_open').digest().slice(0, 8),
|
|
67
|
+
strategy_branch_close: crypto.createHash('sha256').update('global:strategy_branch_close').digest().slice(0, 8),
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
// ── Anchor account discriminators (sha256("account:<Name>")[0..8]) ─────────────
|
|
@@ -134,6 +139,27 @@ function encodeU64(n) {
|
|
|
134
139
|
return buf;
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Pad or truncate a label string to exactly 32 bytes (for branch PDA seeds).
|
|
144
|
+
* @param {string} label
|
|
145
|
+
* @returns {Buffer} 32-byte buffer
|
|
146
|
+
*/
|
|
147
|
+
function padLabel(label) {
|
|
148
|
+
const src = Buffer.from(label, 'utf8');
|
|
149
|
+
const out = Buffer.alloc(32);
|
|
150
|
+
src.copy(out, 0, 0, Math.min(src.length, 32));
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Compute sha256 of a string and return a 32-byte Buffer.
|
|
156
|
+
* @param {string} s
|
|
157
|
+
* @returns {Buffer}
|
|
158
|
+
*/
|
|
159
|
+
function sha256Str(s) {
|
|
160
|
+
return crypto.createHash('sha256').update(s).digest();
|
|
161
|
+
}
|
|
162
|
+
|
|
137
163
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
164
|
// Borsh decoding helpers
|
|
139
165
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -900,15 +926,296 @@ class AgentClient {
|
|
|
900
926
|
// Most recent first
|
|
901
927
|
return results.reverse();
|
|
902
928
|
}
|
|
929
|
+
|
|
930
|
+
// ── v2 Methods ──────────────────────────────────────────────────────────────
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Write a decision to the on-chain decision tree.
|
|
934
|
+
*
|
|
935
|
+
* @param {Keypair} agentKeypair The agent keypair (signer + fee payer)
|
|
936
|
+
* @param {string} branchLabel Strategy branch this decision belongs to
|
|
937
|
+
* @param {string} cid IPFS CID of the decision payload
|
|
938
|
+
* @param {number} outcome 0=pending, 1=executed, 2=rejected
|
|
939
|
+
* @param {number} confidence 0-10000 basis points (8200 = 82%)
|
|
940
|
+
* @param {Buffer|null} [parentHash] 32-byte parent decision hash; zeros for root
|
|
941
|
+
* @returns {Promise<{ sig: string, decisionHash: string, pda: string }>}
|
|
942
|
+
*/
|
|
943
|
+
async decisionWrite(agentKeypair, branchLabel, cid, outcome, confidence, parentHash = null) {
|
|
944
|
+
assertIsSigner(agentKeypair, 'agentKeypair');
|
|
945
|
+
validateString(branchLabel, 'branchLabel', 64);
|
|
946
|
+
validateString(cid, 'cid', 64);
|
|
947
|
+
|
|
948
|
+
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');
|
|
950
|
+
}
|
|
951
|
+
if (typeof confidence !== 'number' || confidence < 0 || confidence > 10000) {
|
|
952
|
+
throw new AgentSDKError('confidence must be 0-10000 basis points', 'INVALID_INPUT');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Derive decision_hash: sha256(JSON.stringify({cid, branchLabel, timestamp}))
|
|
956
|
+
const timestamp = Date.now();
|
|
957
|
+
const decisionHash = sha256Str(JSON.stringify({ cid, branchLabel, timestamp }));
|
|
958
|
+
|
|
959
|
+
// parent_hash: provided Buffer(32) or zeros
|
|
960
|
+
const parentHashBuf = (parentHash instanceof Buffer && parentHash.length === 32)
|
|
961
|
+
? parentHash
|
|
962
|
+
: Buffer.alloc(32);
|
|
963
|
+
|
|
964
|
+
// Derive AgentRecord PDA
|
|
965
|
+
const { pda: agentRecordPDA } = AgentClient.deriveAgentRecord(agentKeypair.publicKey);
|
|
966
|
+
|
|
967
|
+
// Derive DecisionRecord PDA: [b"decision", agentRecord, decision_hash]
|
|
968
|
+
const [decisionRecordPDA] = PublicKey.findProgramAddressSync(
|
|
969
|
+
[Buffer.from('decision'), agentRecordPDA.toBuffer(), decisionHash],
|
|
970
|
+
PROGRAM_ID
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
// Encode confidence as u32 LE
|
|
974
|
+
const confidenceBuf = Buffer.alloc(4);
|
|
975
|
+
confidenceBuf.writeUInt32LE(confidence, 0);
|
|
976
|
+
|
|
977
|
+
const data = Buffer.concat([
|
|
978
|
+
DISCRIMINATORS.decision_write,
|
|
979
|
+
decisionHash, // [u8;32]
|
|
980
|
+
parentHashBuf, // [u8;32]
|
|
981
|
+
encodeString(branchLabel), // string (4-byte LE len + utf8)
|
|
982
|
+
encodeString(cid), // string
|
|
983
|
+
Buffer.from([outcome]), // u8
|
|
984
|
+
confidenceBuf, // u32 LE
|
|
985
|
+
]);
|
|
986
|
+
|
|
987
|
+
const ix = new TransactionInstruction({
|
|
988
|
+
programId: PROGRAM_ID,
|
|
989
|
+
keys: [
|
|
990
|
+
{ pubkey: agentKeypair.publicKey, isSigner: true, isWritable: true }, // agent_authority
|
|
991
|
+
{ pubkey: agentRecordPDA, isSigner: false, isWritable: true }, // agent_record
|
|
992
|
+
{ pubkey: decisionRecordPDA, isSigner: false, isWritable: true }, // decision_record (init)
|
|
993
|
+
{ pubkey: TREASURY, isSigner: false, isWritable: true }, // treasury
|
|
994
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system_program
|
|
995
|
+
],
|
|
996
|
+
data,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const tx = new Transaction().add(ix);
|
|
1000
|
+
const sig = await this._sendAndConfirm(tx, [agentKeypair]);
|
|
1001
|
+
|
|
1002
|
+
return {
|
|
1003
|
+
sig,
|
|
1004
|
+
decisionHash: decisionHash.toString('hex'),
|
|
1005
|
+
pda: decisionRecordPDA.toBase58(),
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Open a new strategy branch on-chain.
|
|
1011
|
+
*
|
|
1012
|
+
* @param {Keypair} agentKeypair The agent keypair (signer + fee payer)
|
|
1013
|
+
* @param {string} label Branch label (max 32 chars — also used for PDA seed)
|
|
1014
|
+
* @param {string} hypothesis Branch hypothesis description (max 256 chars)
|
|
1015
|
+
* @param {string} [parentBranch] Parent branch label, or '' for top-level
|
|
1016
|
+
* @returns {Promise<{ sig: string, branchPda: string }>}
|
|
1017
|
+
*/
|
|
1018
|
+
async branchOpen(agentKeypair, label, hypothesis, parentBranch = '') {
|
|
1019
|
+
assertIsSigner(agentKeypair, 'agentKeypair');
|
|
1020
|
+
validateString(label, 'label', 32);
|
|
1021
|
+
validateString(hypothesis, 'hypothesis', 256);
|
|
1022
|
+
if (parentBranch !== '') {
|
|
1023
|
+
validateString(parentBranch, 'parentBranch', 32);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const { pda: agentRecordPDA } = AgentClient.deriveAgentRecord(agentKeypair.publicKey);
|
|
1027
|
+
|
|
1028
|
+
// Derive BranchRecord PDA: [b"branch", agentRecord, padLabel(label)]
|
|
1029
|
+
const [branchRecordPDA] = PublicKey.findProgramAddressSync(
|
|
1030
|
+
[Buffer.from('branch'), agentRecordPDA.toBuffer(), padLabel(label)],
|
|
1031
|
+
PROGRAM_ID
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
const data = Buffer.concat([
|
|
1035
|
+
DISCRIMINATORS.strategy_branch_open,
|
|
1036
|
+
encodeString(label),
|
|
1037
|
+
encodeString(parentBranch),
|
|
1038
|
+
encodeString(hypothesis),
|
|
1039
|
+
]);
|
|
1040
|
+
|
|
1041
|
+
const ix = new TransactionInstruction({
|
|
1042
|
+
programId: PROGRAM_ID,
|
|
1043
|
+
keys: [
|
|
1044
|
+
{ pubkey: agentKeypair.publicKey, isSigner: true, isWritable: true }, // agent_authority
|
|
1045
|
+
{ pubkey: agentRecordPDA, isSigner: false, isWritable: true }, // agent_record
|
|
1046
|
+
{ pubkey: branchRecordPDA, isSigner: false, isWritable: true }, // branch_record (init)
|
|
1047
|
+
{ pubkey: TREASURY, isSigner: false, isWritable: true }, // treasury
|
|
1048
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system_program
|
|
1049
|
+
],
|
|
1050
|
+
data,
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
const tx = new Transaction().add(ix);
|
|
1054
|
+
const sig = await this._sendAndConfirm(tx, [agentKeypair]);
|
|
1055
|
+
|
|
1056
|
+
return {
|
|
1057
|
+
sig,
|
|
1058
|
+
branchPda: branchRecordPDA.toBase58(),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Close a strategy branch on-chain.
|
|
1064
|
+
*
|
|
1065
|
+
* @param {Keypair} agentKeypair The agent keypair (signer + fee payer)
|
|
1066
|
+
* @param {string} label Branch label to close (must match opened label)
|
|
1067
|
+
* @param {number} outcome 1=success, 2=failure, 3=abandoned
|
|
1068
|
+
* @param {string} summaryCid IPFS CID of the closing summary
|
|
1069
|
+
* @returns {Promise<{ sig: string }>}
|
|
1070
|
+
*/
|
|
1071
|
+
async branchClose(agentKeypair, label, outcome, summaryCid) {
|
|
1072
|
+
assertIsSigner(agentKeypair, 'agentKeypair');
|
|
1073
|
+
validateString(label, 'label', 32);
|
|
1074
|
+
validateString(summaryCid, 'summaryCid', 64);
|
|
1075
|
+
|
|
1076
|
+
if (typeof outcome !== 'number' || ![1, 2, 3].includes(outcome)) {
|
|
1077
|
+
throw new AgentSDKError('outcome must be 1 (success), 2 (failure), or 3 (abandoned)', 'INVALID_INPUT');
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const { pda: agentRecordPDA } = AgentClient.deriveAgentRecord(agentKeypair.publicKey);
|
|
1081
|
+
|
|
1082
|
+
// Derive BranchRecord PDA: [b"branch", agentRecord, padLabel(label)]
|
|
1083
|
+
const [branchRecordPDA] = PublicKey.findProgramAddressSync(
|
|
1084
|
+
[Buffer.from('branch'), agentRecordPDA.toBuffer(), padLabel(label)],
|
|
1085
|
+
PROGRAM_ID
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
const data = Buffer.concat([
|
|
1089
|
+
DISCRIMINATORS.strategy_branch_close,
|
|
1090
|
+
encodeString(label),
|
|
1091
|
+
Buffer.from([outcome]), // u8
|
|
1092
|
+
encodeString(summaryCid),
|
|
1093
|
+
]);
|
|
1094
|
+
|
|
1095
|
+
const ix = new TransactionInstruction({
|
|
1096
|
+
programId: PROGRAM_ID,
|
|
1097
|
+
keys: [
|
|
1098
|
+
{ pubkey: agentKeypair.publicKey, isSigner: true, isWritable: true }, // agent_authority
|
|
1099
|
+
{ pubkey: agentRecordPDA, isSigner: false, isWritable: false }, // agent_record (NOT writable for close)
|
|
1100
|
+
{ pubkey: branchRecordPDA, isSigner: false, isWritable: true }, // branch_record
|
|
1101
|
+
{ pubkey: TREASURY, isSigner: false, isWritable: true }, // treasury
|
|
1102
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system_program
|
|
1103
|
+
],
|
|
1104
|
+
data,
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
const tx = new Transaction().add(ix);
|
|
1108
|
+
const sig = await this._sendAndConfirm(tx, [agentKeypair]);
|
|
1109
|
+
|
|
1110
|
+
return { sig };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Fetch last N decisions/memories from chain for an agent by reading transaction history.
|
|
1115
|
+
* Does NOT call serve_context on-chain (requires validator signature).
|
|
1116
|
+
* Reads transactions directly from RPC and filters for program instructions.
|
|
1117
|
+
*
|
|
1118
|
+
* @param {PublicKey|string} agentPda The AgentRecord PDA address
|
|
1119
|
+
* @param {number} [limit] Max number of entries to return (default 10)
|
|
1120
|
+
* @returns {Promise<{ entries: Array<{slot: number, sig: string, instruction: string, blockTime: number|null}>, count: number }>}
|
|
1121
|
+
*/
|
|
1122
|
+
async contextGet(agentPda, limit = 10) {
|
|
1123
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
1124
|
+
throw new AgentSDKError('limit must be a positive integer', 'INVALID_INPUT');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const agentPdaPubkey = new PublicKey(agentPda);
|
|
1128
|
+
const connection = this._getConnection();
|
|
1129
|
+
|
|
1130
|
+
// Fetch recent signatures for this address
|
|
1131
|
+
const sigs = await connection.getSignaturesForAddress(agentPdaPubkey, { limit });
|
|
1132
|
+
|
|
1133
|
+
const entries = [];
|
|
1134
|
+
|
|
1135
|
+
// Fetch and parse each transaction
|
|
1136
|
+
await Promise.all(sigs.map(async (sigInfo) => {
|
|
1137
|
+
try {
|
|
1138
|
+
const tx = await connection.getParsedTransaction(sigInfo.signature, {
|
|
1139
|
+
maxSupportedTransactionVersion: 0,
|
|
1140
|
+
});
|
|
1141
|
+
if (!tx || !tx.transaction) return;
|
|
1142
|
+
|
|
1143
|
+
const instructions = tx.transaction.message.instructions || [];
|
|
1144
|
+
for (const ix of instructions) {
|
|
1145
|
+
const programId = ix.programId ? ix.programId.toBase58() : null;
|
|
1146
|
+
if (programId !== PROGRAM_ID.toBase58()) continue;
|
|
1147
|
+
|
|
1148
|
+
// Classify instruction type from data discriminator if available
|
|
1149
|
+
let instructionType = 'Unknown';
|
|
1150
|
+
if (ix.data) {
|
|
1151
|
+
try {
|
|
1152
|
+
const rawData = Buffer.from(bs58decode(ix.data));
|
|
1153
|
+
const disc = rawData.slice(0, 8);
|
|
1154
|
+
if (disc.equals(DISCRIMINATORS.store_memory)) instructionType = 'StoreMemory';
|
|
1155
|
+
else if (disc.equals(DISCRIMINATORS.decision_write)) instructionType = 'DecisionWrite';
|
|
1156
|
+
else if (disc.equals(DISCRIMINATORS.strategy_branch_open)) instructionType = 'BranchOpen';
|
|
1157
|
+
else if (disc.equals(DISCRIMINATORS.strategy_branch_close)) instructionType = 'BranchClose';
|
|
1158
|
+
else if (disc.equals(DISCRIMINATORS.register_agent)) instructionType = 'RegisterAgent';
|
|
1159
|
+
} catch (_) { /* unparseable — leave as Unknown */ }
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
entries.push({
|
|
1163
|
+
slot: sigInfo.slot,
|
|
1164
|
+
sig: sigInfo.signature,
|
|
1165
|
+
instruction: instructionType,
|
|
1166
|
+
blockTime: sigInfo.blockTime || null,
|
|
1167
|
+
});
|
|
1168
|
+
break; // one entry per tx
|
|
1169
|
+
}
|
|
1170
|
+
} catch (_) {
|
|
1171
|
+
// skip transactions we can't parse
|
|
1172
|
+
}
|
|
1173
|
+
}));
|
|
1174
|
+
|
|
1175
|
+
// Sort by slot descending (most recent first)
|
|
1176
|
+
entries.sort((a, b) => b.slot - a.slot);
|
|
1177
|
+
|
|
1178
|
+
return { entries, count: entries.length };
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Utility: derive the AgentRecord PDA for any public key.
|
|
1183
|
+
*
|
|
1184
|
+
* @param {PublicKey|string} agentPublicKey
|
|
1185
|
+
* @returns {{ pda: PublicKey, bump: number }}
|
|
1186
|
+
*/
|
|
1187
|
+
getAgentPda(agentPublicKey) {
|
|
1188
|
+
const [pda, bump] = PublicKey.findProgramAddressSync(
|
|
1189
|
+
[Buffer.from('agent'), new PublicKey(agentPublicKey).toBuffer()],
|
|
1190
|
+
PROGRAM_ID
|
|
1191
|
+
);
|
|
1192
|
+
return { pda, bump };
|
|
1193
|
+
}
|
|
903
1194
|
}
|
|
904
1195
|
|
|
905
1196
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
906
1197
|
// Exports
|
|
907
1198
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
908
1199
|
|
|
1200
|
+
/**
|
|
1201
|
+
* Standalone utility: derive the AgentRecord PDA for any public key.
|
|
1202
|
+
* Mirrors AgentClient#getAgentPda but available without instantiating a client.
|
|
1203
|
+
*
|
|
1204
|
+
* @param {PublicKey|string} agentPublicKey
|
|
1205
|
+
* @returns {{ pda: PublicKey, bump: number }}
|
|
1206
|
+
*/
|
|
1207
|
+
function getAgentPda(agentPublicKey) {
|
|
1208
|
+
const [pda, bump] = PublicKey.findProgramAddressSync(
|
|
1209
|
+
[Buffer.from('agent'), new PublicKey(agentPublicKey).toBuffer()],
|
|
1210
|
+
PROGRAM_ID
|
|
1211
|
+
);
|
|
1212
|
+
return { pda, bump };
|
|
1213
|
+
}
|
|
1214
|
+
|
|
909
1215
|
module.exports = {
|
|
910
1216
|
AgentClient,
|
|
911
1217
|
AgentSDKError,
|
|
1218
|
+
getAgentPda,
|
|
912
1219
|
PROGRAM_ID,
|
|
913
1220
|
TREASURY,
|
|
914
1221
|
DEFAULT_RPC_URL,
|