repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * leviathan/src/identity/index.ts — Keypair + on-chain identity |
| #3 | * |
| #4 | * SAFETY: This file generates and manages keypairs but NEVER logs them. |
| #5 | * The keystore.json is stored at ~/.openclawd/keystore.json (mode 0600). |
| #6 | * No private key is ever passed to the LLM, the TUI, or any log. |
| #7 | * |
| #8 | * On-chain identity: Metaplex MPL Core Asset + SAS attestation. |
| #9 | * The leviathan's pubkey IS its identity for life. |
| #10 | */ |
| #11 | |
| #12 | import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'; |
| #13 | import { join } from 'node:path'; |
| #14 | import { homedir } from 'node:os'; |
| #15 | import nacl from 'tweetnacl'; |
| #16 | import bs58 from 'bs58'; |
| #17 | |
| #18 | const OPENCLAWD_DIR = join(homedir(), '.openclawd'); |
| #19 | const KEYSTORE_PATH = join(OPENCLAWD_DIR, 'keystore.json'); |
| #20 | |
| #21 | interface Keystore { |
| #22 | pubkey: string; |
| #23 | // secret is stored as base58 — NEVER log, never pass to LLM |
| #24 | _secret: string; |
| #25 | createdAt: string; |
| #26 | } |
| #27 | |
| #28 | /** Generate a new Ed25519 keypair. Persists to keystore. */ |
| #29 | export function generateKeypair(): { pubkey: string } { |
| #30 | mkdirSync(OPENCLAWD_DIR, { recursive: true, mode: 0o700 }); |
| #31 | |
| #32 | const kp = nacl.sign.keyPair(); |
| #33 | const pubkey = bs58.encode(kp.publicKey); |
| #34 | |
| #35 | const keystore: Keystore = { |
| #36 | pubkey, |
| #37 | _secret: bs58.encode(kp.secretKey), |
| #38 | createdAt: new Date().toISOString(), |
| #39 | }; |
| #40 | |
| #41 | writeFileSync(KEYSTORE_PATH, JSON.stringify(keystore), { mode: 0o600 }); |
| #42 | return { pubkey }; |
| #43 | } |
| #44 | |
| #45 | /** Load pubkey from existing keystore (never exposes secret) */ |
| #46 | export function loadPubkey(): string | null { |
| #47 | if (!existsSync(KEYSTORE_PATH)) return null; |
| #48 | try { |
| #49 | const ks = JSON.parse(readFileSync(KEYSTORE_PATH, 'utf8')) as Keystore; |
| #50 | return ks.pubkey; |
| #51 | } catch { |
| #52 | return null; |
| #53 | } |
| #54 | } |
| #55 | |
| #56 | /** Sign a message with the stored keypair (returns base58 signature) */ |
| #57 | export function signMessage(message: Uint8Array): string { |
| #58 | const ks = JSON.parse(readFileSync(KEYSTORE_PATH, 'utf8')) as Keystore; |
| #59 | const secret = bs58.decode(ks._secret); |
| #60 | const sig = nacl.sign.detached(message, secret); |
| #61 | return bs58.encode(sig); |
| #62 | } |
| #63 | |
| #64 | /** Check if a keystore exists (i.e., leviathan has been spawned) */ |
| #65 | export function isSpawned(): boolean { |
| #66 | return existsSync(KEYSTORE_PATH); |
| #67 | } |
| #68 | |
| #69 | /** Format pubkey for safe display (abbreviated) */ |
| #70 | export function shortPubkey(pubkey: string): string { |
| #71 | return pubkey.slice(0, 6) + '…' + pubkey.slice(-4); |
| #72 | } |
| #73 | |
| #74 | /** |
| #75 | * Build the SAS (Solana Attestation Service) attestation payload. |
| #76 | * In production, this is submitted on-chain via the SAS program. |
| #77 | * Here we return the payload for inspection / dry-run. |
| #78 | */ |
| #79 | export function buildSASAttestation(opts: { |
| #80 | pubkey: string; |
| #81 | name: string; |
| #82 | creatorPubkey: string; |
| #83 | parentPubkey?: string; |
| #84 | constitutionHash: string; |
| #85 | }): Record<string, unknown> { |
| #86 | return { |
| #87 | schema: 'clawd-agent-v1', |
| #88 | pubkey: opts.pubkey, |
| #89 | name: opts.name, |
| #90 | creatorPubkey: opts.creatorPubkey, |
| #91 | parentPubkey: opts.parentPubkey ?? null, |
| #92 | spawnedAt: new Date().toISOString(), |
| #93 | constitutionHash: opts.constitutionHash, |
| #94 | network: process.env['SOLANA_NETWORK'] ?? 'devnet', |
| #95 | version: '1.0.0', |
| #96 | }; |
| #97 | } |
| #98 |