@crabspace/cli 0.2.4 → 0.2.6

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/commands/init.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { loadKeypair, signForAction } from '../lib/sign.js';
9
9
  import { writeConfig, configExists, readConfig, getConfigDir } from '../lib/config.js';
10
- import { mkdirSync, writeFileSync, existsSync } from 'fs';
10
+ import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
11
11
  import { join } from 'path';
12
12
  import { createInterface } from 'readline';
13
13
 
@@ -217,6 +217,31 @@ export async function init(args) {
217
217
  };
218
218
  writeConfig(config);
219
219
 
220
+ // Initialize IsnadIdentity on-chain if not already
221
+ try {
222
+ console.log('');
223
+ console.log('⛓️ Checking Identity PDA on-chain...');
224
+ const { Keypair: SolKeypair } = await import('@solana/web3.js');
225
+ const { initializeOnChain } = await import('../lib/anchor.js');
226
+
227
+ const keypairPath = args.keypair || '~/.config/solana/id.json';
228
+ const resolvedPath = keypairPath.replace('~', process.env.HOME);
229
+ const keypairJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
230
+ const solKeypair = SolKeypair.fromSecretKey(Uint8Array.from(keypairJson));
231
+
232
+ const rpcUrl = args['rpc-url'] || 'https://api.mainnet-beta.solana.com';
233
+ const isnadHash = verifyData.isnad_hash || '0'.repeat(64);
234
+
235
+ const txSig = await initializeOnChain(solKeypair, isnadHash, rpcUrl);
236
+ if (txSig === 'already-initialized') {
237
+ console.log(' Identity PDA already exists.');
238
+ } else {
239
+ console.log(` On-chain init TX: ${txSig}`);
240
+ }
241
+ } catch (anchorErr) {
242
+ console.log(` ⚠️ On-chain init failed (non-blocking): ${anchorErr.message}`);
243
+ }
244
+
220
245
  console.log('');
221
246
  console.log('✅ Config saved to ~/.crabspace/config.json');
222
247
  console.log(` Agent: ${config.agentName} (id: ${config.agentId})`);
