@towns-labs/wallet 7.2.0 → 7.3.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.
package/README.md CHANGED
@@ -1,74 +1,64 @@
1
- # towns wallet-cli
1
+ # tw — Towns Wallet CLI
2
2
 
3
- CLI for `tw` account onboarding, session key lifecycle, and relayed transactions.
3
+ Your keys. Your account. No signup. No login. Just run it.
4
4
 
5
- Built on [incur](https://github.com/wevm/incur) every command supports `--help`, `--json`, `--format`, and can run as an MCP server via `--mcp`.
6
-
7
- ## Run with bunx
5
+ `tw` is a local-first CLI for managing smart accounts, session keys, and on-chain permissions on Towns Protocol. It works for humans at the terminal and for agents over `--json` or `--mcp`.
8
6
 
9
7
  ```bash
10
8
  bunx @towns-labs/wallet --help
11
9
  ```
12
10
 
13
- ## Permissionless
11
+ ---
14
12
 
15
- `tw` is fully permissionless: no signup, no account registration, and no login flow. You can run it immediately with local keys.
13
+ ## Quick Start
16
14
 
17
- ## Output Modes
15
+ Create an account and send USDC in under a minute:
18
16
 
19
- incur handles output formatting automatically:
17
+ ```bash
18
+ # 1. Create a local account (interactive password prompt)
19
+ tw account create --profile main
20
20
 
21
- - `--json` structured JSON output
22
- - `--format table` tabular output
23
- - Default: human-readable text
21
+ # 2. Fund it
22
+ tw address --qr --amount 100
24
23
 
25
- ## Help and Discovery
24
+ # 3. Send USDC
25
+ tw account send 10 vitalik.eth
26
+ ```
26
27
 
27
- ```bash
28
- # Show all commands
29
- tw --help
28
+ No custody service. No API key. Everything runs locally with encrypted keystores.
30
29
 
31
- # Show help for a specific command
32
- tw account create --help
30
+ ---
33
31
 
34
- # Machine-readable command manifest (for LLMs/agents)
35
- tw --llms
32
+ ## How It Works
36
33
 
37
- # Run as an MCP server
38
- tw --mcp
34
+ `tw` manages a local keystore that holds your root key and session keys. Your root key creates and controls your on-chain smart account. Session keys are scoped signers — they can send transactions through the Towns relayer without exposing your root key.
39
35
 
40
- # Version
41
- tw --version
36
+ ```text
37
+ ~/.config/towns/tw/profiles/<env>/<profile>/default.keystore.json
38
+ ~/.config/towns/tw/profiles/<env>/<profile>/sessions/<session-name>.json
42
39
  ```
43
40
 
44
- ## Account Create
41
+ For agents, the session daemon keeps decrypted keys in memory for a bounded duration so automated workflows can sign without prompting for a password on every call.
45
42
 
46
- Create a new account (interactive password prompt):
43
+ ---
47
44
 
48
- ```bash
49
- tw account create
50
- ```
45
+ ## Commands
51
46
 
52
- Create a named profile:
47
+ ### Account
48
+
49
+ #### `account create` — Create a new account
53
50
 
54
51
  ```bash
55
52
  tw account create --profile agent
56
53
  ```
57
54
 
58
- Keystore layout:
59
-
60
- ```text
61
- ~/.config/towns/tw/profiles/<env>/<profile>/default.keystore.json
62
- ~/.config/towns/tw/profiles/<env>/<profile>/sessions/<session-name>.json
63
- ```
64
-
65
- Resume a previously created profile:
55
+ Resume a previously interrupted flow:
66
56
 
67
57
  ```bash
68
58
  tw account create --resume --profile agent
69
59
  ```
70
60
 
71
- Non-interactive mode (stdin password + JSON output):
61
+ Non-interactive (stdin password + JSON):
72
62
 
73
63
  ```bash
74
64
  echo "my-password" | tw account create --password-stdin --json
@@ -80,461 +70,434 @@ Use an explicit keystore path:
80
70
  tw account create --keystore-path ~/.config/towns/tw/profiles/prod/team/default.keystore.json
81
71
  ```
82
72
 
83
- ## Account Export
84
-
85
- Export metadata only (safe default):
86
-
87
- ```bash
88
- tw account export --profile agent
89
- ```
90
-
91
- Export metadata in JSON:
73
+ #### `account status` — Check readiness
92
74
 
93
75
  ```bash
94
- tw account export --profile agent --json
76
+ tw account status --profile agent --json
95
77
  ```
96
78
 
97
- Export decrypted private keys (interactive confirmation required):
79
+ #### `account balance` Check USDC balance
98
80
 
99
81
  ```bash
100
- tw account export --profile agent --show-private
82
+ tw account balance --profile agent
83
+ tw account balance --profile agent --chain polygon
101
84
  ```
102
85
 
103
- ## Account Address
104
-
105
- Print the main/root account address:
86
+ #### `account address` — Print root address
106
87
 
107
88
  ```bash
108
89
  tw account address --profile agent
109
90
  ```
110
91
 
111
- ## Address (Root Alias)
112
-
113
- `tw address` is a root alias for `tw account address` with optional funding display helpers.
114
-
115
- Print address + default funding context (USDC on Base):
92
+ #### `account send` — Send USDC
116
93
 
117
94
  ```bash
118
- tw address
95
+ tw account send 1 0x1111111111111111111111111111111111111111
96
+ tw account send 2.5 vitalik.eth --chain base
97
+ tw account send 10 treasury --chain polygon --json
119
98
  ```
120
99
 
121
- Show a funding payment link:
100
+ Use a specific local session (without changing active session):
122
101
 
123
102
  ```bash
124
- tw address --link --amount 100
103
+ tw account send 1 0x... --profile agent --session worker-2
125
104
  ```
126
105
 
127
- Show a QR for the same payment payload:
106
+ Use a portable session file (no root keystore required):
128
107
 
129
108
  ```bash
130
- tw address --qr --amount 100
109
+ tw account send 1 0x... --chain base --session-file ./worker-1.session.json
131
110
  ```
132
111
 
133
- Custom token amount requires explicit decimals:
134
-
135
- ```bash
136
- tw address --token 0x1111111111111111111111111111111111111111 --amount 1 --decimals 18 --link
137
- ```
112
+ `--session` and `--session-file` are mutually exclusive.
138
113
 
139
- ## Account Balance
114
+ #### `account swap` — Swap tokens on the same chain
140
115
 
141
- Check USDC balance for the main/root account on Base (default):
116
+ Powered by [relay.link](https://relay.link). Interactive confirmation by default.
142
117
 
143
118
  ```bash
144
- tw account balance --profile agent
119
+ tw account swap --from ETH --to USDC --amount 0.1 --chain base
120
+ tw account swap --from USDC --to ETH --amount 50 --chain base --yes --json
145
121
  ```
146
122
 
147
- Check USDC balance on Polygon:
123
+ #### `account bridge` Bridge tokens cross-chain
148
124
 
149
125
  ```bash
150
- tw account balance --profile agent --chain polygon
126
+ tw account bridge --token USDC --amount 100 --to-chain polygon
127
+ tw account bridge --token ETH --amount 0.5 --to-chain base --recipient 0x... --yes --json
151
128
  ```
152
129
 
153
- ## Account Send
130
+ #### `account delegate` — Delegate on additional chains
154
131
 
155
- Send USDC to an address:
132
+ If your account was created on Base and you need it on Polygon:
156
133
 
157
134
  ```bash
158
- tw account send 1 0x1111111111111111111111111111111111111111
135
+ echo "my-password" | tw account delegate --chain polygon --profile agent --password-stdin
159
136
  ```
160
137
 
161
- Send USDC to ENS on Base:
138
+ #### `account history` Relayer call history
162
139
 
163
140
  ```bash
164
- tw account send 2.5 vitalik.eth --chain base
141
+ tw account history --profile agent
142
+ tw account history --address 0x... --limit 10 --offset 20
143
+ tw account history --chain base,polygon --json
165
144
  ```
166
145
 
167
- Send USDC on Polygon with JSON output:
146
+ `--limit` defaults to 20 (max 100). `offset + limit` must be <= 1000.
147
+
148
+ #### `account export` — Export metadata or private keys
168
149
 
169
150
  ```bash
170
- tw account send 10 0x1111111111111111111111111111111111111111 --chain polygon --json
151
+ tw account export --profile agent --json
152
+ tw account export --profile agent --show-private
171
153
  ```
172
154
 
173
- Send using a specific local session by name (without changing active session):
155
+ #### `account nonce` Read account nonce
174
156
 
175
157
  ```bash
176
- tw account send 1 0x1111111111111111111111111111111111111111 --profile agent --session worker-2
158
+ tw account nonce --profile agent
177
159
  ```
178
160
 
179
- ## Account History
180
-
181
- Get paginated relayer call history for an EOA. By default this uses your local profile root EOA from keystore:
161
+ #### `account update password` — Rotate keystore password
182
162
 
183
163
  ```bash
184
- tw account history --profile agent
164
+ tw account update password --profile agent
165
+ printf "old\nnew\n" | tw account update password --current-password-stdin --new-password-stdin --json
185
166
  ```
186
167
 
187
- Query an explicit EOA (without reading local keystore):
168
+ ---
169
+
170
+ ### Address
171
+
172
+ `tw address` is a top-level shortcut for showing your funding address with optional payment helpers.
188
173
 
189
174
  ```bash
190
- tw account history --address 0x1111111111111111111111111111111111111111 --limit 10 --offset 20
175
+ tw address
176
+ tw address --link --amount 100
177
+ tw address --qr --amount 100
191
178
  ```
192
179
 
193
- Filter by one or more chains:
180
+ Custom token:
194
181
 
195
182
  ```bash
196
- tw account history --chain base,polygon --json
183
+ tw address --token 0x... --amount 1 --decimals 18 --link
197
184
  ```
198
185
 
199
- Notes:
186
+ ---
200
187
 
201
- - `--limit` defaults to `20` and max is `100`
202
- - `--offset` defaults to `0`
203
- - `offset + limit` must be `<= 1000`
204
- - History queries are EOA-based (root EOA), not delegated account address
188
+ ### Session Keys
205
189
 
206
- ## Contacts
190
+ Session keys are scoped signers that can act on behalf of your account without exposing the root key.
207
191
 
208
- Store aliases in a global contacts file:
192
+ #### `session create` — Create and authorize a session key
209
193
 
210
- ```text
211
- ~/.config/towns/tw/contacts.json
194
+ ```bash
195
+ echo "my-password" | tw session create worker-1 --profile agent --password-stdin --json
212
196
  ```
213
197
 
214
- Add contact aliases:
198
+ Create and activate immediately:
215
199
 
216
200
  ```bash
217
- tw contacts add vitalik vitalik.eth
218
- tw contacts add treasury 0x1111111111111111111111111111111111111111
201
+ echo "my-password" | tw session create worker-2 --profile agent --activate --password-stdin
219
202
  ```
220
203
 
221
- List or show contacts:
204
+ Grant full wildcard access:
222
205
 
223
206
  ```bash
224
- tw contacts list --json
225
- tw contacts show vitalik --json
207
+ echo "my-password" | tw session create worker-admin --profile agent --full-access --password-stdin
226
208
  ```
227
209
 
228
- Update, rename, and remove:
210
+ `--full-access` is mutually exclusive with `--target`, `--selector`, `--spend-limit`, `--spend-limit-raw`, and `--spend-period`. Omitting both `--target` and `--selector` uses wildcard call permissions by default.
211
+
212
+ Resume an interrupted flow:
229
213
 
230
214
  ```bash
231
- tw contacts update vitalik 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
232
- tw contacts rename vitalik vitalik-main
233
- tw contacts remove vitalik-main
215
+ echo "my-password" | tw session create worker-1 --profile agent --resume --password-stdin --json
234
216
  ```
235
217
 
236
- Export and import:
218
+ #### `session list` — List local sessions
237
219
 
238
220
  ```bash
239
- tw contacts export --json
240
- tw contacts import ./contacts.json --json
241
- tw contacts import ./contacts.json --force --json
221
+ tw session list --profile agent --json
222
+ tw session list --profile agent --on-chain --json
242
223
  ```
243
224
 
244
- Import returns deterministic summary fields:
245
-
246
- - `importedCount`
247
- - `skippedInvalidCount`
248
- - `skippedConflictCount`
249
- - `overwrittenCount`
250
-
251
- Send via alias:
225
+ #### `session rotate` Rotate the active session
252
226
 
253
227
  ```bash
254
- tw account send 10 vitalik
228
+ echo "my-password" | tw session rotate --profile agent --password-stdin --json
229
+ echo "my-password" | tw session rotate --profile agent --new-name worker-3 --password-stdin
230
+ echo "my-password" | tw session rotate --profile agent --resume --password-stdin --json
255
231
  ```
256
232
 
257
- ENS safety behavior for ENS-backed contacts:
258
-
259
- - If ENS re-resolution fails, `tw account send` falls back to the stored address.
260
- - If ENS re-resolution succeeds but points to a different address than stored, send aborts and requires explicit `tw contacts update`.
261
-
262
- Send with a portable session file (no root keystore required):
233
+ #### `session revoke` Revoke a session on-chain
263
234
 
264
235
  ```bash
265
- tw account send 1 0x1111111111111111111111111111111111111111 \
266
- --chain base --session-file ./worker-1.session.json
236
+ echo "my-password" | tw session revoke worker-1 --profile agent --password-stdin --json
237
+ echo "my-password" | tw session revoke worker-2 --profile agent --force --password-stdin
238
+ echo "my-password" | tw session revoke worker-2 --profile agent --resume --password-stdin --json
267
239
  ```
268
240
 
269
- `--session` and `--session-file` are mutually exclusive.
270
-
271
- ## Account Status
272
-
273
- Show compact readiness status:
241
+ #### `session export` Export as portable file
274
242
 
275
243
  ```bash
276
- tw account status --profile agent
244
+ TW_PASSWORD="my-password" TW_EXPORT_PASSWORD="export-password" \
245
+ tw session export worker-1 --profile agent --output ./worker-1.session.json
277
246
  ```
278
247
 
279
- JSON output for automation:
248
+ Or via stdin:
280
249
 
281
250
  ```bash
282
- tw account status --profile agent --chain polygon --json
251
+ echo "export-password" | tw session export worker-1 --profile agent \
252
+ --output ./worker-1.session.json --export-password-stdin
283
253
  ```
284
254
 
285
- ## Account Update Password
286
-
287
- Rotate local keystore encryption password (interactive):
255
+ #### `session import` — Import a portable session
288
256
 
289
257
  ```bash
290
- tw account update password --profile agent
258
+ tw session import ./worker-1.session.json --profile worker-1
291
259
  ```
292
260
 
293
- Rotate password non-interactively using stdin lines:
261
+ Creates a session-only profile that can execute `account send` but cannot run manager commands requiring a root keystore.
294
262
 
295
- ```bash
296
- printf "old-password\nnew-password\n" | tw account update password --current-password-stdin --new-password-stdin --json
297
- ```
263
+ ---
298
264
 
299
- ## Session Create
265
+ ### Session Daemon
300
266
 
301
- Create and authorize a scoped session key in one intent:
267
+ The daemon holds decrypted session keys in memory so automated workflows can sign without re-entering passwords.
268
+
269
+ #### `session start` — Start the daemon
302
270
 
303
271
  ```bash
304
- echo "my-password" | tw session create worker-1 --profile agent --password-stdin --json
272
+ tw session start --json
273
+ tw session start --foreground --json
305
274
  ```
306
275
 
307
- Create and activate immediately:
276
+ Idempotent if already running, returns the active pid/socket.
277
+
278
+ #### `session unlock` — Load a key into daemon memory
308
279
 
309
280
  ```bash
310
- echo "my-password" | tw session create worker-2 --profile agent --activate --password-stdin
281
+ echo "my-password" | tw session unlock worker-1 --profile agent --password-stdin --duration 15m --json
311
282
  ```
312
283
 
313
- Resume a previously interrupted create flow:
284
+ Key material stays in daemon memory only; encrypted files remain unchanged.
285
+
286
+ #### `session lock` — Remove a key from daemon memory
314
287
 
315
288
  ```bash
316
- echo "my-password" | tw session create worker-1 --profile agent --resume --password-stdin --json
289
+ tw session lock worker-1 --json
317
290
  ```
318
291
 
319
- Grant full wildcard access (explicit opt-in):
292
+ #### `session status` Check daemon health
320
293
 
321
294
  ```bash
322
- echo "my-password" | tw session create worker-admin --profile agent --full-access --password-stdin
295
+ tw session status --json
323
296
  ```
324
297
 
325
- `--full-access` is mutually exclusive with `--target`, `--selector`, `--spend-limit`, `--spend-limit-raw`, and `--spend-period`.
326
- Omitting both `--target` and `--selector` uses wildcard call permissions by default; you do not need to pass magic wildcard values.
327
- `--spend-limit` accepts human USDC amounts (for example `10` = `10 USDC`). Use `--spend-limit-raw` for base units.
328
-
329
- ## Session List
330
-
331
- List local sessions:
298
+ #### `session stop` Shut down the daemon
332
299
 
333
300
  ```bash
334
- tw session list --profile agent --json
301
+ tw session stop --json
335
302
  ```
336
303
 
337
- Include on-chain key status:
304
+ ---
338
305
 
339
- ```bash
340
- tw session list --profile agent --on-chain --json
341
- ```
306
+ ### Permissions
342
307
 
343
- ## Session Export
308
+ Fine-grained on-chain permission rules for session keys.
344
309
 
345
- Export a session key as a portable file:
310
+ #### `permissions list` List keys and their permissions
346
311
 
347
312
  ```bash
348
- TW_PASSWORD="my-password" TW_EXPORT_PASSWORD="export-password" \
349
- tw session export worker-1 --profile agent --output ./worker-1.session.json
313
+ tw permissions list --profile agent --json
350
314
  ```
351
315
 
352
- Or provide the export password via stdin:
316
+ #### `permissions show` Show rules for a specific key
353
317
 
354
318
  ```bash
355
- echo "export-password" | tw session export worker-1 --profile agent \
356
- --output ./worker-1.session.json --export-password-stdin
319
+ tw permissions show agent-key --profile agent --json
320
+ tw permissions show --key-hash 0xaaa... --profile agent --json
357
321
  ```
358
322
 
359
- ## Session Import
323
+ #### `permissions grant` — Grant a permission rule
360
324
 
361
- Import a portable session as a session-only profile:
325
+ Wildcard call permission:
362
326
 
363
327
  ```bash
364
- tw session import ./worker-1.session.json --profile worker-1
328
+ echo "my-password" | tw permissions grant agent-key --type call --target any --selector any --profile agent --password-stdin --json
365
329
  ```
366
330
 
367
- This creates:
331
+ Daily USDC spend limit:
368
332
 
369
333
  ```bash
370
- ~/.config/towns/tw/profiles/worker-1/session.json
334
+ echo "my-password" | tw permissions grant agent-key --type spend --token USDC --spend-limit 100 --period day --profile agent --password-stdin --json
371
335
  ```
372
336
 
373
- Session-only profiles can execute `account send` but cannot run manager commands that require a root keystore.
337
+ Use `--spend-limit-raw` for base units instead of human amounts.
374
338
 
375
- ## Session Rotate
339
+ #### `permissions revoke` — Revoke permission rules
376
340
 
377
- Rotate the active session (auto-generates new name):
341
+ Revoke a single rule:
378
342
 
379
343
  ```bash
380
- echo "my-password" | tw session rotate --profile agent --password-stdin --json
344
+ echo "my-password" | tw permissions revoke agent-key --rule call:0x...:0xa9059cbb --profile agent --password-stdin --json
381
345
  ```
382
346
 
383
- Rotate to an explicit new session name:
347
+ Revoke all rules for a key:
384
348
 
385
349
  ```bash
386
- echo "my-password" | tw session rotate --profile agent --new-name worker-3 --password-stdin
350
+ echo "my-password" | tw permissions revoke agent-key --all --profile agent --password-stdin --json
387
351
  ```
388
352
 
389
- `--full-access` is mutually exclusive with `--target`, `--selector`, `--spend-limit`, `--spend-limit-raw`, and `--spend-period`.
390
- Omitting both `--target` and `--selector` uses wildcard call permissions by default; you do not need to pass magic wildcard values.
391
- `--spend-limit` accepts human USDC amounts (for example `10` = `10 USDC`). Use `--spend-limit-raw` for base units.
353
+ ---
392
354
 
393
- Resume interrupted rotation:
355
+ ### Escrow
394
356
 
395
- ```bash
396
- echo "my-password" | tw session rotate --profile agent --resume --password-stdin --json
397
- ```
357
+ USDC escrow: lock funds as buyer with a seller and oracle; settle (oracle signs) or refund after deadline.
398
358
 
399
- ## Session Revoke
400
-
401
- Revoke a non-active session on-chain, verify, then delete local file:
359
+ #### `escrow create` — Create a new USDC escrow
402
360
 
403
361
  ```bash
404
- echo "my-password" | tw session revoke worker-1 --profile agent --password-stdin --json
362
+ tw escrow create 50 0x1111111111111111111111111111111111111111 \
363
+ --oracle 0x2222222222222222222222222222222222222222 --deadline 24h
364
+ tw escrow create 100 0x... --oracle 0x... --deadline 2d --chain base --env prod --json
405
365
  ```
406
366
 
407
- Force revoke active session:
408
-
409
- ```bash
410
- echo "my-password" | tw session revoke worker-2 --profile agent --force --password-stdin
411
- ```
367
+ - **Amount** and **seller** are positional; **oracle** and **deadline** are required options.
368
+ - **Deadline**: relative (`1h`, `2d`, `30m`, `1w`) or unix timestamp. After deadline, anyone can call `escrow refund`.
369
+ - Optional **salt** (hex, up to 12 bytes) for unique escrow IDs on repeated orders.
370
+ - Uses session key (daemon or direct) to sign; supports `--session-file` like `account send`.
412
371
 
413
- Idempotent retry/cleanup when already revoked on-chain:
372
+ #### `escrow status` Check on-chain status
414
373
 
415
374
  ```bash
416
- echo "my-password" | tw session revoke worker-2 --profile agent --resume --password-stdin --json
375
+ tw escrow status 0x<64 hex chars> --env prod
376
+ tw escrow status 0x... --chain base --json
417
377
  ```
418
378
 
419
- ## Agent Commands
379
+ Escrow ID must be the full 32-byte hex (0x + 64 hex chars), e.g. from `escrow create` output.
420
380
 
421
- Agents are session-key-backed Towns identities with their own encryption device and named channel bindings.
381
+ #### `escrow settle` Settle (release USDC to seller)
422
382
 
423
- Agent keystore files live alongside normal sessions:
383
+ Oracle signs a settlement; any account can submit the signed settlement via relayer.
424
384
 
425
- ```text
426
- ~/.config/towns/tw/profiles/<env>/<profile>/sessions/agent-<name>.json
427
- ~/.config/towns/tw/profiles/<env>/<profile>/agent-channels.json
385
+ ```bash
386
+ TW_ORACLE_PRIVATE_KEY=0x... tw escrow settle 0x<escrowId> \
387
+ --settlement-id 0x<orderId> --oracle 0x2222... --profile agent
428
388
  ```
429
389
 
430
- Create two local agents:
390
+ Or with pre-signed signature (oracle signs offline):
431
391
 
432
392
  ```bash
433
- TW_PASSWORD="my-password" tw agent create alice --profile agent
434
- TW_PASSWORD="my-password" tw agent create bob --profile agent
393
+ tw escrow settle 0x<escrowId> --settlement-id 0x... --oracle 0x... --signature 0x... --profile agent
435
394
  ```
436
395
 
437
- List local agents:
396
+ Settlement ID is the bytes32 order ID used at creation (see create output or `escrow status`).
438
397
 
439
- ```bash
440
- TW_PASSWORD="my-password" tw agent list --profile agent --json
441
- ```
398
+ #### `escrow refund` — Refund to buyer after deadline
442
399
 
443
- Create or bind a named channel:
400
+ Permissionless: after the escrow deadline, anyone can trigger refund to the buyer.
444
401
 
445
402
  ```bash
446
- TW_PASSWORD="my-password" tw agent connect --from alice --channel art --to bob --profile agent
403
+ tw escrow refund 0x<escrowId> --profile agent
404
+ tw escrow refund 0x<escrowId> --env prod --json
447
405
  ```
448
406
 
449
- The first `connect` returns:
407
+ ---
450
408
 
451
- - `streamId` — the underlying GDM stream
452
- - `secret` — the shared rendezvous secret, returned only when the binding is first created
409
+ ### Contacts
453
410
 
454
- Bind the same named channel from the other side:
411
+ Store recipient aliases so you never have to copy-paste addresses.
455
412
 
456
- ```bash
457
- TW_PASSWORD="my-password" tw agent connect --from bob --channel art --secret "<shared-secret>" --to alice --profile agent
413
+ ```text
414
+ ~/.config/towns/tw/contacts.json
458
415
  ```
459
416
 
460
- Send to a named channel:
461
-
462
417
  ```bash
463
- TW_PASSWORD="my-password" tw agent send --from alice --channel art "hello from alice" --profile agent
418
+ tw contacts add vitalik vitalik.eth
419
+ tw contacts add treasury 0x1111111111111111111111111111111111111111
420
+ tw contacts list --json
421
+ tw contacts show vitalik --json
422
+ tw contacts update vitalik 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
423
+ tw contacts rename vitalik vitalik-main
424
+ tw contacts remove vitalik-main
464
425
  ```
465
426
 
466
- Send directly to a raw stream ID:
427
+ Export and import:
467
428
 
468
429
  ```bash
469
- TW_PASSWORD="my-password" tw agent send --from alice 77aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "debug message" --profile agent
430
+ tw contacts export --json
431
+ tw contacts import ./contacts.json --json
432
+ tw contacts import ./contacts.json --force --json
470
433
  ```
471
434
 
472
- Listen on a named channel:
435
+ Import returns `importedCount`, `skippedInvalidCount`, `skippedConflictCount`, and `overwrittenCount`.
436
+
437
+ Send via alias:
473
438
 
474
439
  ```bash
475
- TW_PASSWORD="my-password" tw agent listen --from bob --channel art --profile agent
440
+ tw account send 10 vitalik
476
441
  ```
477
442
 
478
- Listen on a specific stream:
443
+ ENS safety: if ENS re-resolves to a different address than stored, send aborts and requires `tw contacts update`.
479
444
 
480
- ```bash
481
- TW_PASSWORD="my-password" tw agent listen --from bob --stream 77aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --profile agent
482
- ```
445
+ ---
483
446
 
484
- Inspect local named bindings:
447
+ ### Agents
485
448
 
486
- ```bash
487
- TW_PASSWORD="my-password" tw agent channels --from alice --profile agent --json
449
+ Agents are session-key-backed Towns identities with their own encryption device and named channel bindings — built for autonomous agent-to-agent communication.
450
+
451
+ ```text
452
+ ~/.config/towns/tw/profiles/<env>/<profile>/sessions/agent-<name>.json
453
+ ~/.config/towns/tw/profiles/<env>/<profile>/agent-channels.json
488
454
  ```
489
455
 
490
- Remove an agent:
456
+ #### `agent create` / `agent list` / `agent remove`
491
457
 
492
458
  ```bash
459
+ TW_PASSWORD="my-password" tw agent create alice --profile agent
460
+ TW_PASSWORD="my-password" tw agent create bob --profile agent
461
+ TW_PASSWORD="my-password" tw agent list --profile agent --json
493
462
  TW_PASSWORD="my-password" tw agent remove bob --profile agent
494
463
  ```
495
464
 
496
- ## Agent Quickstart
497
-
498
- This is the shortest end-to-end flow to start a conversation between two local agents.
465
+ #### `agent connect` — Create or bind a named channel
499
466
 
500
- 1. Create or reuse a wallet profile:
467
+ First side creates the channel and returns the shared secret:
501
468
 
502
469
  ```bash
503
- tw account create --profile agent
470
+ TW_PASSWORD="my-password" tw agent connect --from alice --channel art --to bob --profile agent
504
471
  ```
505
472
 
506
- 2. Create two agents:
473
+ Second side binds using that secret:
507
474
 
508
475
  ```bash
509
- TW_PASSWORD="my-password" tw agent create alice --profile agent
510
- TW_PASSWORD="my-password" tw agent create bob --profile agent
476
+ TW_PASSWORD="my-password" tw agent connect --from bob --channel art --secret "<shared-secret>" --to alice --profile agent
511
477
  ```
512
478
 
513
- 3. Create a named channel and copy the returned `secret` and `streamId`:
479
+ #### `agent send` Send a message
514
480
 
515
- ```bash
516
- TW_PASSWORD="my-password" tw agent connect --from alice --channel art --to bob --profile agent
517
- ```
518
-
519
- 4. Bind the same channel from Bob using that secret:
481
+ To a named channel:
520
482
 
521
483
  ```bash
522
- TW_PASSWORD="my-password" tw agent connect --from bob --channel art --secret "<shared-secret>" --to alice --profile agent
484
+ TW_PASSWORD="my-password" tw agent send --from alice --channel art "hello from alice" --profile agent
523
485
  ```
524
486
 
525
- 5. Start a listener in one terminal:
487
+ To a raw stream ID:
526
488
 
527
489
  ```bash
528
- TW_PASSWORD="my-password" tw agent listen --from bob --channel art --profile agent
490
+ TW_PASSWORD="my-password" tw agent send --from alice 77aaa... "debug message" --profile agent
529
491
  ```
530
492
 
531
- 6. Send from another terminal:
493
+ #### `agent listen` Listen for messages
532
494
 
533
495
  ```bash
534
- TW_PASSWORD="my-password" tw agent send --from alice --channel art "hello from alice" --profile agent
496
+ TW_PASSWORD="my-password" tw agent listen --from bob --channel art --profile agent
497
+ TW_PASSWORD="my-password" tw agent listen --from bob --stream 77aaa... --profile agent
535
498
  ```
536
499
 
537
- 7. Bob's listener prints NDJSON when the message arrives:
500
+ Messages arrive as NDJSON:
538
501
 
539
502
  ```json
540
503
  {
@@ -547,85 +510,70 @@ TW_PASSWORD="my-password" tw agent send --from alice --channel art "hello from a
547
510
  }
548
511
  ```
549
512
 
550
- 8. Inspect the local binding if you need the underlying stream:
513
+ #### `agent channels` Inspect bindings
551
514
 
552
515
  ```bash
553
- TW_PASSWORD="my-password" tw agent channels --from alice --profile agent
554
- ```
555
-
556
- For a single-command live verification, run the built-in smoke script:
557
-
558
- ```bash
559
- cd packages/wallet
560
- TW_PASSWORD="my-password" bun run smoke:agent
516
+ TW_PASSWORD="my-password" tw agent channels --from alice --profile agent --json
561
517
  ```
562
518
 
563
- ## Permissions List
564
-
565
- List all on-chain keys with summarized call/spend permissions:
519
+ #### Agent Quickstart
566
520
 
567
521
  ```bash
568
- tw permissions list --profile agent --json
569
- ```
570
-
571
- ## Permissions Show
522
+ # Create profile + two agents
523
+ tw account create --profile agent
524
+ TW_PASSWORD="pw" tw agent create alice --profile agent
525
+ TW_PASSWORD="pw" tw agent create bob --profile agent
572
526
 
573
- Show full permission rules for a key using positional `<key-ref>`:
527
+ # Alice creates a channel copy the returned secret
528
+ TW_PASSWORD="pw" tw agent connect --from alice --channel art --to bob --profile agent
574
529
 
575
- ```bash
576
- tw permissions show agent-key --profile agent --json
577
- ```
530
+ # Bob binds with that secret
531
+ TW_PASSWORD="pw" tw agent connect --from bob --channel art --secret "<secret>" --to alice --profile agent
578
532
 
579
- Explicit selectors are also supported:
533
+ # Terminal 1: listen
534
+ TW_PASSWORD="pw" tw agent listen --from bob --channel art --profile agent
580
535
 
581
- ```bash
582
- tw permissions show --key-hash 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --profile agent --json
536
+ # Terminal 2: send
537
+ TW_PASSWORD="pw" tw agent send --from alice --channel art "hello" --profile agent
583
538
  ```
584
539
 
585
- ## Permissions Grant
586
-
587
- Grant wildcard call permission:
540
+ Smoke test:
588
541
 
589
542
  ```bash
590
- echo "my-password" | tw permissions grant agent-key --type call --target any --selector any --profile agent --password-stdin --json
543
+ cd packages/wallet
544
+ TW_PASSWORD="my-password" bun run smoke:agent
591
545
  ```
592
546
 
593
- Grant spend permission for daily USDC limit:
547
+ ---
594
548
 
595
- ```bash
596
- echo "my-password" | tw permissions grant agent-key --type spend --token USDC --spend-limit 100 --period day --profile agent --password-stdin --json
597
- ```
549
+ ## Agent & Automation Integration
598
550
 
599
- For raw base units instead of human USDC amounts, use:
551
+ Every command supports `--json` for machine-readable output. Treat JSON output schemas as the stable contract.
600
552
 
601
553
  ```bash
602
- echo "my-password" | tw permissions grant agent-key --type spend --token USDC --spend-limit-raw 100000000 --period day --profile agent --password-stdin --json
554
+ tw <command> --json
603
555
  ```
604
556
 
605
- ## Permissions Revoke
606
-
607
- Revoke one rule by stable rule id:
608
-
609
- ```bash
610
- echo "my-password" | tw permissions revoke agent-key --rule call:0x2222222222222222222222222222222222222222:0xa9059cbb --profile agent --password-stdin --json
611
- ```
557
+ ### Password Automation
612
558
 
613
- Revoke all call/spend rules for a key:
559
+ Use `TW_PASSWORD` to skip interactive prompts:
614
560
 
615
561
  ```bash
616
- echo "my-password" | tw permissions revoke agent-key --all --profile agent --password-stdin --json
562
+ TW_PASSWORD="my-password" tw session list --profile agent --json
617
563
  ```
618
564
 
619
- ## Password Automation
620
-
621
- Use `TW_PASSWORD` for non-interactive flows:
565
+ Or pipe via stdin for commands that accept `--password-stdin`:
622
566
 
623
567
  ```bash
624
- TW_PASSWORD="my-password" tw session list --profile agent --json
568
+ echo "my-password" | tw session rotate --profile agent --password-stdin --json
625
569
  ```
626
570
 
627
- Stdin is supported for commands requiring keystore decryption/signing:
571
+ ### Discovery
628
572
 
629
573
  ```bash
630
- echo "my-password" | tw session rotate --profile agent --password-stdin --json
574
+ tw --help # All commands
575
+ tw account send --help # Command-specific help
576
+ tw --llms # Machine-readable command manifest
577
+ tw --mcp # Run as MCP server
578
+ tw --version # Version
631
579
  ```