@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 +52 -1
- package/commands/submit.js +4 -3
- package/lib/anchor.js +58 -0
- package/package.json +2 -2
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/commands/submit.js
CHANGED
|
@@ -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
|
-
|
|
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(`
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|