@@ -265,6 +290,32 @@ export async function init(args) {
265
290
  console.log('📂 Scaffolding identity files...');
266
291
  const paths = scaffoldIdentityFiles(config, data.bios_seed);
267
292
 
293
+ // 6. Initialize IsnadIdentity on-chain (non-blocking)
294
+ try {
295
+ console.log('');
296
+ console.log('⛓️ Initializing Identity PDA on-chain...');
297
+ const { Keypair: SolKeypair } = await import('@solana/web3.js');
298
+ const { initializeOnChain } = await import('../lib/anchor.js');
299
+
300
+ const keypairPath = args.keypair || '~/.config/solana/id.json';
301
+ const resolvedPath = keypairPath.replace('~', process.env.HOME);
302
+ const keypairJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
303
+ const solKeypair = SolKeypair.fromSecretKey(Uint8Array.from(keypairJson));
304
+
305
+ const rpcUrl = args['rpc-url'] || 'https://api.mainnet-beta.solana.com';
306
+ const isnadHash = data.agent?.isnad_hash || '0'.repeat(64);
307
+
308
+ const txSig = await initializeOnChain(solKeypair, isnadHash, rpcUrl);
309
+ if (txSig === 'already-initialized') {
310
+ console.log(' Identity PDA already exists.');
311
+ } else {
312
+ console.log(` On-chain init TX: ${txSig}`);
313
+ }
314
+ } catch (anchorErr) {
315
+ console.log(` ⚠️ On-chain init failed (non-blocking): ${anchorErr.message}`);
316
+ console.log(` Fix: Ensure wallet has SOL, then run \`crabspace submit\` later.`);
317
+ }
318
+
268
319
  console.log('');
269
320
  console.log('✅ Agent registered successfully!');
270
321
  console.log('');
@@ -168,7 +168,7 @@ export async function submit(args) {
168
168
  const keypairJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
169
169
  const solKeypair = SolKeypair.fromSecretKey(Uint8Array.from(keypairJson));
170
170
 
171
- const rpcUrl = args['rpc-url'] || 'https://api.devnet.solana.com';
171
+ // Use the rpcUrl defined at the start of the function
172
172
  txSig = await anchorOnChain(solKeypair, contentHash, rpcUrl);
173
173
 
174
174
  // PATCH the anchor route to link the tx sig to the DB entry
@@ -182,7 +182,7 @@ export async function submit(args) {
182
182
  } catch (anchorErr) {
183
183
  console.log('');
184
184
  console.log(` ⚠️ Work encrypted and saved to database. On-chain anchor FAILED: ${anchorErr.message}`);
185
- console.log(` Retry: crabspace anchor --id ${workId || '<workId>'}`);
185
+ console.log(` Fix: Ensure wallet has SOL on the correct network, then retry by running submit again.`);
186
186
  }
187
187
  }
188
188
 
@@ -200,7 +200,8 @@ export async function submit(args) {
200
200
  console.log(` Hash: ${contentHash.slice(0, 16)}...`);
201
201
  if (txSig) {
202
202
  console.log(` TX: ${txSig}`);
203
- console.log(` Explorer: https://explorer.solana.com/tx/${txSig}?cluster=devnet`);
203
+ const envSuffix = rpcUrl.includes('devnet') ? '?cluster=devnet' : '';
204
+ console.log(` Explorer: https://explorer.solana.com/tx/${txSig}${envSuffix}`);
204
205
  } else {
205
206
  console.log(' Chain: stored in database (on-chain anchoring pending)');
206
207
  }
package/lib/anchor.js CHANGED
@@ -24,6 +24,11 @@ const LOG_WORK_DISCRIMINATOR = Buffer.from([
24
24
  0xaa, 0x88, 0x30, 0x67, 0xdc, 0x86, 0xee, 0x73
25
25
  ]);
26
26
 
27
+ // Anchor discriminator for initialize (first 8 bytes of sha256("global:initialize"))
28
+ const INITIALIZE_DISCRIMINATOR = Buffer.from([
29
+ 0xaf, 0xaf, 0x6d, 0x1f, 0x0d, 0x98, 0x9b, 0xed
30
+ ]);
31
+
27
32
  /**
28
33
  * Derive the IsnadIdentity PDA for a given creator wallet.
29
34
  * Seeds: ["isnad", creator_pubkey]
@@ -90,6 +95,59 @@ export async function anchorOnChain(keypair, workHash, rpcUrl = 'https://api.dev
90
95
  return signature;
91
96
  }
92
97
 
98
+ /**
99
+ * Initialize an agent identity on-chain by calling the initialize instruction.
100
+ *
101
+ * @param {Keypair} keypair - The agent's Solana keypair (creator/payer)
102
+ * @param {string} headHash - Hex string of the initial work hash
103
+ * @param {string} rpcUrl - Solana RPC endpoint
104
+ * @returns {string} Transaction signature
105
+ */
106
+ export async function initializeOnChain(keypair, headHash, rpcUrl = 'https://api.mainnet-beta.solana.com') {
107
+ const connection = new Connection(rpcUrl, 'confirmed');
108
+ const creatorPubkey = keypair.publicKey;
109
+
110
+ const [identityPda] = deriveIdentityPda(creatorPubkey);
111
+
112
+ const hashHex = (headHash || '0'.repeat(64)).replace('0x', '');
113
+ const hashBytes = Buffer.from(hashHex, 'hex');
114
+ const finalHash = new Uint8Array(32);
115
+ finalHash.set(new Uint8Array(hashBytes));
116
+
117
+ const data = Buffer.concat([INITIALIZE_DISCRIMINATOR, Buffer.from(finalHash)]);
118
+
119
+ const ix = new TransactionInstruction({
120
+ keys: [
121
+ { pubkey: identityPda, isSigner: false, isWritable: true }, // identity account
122
+ { pubkey: creatorPubkey, isSigner: true, isWritable: true }, // creator (signer, payer)
123
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system program
124
+ ],
125
+ programId: PROGRAM_ID,
126
+ data,
127
+ });
128
+
129
+ const { blockhash } = await connection.getLatestBlockhash('confirmed');
130
+ const messageV0 = new TransactionMessage({
131
+ payerKey: creatorPubkey,
132
+ recentBlockhash: blockhash,
133
+ instructions: [ix],
134
+ }).compileToV0Message();
135
+
136
+ const tx = new VersionedTransaction(messageV0);
137
+ tx.sign([keypair]);
138
+
139
+ try {
140
+ const signature = await connection.sendTransaction(tx, { skipPreflight: false });
141
+ await connection.confirmTransaction(signature, 'confirmed');
142
+ return signature;
143
+ } catch (e) {
144
+ if (e.message && e.message.includes('already in use')) {
145
+ return 'already-initialized';
146
+ }
147
+ throw e;
148
+ }
149
+ }
150
+
93
151
  /**
94
152
  * Pay the CrabSpace work entry fee by transferring lamports to the treasury.
95
153
  * Called automatically by submit.js when the API returns HTTP 402.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crabspace/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Identity persistence for AI agents. Register, log work, anchor on-chain.",
5
5
  "bin": {
6
6
  "crabspace": "index.js"
@@ -39,4 +39,4 @@
39
39
  "bs58": "^6.0.0",
40
40
  "@solana/web3.js": "^1.98.0"
41
41
  }
42
- }
42
+ }