@buildersgarden/siwa 0.0.1

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/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # SIWA — Sign In With Agent
2
+
3
+ A Claude Code skill for registering AI agents on the [ERC-8004 (Trustless Agents)](https://github.com/builders-garden/ERC-8004) standard and authenticating them via SIWA, a challenge-response protocol inspired by [EIP-4361 (SIWE)](https://eips.ethereum.org/EIPS/eip-4361).
4
+
5
+ ## What it does
6
+
7
+ - **Create Wallet** — Generate an Ethereum wallet with secure key storage (encrypted V3 file, keyring proxy, or env var)
8
+ - **Register Agent (Sign Up)** — Mint an ERC-721 identity NFT on the ERC-8004 Identity Registry with metadata (endpoints, trust model, services)
9
+ - **Authenticate (Sign In)** — Prove ownership of an onchain agent identity by signing a structured SIWA message; receive a JWT from the relying party
10
+
11
+ ## Project Structure
12
+
13
+ ```
14
+ scripts/ Core skill implementation
15
+ keystore.ts Secure key storage (3 backends)
16
+ memory.ts MEMORY.md read/write helpers
17
+ siwa.ts SIWA message building, signing, verification
18
+ create_wallet.ts Wallet creation flow
19
+ register_agent.ts Onchain registration flow
20
+
21
+ references/ Protocol documentation
22
+ siwa-spec.md Full SIWA specification
23
+ security-model.md Threat model and keystore architecture
24
+ contract-addresses.md Deployed registry addresses
25
+ registration-guide.md Registration file schema
26
+
27
+ assets/ Templates
28
+ MEMORY.md.template
29
+ registration-template.json
30
+
31
+ test/ Local test environment (Express server + CLI agent)
32
+ ```
33
+
34
+ ## Quick Start (Local Test)
35
+
36
+ ```bash
37
+ cd test
38
+ pnpm install
39
+
40
+ # Terminal 1: Start the SIWA relying-party server
41
+ pnpm run server
42
+
43
+ # Terminal 2: Run the full agent flow (create wallet → register → sign in → authenticated call)
44
+ pnpm run agent:flow
45
+
46
+ # Or run both at once:
47
+ pnpm run dev
48
+ ```
49
+
50
+ See [`test/README.md`](test/README.md) for full details on the test environment.
51
+
52
+ ## Security Model
53
+
54
+ The agent's private key never enters the agent's context window. All cryptographic operations are handled by the keystore module, which loads the key, uses it, and discards it immediately.
55
+
56
+ | Backend | Storage | Use case |
57
+ |---------|---------|----------|
58
+ | `proxy` | HMAC-authenticated HTTP to a keyring proxy server | Production — process isolation, key never enters agent |
59
+ | `encrypted-file` | Ethereum V3 JSON Keystore (AES-128-CTR + scrypt) | Local development, Docker, CI |
60
+ | `env` | `AGENT_PRIVATE_KEY` environment variable | Testing only |
61
+
62
+ See [`references/security-model.md`](references/security-model.md) for the full threat model.
63
+
64
+ ## Tech Stack
65
+
66
+ - **TypeScript** (ES modules, strict mode)
67
+ - **ethers.js** v6 — wallet management and contract interaction
68
+ - **pnpm** — package manager
69
+
70
+ ## References
71
+
72
+ - [SKILL.md](SKILL.md) — Full skill documentation and API reference
73
+ - [ERC-8004 specification](https://github.com/builders-garden/ERC-8004)
74
+ - [SIWA protocol spec](references/siwa-spec.md)
75
+
@@ -0,0 +1,5 @@
1
+ export * from './keystore.js';
2
+ export * from './siwa.js';
3
+ export * from './memory.js';
4
+ export * from './proxy-auth.js';
5
+ export * from './registry.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './keystore.js';
2
+ export * from './siwa.js';
3
+ export * from './memory.js';
4
+ export * from './proxy-auth.js';
5
+ export * from './registry.js';
@@ -0,0 +1,126 @@
1
+ /**
2
+ * keystore.ts
3
+ *
4
+ * Secure private key storage abstraction for ERC-8004 agents.
5
+ *
6
+ * Three backends, in order of preference:
7
+ * 0. Keyring Proxy (via HMAC-authenticated HTTP) — key never enters agent process
8
+ * 1. Ethereum V3 Encrypted JSON Keystore (via ethers.js) — password-encrypted file on disk
9
+ * 2. Environment variable fallback (AGENT_PRIVATE_KEY) — least secure, for CI/testing only
10
+ *
11
+ * The private key NEVER leaves this module as a return value.
12
+ * External code interacts only through:
13
+ * - createWallet() → returns { address } (no private key)
14
+ * - importWallet(pk) → stores and returns { address }
15
+ * - signMessage(msg) → returns { signature }
16
+ * - signTransaction(tx) → returns { signedTx }
17
+ * - signAuthorization(auth) → returns signed EIP-7702 authorization (no key exposed)
18
+ * - getAddress() → returns the public address
19
+ * - hasWallet() → returns boolean
20
+ *
21
+ * EIP-7702 Support (requires ethers >= 6.14.3):
22
+ * Wallets are standard EOAs created via ethers.Wallet.createRandom().
23
+ * EIP-7702 allows these EOAs to temporarily delegate to smart contract
24
+ * implementations via authorization lists in type 4 transactions.
25
+ * Use signAuthorization() to sign delegation authorizations without
26
+ * exposing the private key.
27
+ *
28
+ * Dependencies:
29
+ * npm install ethers
30
+ *
31
+ * Configuration (via env vars or passed options):
32
+ * KEYSTORE_BACKEND — "encrypted-file" | "env" | "proxy" (auto-detected if omitted)
33
+ * KEYSTORE_PASSWORD — Password for encrypted-file backend (prompted interactively if omitted)
34
+ * KEYSTORE_PATH — Path to encrypted keystore file (default: ./agent-keystore.json)
35
+ * AGENT_PRIVATE_KEY — Fallback for env backend only
36
+ */
37
+ import { ethers } from 'ethers';
38
+ export type KeystoreBackend = 'encrypted-file' | 'env' | 'proxy';
39
+ export interface KeystoreConfig {
40
+ backend?: KeystoreBackend;
41
+ keystorePath?: string;
42
+ password?: string;
43
+ proxyUrl?: string;
44
+ proxySecret?: string;
45
+ }
46
+ export interface WalletInfo {
47
+ address: string;
48
+ backend: KeystoreBackend;
49
+ keystorePath?: string;
50
+ }
51
+ export interface SignResult {
52
+ signature: string;
53
+ address: string;
54
+ }
55
+ export interface AuthorizationRequest {
56
+ address: string;
57
+ chainId?: number;
58
+ nonce?: number;
59
+ }
60
+ export interface SignedAuthorization {
61
+ address: string;
62
+ nonce: number;
63
+ chainId: number;
64
+ yParity: number;
65
+ r: string;
66
+ s: string;
67
+ }
68
+ export declare function detectBackend(): Promise<KeystoreBackend>;
69
+ /**
70
+ * Create a new random wallet and store it securely.
71
+ * Returns only the public address — NEVER the private key.
72
+ */
73
+ export declare function createWallet(config?: KeystoreConfig): Promise<WalletInfo>;
74
+ /**
75
+ * Import an existing private key into the secure store.
76
+ * After calling this, the caller should discard its copy of the key.
77
+ * Returns only the public address.
78
+ */
79
+ export declare function importWallet(privateKey: string, config?: KeystoreConfig): Promise<WalletInfo>;
80
+ /**
81
+ * Check if a wallet is available in any backend.
82
+ */
83
+ export declare function hasWallet(config?: KeystoreConfig): Promise<boolean>;
84
+ /**
85
+ * Get the wallet's public address (no private key exposed).
86
+ */
87
+ export declare function getAddress(config?: KeystoreConfig): Promise<string | null>;
88
+ /**
89
+ * Sign a message (EIP-191 personal_sign).
90
+ * The private key is loaded, used, and immediately discarded.
91
+ * Only the signature is returned.
92
+ */
93
+ export declare function signMessage(message: string, config?: KeystoreConfig): Promise<SignResult>;
94
+ /**
95
+ * Sign a transaction.
96
+ * The private key is loaded, used, and immediately discarded.
97
+ * Only the signed transaction is returned.
98
+ */
99
+ export declare function signTransaction(tx: ethers.TransactionRequest, config?: KeystoreConfig): Promise<{
100
+ signedTx: string;
101
+ address: string;
102
+ }>;
103
+ /**
104
+ * Sign an EIP-7702 authorization for delegating the EOA to a contract.
105
+ * Requires ethers >= 6.14.3.
106
+ *
107
+ * This allows the agent's EOA to temporarily act as a smart contract
108
+ * during a type 4 transaction. The private key is loaded, used, and
109
+ * immediately discarded — only the signed authorization tuple is returned.
110
+ *
111
+ * @param auth — Authorization request (target contract address, optional chainId/nonce)
112
+ * @returns Signed authorization tuple (address, nonce, chainId, yParity, r, s)
113
+ */
114
+ export declare function signAuthorization(auth: AuthorizationRequest, config?: KeystoreConfig): Promise<SignedAuthorization>;
115
+ /**
116
+ * Get a connected signer (for contract interactions).
117
+ * NOTE: This returns a signer with the private key in memory.
118
+ * Use only within a narrow scope and discard immediately.
119
+ * Prefer signMessage() / signTransaction() when possible.
120
+ */
121
+ export declare function getSigner(provider: ethers.Provider, config?: KeystoreConfig): Promise<ethers.Wallet>;
122
+ /**
123
+ * Delete the stored wallet from the active backend.
124
+ * DESTRUCTIVE — the identity is lost if no backup exists.
125
+ */
126
+ export declare function deleteWallet(config?: KeystoreConfig): Promise<boolean>;
@@ -0,0 +1,355 @@
1
+ /**
2
+ * keystore.ts
3
+ *
4
+ * Secure private key storage abstraction for ERC-8004 agents.
5
+ *
6
+ * Three backends, in order of preference:
7
+ * 0. Keyring Proxy (via HMAC-authenticated HTTP) — key never enters agent process
8
+ * 1. Ethereum V3 Encrypted JSON Keystore (via ethers.js) — password-encrypted file on disk
9
+ * 2. Environment variable fallback (AGENT_PRIVATE_KEY) — least secure, for CI/testing only
10
+ *
11
+ * The private key NEVER leaves this module as a return value.
12
+ * External code interacts only through:
13
+ * - createWallet() → returns { address } (no private key)
14
+ * - importWallet(pk) → stores and returns { address }
15
+ * - signMessage(msg) → returns { signature }
16
+ * - signTransaction(tx) → returns { signedTx }
17
+ * - signAuthorization(auth) → returns signed EIP-7702 authorization (no key exposed)
18
+ * - getAddress() → returns the public address
19
+ * - hasWallet() → returns boolean
20
+ *
21
+ * EIP-7702 Support (requires ethers >= 6.14.3):
22
+ * Wallets are standard EOAs created via ethers.Wallet.createRandom().
23
+ * EIP-7702 allows these EOAs to temporarily delegate to smart contract
24
+ * implementations via authorization lists in type 4 transactions.
25
+ * Use signAuthorization() to sign delegation authorizations without
26
+ * exposing the private key.
27
+ *
28
+ * Dependencies:
29
+ * npm install ethers
30
+ *
31
+ * Configuration (via env vars or passed options):
32
+ * KEYSTORE_BACKEND — "encrypted-file" | "env" | "proxy" (auto-detected if omitted)
33
+ * KEYSTORE_PASSWORD — Password for encrypted-file backend (prompted interactively if omitted)
34
+ * KEYSTORE_PATH — Path to encrypted keystore file (default: ./agent-keystore.json)
35
+ * AGENT_PRIVATE_KEY — Fallback for env backend only
36
+ */
37
+ import { ethers } from 'ethers';
38
+ import * as fs from 'fs';
39
+ import * as crypto from 'crypto';
40
+ import { computeHmac } from './proxy-auth.js';
41
+ // ---------------------------------------------------------------------------
42
+ // Constants
43
+ // ---------------------------------------------------------------------------
44
+ const DEFAULT_KEYSTORE_PATH = './agent-keystore.json';
45
+ // ---------------------------------------------------------------------------
46
+ // Proxy backend — HMAC-authenticated HTTP to a keyring proxy server
47
+ // ---------------------------------------------------------------------------
48
+ async function proxyRequest(config, endpoint, body = {}) {
49
+ const url = config.proxyUrl || process.env.KEYRING_PROXY_URL;
50
+ const secret = config.proxySecret || process.env.KEYRING_PROXY_SECRET;
51
+ if (!url)
52
+ throw new Error('Proxy backend requires KEYRING_PROXY_URL or config.proxyUrl');
53
+ if (!secret)
54
+ throw new Error('Proxy backend requires KEYRING_PROXY_SECRET or config.proxySecret');
55
+ const bodyStr = JSON.stringify(body, (_key, value) => typeof value === 'bigint' ? '0x' + value.toString(16) : value);
56
+ const hmacHeaders = computeHmac(secret, 'POST', endpoint, bodyStr);
57
+ const res = await fetch(`${url}${endpoint}`, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ ...hmacHeaders,
62
+ },
63
+ body: bodyStr,
64
+ });
65
+ if (!res.ok) {
66
+ const text = await res.text();
67
+ throw new Error(`Proxy ${endpoint} failed (${res.status}): ${text}`);
68
+ }
69
+ return res.json();
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Backend detection
73
+ // ---------------------------------------------------------------------------
74
+ export async function detectBackend() {
75
+ // 0. Proxy backend (if URL is set)
76
+ if (process.env.KEYRING_PROXY_URL)
77
+ return 'proxy';
78
+ // 1. Check for existing encrypted keystore file
79
+ if (fs.existsSync(process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH)) {
80
+ return 'encrypted-file';
81
+ }
82
+ // 2. Check for env var
83
+ if (process.env.AGENT_PRIVATE_KEY)
84
+ return 'env';
85
+ // 3. Default to encrypted-file (will be created on first use)
86
+ return 'encrypted-file';
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Encrypted V3 JSON Keystore backend (ethers.js built-in)
90
+ // ---------------------------------------------------------------------------
91
+ async function encryptedFileStore(privateKey, address, password, filePath) {
92
+ const account = { address, privateKey };
93
+ // ethers v6: encryptKeystoreJsonSync or encryptKeystoreJson
94
+ const json = await ethers.encryptKeystoreJson(account, password);
95
+ fs.writeFileSync(filePath, json, { mode: 0o600 }); // Owner-only read/write
96
+ }
97
+ async function encryptedFileLoad(password, filePath) {
98
+ if (!fs.existsSync(filePath))
99
+ return null;
100
+ const json = fs.readFileSync(filePath, 'utf-8');
101
+ const wallet = await ethers.Wallet.fromEncryptedJson(json, password);
102
+ return wallet.privateKey;
103
+ }
104
+ function encryptedFileExists(filePath) {
105
+ return fs.existsSync(filePath);
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Password derivation for encrypted-file backend
109
+ //
110
+ // When no explicit password is given, derive one from the machine's identity.
111
+ // This is NOT meant as a strong user password — it's a fallback so agents
112
+ // can operate without interactive prompts. For production, always set
113
+ // KEYSTORE_PASSWORD or use the OS keychain.
114
+ // ---------------------------------------------------------------------------
115
+ function deriveMachinePassword() {
116
+ const factors = [
117
+ process.env.USER || process.env.USERNAME || 'agent',
118
+ process.env.HOME || process.env.USERPROFILE || '/tmp',
119
+ require('os').hostname(),
120
+ require('os').platform(),
121
+ ];
122
+ return crypto
123
+ .createHash('sha256')
124
+ .update(factors.join(':'))
125
+ .digest('hex');
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Public API — the ONLY way external code touches private keys
129
+ // ---------------------------------------------------------------------------
130
+ /**
131
+ * Create a new random wallet and store it securely.
132
+ * Returns only the public address — NEVER the private key.
133
+ */
134
+ export async function createWallet(config = {}) {
135
+ const backend = config.backend || await detectBackend();
136
+ const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
137
+ if (backend === 'proxy') {
138
+ const data = await proxyRequest(config, '/create-wallet');
139
+ return { address: data.address, backend, keystorePath: undefined };
140
+ }
141
+ const wallet = ethers.Wallet.createRandom();
142
+ const privateKey = wallet.privateKey;
143
+ const address = wallet.address;
144
+ switch (backend) {
145
+ case 'encrypted-file': {
146
+ const password = config.password || process.env.KEYSTORE_PASSWORD || deriveMachinePassword();
147
+ await encryptedFileStore(privateKey, address, password, keystorePath);
148
+ break;
149
+ }
150
+ case 'env':
151
+ // For env backend, we print the key ONCE for the operator to capture.
152
+ // This is the ONLY time the raw key is ever exposed.
153
+ console.log('=== ENV BACKEND (testing only) ===');
154
+ console.log(`Set this in your environment:`);
155
+ console.log(` export AGENT_PRIVATE_KEY="${privateKey}"`);
156
+ console.log('=================================');
157
+ break;
158
+ }
159
+ return {
160
+ address,
161
+ backend,
162
+ keystorePath: backend === 'encrypted-file' ? keystorePath : undefined,
163
+ };
164
+ }
165
+ /**
166
+ * Import an existing private key into the secure store.
167
+ * After calling this, the caller should discard its copy of the key.
168
+ * Returns only the public address.
169
+ */
170
+ export async function importWallet(privateKey, config = {}) {
171
+ const backend = config.backend || await detectBackend();
172
+ if (backend === 'proxy') {
173
+ throw new Error('importWallet() is not supported via proxy. Import the wallet on the proxy server directly.');
174
+ }
175
+ const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
176
+ const wallet = new ethers.Wallet(privateKey);
177
+ const address = wallet.address;
178
+ switch (backend) {
179
+ case 'encrypted-file': {
180
+ const password = config.password || process.env.KEYSTORE_PASSWORD || deriveMachinePassword();
181
+ await encryptedFileStore(privateKey, address, password, keystorePath);
182
+ break;
183
+ }
184
+ case 'env':
185
+ // Nothing to persist for env backend
186
+ break;
187
+ }
188
+ return {
189
+ address,
190
+ backend,
191
+ keystorePath: backend === 'encrypted-file' ? keystorePath : undefined,
192
+ };
193
+ }
194
+ /**
195
+ * Check if a wallet is available in any backend.
196
+ */
197
+ export async function hasWallet(config = {}) {
198
+ const backend = config.backend || await detectBackend();
199
+ if (backend === 'proxy') {
200
+ const data = await proxyRequest(config, '/has-wallet');
201
+ return data.hasWallet;
202
+ }
203
+ const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
204
+ switch (backend) {
205
+ case 'encrypted-file':
206
+ return encryptedFileExists(keystorePath);
207
+ case 'env':
208
+ return !!process.env.AGENT_PRIVATE_KEY;
209
+ }
210
+ }
211
+ /**
212
+ * Get the wallet's public address (no private key exposed).
213
+ */
214
+ export async function getAddress(config = {}) {
215
+ const backend = config.backend || await detectBackend();
216
+ if (backend === 'proxy') {
217
+ const data = await proxyRequest(config, '/get-address');
218
+ return data.address;
219
+ }
220
+ const wallet = await _loadWalletInternal(config);
221
+ if (!wallet)
222
+ return null;
223
+ const address = wallet.address;
224
+ // wallet goes out of scope and is GC'd — private key not returned
225
+ return address;
226
+ }
227
+ /**
228
+ * Sign a message (EIP-191 personal_sign).
229
+ * The private key is loaded, used, and immediately discarded.
230
+ * Only the signature is returned.
231
+ */
232
+ export async function signMessage(message, config = {}) {
233
+ const backend = config.backend || await detectBackend();
234
+ if (backend === 'proxy') {
235
+ const data = await proxyRequest(config, '/sign-message', { message });
236
+ return { signature: data.signature, address: data.address };
237
+ }
238
+ const wallet = await _loadWalletInternal(config);
239
+ if (!wallet)
240
+ throw new Error('No wallet found. Run createWallet() first.');
241
+ const signature = await wallet.signMessage(message);
242
+ const address = wallet.address;
243
+ // wallet goes out of scope — private key discarded
244
+ return { signature, address };
245
+ }
246
+ /**
247
+ * Sign a transaction.
248
+ * The private key is loaded, used, and immediately discarded.
249
+ * Only the signed transaction is returned.
250
+ */
251
+ export async function signTransaction(tx, config = {}) {
252
+ const backend = config.backend || await detectBackend();
253
+ if (backend === 'proxy') {
254
+ const data = await proxyRequest(config, '/sign-transaction', { tx: tx });
255
+ return { signedTx: data.signedTx, address: data.address };
256
+ }
257
+ const wallet = await _loadWalletInternal(config);
258
+ if (!wallet)
259
+ throw new Error('No wallet found. Run createWallet() first.');
260
+ const signedTx = await wallet.signTransaction(tx);
261
+ const address = wallet.address;
262
+ return { signedTx, address };
263
+ }
264
+ /**
265
+ * Sign an EIP-7702 authorization for delegating the EOA to a contract.
266
+ * Requires ethers >= 6.14.3.
267
+ *
268
+ * This allows the agent's EOA to temporarily act as a smart contract
269
+ * during a type 4 transaction. The private key is loaded, used, and
270
+ * immediately discarded — only the signed authorization tuple is returned.
271
+ *
272
+ * @param auth — Authorization request (target contract address, optional chainId/nonce)
273
+ * @returns Signed authorization tuple (address, nonce, chainId, yParity, r, s)
274
+ */
275
+ export async function signAuthorization(auth, config = {}) {
276
+ const backend = config.backend || await detectBackend();
277
+ if (backend === 'proxy') {
278
+ const data = await proxyRequest(config, '/sign-authorization', { auth });
279
+ return data;
280
+ }
281
+ const wallet = await _loadWalletInternal(config);
282
+ if (!wallet)
283
+ throw new Error('No wallet found. Run createWallet() first.');
284
+ // ethers v6.14.3+ exposes wallet.authorize()
285
+ if (typeof wallet.authorize !== 'function') {
286
+ throw new Error('wallet.authorize() not available. EIP-7702 requires ethers >= 6.14.3. ' +
287
+ 'Run: pnpm add ethers@latest');
288
+ }
289
+ const authorization = await wallet.authorize({
290
+ address: auth.address,
291
+ ...(auth.chainId !== undefined && { chainId: auth.chainId }),
292
+ ...(auth.nonce !== undefined && { nonce: auth.nonce }),
293
+ });
294
+ // wallet goes out of scope — private key discarded
295
+ return authorization;
296
+ }
297
+ /**
298
+ * Get a connected signer (for contract interactions).
299
+ * NOTE: This returns a signer with the private key in memory.
300
+ * Use only within a narrow scope and discard immediately.
301
+ * Prefer signMessage() / signTransaction() when possible.
302
+ */
303
+ export async function getSigner(provider, config = {}) {
304
+ const backend = config.backend || await detectBackend();
305
+ if (backend === 'proxy') {
306
+ throw new Error('getSigner() is not supported via proxy. The private key cannot be serialized over HTTP. Use signMessage() or signTransaction() instead.');
307
+ }
308
+ const wallet = await _loadWalletInternal(config);
309
+ if (!wallet)
310
+ throw new Error('No wallet found. Run createWallet() first.');
311
+ return wallet.connect(provider);
312
+ }
313
+ /**
314
+ * Delete the stored wallet from the active backend.
315
+ * DESTRUCTIVE — the identity is lost if no backup exists.
316
+ */
317
+ export async function deleteWallet(config = {}) {
318
+ const backend = config.backend || await detectBackend();
319
+ if (backend === 'proxy') {
320
+ throw new Error('deleteWallet() is not supported via proxy. Delete the wallet on the proxy server directly.');
321
+ }
322
+ const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
323
+ switch (backend) {
324
+ case 'encrypted-file':
325
+ if (fs.existsSync(keystorePath)) {
326
+ fs.unlinkSync(keystorePath);
327
+ return true;
328
+ }
329
+ return false;
330
+ case 'env':
331
+ console.warn('Cannot delete env-based wallet. Unset AGENT_PRIVATE_KEY manually.');
332
+ return false;
333
+ }
334
+ }
335
+ // ---------------------------------------------------------------------------
336
+ // Internal — loads the wallet. NEVER exposed publicly.
337
+ // ---------------------------------------------------------------------------
338
+ async function _loadWalletInternal(config = {}) {
339
+ const backend = config.backend || await detectBackend();
340
+ const keystorePath = config.keystorePath || process.env.KEYSTORE_PATH || DEFAULT_KEYSTORE_PATH;
341
+ let privateKey = null;
342
+ switch (backend) {
343
+ case 'encrypted-file': {
344
+ const password = config.password || process.env.KEYSTORE_PASSWORD || deriveMachinePassword();
345
+ privateKey = await encryptedFileLoad(password, keystorePath);
346
+ break;
347
+ }
348
+ case 'env':
349
+ privateKey = process.env.AGENT_PRIVATE_KEY || null;
350
+ break;
351
+ }
352
+ if (!privateKey)
353
+ return null;
354
+ return new ethers.Wallet(privateKey);
355
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * memory.ts
3
+ *
4
+ * Read/write helpers for the agent's MEMORY.md public identity file.
5
+ * MEMORY.md uses the pattern: - **Key**: `value`
6
+ *
7
+ * IMPORTANT: This file stores ONLY public data (address, agentId, etc.).
8
+ * Private keys are managed exclusively by keystore.ts.
9
+ *
10
+ * Dependencies: fs (Node built-in)
11
+ */
12
+ /**
13
+ * Ensure MEMORY.md exists. If not, copy from template or create minimal.
14
+ */
15
+ export declare function ensureMemoryExists(memoryPath?: string, templatePath?: string): void;
16
+ /**
17
+ * Read all populated fields from MEMORY.md.
18
+ * Returns a map of Key → value (skips <NOT SET> fields).
19
+ */
20
+ export declare function readMemory(memoryPath?: string): Record<string, string>;
21
+ /**
22
+ * Write a single field value in MEMORY.md.
23
+ */
24
+ export declare function writeMemoryField(key: string, value: string, memoryPath?: string): void;
25
+ /**
26
+ * Append a line under a ## Section header in MEMORY.md.
27
+ */
28
+ export declare function appendToMemorySection(section: string, line: string, memoryPath?: string): void;
29
+ /**
30
+ * Check if the agent has a wallet address recorded.
31
+ * Note: This only checks MEMORY.md — the actual key is in the keystore.
32
+ */
33
+ export declare function hasWalletRecord(memoryPath?: string): boolean;
34
+ /**
35
+ * Check if the agent is registered onchain.
36
+ */
37
+ export declare function isRegistered(memoryPath?: string): boolean;