@towns-labs/wallet 7.2.0 → 7.3.1

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