epistery 1.5.10 → 2.0.0

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.
Files changed (73) hide show
  1. package/.test.env.example +0 -1
  2. package/README.md +288 -322
  3. package/cli/epistery.mjs +2 -583
  4. package/client/wallet.js +36 -84
  5. package/client/witness.js +44 -643
  6. package/dist/epistery.d.ts +1 -189
  7. package/dist/epistery.d.ts.map +1 -1
  8. package/dist/epistery.js +83 -1528
  9. package/dist/epistery.js.map +1 -1
  10. package/dist/utils/CliWallet.d.ts +3 -2
  11. package/dist/utils/CliWallet.d.ts.map +1 -1
  12. package/dist/utils/CliWallet.js +6 -3
  13. package/dist/utils/CliWallet.js.map +1 -1
  14. package/dist/utils/Utils.d.ts +2 -174
  15. package/dist/utils/Utils.d.ts.map +1 -1
  16. package/dist/utils/Utils.js +22 -1156
  17. package/dist/utils/Utils.js.map +1 -1
  18. package/dist/utils/types.d.ts +4 -127
  19. package/dist/utils/types.d.ts.map +1 -1
  20. package/dist/utils/types.js +0 -13
  21. package/dist/utils/types.js.map +1 -1
  22. package/index.mjs +44 -596
  23. package/package.json +1 -5
  24. package/routes/connect.mjs +35 -35
  25. package/routes/fido.mjs +1 -1
  26. package/routes/identity.mjs +3 -29
  27. package/routes/index.mjs +6 -38
  28. package/routes/status.mjs +4 -82
  29. package/src/epistery.ts +86 -1918
  30. package/src/utils/CliWallet.ts +11 -5
  31. package/src/utils/Utils.ts +24 -1471
  32. package/src/utils/types.ts +15 -145
  33. package/test/fixtures/wallets.ts +6 -3
  34. package/test/routes/connect.test.ts +12 -6
  35. package/test/setup.ts +2 -43
  36. package/test/utils.ts +9 -41
  37. package/EpisteryRegistry.md +0 -396
  38. package/MONGODB_GOTCHA.md +0 -69
  39. package/MobileIdentity.md +0 -182
  40. package/client/notabot.js +0 -679
  41. package/client/status.html +0 -1580
  42. package/contracts/IAddressNaming.sol +0 -42
  43. package/contracts/ICreditAccount.sol +0 -48
  44. package/contracts/IUserRegistry.sol +0 -34
  45. package/contracts/IdentityContract.sol +0 -421
  46. package/contracts/agent.sol +0 -1095
  47. package/demo/README.md +0 -50
  48. package/demo/index.html +0 -13
  49. package/demo/package.json +0 -15
  50. package/demo/server.mjs +0 -174
  51. package/demo/whitelist-test.html +0 -710
  52. package/hardhat.config.js +0 -61
  53. package/routes/approval.mjs +0 -257
  54. package/routes/contract.mjs +0 -23
  55. package/routes/data.mjs +0 -394
  56. package/routes/list.mjs +0 -76
  57. package/routes/notabot.mjs +0 -268
  58. package/routes/whitelist/client/admin.html +0 -1249
  59. package/routes/whitelist/client/client.js +0 -289
  60. package/routes/whitelist/client/icon.svg +0 -5
  61. package/routes/whitelist/client/widget.html +0 -90
  62. package/routes/whitelist/index.mjs +0 -832
  63. package/scripts/deploy-agent.js +0 -206
  64. package/scripts/fund-test-wallets.js +0 -204
  65. package/scripts/setup-test-env.js +0 -191
  66. package/scripts/verify-agent.js +0 -39
  67. package/src/utils/Aqua.ts +0 -198
  68. package/test/routes/approval.test.ts +0 -282
  69. package/test/routes/contract.test.ts +0 -58
  70. package/test/routes/data.test.ts +0 -474
  71. package/test/routes/list.test.ts +0 -172
  72. package/test/routes/notabot.test.ts +0 -157
  73. package/test/routes/whitelist.test.ts +0 -596
package/README.md CHANGED
@@ -2,399 +2,365 @@
2
2
 
3
3
  _Epistemology is the study of knowledge. An Epistery, it follows, is a place to share the knowledge of knowledge._
4
4
 
