@buildersgarden/siwa 0.0.5 → 0.0.7

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 CHANGED
@@ -4,29 +4,27 @@ A Claude Code skill for registering AI agents on the [ERC-8004 (Trustless Agents
4
4
 
5
5
  ## What it does
6
6
 
7
- - **Create Wallet** — Generate an Ethereum wallet with secure key storage (encrypted V3 file, keyring proxy, or env var)
7
+ - **Create Wallet** — Generate an Ethereum wallet via a keyring proxy (private key never enters the agent process)
8
8
  - **Register Agent (Sign Up)** — Mint an ERC-721 identity NFT on the ERC-8004 Identity Registry with metadata (endpoints, trust model, services)
9
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
10
 
11
11
  ## Project Structure
12
12
 
13
13
  ```
14
- scripts/ Core skill implementation
15
- keystore.ts Secure key storage (3 backends)
16
- memory.ts MEMORY.md read/write helpers
14
+ src/ Core SDK modules
15
+ keystore.ts Proxy-only keystore (signing delegated over HMAC-authenticated HTTP)
16
+ identity.ts IDENTITY.md read/write helpers
17
17
  siwa.ts SIWA message building, signing, verification
18
- create_wallet.ts Wallet creation flow
19
- register_agent.ts Onchain registration flow
18
+ proxy-auth.ts HMAC-SHA256 authentication utilities
19
+ registry.ts Onchain agent profile & reputation lookups
20
+ addresses.ts Deployed contract addresses
20
21
 
21
22
  references/ Protocol documentation
22
23
  siwa-spec.md Full SIWA specification
23
24
  security-model.md Threat model and keystore architecture
24
- contract-addresses.md Deployed registry addresses
25
- registration-guide.md Registration file schema
26
25
 
27
26
  assets/ Templates
28
- MEMORY.md.template
29
- registration-template.json
27
+ IDENTITY.template.md
30
28
 
31
29
  test/ Local test environment (Express server + CLI agent)
32
30
  ```
@@ -51,13 +49,7 @@ See [`test/README.md`](test/README.md) for full details on the test environment.
51
49
 
52
50
  ## Security Model
53
51
 
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 |
52
+ The agent's private key never enters the agent process. All signing is delegated to a **keyring proxy** over HMAC-authenticated HTTP. Even full agent compromise cannot extract the key only request signatures.
61
53
 
62
54
  See [`references/security-model.md`](references/security-model.md) for the full threat model.
63
55
 
@@ -0,0 +1,47 @@
1
+ /**
2
+ * identity.ts
3
+ *
4
+ * Read/write helpers for the agent's IDENTITY.md file.
5
+ * Minimal 4-field identity: Address, Agent ID, Agent Registry, Chain ID.
6
+ *
7
+ * IDENTITY.md uses the pattern: - **Key**: `value`
8
+ *
9
+ * Registration checks are done onchain via ownerOf() when a PublicClient
10
+ * is provided, otherwise the local file is used as a cache.
11
+ *
12
+ * Dependencies: fs (Node built-in), viem (optional for onchain checks)
13
+ */
14
+ import type { PublicClient } from 'viem';
15
+ export interface AgentIdentity {
16
+ address?: string;
17
+ agentId?: number;
18
+ agentRegistry?: string;
19
+ chainId?: number;
20
+ }
21
+ /**
22
+ * Ensure IDENTITY.md exists. If not, copy from template or create minimal.
23
+ */
24
+ export declare function ensureIdentityExists(identityPath?: string, templatePath?: string): void;
25
+ /**
26
+ * Read the agent identity from IDENTITY.md.
27
+ * Returns typed AgentIdentity with parsed values.
28
+ */
29
+ export declare function readIdentity(identityPath?: string): AgentIdentity;
30
+ /**
31
+ * Write a single field value in IDENTITY.md.
32
+ */
33
+ export declare function writeIdentityField(key: string, value: string, identityPath?: string): void;
34
+ /**
35
+ * Check if the agent has a wallet address recorded in IDENTITY.md.
36
+ */
37
+ export declare function hasWalletRecord(identityPath?: string): boolean;
38
+ /**
39
+ * Check if the agent is registered.
40
+ *
41
+ * Without a client: returns true if IDENTITY.md has an Agent ID and Agent Registry (local cache).
42
+ * With a client: performs an onchain ownerOf(agentId) check on the registry contract.
43
+ */
44
+ export declare function isRegistered(options?: {
45
+ identityPath?: string;
46
+ client?: PublicClient;
47
+ }): Promise<boolean>;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * identity.ts
3
+ *
4
+ * Read/write helpers for the agent's IDENTITY.md file.
5
+ * Minimal 4-field identity: Address, Agent ID, Agent Registry, Chain ID.
6
+ *
7
+ * IDENTITY.md uses the pattern: - **Key**: `value`
8
+ *
9
+ * Registration checks are done onchain via ownerOf() when a PublicClient
10
+ * is provided, otherwise the local file is used as a cache.
11
+ *
12
+ * Dependencies: fs (Node built-in), viem (optional for onchain checks)
13
+ */
14
+ import * as fs from 'fs';
15
+ const DEFAULT_IDENTITY_PATH = './IDENTITY.md';
16
+ // ---------------------------------------------------------------------------
17
+ // File Operations
18
+ // ---------------------------------------------------------------------------
19
+ /**
20
+ * Ensure IDENTITY.md exists. If not, copy from template or create minimal.
21
+ */
22
+ export function ensureIdentityExists(identityPath = DEFAULT_IDENTITY_PATH, templatePath) {
23
+ if (fs.existsSync(identityPath))
24
+ return;
25
+ if (templatePath && fs.existsSync(templatePath)) {
26
+ fs.copyFileSync(templatePath, identityPath);
27
+ }
28
+ else {
29
+ const minimal = `# Agent Identity
30
+ - **Address**: \`<NOT SET>\`
31
+ - **Agent ID**: \`<NOT SET>\`
32
+ - **Agent Registry**: \`<NOT SET>\`
33
+ - **Chain ID**: \`<NOT SET>\`
34
+ `;
35
+ fs.writeFileSync(identityPath, minimal);
36
+ }
37
+ }
38
+ /**
39
+ * Read the agent identity from IDENTITY.md.
40
+ * Returns typed AgentIdentity with parsed values.
41
+ */
42
+ export function readIdentity(identityPath = DEFAULT_IDENTITY_PATH) {
43
+ if (!fs.existsSync(identityPath))
44
+ return {};
45
+ const content = fs.readFileSync(identityPath, 'utf-8');
46
+ const fields = {};
47
+ for (const line of content.split('\n')) {
48
+ const match = line.match(/^- \*\*(.+?)\*\*:\s*`(.+?)`/);
49
+ if (match && match[2] !== '<NOT SET>') {
50
+ fields[match[1]] = match[2];
51
+ }
52
+ }
53
+ const identity = {};
54
+ if (fields['Address'])
55
+ identity.address = fields['Address'];
56
+ if (fields['Agent ID'])
57
+ identity.agentId = parseInt(fields['Agent ID']);
58
+ if (fields['Agent Registry'])
59
+ identity.agentRegistry = fields['Agent Registry'];
60
+ if (fields['Chain ID'])
61
+ identity.chainId = parseInt(fields['Chain ID']);
62
+ return identity;
63
+ }
64
+ /**
65
+ * Write a single field value in IDENTITY.md.
66
+ */
67
+ export function writeIdentityField(key, value, identityPath = DEFAULT_IDENTITY_PATH) {
68
+ let content = fs.readFileSync(identityPath, 'utf-8');
69
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
70
+ const pattern = new RegExp(`(- \\*\\*${escaped}\\*\\*:\\s*)\`.+?\``);
71
+ if (pattern.test(content)) {
72
+ content = content.replace(pattern, `$1\`${value}\``);
73
+ }
74
+ else {
75
+ content += `\n- **${key}**: \`${value}\`\n`;
76
+ }
77
+ fs.writeFileSync(identityPath, content);
78
+ }
79
+ /**
80
+ * Check if the agent has a wallet address recorded in IDENTITY.md.
81
+ */
82
+ export function hasWalletRecord(identityPath = DEFAULT_IDENTITY_PATH) {
83
+ const identity = readIdentity(identityPath);
84
+ return !!identity.address;
85
+ }
86
+ /**
87
+ * Check if the agent is registered.
88
+ *
89
+ * Without a client: returns true if IDENTITY.md has an Agent ID and Agent Registry (local cache).
90
+ * With a client: performs an onchain ownerOf(agentId) check on the registry contract.
91
+ */
92
+ export async function isRegistered(options) {
93
+ const identityPath = options?.identityPath ?? DEFAULT_IDENTITY_PATH;
94
+ const identity = readIdentity(identityPath);
95
+ if (!identity.agentId || !identity.agentRegistry)
96
+ return false;
97
+ const client = options?.client;
98
+ if (!client)
99
+ return true; // Local cache says registered
100
+ // Onchain check: ownerOf(agentId) on the registry
101
+ const registryParts = identity.agentRegistry.split(':');
102
+ if (registryParts.length !== 3 || registryParts[0] !== 'eip155')
103
+ return false;
104
+ const registryAddress = registryParts[2];
105
+ try {
106
+ await client.readContract({
107
+ address: registryAddress,
108
+ abi: [{
109
+ name: 'ownerOf',
110
+ type: 'function',
111
+ stateMutability: 'view',
112
+ inputs: [{ name: 'tokenId', type: 'uint256' }],
113
+ outputs: [{ name: '', type: 'address' }],
114
+ }],
115
+ functionName: 'ownerOf',
116
+ args: [BigInt(identity.agentId)],
117
+ });
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './keystore.js';
2
2
  export * from './siwa.js';
3
- export * from './memory.js';
3
+ export * from './identity.js';
4
4
  export * from './proxy-auth.js';
5
5
  export * from './registry.js';
6
6
  export * from './addresses.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './keystore.js';
2
2
  export * from './siwa.js';
3
- export * from './memory.js';
3
+ export * from './identity.js';
4
4
  export * from './proxy-auth.js';
5
5
  export * from './registry.js';
6
6
  export * from './addresses.js';
@@ -1,52 +1,30 @@
1
1
  /**
2
2
  * keystore.ts
3
3
  *
4
- * Secure private key storage abstraction for ERC-8004 agents.
4
+ * Secure signing abstraction for ERC-8004 agents.
5
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 @noble/ciphers) password-encrypted file on disk
9
- * 2. Environment variable fallback (AGENT_PRIVATE_KEY) — least secure, for CI/testing only
6
+ * All signing is delegated to a **keyring proxy server** — a separate process
7
+ * that holds the encrypted private key and exposes only HMAC-authenticated
8
+ * signing endpoints. The private key NEVER enters the agent process.
10
9
  *
11
- * The private key NEVER leaves this module as a return value.
12
10
  * External code interacts only through:
13
11
  * - 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)
12
+ * - signMessage(msg) → returns { signature, address }
13
+ * - signTransaction(tx) → returns { signedTx, address }
14
+ * - signAuthorization(auth) → returns signed EIP-7702 authorization
18
15
  * - getAddress() → returns the public address
19
16
  * - hasWallet() → returns boolean
20
17
  *
21
- * EIP-7702 Support:
22
- * Wallets are standard EOAs created via viem's generatePrivateKey().
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 viem
30
- *
31
18
  * 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
19
+ * KEYRING_PROXY_URL URL of the keyring proxy server
20
+ * KEYRING_PROXY_SECRET HMAC shared secret
36
21
  */
37
- import { type WalletClient, type Account, type Chain, type Transport } from "viem";
38
- export type KeystoreBackend = "encrypted-file" | "env" | "proxy";
39
22
  export interface KeystoreConfig {
40
- backend?: KeystoreBackend;
41
- keystorePath?: string;
42
- password?: string;
43
23
  proxyUrl?: string;
44
24
  proxySecret?: string;
45
25
  }
46
26
  export interface WalletInfo {
47
27
  address: string;
48
- backend: KeystoreBackend;
49
- keystorePath?: string;
50
28
  }
51
29
  export interface SignResult {
52
30
  signature: string;
@@ -79,20 +57,13 @@ export interface TransactionLike {
79
57
  gasPrice?: bigint | null;
80
58
  accessList?: any[];
81
59
  }
82
- export declare function detectBackend(): Promise<KeystoreBackend>;
83
60
  /**
84
- * Create a new random wallet and store it securely.
61
+ * Create a new random wallet via the keyring proxy.
85
62
  * Returns only the public address — NEVER the private key.
86
63
  */
87
64
  export declare function createWallet(config?: KeystoreConfig): Promise<WalletInfo>;
88
65
  /**
89
- * Import an existing private key into the secure store.
90
- * After calling this, the caller should discard its copy of the key.
91
- * Returns only the public address.
92
- */
93
- export declare function importWallet(privateKey: string, config?: KeystoreConfig): Promise<WalletInfo>;
94
- /**
95
- * Check if a wallet is available in any backend.
66
+ * Check if a wallet is available via the keyring proxy.
96
67
  */
97
68
  export declare function hasWallet(config?: KeystoreConfig): Promise<boolean>;
98
69
  /**
@@ -100,14 +71,12 @@ export declare function hasWallet(config?: KeystoreConfig): Promise<boolean>;
100
71
  */
101
72
  export declare function getAddress(config?: KeystoreConfig): Promise<string | null>;
102
73
  /**
103
- * Sign a message (EIP-191 personal_sign).
104
- * The private key is loaded, used, and immediately discarded.
74
+ * Sign a message (EIP-191 personal_sign) via the keyring proxy.
105
75
  * Only the signature is returned.
106
76
  */
107
77
  export declare function signMessage(message: string, config?: KeystoreConfig): Promise<SignResult>;
108
78
  /**
109
- * Sign a transaction.
110
- * The private key is loaded, used, and immediately discarded.
79
+ * Sign a transaction via the keyring proxy.
111
80
  * Only the signed transaction is returned.
112
81
  */
113
82
  export declare function signTransaction(tx: TransactionLike, config?: KeystoreConfig): Promise<{
@@ -118,22 +87,6 @@ export declare function signTransaction(tx: TransactionLike, config?: KeystoreCo
118
87
  * Sign an EIP-7702 authorization for delegating the EOA to a contract.
119
88
  *
120
89
  * This allows the agent's EOA to temporarily act as a smart contract
121
- * during a type 4 transaction. The private key is loaded, used, and
122
- * immediately discarded — only the signed authorization tuple is returned.
123
- *
124
- * @param auth — Authorization request (target contract address, optional chainId/nonce)
125
- * @returns Signed authorization tuple (address, nonce, chainId, yParity, r, s)
90
+ * during a type 4 transaction. Only the signed authorization tuple is returned.
126
91
  */
127
92
  export declare function signAuthorization(auth: AuthorizationRequest, config?: KeystoreConfig): Promise<SignedAuthorization>;
128
- /**
129
- * Get a wallet client for contract interactions.
130
- * NOTE: This creates a client with the private key in memory.
131
- * Use only within a narrow scope and discard immediately.
132
- * Prefer signMessage() / signTransaction() when possible.
133
- */
134
- export declare function getWalletClient(rpcUrl: string, config?: KeystoreConfig): Promise<WalletClient<Transport, Chain | undefined, Account>>;
135
- /**
136
- * Delete the stored wallet from the active backend.
137
- * DESTRUCTIVE — the identity is lost if no backup exists.
138
- */
139
- export declare function deleteWallet(config?: KeystoreConfig): Promise<boolean>;