@crabspace/cli 0.2.5 → 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('');
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.5",
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"