5
- **Epistery** is blockchain-based middleware that provides websites and applications with decentralized authentication, data ownership verification, and trusted data exchange. It serves as a neutral foundation for web applications to identify users, verify data provenance, and conduct digital business without relying on centralized gatekeepers.
6
-
7
- ## What Does Epistery Do?
8
-
9
- Epistery establishes a transactional wallet for both browser and server along with the session handshake. This provides:
10
-
11
- - **Decentralized Authentication**: Wallet-based user authentication using cryptographic signatures
12
- - **Data Wallets**: Blockchain smart contracts for data ownership, encryption, sharing, and transfer
13
- - **Whitelist Management**: On-chain access control for domains and users
14
- - **CLI Tools**: Command-line interface for authenticated API requests using bot mode
15
- - **Client Libraries**: Browser-based wallet and authentication tools
16
- - **Configuration Management**: Path-based filesystem-like API for secure configuration storage
17
-
18
- >*NOTE:* The client wallet (signing key) is held in localStorage under strict domain rules unless the user presents
19
- > a selected wallet from a web3 plugin
20
-
21
- ## Quick Start
22
-
23
- ### Installation
24
-
25
- ```bash
26
- npm install epistery
27
- ```
28
-
29
- ### Server Setup
30
-
31
- Initialize a domain to create its blockchain wallet:
32
-
33
- ```bash
34
- npx epistery initialize mydomain.com
35
- ```
36
-
37
- Integrate Epistery into your Express application:
5
+ **Epistery is the identity foundation for web applications.** It gives a host one
6
+ thing it can trust on every request — a cryptographically proven address — and
7
+ binds that address to an on-chain IdentityContract when the user wants a durable
8
+ multi-device identity. Everything else (data, ACLs, naming, content) is the host
9
+ application's concern, not epistery's.
10
+
11
+ > **Status sealed contract, v1.2 (2026-05-27).** This README defines what
12
+ > epistery is responsible for, what it is not, and its interface. Code is held to
13
+ > this document. The `agent.sol` surface (data wallets, approvals, whitelist /
14
+ > lists / roles, name registry, notabot) was **removed in v1.2**; see the wiki
15
+ > archives ([[NotABot]], [[Whitelist]], [[DataWallets]], [[Approvals]],
16
+ > [[ContractStandards]]) and git tag `epistery-pre-identity-refactor` for the
17
+ > retired implementations. The dated **[Known divergences](#known-divergences-audit)**
18
+ > section at the end lists where the current code still fails this contract.
19
+ > If behavior and this document disagree, that is a bug in the code, not the doc.
20
+
21
+ ---
22
+
23
+ ## Responsibility (what epistery OWNS)
24
+
25
+ Epistery is the **single owner** of:
26
+
27
+ 1. **Identity** — proving *who* a request is from. The proof is a wallet
28
+ signature, carried either by a short-lived signed session cookie (`_epistery`,
29
+ established via the `/connect` handshake) or a per-request `Bot` signature.
30
+ The result is a trusted **address** on `req.episteryClient`.
31
+ 2. **Identity binding** — relating a device key (rivet) to an **IdentityContract**,
32
+ verified on-chain (`isAuthorized`). When bound, epistery presents the *contract*
33
+ as the identity.
34
+ 3. **Key custody (client)** — generating and protecting the user's signing key in
35
+ the browser (non-extractable; see [Key custody](#identity--key-custody)).
36
+ 4. **Domain/server wallet & config** — the host's own wallet and the path-based
37
+ `~/.epistery` configuration.
38
+ 5. **FIDO blob storage** — server-side backup of WebAuthn-PRF-wrapped rivet keys
39
+ so they survive iOS ITP IndexedDB eviction.
40
+
41
+ No consumer may bypass, re-derive, or duplicate any of these. In particular: a
42
+ downstream service **never** trusts a client-supplied identity header and
43
+ **never** re-implements identity resolution.
44
+
45
+ ---
46
+
47
+ ## What Epistery DOES
48
+
49
+ - **Authenticates every request** to a trusted address (`req.episteryClient`),
50
+ via signed `_epistery` session cookie or `Bot` signature.
51
+ - **Mints/loads wallets** for the browser (rivet / FIDO / web3) and server
52
+ (per-domain).
53
+ - **Binds a device to an IdentityContract** and verifies that binding on-chain.
54
+ - **Serves client libraries** at `/lib/*` (`witness.js`, `wallet.js`, `ethers.js`,
55
+ …) and contract artifacts at `/artifacts/*` for consumers.
56
+ - **Persists FIDO blobs** (`/fido/blob`) — encrypted, PRF-wrapped rivet keys for
57
+ WebAuthn-backed identities.
58
+ - **Exposes a CLI** for stateless bot-authenticated requests (`curl`), the
59
+ Streamable-HTTP MCP bridge (`mcp`), domain initialization, and basic info.
60
+
61
+ ## What Epistery does NOT do
62
+
63
+ - **Does not store application data.** Apps own their storage; epistery records
64
+ identity, not your documents.
65
+ - **Does not manage contracts.** It *binds keys to existing contracts* and
66
+ *verifies* the binding on-chain — it does not deploy, write to, or own
67
+ application contracts. Contract creation and on-chain ACL/state live in host
68
+ contracts (e.g. `IdentityContractV3.sol`, `DomainContract.sol`).
69
+ - **Does not define application- or session-level ACLs.** Authorization is the
70
+ host's job, evaluated against the trusted address epistery provides.
71
+ - **Does not run a name registry.** Per-domain naming is a relay service; epistery
72
+ carries no name → address mapping.
73
+ - **Does not accept a client's claim of identity.** The only identity is the one
74
+ epistery itself proved (`req.episteryClient`). There is no "I am contract X"
75
+ header. Contract identity claims are verified on-chain at `/connect` and sealed
76
+ into the signed cookie.
77
+ - **Does not let downstream code adjudicate auth.** Re-deriving identity or
78
+ re-checking signatures outside epistery is a contract violation.
79
+
80
+ ---
81
+
82
+ ## The trust contract: `req.episteryClient`
83
+
84
+ The attach middleware sets exactly this on each request (or leaves it `undefined`):
85
+
86
+ | Field | Meaning |
87
+ |-------------------|---------|
88
+ | `signerAddress` | **The signer.** The rivet whose signature was verified (cookie session or `Bot`). Always non-null. The only thing the client can assert by itself. |
89
+ | `contractAddress` | **A verified contract claim.** When the client claimed an IdentityContract at `/connect`, this is that contract, verified on-chain via `isAuthorized(contractAddress, signerAddress)`. `null` when no claim. |
90
+ | `identityAddress` | **The canonical identity.** Derived: `contractAddress || signerAddress`. This is what host ACLs evaluate against. Always non-null. |
91
+ | `publicKey` | The signer's public key. |
92
+ | `authenticated` | Whether the session/handshake completed. |
93
+ | `authType` | `"bot"` for `Bot`-signed requests; `"cookie"` for session-cookie. |
94
+
95
+ The three roles are kept separate on purpose. `signerAddress` is a fact the
96
+ client proves; `contractAddress` is a claim the server verifies; `identityAddress`
97
+ is the server's derivation. The wire never asks the client to pick which role
98
+ its address plays.
99
+
100
+ **Rule for consumers:** authorize against `identityAddress`. The signer vs.
101
+ contract distinction is available but rarely your concern.
38
102
 
39
103
  ```javascript
40
- import express from 'express';
41
- import https from 'https';
42
- import { Epistery } from 'epistery';
43
-
44
- const app = express();
45
-
46
- // Connect and attach epistery
47
- const epistery = await Epistery.connect();
48
- await epistery.setDomain('mydomain.com');
49
- await epistery.attach(app);
50
-
51
- // Optional: Add authentication callback
52
- const episteryWithAuth = await Epistery.connect({
53
- authentication: async (clientInfo) => {
54
- // clientInfo.address contains the wallet address
55
- // Return user profile or null
56
- return await getUserProfile(clientInfo.address);
57
- },
58
- onAuthenticated: async (clientInfo, req, res) => {
59
- // Called after successful authentication
60
- console.log('User authenticated:', clientInfo.address);
61
- }
104
+ app.get('/thing', (req, res) => {
105
+ const me = req.episteryClient; // the ONLY source of identity
106
+ if (!me?.authenticated) return res.status(401).end();
107
+ // authorize against your host's contracts / policy using me.identityAddress
62
108
  });
63
-
64
- // Start your server
65
- const https_server = https.createServer(epistery.config.SNI, app);
66
- https_server.listen(443);
67
109
  ```
68
110
 
69
- This automatically mounts RFC 8615-compliant routes under `/.well-known/epistery/`:
70
- - `/.well-known/epistery` - Server wallet status (JSON)
71
- - `/.well-known/epistery/status` - Human-readable status page
72
- - `/.well-known/epistery/connect` - Client key exchange endpoint
73
- - `/.well-known/epistery/data/*` - Data wallet operations
74
- - `/.well-known/epistery/whitelist` - Access control endpoints
111
+ ### The wire (POST `/connect`)
75
112
 
76
- ## Core Features
113
+ The handshake body carries facts only:
77
114
 
78
- ### 1. Authentication
115
+ | Field | Required | Meaning |
116
+ |-------------------|----------|---------|
117
+ | `signerAddress` | yes | The rivet. Must equal the address recovered from `signature` over `message`. |
118
+ | `signerPublicKey` | yes | The signer's public key. |
119
+ | `contractAddress` | yes | An IdentityContract claim, or `null`. When non-null, the server verifies it on-chain via `isAuthorized(contractAddress, signerAddress)`. |
120
+ | `challenge`, `message`, `signature` | yes | Proof of signer (see [Identity & key custody](#identity--key-custody)). |
121
+ | `walletSource` | no | `"rivet"` / `"fido"` / `"web3"` / etc. — informational. |
79
122
 
80
- Epistery provides cryptographic authentication using Ethereum wallets:
123
+ There is no `clientAddress`, no `identityAddress` on the wire. Either of those
124
+ would force the receiver to guess which role the address plays. The server
125
+ derives `identityAddress` from the two facts and exposes it on
126
+ `req.episteryClient`; the client never tells the server what its identity *is*.
81
127
 
82
- **Client-side:**
83
- ```javascript
84
- // Load client library in your HTML
85
- <script src="/.well-known/epistery/lib/client.js"></script>
86
- <script>
87
- const client = new EpisteryClient();
88
- await client.connect(); // Automatic key exchange
89
- console.log('Connected as:', client.address);
90
- </script>
91
- ```
128
+ ---
92
129
 
93
- **Server-side:**
94
- ```javascript
95
- // Access authenticated client in routes
96
- app.get('/profile', (req, res) => {
97
- if (req.episteryClient?.authenticated) {
98
- res.json({ address: req.episteryClient.address });
99
- } else {
100
- res.status(401).json({ error: 'Not authenticated' });
101
- }
102
- });
103
- ```
104
-
105
- ### 2. Data Wallets
130
+ ## Identity & key custody
106
131
 
107
- Data wallets are blockchain smart contracts that provide ownership, encryption, sharing, and transfer capabilities for any data. They combine on-chain ownership records with off-chain storage:
132
+ The browser signing key is created and protected by epistery. Custody depends on
133
+ wallet type:
108
134
 
109
- ```javascript
110
- // Client creates data wallet
111
- const dataWallet = await client.write({
112
- title: 'My Document',
113
- content: 'Document content...',
114
- metadata: { tags: ['important'] }
115
- });
135
+ | Wallet | Key custody | Security property |
136
+ |----------------|-------------|-------------------|
137
+ | **RivetWallet** (default) | secp256k1 private key **encrypted at rest** by a **non-extractable** AES-GCM `CryptoKey` held in IndexedDB (WebCrypto). Only ciphertext + a key id are persisted. **Refuses to create the wallet if WebCrypto is unavailable** — no plaintext fallback. | The signing key cannot be exported — the core "unextractable device key" property. |
138
+ | **FidoWallet** | Rivet key wrapped by a WebAuthn PRF secret (Secure Enclave); blob optionally backed up server-side via `/fido/blob` (survives iOS ITP eviction). | Key release gated by platform authenticator. |
139
+ | **Web3Wallet** | External plugin (e.g. MetaMask) holds the key. | Custody is the plugin's. |
116
140
 
117
- // Read data wallet
118
- const data = await client.read();
141
+ A device can hold **multiple independent rivets** (Browser/FIDO/Web3 are all
142
+ rivets different ways of presenting a device-locked signing key). This is how
143
+ the system enforces one-key-one-identity without a hard cross-context check: the
144
+ user mints another isolated rivet rather than pointing one key at two contracts.
119
145
 
120
- // Transfer ownership to another address
121
- await client.transferOwnership(newOwnerAddress);
122
- ```
146
+ Server/domain wallets live in `~/.epistery/<domain>/config.ini` (0600).
123
147
 
124
- **Data Wallet Features:**
125
- - **Blockchain Contracts**: Each data wallet is a smart contract on-chain
126
- - **Encryption**: Data can be encrypted before storage
127
- - **Sharing**: Grant read/write access to specific addresses
128
- - **Transferable**: Ownership can be transferred to other wallets
129
- - **IPFS Storage**: Content stored on IPFS by default, with hashes on-chain
130
- - **Provenance Tracking**: Full ownership and modification history on-chain
148
+ ### The `/connect` handshake & contract binding
131
149
 
132
- ### 3. Whitelist Management
133
-
134
- Control who can access your domain using on-chain whitelists:
135
-
136
- ```javascript
137
- // Check if address is whitelisted
138
- const isAllowed = await epistery.isWhitelisted('0x1234...');
139
-
140
- // Get full whitelist
141
- const whitelist = await epistery.getWhitelist();
142
- ```
150
+ 1. The client `Witness` signs a challenge with its rivet and POSTs to `/connect`
151
+ with `signerAddress` (the rivet), `signerPublicKey`, and `contractAddress`
152
+ (the claim, or `null`).
153
+ 2. The server verifies the signature recovers to `signerAddress`. If
154
+ `contractAddress` is non-null, it calls `IdentityContract.isAuthorized(signerAddress)`
155
+ **on-chain** the chain is truth.
156
+ 3. On success it issues the signed `_epistery` cookie, recording `signerAddress`
157
+ and (if verified) `contractAddress`. The auth middleware then exposes
158
+ `req.episteryClient.identityAddress = contractAddress || signerAddress`.
143
159
 
144
- Whitelist data is stored on the blockchain and managed through your domain's wallet.
160
+ A rivet is bound to a contract client-side via `wallet.upgradeToContract(contract)`
161
+ — afterward the wallet's derived `identityAddress` is the contract while
162
+ `signerAddress` is still the rivet. A fresh key exchange follows; the witness
163
+ short-circuits when (and only when) the cookie's `identityAddress` already
164
+ matches the wallet's `identityAddress`. The rivet→contract relation in
165
+ localStorage is not cryptographically sealed in the browser — but spoofing it
166
+ is useless: the contract knows its authorized signers and can't be spoofed; the
167
+ on-chain verification at `/connect` is the gate.
145
168
 
146
- ### 4. CLI Tools
169
+ ---
147
170
 
148
- The Epistery CLI enables authenticated API requests from the command line using bot authentication (stateless, signs each request):
149
-
150
- ```bash
151
- # Initialize a CLI wallet
152
- epistery initialize localhost
153
- epistery set-default localhost
154
-
155
- # Make authenticated GET request
156
- epistery curl https://api.example.com/data
157
-
158
- # PUT request with JSON data (note single quotes)
159
- epistery curl -X PUT -d '{"title":"Test","body":"Content"}' https://api.example.com/wiki/Test
160
-
161
- # Use specific wallet
162
- epistery curl -w production.example.com https://api.example.com/data
163
-
164
- # Verbose output for debugging
165
- epistery curl -v https://api.example.com/data
166
- ```
171
+ ## HTTP interface
167
172
 
168
- Perfect for:
169
- - Testing authenticated endpoints
170
- - Building automation scripts
171
- - Creating bots and agents
172
- - CI/CD integration
173
+ Mounted under `rootPath` (default `/.well-known/epistery`, RFC 8615):
173
174
 
174
- **CLI uses bot authentication** - each request is independently signed with the wallet's private key, no session management required.
175
+ | Path | Methods | Purpose |
176
+ |------|---------|---------|
177
+ | `/` | GET | Server status JSON (`Witness.connect` probes this for chain/provider info). No HTML UI. |
178
+ | `/lib/:module` | GET | Client libraries (`witness.js`, `wallet.js`, `client.js`, `ethers.js`, …) |
179
+ | `/artifacts/:file` | GET | Contract ABIs/artifacts |
180
+ | `/connect` | GET / POST | Session check / key-exchange handshake (sets `_epistery`; on-chain `isAuthorized` verify for contract claims) |
181
+ | `/create` | GET | Wallet creation helper |
182
+ | `/auth/account/claim`, `/auth/dns/claim`, `/auth/account/check-admin` | GET/POST | Domain claiming & admin checks |
183
+ | `/identity/prepare-add-rivet` | POST | Unsigned tx for adding a rivet to an existing IdentityContract (client signs, then `/data/submit-signed`-style broadcast) |
184
+ | `/domain/initialize` | POST | Initialize a domain wallet |
185
+ | `/fido/blob`, `/fido/blob/:credentialId` | POST/GET | PRF-wrapped rivet key blob storage |
175
186
 
176
- See [CLI.md](CLI.md) for complete CLI documentation.
187
+ ---
177
188
 
178
- ## Configuration
189
+ ## Server API
179
190
 
180
- Epistery uses a path-based configuration system stored in `~/.epistery/` with a filesystem-like API:
191
+ ```javascript
192
+ import { Epistery, Config } from 'epistery';
181
193
 
194
+ const epistery = await Epistery.connect({
195
+ authentication: async (clientInfo) => { /* return profile or null */ },
196
+ onAuthenticated: async (clientInfo, req, res) => { /* post-auth hook */ },
197
+ });
198
+ await epistery.setDomain('mydomain.com');
199
+ await epistery.attach(app); // mounts middleware + routes under rootPath
182
200
  ```
183
- ~/.epistery/
184
- ├── config.ini # Root config (profile, IPFS, defaults)
185
- ├── mydomain.com/
186
- │ └── config.ini # Domain config (wallet, provider)
187
- └── .ssl/
188
- └── mydomain.com/ # SSL certificates (optional)
189
- ```
190
-
191
- ### Root Config (`~/.epistery/config.ini`)
192
-
193
- ```ini
194
- [profile]
195
- name=Your Name
196
- email=you@example.com
197
-
198
- [ipfs]
199
- url=https://rootz.digital/api/v0
200
-
201
- [cli]
202
- default_domain=localhost
203
-
204
- # Which chain the claim-page dropdown selects by default.
205
- [default]
206
- defaultChainId=137
207
-
208
- # Private RPC overrides, keyed by chainId. Only needed when the chain's
209
- # built-in public RPC isn't sufficient (rate-limited, needs an API key).
210
- # Everything else — name, public RPC, currency, fee policy — lives in
211
- # the chain classes (src/chains/) and doesn't need to be configured.
212
201
 
213
- [default.rpc.137]
214
- privateRpc=https://polygon-mainnet.infura.io/v3/YOUR_KEY
202
+ The `clientInfo` passed to both hooks has the same shape as
203
+ `req.episteryClient`: `{ signerAddress, contractAddress, identityAddress,
204
+ publicKey }` (plus `authenticated` and `profile` after `authentication`
205
+ resolves). Authorize against `identityAddress`.
215
206
 
216
- [default.rpc.1]
217
- privateRpc=https://mainnet.infura.io/v3/YOUR_KEY
218
- ```
219
-
220
- Legacy single-provider format is also supported:
207
+ `Epistery` (exported as `EpisteryAttach`): `connect`, `setDomain`, `attach`,
208
+ `resolveClient(req)` (auth resolution for non-middleware contexts, e.g. WebSocket
209
+ upgrades), `buildStatus`, `routes`.
221
210
 
222
- ```ini
223
- [default.provider]
224
- chainId=137
225
- privateRpc=https://polygon-mainnet.infura.io/v3/YOUR_KEY
226
- ```
211
+ Also exported: `Config`, `chainFor`, `registerChain`, `configuredChains`,
212
+ `defaultChainId`, `Chain`.
227
213
 
228
- ### Domain Config (`~/.epistery/mydomain.com/config.ini`)
214
+ The core `Epistery` static API (`src/epistery.ts`): `initialize`, `createWallet`,
215
+ `getStatus`, `handleKeyExchange` (consumed by `/connect`),
216
+ `prepareAddRivetToContract` (unsigned tx builder), `submitSignedTransaction`
217
+ (generic broadcaster for client-signed transactions — this is the
218
+ "server-requests-signature, interactive wallet (FIDO/MetaMask) signs, then submit"
219
+ path).
229
220
 
230
- ```ini
231
- [domain]
232
- domain=mydomain.com
221
+ ### Config
233
222
 
234
- [wallet]
235
- address=0x...
236
- mnemonic=word word word...
237
- publicKey=0x04...
238
- privateKey=0x...
223
+ Path-based ini config under `~/.epistery` (`src/utils/Config.ts`):
239
224
 
240
- [provider]
241
- chainId=420420422
242
- name=polkadot-hub-testnet
243
- rpc=https://testnet-passet-hub-eth-rpc.polkadot.io
225
+ ```javascript
226
+ import { Config } from 'epistery';
227
+ const config = new Config();
228
+ config.setPath('/'); // ~/.epistery/config.ini (root)
229
+ config.load();
230
+ config.data.profile.email = 'user@example.com';
231
+ config.save();
232
+ config.setPath('/mydomain.com'); // ~/.epistery/mydomain.com/config.ini
244
233
  ```
245
234
 
246
- The Config class provides a path-based API that works like navigating a filesystem - set a path, load data, modify it, and save. This makes configuration management simple and predictable across all Epistery applications.
235
+ Methods: `setPath`, `getPath`, `load`, `save` (+ `data`).
247
236
 
248
- ## Advanced Usage
237
+ ---
249
238
 
250
- ### Custom Authentication
239
+ ## Client API (`Witness`)
251
240
 
252
- Integrate with your existing user system:
241
+ Served at `/.well-known/epistery/lib/witness.js`:
253
242
 
254
243
  ```javascript
255
- const epistery = await Epistery.connect({
256
- authentication: async (clientInfo) => {
257
- // clientInfo: { address, publicKey }
258
-
259
- // Look up user in your database
260
- const user = await db.users.findOne({
261
- walletAddress: clientInfo.address
262
- });
263
-
264
- if (!user) return null;
265
-
266
- // Return profile data
267
- return {
268
- id: user.id,
269
- username: user.username,
270
- permissions: user.permissions
271
- };
272
- },
273
- onAuthenticated: async (clientInfo, req, res) => {
274
- // Called after successful authentication
275
- // clientInfo includes: address, publicKey, profile, authenticated
276
-
277
- // Set up session, log authentication, etc.
278
- req.session.userId = clientInfo.profile.id;
279
- }
280
- });
244
+ import Witness from '/.well-known/epistery/lib/witness.js';
245
+ const witness = await Witness.connect({ rootPath: '/' }); // creates/loads wallet, runs key exchange
281
246
  ```
282
247
 
283
- ### Configuration Management
248
+ Public surface: `connect`, `performKeyExchange`, `getWallets`, `getStatus`,
249
+ `addBrowserWallet` / `addFidoWallet` / `addWeb3Wallet`, `setDefaultWallet`,
250
+ `removeWallet`, `updateWalletLabel`, `bindToEpisteryIdentity` (cross-host identity
251
+ ferry). Wallet classes: `RivetWallet`, `FidoWallet`, `Web3Wallet`; binding via
252
+ `wallet.upgradeToContract`.
284
253
 
285
- Use Epistery's Config class for secure, path-based configuration:
254
+ Identity properties on every wallet — the canonical surface for client code
255
+ deciding "who am I right now":
286
256
 
287
- ```javascript
288
- import { Config } from 'epistery';
257
+ | Property | Meaning |
258
+ |-----------------------|---------|
259
+ | `wallet.signerAddress` | The rivet — the address we sign with. |
260
+ | `wallet.contractAddress` | The bound IdentityContract, or `null`. |
261
+ | `wallet.identityAddress` | Derived: `contractAddress || signerAddress`. What host UI and ACLs should reference. |
289
262
 
290
- const config = new Config('epistery');
263
+ ---
291
264
 
292
- // Navigate filesystem-like paths
293
- config.setPath('/');
294
- config.load();
295
- config.data.profile.email = 'user@example.com';
296
- config.save();
265
+ ## CLI
297
266
 
298
- // Domain-specific config
299
- config.setPath('/mydomain.com');
300
- config.load();
301
- config.data.verified = true;
302
- config.save();
267
+ Stateless bot authentication (each request independently signed):
303
268
 
304
- // Arbitrary paths
305
- config.setPath('/.ssl/mydomain.com');
306
- config.load();
307
- config.data.certData = '...';
308
- config.save();
269
+ ```bash
270
+ epistery initialize localhost
271
+ epistery set-default localhost
272
+ epistery info localhost
273
+ epistery curl https://api.example.com/data
274
+ epistery curl -X PUT -d '{"title":"Test"}' https://api.example.com/wiki/Test
275
+ epistery curl -b -w production.example.com https://api.example.com/data # -b bot, -w wallet, -v verbose
276
+ epistery mcp https://api.example.com # stdio MCP bridge with bot-auth
309
277
  ```
310
278
 
311
- ## Architecture
312
-
313
- Epistery follows a plugin architecture that integrates seamlessly with Express.js applications:
314
-
315
- - **Server Module** (`/src/epistery.ts`): Core wallet and data wallet operations
316
- - **Client Libraries** (`/client/*.js`): Browser-side authentication and data wallet tools
317
- - **CLI** (`/cli/epistery.mjs`): Command-line interface for authenticated requests
318
- - **Utils** (`/src/utils/`): Configuration, crypto operations, and Aqua protocol implementation
319
- - **Chains** (`/src/chains/`): Per-chain provider, fee policy, and gas estimation
320
-
321
- All endpoints follow RFC 8615 well-known URIs standard for service discovery.
322
-
323
- See [Architecture.md](Architecture.md) for detailed architecture documentation.
279
+ Commands: `initialize`, `set-default`, `info`, `curl`, `mcp`, `help`. See
280
+ [CLI.md](CLI.md).
324
281
 
325
- ### Chain Support
282
+ ---
326
283
 
327
- Each EVM chain epistery talks to is represented by a `Chain` object that owns the JSON-RPC provider, fee policy, gas estimation strategy, and default public RPC. No configuration is needed to use a built-in chain — every detail is in the class itself.
284
+ ## Chains
328
285
 
329
- | Chain | ID | Fee Model | Default RPC |
330
- |-------|----|-----------|-------------|
331
- | Polygon Mainnet | 137 | EIP-1559, 25 gwei priority floor | polygon-rpc.com |
332
- | Polygon Amoy | 80002 | EIP-1559, 25 gwei priority floor | rpc-amoy.polygon.technology |
333
- | Ethereum Mainnet | 1 | Standard EIP-1559 | eth.llamarpc.com |
334
- | Sepolia Testnet | 11155111 | Standard EIP-1559 | eth-sepolia.public.blastapi.io |
335
- | Japan Open Chain | 81 | Legacy gasPrice, 30 gwei floor | rpc-2.japanopenchain.org |
286
+ Each EVM chain is a `Chain` object owning its RPC, fee policy, and gas strategy.
287
+ Only `chainId` is required; everything else comes from the class. Use
288
+ `chainFor({ chainId })`; add a chain by extending `Chain` + `registerChain()`.
289
+ See [src/chains/README.md](src/chains/README.md).
336
290
 
337
- Use `chainFor()` to get a chain instance. Only `chainId` is required — everything else comes from the chain class defaults:
291
+ ---
338
292
 
339
- ```javascript
340
- import { chainFor, registeredChains } from 'epistery';
293
+ ## Versioning & local development
341
294
 
342
- // Minimal chain class supplies name, RPC, currency, fee policy
343
- const chain = chainFor({ chainId: 137 });
295
+ - Consumers depend on the **published** package: `npm install epistery@latest`.
296
+ - Local cross-package work installs a **temporary relative path**
297
+ (`npm install ../../rootz/epistery`) for testing only.
298
+ - Publishing to npm and any deployment is a deliberate, human-performed step. No
299
+ tooling or agent publishes, bumps versions, or deploys on its own.
344
300
 
345
- // Or with an override — privateRpc for server-side calls with an API key
346
- const chain = chainFor({ chainId: 137, privateRpc: 'https://polygon-mainnet.infura.io/v3/KEY' });
301
+ ---
347
302
 
348
- // Provider with explicit network info (no "could not detect network" errors)
349
- const wallet = ethers.Wallet.fromMnemonic(mnemonic).connect(chain.provider);
303
+ ## Known divergences (audit)
350
304
 
351
- // Per-chain fee data for transaction overrides
352
- const feeData = await chain.getFeeData();
353
- // → Polygon: { maxPriorityFeePerGas: 25 gwei, maxFeePerGas: 50 gwei }
354
- // → JOC: { gasPrice: 30 gwei }
355
- // → Ethereum: { maxPriorityFeePerGas: <network>, maxFeePerGas: <network> }
305
+ Where the code currently fails the contract above. Dated; remove as fixed.
356
306
 
357
- // Get the full built-in chain list (for UI dropdowns, etc.)
358
- const chains = registeredChains();
359
- ```
307
+ **Resolved in v1.2 (2026-05-27 identity-only refactor)**
360
308
 
361
- Adding a new chain is a single file — extend `Chain`, override the fee hooks that differ, and call `registerChain()`. No edits to existing code. See [src/chains/README.md](src/chains/README.md) for details.
309
+ - **Plaintext private keys.** `BrowserWallet` (extractable-key legacy wallet) is
310
+ removed; `RivetWallet` WebCrypto fallback now **throws** rather than silently
311
+ storing a plaintext key. No code path persists a cleartext signing key.
312
+ - **`agent.sol` surface.** Data wallets (`/data/*`), approvals (`/approval/*`),
313
+ whitelist (`/whitelist/*`), on-chain lists/roles (`/lists`, `/list`), name
314
+ registry (`resolveName`/`setAddressName`), `notabot`, contract creation
315
+ (`/identity/prepare-deploy`), `contracts/` directory — all removed. epistery
316
+ is now identity + storage/config + FIDO blob only.
317
+ - **Client header trust path.** Removed at the server boundary in v1.2: the
318
+ middleware no longer reads `x-identity-contract`; identity is the verified
319
+ identity epistery itself proved.
362
320
 
363
- ## Use Cases
321
+ **Resolved in v1.2 follow-up (2026-05-28 naming cutover)**
364
322
 
365
- - **Decentralized Wikis**: User authentication and content ownership without central accounts
366
- - **API Authentication**: Replace API keys with wallet-based authentication
367
- - **Content Attribution**: Track content provenance and ownership on-chain
368
- - **Access Control**: Manage permissions through blockchain whitelists
369
- - **Bot/Agent Authentication**: Secure automation with wallet-based identity
323
+ - **Ambiguous identity vocabulary; no in-session rivet→contract upgrade.** The
324
+ wire used `clientAddress` (alternately the signer or the identity), the
325
+ server reconstructed which-was-meant on the fly, and `Witness.performKeyExchange`
326
+ short-circuited by comparing the cookie's address to the signer — so a
327
+ device that already had a rivet cookie could never have its session re-issued
328
+ as contract-bound. Replaced with three distinct names everywhere: `signerAddress`
329
+ (fact, asserted), `contractAddress` (claim, server-verified on-chain), and the
330
+ derived `identityAddress` (= `contractAddress || signerAddress`, server-only).
331
+ The witness short-circuits when (and only when) the cookie's `identityAddress`
332
+ matches the wallet's `identityAddress`. The pre-cutover wire shape
333
+ (`clientAddress` / `clientPublicKey`) is removed without aliases — old
334
+ consumers fail at the handshake instead of silently degrading.
370
335
 
371
- ## Security
336
+ **Outstanding**
372
337
 
373
- - **Private Key Protection**: Domain configs stored with 0600 permissions (user-only access)
374
- - **Signature-Only Transmission**: Private keys never transmitted, only cryptographic signatures
375
- - **Wallet Isolation**: Each domain has its own isolated wallet
376
- - **Bot Authentication**: Stateless authentication with per-request signing and timestamp-based replay protection
377
- - **Encrypted Key Exchange**: Browser clients use ECDH for secure shared secret establishment
378
- - **On-Chain Verification**: Whitelist and ownership data stored immutably on blockchain
338
+ 1. **Downstream identity bypass (consumer: `epistery.app`).** Consumers have
339
+ asserted contract identity via a spoofable `x-identity-contract` header +
340
+ localStorage instead of consuming the verified `_epistery` cookie. Now
341
+ unblocked by the cutover above: the consumer's adopt path should call
342
+ `wallet.upgradeToContract(C)` + `Witness.performKeyExchange()` and read
343
+ identity from `req.episteryClient.identityAddress`.
379
344
 
380
- ## Testing
345
+ 2. **Wallet-internal `address` field still flips on upgrade.** `RivetWallet.upgradeToContract`
346
+ still overwrites `wallet.address` with the contract address (the original
347
+ rivet survives as `wallet.rivetAddress`). The new `wallet.signerAddress` /
348
+ `wallet.identityAddress` getters cover the boundary, but every internal
349
+ caller of `wallet.address` reads an overloaded value. Phase 1b: rename the
350
+ persistence shape (with one-time IndexedDB migration so existing user
351
+ wallets keep working) and convert call sites.
381
352
 
382
- ```bash
383
- # Setup test environment (generates wallets automatically)
384
- npm run test:setup
385
-
386
- # Run tests
387
- npm test
388
- ```
353
+ 3. **`PrepareTransactionRequest`/`Response` types.** Reference removed
354
+ `agent.sol` operations (`write` / `transferOwnership` / `createApproval`
355
+ / etc.); imported but no longer consumed. Delete in the next dead-code sweep.
389
356
 
390
- The setup script creates `.test.env` with generated wallet credentials. For integration tests requiring a deployed contract, add `TEST_CONTRACT_ADDRESS` to `.test.env` after running `npm run deploy:agent`.
357
+ ---
391
358
 
392
359
  ## License
393
360
 
394
- MIT License - see [LICENSE](LICENSE) for details
361
+ MIT see [LICENSE](LICENSE).
395
362
 
396
363
  ## Links
397
364
 
398
- - **Homepage**: https://epistery.com
399
- - **Repository**: https://github.com/rootz-global/epistery
400
- - **Documentation**: See [CLI.md](CLI.md), [Architecture.md](Architecture.md), [SESSION.md](SESSION.md)
365
+ - Repository: https://github.com/rootz-global/epistery
366
+ - See [CLI.md](CLI.md), [Architecture.md](Architecture.md), [SESSION.md](SESSION.md)