@buildersgarden/siwa 0.0.5 → 0.0.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/README.md +9 -17
- package/dist/identity.d.ts +47 -0
- package/dist/identity.js +123 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/keystore.d.ts +14 -61
- package/dist/keystore.js +31 -474
- package/dist/siwa.d.ts +115 -13
- package/dist/siwa.js +246 -57
- package/package.json +4 -6
- package/dist/memory.d.ts +0 -37
- package/dist/memory.js +0 -134
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
|
|
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
|
-
|
|
15
|
-
keystore.ts
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
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>;
|
package/dist/identity.js
ADDED
|
@@ -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
package/dist/index.js
CHANGED
package/dist/keystore.d.ts
CHANGED
|
@@ -1,52 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* keystore.ts
|
|
3
3
|
*
|
|
4
|
-
* Secure
|
|
4
|
+
* Secure signing abstraction for ERC-8004 agents.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
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
|
-
*
|
|
33
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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.
|
|
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>;
|