@toon-protocol/townhouse 0.1.0-rc5
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/LICENSE +190 -0
- package/README.md +386 -0
- package/dist/chunk-IB6TNCUQ.js +8274 -0
- package/dist/chunk-IB6TNCUQ.js.map +1 -0
- package/dist/chunk-UTFWPLTB.js +59 -0
- package/dist/chunk-UTFWPLTB.js.map +1 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +684 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose/townhouse-dev.yml +406 -0
- package/dist/compose/townhouse-hs.yml +276 -0
- package/dist/demo-MJR47QHZ.js +117 -0
- package/dist/demo-MJR47QHZ.js.map +1 -0
- package/dist/image-manifest.json +32 -0
- package/dist/index.d.ts +1410 -0
- package/dist/index.js +180 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Townhouse — Hidden Service mode (HS) operator compose template
|
|
2
|
+
#
|
|
3
|
+
# THIS IS A BUILD-TIME TEMPLATE. Do not use it directly.
|
|
4
|
+
# The digest placeholders (${TOON_*_DIGEST}) are substituted by
|
|
5
|
+
# scripts/render-compose-template.mjs during `pnpm build` (when
|
|
6
|
+
# dist/image-manifest.json is present) to produce the fully-resolved
|
|
7
|
+
# dist/compose/townhouse-hs.yml that ships inside the npm tarball.
|
|
8
|
+
#
|
|
9
|
+
# Resolved copy location at runtime: ~/.townhouse/compose/townhouse-hs.yml
|
|
10
|
+
# - Written by materializeComposeTemplate('hs') in packages/townhouse/src/compose-loader.ts
|
|
11
|
+
# - File mode: 0o600 (operator-secret — may embed private keys at deploy time)
|
|
12
|
+
#
|
|
13
|
+
# Story ownership:
|
|
14
|
+
# - Placeholder substitution: Story 45.2 (this file)
|
|
15
|
+
# - Boot sequence (townhouse hs up): Story 45.4
|
|
16
|
+
#
|
|
17
|
+
# Architecture (HS-mode v1, Epic 45):
|
|
18
|
+
# - Apex connector: standalone, anon HS publishing in-process (connector v3.5.x)
|
|
19
|
+
# - townhouse-api: containerized host API — owns /var/run/docker.sock, calls
|
|
20
|
+
# connector admin API, manages lifecycle for lazy-provisioned peers
|
|
21
|
+
# - town/mill/dvm: lazy-provisioned via Docker Compose profiles (Epic 46)
|
|
22
|
+
# Story 45.4 boots only connector + townhouse-api at apex install
|
|
23
|
+
#
|
|
24
|
+
# Digest placeholders (substituted at build time from dist/image-manifest.json):
|
|
25
|
+
# @sha256:48cb3df422019e252b7b1b5506b2fc666ea206ec57024eb68acbf781ebeb1f11 → @sha256:<hex>
|
|
26
|
+
# @sha256:ba83fb6df536dec240f27fb7449db59e803207221c54e61ea019375ddc0461a0 → @sha256:<hex>
|
|
27
|
+
# @sha256:38e8c6460a3fc562dab28284b4f51958d8417ce10a7152bbb4224fc00cc42df5 → @sha256:<hex>
|
|
28
|
+
# @sha256:26a2aff4cb490d11f8eab9cff3272475c951e7cc87378a1af6c702b529d74e10 → @sha256:<hex>
|
|
29
|
+
# @sha256:4a24ccb0997d7b025997e670546032f6a84cd18a77c490509016b85e181a344e → @sha256:<hex>
|
|
30
|
+
#
|
|
31
|
+
# Scope guard (Story 45.2 does NOT include):
|
|
32
|
+
# - ator-sidecar / ator-sidecar-relay (connector v3.5.x does HS publishing in-process)
|
|
33
|
+
# - anvil / solana / faucet (dev-stack concerns, not operator-facing)
|
|
34
|
+
# - build: directives (all images are digest-pinned GHCR pulls)
|
|
35
|
+
#
|
|
36
|
+
# Port allocation (HS-mode binds canonical ports — single-tenant operator box):
|
|
37
|
+
# 127.0.0.1:9401 connector admin
|
|
38
|
+
# 127.0.0.1:28090 townhouse-api Fastify
|
|
39
|
+
# 127.0.0.1:7100,3100 town (relay WS, BLS health) — profile gated
|
|
40
|
+
# 127.0.0.1:3200 mill BLS health — profile gated
|
|
41
|
+
# 127.0.0.1:3400 dvm BLS health — profile gated
|
|
42
|
+
#
|
|
43
|
+
# These collide with the contributor dev stack's 28xxx-namespaced bindings
|
|
44
|
+
# (28080:9401, 28100:3100, 28110:3100, 28200:3200, 28210:3200, 28400:3400,
|
|
45
|
+
# 28700:7100, 28710:7100). HS-mode and the contributor dev stack
|
|
46
|
+
# (scripts/townhouse-dev-infra.sh) MUST NOT run concurrently on the same
|
|
47
|
+
# machine. If you need to run multiple townhouse instances on a multi-tenant
|
|
48
|
+
# box, open an enhancement issue — the canonical ports here are intentional
|
|
49
|
+
# for the apex operator path (Story 45.4 `townhouse hs up`).
|
|
50
|
+
|
|
51
|
+
networks:
|
|
52
|
+
townhouse-hs-net:
|
|
53
|
+
driver: bridge
|
|
54
|
+
|
|
55
|
+
volumes:
|
|
56
|
+
# Named volume for the connector's .anyone keypair + HS state.
|
|
57
|
+
# Survives `docker compose down`; delete to rotate the .anyone address.
|
|
58
|
+
townhouse-hs-anon:
|
|
59
|
+
townhouse-hs-town-data:
|
|
60
|
+
townhouse-hs-mill-data:
|
|
61
|
+
townhouse-hs-dvm-data:
|
|
62
|
+
|
|
63
|
+
services:
|
|
64
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
65
|
+
# Apex connector — terminates inbound BTP, publishes .anyone HS
|
|
66
|
+
#
|
|
67
|
+
# The connector image embeds @anyone-protocol/anyone-client v1.1.x+
|
|
68
|
+
# which handles HS publishing in-process (no sidecar required for v3.5.x+).
|
|
69
|
+
# Config file written by `townhouse hs up` (Story 45.4) on first-run.
|
|
70
|
+
#
|
|
71
|
+
# NFR7: connector MUST NOT mount /var/run/docker.sock.
|
|
72
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
73
|
+
connector:
|
|
74
|
+
image: ghcr.io/toon-protocol/connector@sha256:4a24ccb0997d7b025997e670546032f6a84cd18a77c490509016b85e181a344e
|
|
75
|
+
container_name: townhouse-hs-connector
|
|
76
|
+
hostname: connector
|
|
77
|
+
networks:
|
|
78
|
+
- townhouse-hs-net
|
|
79
|
+
ports:
|
|
80
|
+
# Admin API on host loopback only (NFR9). Operator uses for status.
|
|
81
|
+
- '127.0.0.1:9401:9401'
|
|
82
|
+
volumes:
|
|
83
|
+
# Rendered connector config (Story 45.4 writes this on first-run).
|
|
84
|
+
- ~/.townhouse/connector.yaml:/config/connector.yaml:ro
|
|
85
|
+
# .anyone keypair + HS state (persists across down/up cycles).
|
|
86
|
+
- townhouse-hs-anon:/var/lib/anon/hs
|
|
87
|
+
environment:
|
|
88
|
+
CONFIG_FILE: /config/connector.yaml
|
|
89
|
+
healthcheck:
|
|
90
|
+
# nosemgrep: trailofbits.generic.wget-unencrypted-url.wget-unencrypted-url -- container-internal probe
|
|
91
|
+
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:9401/health']
|
|
92
|
+
interval: 10s
|
|
93
|
+
timeout: 5s
|
|
94
|
+
retries: 5
|
|
95
|
+
start_period: 30s
|
|
96
|
+
restart: unless-stopped
|
|
97
|
+
|
|
98
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
99
|
+
# Townhouse API — containerized host API (NEW in HS-mode v1)
|
|
100
|
+
#
|
|
101
|
+
# Owns /var/run/docker.sock (the only service that may). Provides:
|
|
102
|
+
# - Fastify REST API for operator dashboard / CLI
|
|
103
|
+
# - Calls connector admin API (/admin/hs-hostname, /admin/peers, etc.)
|
|
104
|
+
# - Manages lifecycle for lazy-provisioned peer containers (Epic 46)
|
|
105
|
+
#
|
|
106
|
+
# Planning doc §4 anchor: "host-side townhouse-api owns dockerode and
|
|
107
|
+
# runs on the townhouse-hs-net so it can reach connector via Docker DNS".
|
|
108
|
+
# Port D21-008: Fastify host API on 127.0.0.1:28090.
|
|
109
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
110
|
+
townhouse-api:
|
|
111
|
+
image: ghcr.io/toon-protocol/townhouse-api@sha256:48cb3df422019e252b7b1b5506b2fc666ea206ec57024eb68acbf781ebeb1f11
|
|
112
|
+
container_name: townhouse-hs-api
|
|
113
|
+
networks:
|
|
114
|
+
- townhouse-hs-net
|
|
115
|
+
depends_on:
|
|
116
|
+
connector:
|
|
117
|
+
condition: service_healthy
|
|
118
|
+
ports:
|
|
119
|
+
# Fastify host API — loopback only (NFR9).
|
|
120
|
+
- '127.0.0.1:28090:28090'
|
|
121
|
+
volumes:
|
|
122
|
+
# Docker socket — townhouse-api is the sole orchestration surface.
|
|
123
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
124
|
+
# Operator home — wallet, config, compose files, snapshots (RW).
|
|
125
|
+
- ~/.townhouse:/.townhouse:rw
|
|
126
|
+
environment:
|
|
127
|
+
# Override entrypoint default '/config/config.yaml' so the API reads from
|
|
128
|
+
# the mounted operator-home dir (where Story 45.4 writes config.yaml).
|
|
129
|
+
TOWNHOUSE_CONFIG: /.townhouse/config.yaml
|
|
130
|
+
# Wallet decryption password — operator must export TOWNHOUSE_WALLET_PASSWORD
|
|
131
|
+
# in the shell that runs `docker compose up`. Compose interpolates the value
|
|
132
|
+
# at up time; the container exits immediately if it is unset (intended).
|
|
133
|
+
TOWNHOUSE_WALLET_PASSWORD: '${TOWNHOUSE_WALLET_PASSWORD:?TOWNHOUSE_WALLET_PASSWORD must be exported before docker compose up}'
|
|
134
|
+
healthcheck:
|
|
135
|
+
# nosemgrep: trailofbits.generic.wget-unencrypted-url.wget-unencrypted-url -- container-internal probe
|
|
136
|
+
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:28090/api/health']
|
|
137
|
+
interval: 10s
|
|
138
|
+
timeout: 5s
|
|
139
|
+
retries: 5
|
|
140
|
+
start_period: 15s
|
|
141
|
+
restart: unless-stopped
|
|
142
|
+
|
|
143
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
144
|
+
# Town — Nostr relay node (profile: town)
|
|
145
|
+
# Lazy-provisioned via Epic 46: `townhouse node add town`
|
|
146
|
+
#
|
|
147
|
+
# SECURITY TODO (Epic 46): the per-node secrets below use `${VAR:-}` empty
|
|
148
|
+
# defaults, which silently start the container with a zero/empty key when
|
|
149
|
+
# the operator forgets to export the env var. Story 45.2 review (R2-MINOR)
|
|
150
|
+
# recommends switching to `${VAR:?msg}` (fail-fast) once Epic 46 boots
|
|
151
|
+
# these profiles — keeping the empty default for now lets `docker compose
|
|
152
|
+
# config` validation pass without injecting per-node secrets, matching
|
|
153
|
+
# Story 45.4's apex-only boot semantic (only connector + townhouse-api
|
|
154
|
+
# start at first run).
|
|
155
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
156
|
+
town:
|
|
157
|
+
image: ghcr.io/toon-protocol/town@sha256:ba83fb6df536dec240f27fb7449db59e803207221c54e61ea019375ddc0461a0
|
|
158
|
+
container_name: townhouse-hs-town
|
|
159
|
+
profiles: [town]
|
|
160
|
+
networks:
|
|
161
|
+
- townhouse-hs-net
|
|
162
|
+
depends_on:
|
|
163
|
+
connector:
|
|
164
|
+
condition: service_healthy
|
|
165
|
+
expose: ['3000']
|
|
166
|
+
ports:
|
|
167
|
+
- '127.0.0.1:7100:7100'
|
|
168
|
+
- '127.0.0.1:3100:3100'
|
|
169
|
+
environment:
|
|
170
|
+
# nosemgrep: detect-insecure-websocket -- Docker-internal
|
|
171
|
+
CONNECTOR_URL: ws://connector:3000
|
|
172
|
+
ILP_ADDRESS: g.townhouse.town
|
|
173
|
+
NODE_ID: town
|
|
174
|
+
PARENT_PEER_ID: apex
|
|
175
|
+
FEE_PER_EVENT: '0'
|
|
176
|
+
# Chain RPC — operator sets EVM_RPC_URL in ~/.townhouse/env or via CLI.
|
|
177
|
+
TOON_RPC_URL: ${EVM_RPC_URL:-}
|
|
178
|
+
NODE_NOSTR_PUBKEY: ''
|
|
179
|
+
NODE_EVM_ADDRESS: ''
|
|
180
|
+
# Derived from HD wallet at runtime (Story 45.4 / Epic 46).
|
|
181
|
+
NODE_NOSTR_SECRET_KEY: '${TOWN_SECRET_KEY:-}'
|
|
182
|
+
SETTLEMENT_PRIVATE_KEY: '${TOWN_SETTLEMENT_PRIVATE_KEY:-}'
|
|
183
|
+
PARENT_EVM_ADDRESS: '${APEX_EVM_ADDRESS:-}'
|
|
184
|
+
TOON_CONNECTOR_LOG_LEVEL: '${TOON_CONNECTOR_LOG_LEVEL:-warn}'
|
|
185
|
+
volumes:
|
|
186
|
+
- townhouse-hs-town-data:/data
|
|
187
|
+
healthcheck:
|
|
188
|
+
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3100/health']
|
|
189
|
+
interval: 30s
|
|
190
|
+
timeout: 10s
|
|
191
|
+
retries: 3
|
|
192
|
+
start_period: 10s
|
|
193
|
+
restart: unless-stopped
|
|
194
|
+
|
|
195
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
196
|
+
# Mill — multi-chain swap node (profile: mill)
|
|
197
|
+
# Lazy-provisioned via Epic 46: `townhouse node add mill`
|
|
198
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
199
|
+
mill:
|
|
200
|
+
image: ghcr.io/toon-protocol/mill@sha256:38e8c6460a3fc562dab28284b4f51958d8417ce10a7152bbb4224fc00cc42df5
|
|
201
|
+
container_name: townhouse-hs-mill
|
|
202
|
+
profiles: [mill]
|
|
203
|
+
networks:
|
|
204
|
+
- townhouse-hs-net
|
|
205
|
+
depends_on:
|
|
206
|
+
connector:
|
|
207
|
+
condition: service_healthy
|
|
208
|
+
expose: ['3000']
|
|
209
|
+
ports:
|
|
210
|
+
- '127.0.0.1:3200:3200'
|
|
211
|
+
environment:
|
|
212
|
+
# nosemgrep: detect-insecure-websocket -- Docker-internal
|
|
213
|
+
CONNECTOR_URL: ws://connector:3000
|
|
214
|
+
FEE_BASIS_POINTS: '0'
|
|
215
|
+
SETTLEMENT_RPC_URL: ${EVM_RPC_URL:-}
|
|
216
|
+
SETTLEMENT_CHAIN_ID: ${EVM_CHAIN_ID:-}
|
|
217
|
+
SETTLEMENT_TOKEN_ADDRESS: ${EVM_USDC_ADDRESS:-}
|
|
218
|
+
SOLANA_RPC_URL: ${SOLANA_RPC_URL:-}
|
|
219
|
+
SOLANA_USDC_MINT: ${SOLANA_USDC_MINT:-}
|
|
220
|
+
NODE_NOSTR_PUBKEY: ''
|
|
221
|
+
NODE_EVM_ADDRESS: ''
|
|
222
|
+
# Derived from HD wallet at runtime (Story 45.4 / Epic 46).
|
|
223
|
+
MILL_MNEMONIC: '${MILL_MNEMONIC:-}'
|
|
224
|
+
NODE_NOSTR_SECRET_KEY: '${MILL_SECRET_KEY:-}'
|
|
225
|
+
MILL_CONFIG_PATH: /config/mill.config.json
|
|
226
|
+
MILL_RELAYS: ${MILL_RELAYS:-}
|
|
227
|
+
SETTLEMENT_PRIVATE_KEY: '${MILL_SETTLEMENT_PRIVATE_KEY:-}'
|
|
228
|
+
PARENT_EVM_ADDRESS: '${APEX_EVM_ADDRESS:-}'
|
|
229
|
+
TOON_CONNECTOR_LOG_LEVEL: '${TOON_CONNECTOR_LOG_LEVEL:-warn}'
|
|
230
|
+
volumes:
|
|
231
|
+
# Operator-managed mill config (Epic 46 provisions this on `townhouse node add mill`).
|
|
232
|
+
- ~/.townhouse/mill.config.json:/config/mill.config.json:ro
|
|
233
|
+
- townhouse-hs-mill-data:/data
|
|
234
|
+
healthcheck:
|
|
235
|
+
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3200/health']
|
|
236
|
+
interval: 30s
|
|
237
|
+
timeout: 10s
|
|
238
|
+
retries: 3
|
|
239
|
+
start_period: 15s
|
|
240
|
+
restart: unless-stopped
|
|
241
|
+
|
|
242
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
243
|
+
# DVM — Arweave upload DVM (profile: dvm)
|
|
244
|
+
# Lazy-provisioned via Epic 46: `townhouse node add dvm`
|
|
245
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
246
|
+
dvm:
|
|
247
|
+
image: ghcr.io/toon-protocol/dvm@sha256:26a2aff4cb490d11f8eab9cff3272475c951e7cc87378a1af6c702b529d74e10
|
|
248
|
+
container_name: townhouse-hs-dvm
|
|
249
|
+
profiles: [dvm]
|
|
250
|
+
networks:
|
|
251
|
+
- townhouse-hs-net
|
|
252
|
+
depends_on:
|
|
253
|
+
connector:
|
|
254
|
+
condition: service_healthy
|
|
255
|
+
expose: ['3300']
|
|
256
|
+
ports:
|
|
257
|
+
- '127.0.0.1:3400:3400'
|
|
258
|
+
environment:
|
|
259
|
+
# nosemgrep: detect-insecure-websocket -- Docker-internal
|
|
260
|
+
CONNECTOR_URL: ws://connector:3000
|
|
261
|
+
FEE_PER_JOB: '0'
|
|
262
|
+
DVM_KIND: '5094'
|
|
263
|
+
NODE_NOSTR_PUBKEY: ''
|
|
264
|
+
NODE_EVM_ADDRESS: ''
|
|
265
|
+
# Derived from HD wallet at runtime (Story 45.4 / Epic 46).
|
|
266
|
+
NODE_NOSTR_SECRET_KEY: '${DVM_SECRET_KEY:-}'
|
|
267
|
+
TURBO_TOKEN: ${TURBO_TOKEN:-}
|
|
268
|
+
volumes:
|
|
269
|
+
- townhouse-hs-dvm-data:/data
|
|
270
|
+
healthcheck:
|
|
271
|
+
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3400/health']
|
|
272
|
+
interval: 30s
|
|
273
|
+
timeout: 10s
|
|
274
|
+
retries: 3
|
|
275
|
+
start_period: 10s
|
|
276
|
+
restart: unless-stopped
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CONNECTOR_IMAGE
|
|
4
|
+
} from "./chunk-UTFWPLTB.js";
|
|
5
|
+
|
|
6
|
+
// src/presets/demo.ts
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { readFileSync, existsSync } from "fs";
|
|
10
|
+
import { resolve } from "path";
|
|
11
|
+
var LOCAL_DEVNET_FALLBACK = {
|
|
12
|
+
anvilUrl: "http://localhost:28545",
|
|
13
|
+
solanaUrl: "http://localhost:28899"
|
|
14
|
+
};
|
|
15
|
+
var DEMO_DETERMINISTIC_PASSWORD = "townhouse-demo-INSECURE-do-not-use-in-prod";
|
|
16
|
+
function resolveChainEndpoints(leasesPath) {
|
|
17
|
+
const localEvm = { rpcUrl: LOCAL_DEVNET_FALLBACK.anvilUrl };
|
|
18
|
+
const localSol = { rpcUrl: LOCAL_DEVNET_FALLBACK.solanaUrl };
|
|
19
|
+
if (!leasesPath || !existsSync(leasesPath)) {
|
|
20
|
+
return { source: "local-fallback", evm: localEvm, solana: localSol };
|
|
21
|
+
}
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(readFileSync(leasesPath, "utf-8"));
|
|
25
|
+
} catch {
|
|
26
|
+
return { source: "local-fallback", evm: localEvm, solana: localSol };
|
|
27
|
+
}
|
|
28
|
+
const evmUrl = typeof parsed.anvil?.url === "string" ? parsed.anvil.url : void 0;
|
|
29
|
+
const evmWs = typeof parsed.anvil?.ws_url === "string" ? parsed.anvil.ws_url : void 0;
|
|
30
|
+
const solUrl = typeof parsed.solana?.url === "string" ? parsed.solana.url : void 0;
|
|
31
|
+
const solWs = typeof parsed.solana?.ws_url === "string" ? parsed.solana.ws_url : void 0;
|
|
32
|
+
return {
|
|
33
|
+
source: leasesPath,
|
|
34
|
+
evm: evmUrl ? { rpcUrl: evmUrl, ...evmWs ? { wsUrl: evmWs } : {} } : localEvm,
|
|
35
|
+
solana: solUrl ? { rpcUrl: solUrl, ...solWs ? { wsUrl: solWs } : {} } : localSol
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function defaultLeasesPath() {
|
|
39
|
+
return resolve(process.cwd(), "deploy", "akash", "leases.json");
|
|
40
|
+
}
|
|
41
|
+
function buildDemoConfig(options) {
|
|
42
|
+
const leasesPath = options.leasesPath === null ? void 0 : options.leasesPath ?? defaultLeasesPath();
|
|
43
|
+
const endpoints = resolveChainEndpoints(leasesPath);
|
|
44
|
+
return {
|
|
45
|
+
nodes: {
|
|
46
|
+
town: {
|
|
47
|
+
enabled: true,
|
|
48
|
+
feePerEvent: 0
|
|
49
|
+
},
|
|
50
|
+
mill: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
feeBasisPoints: 0,
|
|
53
|
+
// Demo pair: EVM (Anvil) <-> Solana. The orchestrator does not
|
|
54
|
+
// currently consume mill.chains directly — it round-trips through
|
|
55
|
+
// YAML so the dashboard / future stories can read it.
|
|
56
|
+
chains: {
|
|
57
|
+
evm: {
|
|
58
|
+
rpcUrl: endpoints.evm.rpcUrl,
|
|
59
|
+
...endpoints.evm.wsUrl ? { wsUrl: endpoints.evm.wsUrl } : {}
|
|
60
|
+
},
|
|
61
|
+
solana: {
|
|
62
|
+
rpcUrl: endpoints.solana.rpcUrl,
|
|
63
|
+
...endpoints.solana.wsUrl ? { wsUrl: endpoints.solana.wsUrl } : {}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
pairs: ["EVM<->SOL"]
|
|
67
|
+
},
|
|
68
|
+
dvm: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
feePerJob: 0,
|
|
71
|
+
// Arweave DVM (kind:5094) — frictionless demo pricing. Operators
|
|
72
|
+
// running for real should raise this; entrypoint-dvm.ts treats the
|
|
73
|
+
// value as msats per byte uploaded to Arweave.
|
|
74
|
+
kindPricing: { "5094": 0 }
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
wallet: {
|
|
78
|
+
encrypted_path: options.walletPath
|
|
79
|
+
},
|
|
80
|
+
connector: {
|
|
81
|
+
image: DEFAULT_CONNECTOR_IMAGE,
|
|
82
|
+
adminPort: 9401
|
|
83
|
+
},
|
|
84
|
+
transport: {
|
|
85
|
+
// 'direct' for the demo because `townhouse up` doesn't bring up a
|
|
86
|
+
// SOCKS5 sidecar — ATOR mode would require one at `socks5://127.0.0.1:28050`
|
|
87
|
+
// (provided by the dev-infra stack but not the operator CLI). Switch
|
|
88
|
+
// to 'ator' once the sidecar story lands in townhouse `up`.
|
|
89
|
+
mode: "direct"
|
|
90
|
+
},
|
|
91
|
+
api: {
|
|
92
|
+
port: 9400,
|
|
93
|
+
host: "127.0.0.1"
|
|
94
|
+
},
|
|
95
|
+
logging: {
|
|
96
|
+
level: "info"
|
|
97
|
+
},
|
|
98
|
+
preset: {
|
|
99
|
+
name: "demo",
|
|
100
|
+
// Source recorded so operators can see at-a-glance whether their demo
|
|
101
|
+
// is hitting Akash or local devnets.
|
|
102
|
+
chainEndpointSource: endpoints.source
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function defaultDemoConfigDir() {
|
|
107
|
+
return join(homedir(), ".townhouse");
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
DEMO_DETERMINISTIC_PASSWORD,
|
|
111
|
+
LOCAL_DEVNET_FALLBACK,
|
|
112
|
+
buildDemoConfig,
|
|
113
|
+
defaultDemoConfigDir,
|
|
114
|
+
defaultLeasesPath,
|
|
115
|
+
resolveChainEndpoints
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=demo-MJR47QHZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presets/demo.ts"],"sourcesContent":["/**\n * `--preset=demo` configuration (Story D2).\n *\n * Non-interactive preset for the TOON demo: 1 town, 1 mill (EVM<->SOL pair),\n * 1 dvm, ATOR transport ON, all fees zeroed (demo = free). Chain RPC URLs are\n * sourced from `deploy/akash/leases.json` if present, otherwise from local\n * devnet defaults documented in CLAUDE.md (Anvil 28545, Solana 28899).\n *\n * Future presets (test, prod) follow the same shape — see {@link PresetBuilder}.\n *\n * NOTE on schema reach: this preset writes mill chain endpoints into a\n * `chains` field on the mill node config. The orchestrator wiring that\n * forwards these into MILL_CONFIG_JSON is out of scope for D2 — for now the\n * field round-trips through the YAML so future stories (and the dashboard)\n * can read it without re-deriving it from leases.json.\n */\n\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { TownhouseConfig } from '../config/schema.js';\nimport { DEFAULT_CONNECTOR_IMAGE } from '../constants.js';\n\n/**\n * Preset identifier — keep the union closed so we surface unknown presets at\n * the CLI boundary instead of silently falling through to defaults.\n */\nexport type PresetName = 'demo';\n\n/**\n * Local-devnet fallback URLs (CLAUDE.md \"Townhouse Dev Stack\" 28xxx range).\n * Used when `deploy/akash/leases.json` is absent or unreadable.\n */\nexport const LOCAL_DEVNET_FALLBACK = {\n anvilUrl: 'http://localhost:28545',\n solanaUrl: 'http://localhost:28899',\n} as const;\n\n/**\n * Deterministic-but-clearly-unsafe password used for the demo wallet when the\n * caller passes `--preset=demo --yes` without `--password`. The string\n * embeds the warning so it shows up in any log scrape; production callers\n * MUST supply their own `--password` (AC-D2-6).\n */\nexport const DEMO_DETERMINISTIC_PASSWORD =\n 'townhouse-demo-INSECURE-do-not-use-in-prod';\n\n/**\n * Shape of `deploy/akash/leases.json` that this preset cares about. The full\n * file emitted by `scripts/akash-deploy.sh` has more fields; we only need\n * RPC + WS URLs here, so the type is intentionally narrow + tolerant.\n */\nexport interface AkashLeases {\n anvil?: {\n url?: string;\n host?: string;\n port?: number | string;\n ws_url?: string;\n };\n solana?: {\n url?: string;\n host?: string;\n port?: number | string;\n ws_host?: string;\n ws_port?: number | string;\n ws_url?: string;\n };\n}\n\nexport interface ResolvedChainEndpoints {\n /** Source of truth for traceability — either an absolute leases.json path or 'local-fallback'. */\n source: string;\n evm: { rpcUrl: string; wsUrl?: string };\n solana: { rpcUrl: string; wsUrl?: string };\n}\n\n/**\n * Read `deploy/akash/leases.json` and extract chain endpoints, falling back\n * to local devnet URLs if the file is missing or any field is unusable.\n *\n * The fallback is per-chain: a leases.json that defines anvil but not solana\n * still gets the local Solana URL (and vice-versa). This keeps half-deployed\n * states usable.\n */\nexport function resolveChainEndpoints(\n leasesPath?: string\n): ResolvedChainEndpoints {\n const localEvm = { rpcUrl: LOCAL_DEVNET_FALLBACK.anvilUrl };\n const localSol = { rpcUrl: LOCAL_DEVNET_FALLBACK.solanaUrl };\n\n if (!leasesPath || !existsSync(leasesPath)) {\n return { source: 'local-fallback', evm: localEvm, solana: localSol };\n }\n\n let parsed: AkashLeases;\n try {\n parsed = JSON.parse(readFileSync(leasesPath, 'utf-8')) as AkashLeases;\n } catch {\n // Malformed JSON — better to demo on local devnets than to error out at\n // wizard-bypass time. Caller never blocks on bad leases data.\n return { source: 'local-fallback', evm: localEvm, solana: localSol };\n }\n\n const evmUrl =\n typeof parsed.anvil?.url === 'string' ? parsed.anvil.url : undefined;\n const evmWs =\n typeof parsed.anvil?.ws_url === 'string' ? parsed.anvil.ws_url : undefined;\n const solUrl =\n typeof parsed.solana?.url === 'string' ? parsed.solana.url : undefined;\n const solWs =\n typeof parsed.solana?.ws_url === 'string'\n ? parsed.solana.ws_url\n : undefined;\n\n return {\n source: leasesPath,\n evm: evmUrl\n ? { rpcUrl: evmUrl, ...(evmWs ? { wsUrl: evmWs } : {}) }\n : localEvm,\n solana: solUrl\n ? { rpcUrl: solUrl, ...(solWs ? { wsUrl: solWs } : {}) }\n : localSol,\n };\n}\n\nexport interface BuildDemoConfigOptions {\n /** Absolute path to the wallet file (typically `<configDir>/wallet.enc`). */\n walletPath: string;\n /**\n * Absolute path to `deploy/akash/leases.json`. Defaults to\n * `<repoRoot>/deploy/akash/leases.json` if not provided. Pass `null` to\n * force local-devnet fallback (used in tests and when the file is known\n * to not exist on the operator's machine).\n */\n leasesPath?: string | null;\n}\n\n/**\n * Default location of the Akash leases file. Resolved relative to CWD —\n * the demo CLI is run from anywhere, but the leases.json that matters lives\n * at `<repo>/deploy/akash/leases.json`.\n */\nexport function defaultLeasesPath(): string {\n return resolve(process.cwd(), 'deploy', 'akash', 'leases.json');\n}\n\n/**\n * Build the full TownhouseConfig for `--preset=demo`.\n *\n * AC-D2-5 invariants enforced here:\n * - 1 town, 1 mill, 1 dvm (all enabled)\n * - feePerEvent = 0, feeBasisPoints = 0, feePerJob = 0\n * - transport.mode = 'ator'\n * - mill.chains contains exactly one EVM<->SOL pair\n */\nexport function buildDemoConfig(\n options: BuildDemoConfigOptions\n): TownhouseConfig {\n const leasesPath =\n options.leasesPath === null\n ? undefined\n : (options.leasesPath ?? defaultLeasesPath());\n\n const endpoints = resolveChainEndpoints(leasesPath);\n\n return {\n nodes: {\n town: {\n enabled: true,\n feePerEvent: 0,\n },\n mill: {\n enabled: true,\n feeBasisPoints: 0,\n // Demo pair: EVM (Anvil) <-> Solana. The orchestrator does not\n // currently consume mill.chains directly — it round-trips through\n // YAML so the dashboard / future stories can read it.\n chains: {\n evm: {\n rpcUrl: endpoints.evm.rpcUrl,\n ...(endpoints.evm.wsUrl ? { wsUrl: endpoints.evm.wsUrl } : {}),\n },\n solana: {\n rpcUrl: endpoints.solana.rpcUrl,\n ...(endpoints.solana.wsUrl\n ? { wsUrl: endpoints.solana.wsUrl }\n : {}),\n },\n },\n pairs: ['EVM<->SOL'],\n },\n dvm: {\n enabled: true,\n feePerJob: 0,\n // Arweave DVM (kind:5094) — frictionless demo pricing. Operators\n // running for real should raise this; entrypoint-dvm.ts treats the\n // value as msats per byte uploaded to Arweave.\n kindPricing: { '5094': 0 },\n },\n },\n wallet: {\n encrypted_path: options.walletPath,\n },\n connector: {\n image: DEFAULT_CONNECTOR_IMAGE,\n adminPort: 9401,\n },\n transport: {\n // 'direct' for the demo because `townhouse up` doesn't bring up a\n // SOCKS5 sidecar — ATOR mode would require one at `socks5://127.0.0.1:28050`\n // (provided by the dev-infra stack but not the operator CLI). Switch\n // to 'ator' once the sidecar story lands in townhouse `up`.\n mode: 'direct',\n },\n api: {\n port: 9400,\n host: '127.0.0.1',\n },\n logging: {\n level: 'info',\n },\n preset: {\n name: 'demo',\n // Source recorded so operators can see at-a-glance whether their demo\n // is hitting Akash or local devnets.\n chainEndpointSource: endpoints.source,\n },\n };\n}\n\n/**\n * Default config dir used by the demo preset when `--config-dir` is omitted.\n * Mirrors the value in cli.ts; duplicated here so tests can construct the\n * same path without importing from the CLI surface.\n */\nexport function defaultDemoConfigDir(): string {\n return join(homedir(), '.townhouse');\n}\n"],"mappings":";;;;;;AAiBA,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,cAAc,kBAAkB;AACzC,SAAS,eAAe;AAejB,IAAM,wBAAwB;AAAA,EACnC,UAAU;AAAA,EACV,WAAW;AACb;AAQO,IAAM,8BACX;AAuCK,SAAS,sBACd,YACwB;AACxB,QAAM,WAAW,EAAE,QAAQ,sBAAsB,SAAS;AAC1D,QAAM,WAAW,EAAE,QAAQ,sBAAsB,UAAU;AAE3D,MAAI,CAAC,cAAc,CAAC,WAAW,UAAU,GAAG;AAC1C,WAAO,EAAE,QAAQ,kBAAkB,KAAK,UAAU,QAAQ,SAAS;AAAA,EACrE;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAAA,EACvD,QAAQ;AAGN,WAAO,EAAE,QAAQ,kBAAkB,KAAK,UAAU,QAAQ,SAAS;AAAA,EACrE;AAEA,QAAM,SACJ,OAAO,OAAO,OAAO,QAAQ,WAAW,OAAO,MAAM,MAAM;AAC7D,QAAM,QACJ,OAAO,OAAO,OAAO,WAAW,WAAW,OAAO,MAAM,SAAS;AACnE,QAAM,SACJ,OAAO,OAAO,QAAQ,QAAQ,WAAW,OAAO,OAAO,MAAM;AAC/D,QAAM,QACJ,OAAO,OAAO,QAAQ,WAAW,WAC7B,OAAO,OAAO,SACd;AAEN,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,KAAK,SACD,EAAE,QAAQ,QAAQ,GAAI,QAAQ,EAAE,OAAO,MAAM,IAAI,CAAC,EAAG,IACrD;AAAA,IACJ,QAAQ,SACJ,EAAE,QAAQ,QAAQ,GAAI,QAAQ,EAAE,OAAO,MAAM,IAAI,CAAC,EAAG,IACrD;AAAA,EACN;AACF;AAmBO,SAAS,oBAA4B;AAC1C,SAAO,QAAQ,QAAQ,IAAI,GAAG,UAAU,SAAS,aAAa;AAChE;AAWO,SAAS,gBACd,SACiB;AACjB,QAAM,aACJ,QAAQ,eAAe,OACnB,SACC,QAAQ,cAAc,kBAAkB;AAE/C,QAAM,YAAY,sBAAsB,UAAU;AAElD,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,gBAAgB;AAAA;AAAA;AAAA;AAAA,QAIhB,QAAQ;AAAA,UACN,KAAK;AAAA,YACH,QAAQ,UAAU,IAAI;AAAA,YACtB,GAAI,UAAU,IAAI,QAAQ,EAAE,OAAO,UAAU,IAAI,MAAM,IAAI,CAAC;AAAA,UAC9D;AAAA,UACA,QAAQ;AAAA,YACN,QAAQ,UAAU,OAAO;AAAA,YACzB,GAAI,UAAU,OAAO,QACjB,EAAE,OAAO,UAAU,OAAO,MAAM,IAChC,CAAC;AAAA,UACP;AAAA,QACF;AAAA,QACA,OAAO,CAAC,WAAW;AAAA,MACrB;AAAA,MACA,KAAK;AAAA,QACH,SAAS;AAAA,QACT,WAAW;AAAA;AAAA;AAAA;AAAA,QAIX,aAAa,EAAE,QAAQ,EAAE;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,gBAAgB,QAAQ;AAAA,IAC1B;AAAA,IACA,WAAW;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKT,MAAM;AAAA,IACR;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA;AAAA;AAAA,MAGN,qBAAqB,UAAU;AAAA,IACjC;AAAA,EACF;AACF;AAOO,SAAS,uBAA+B;AAC7C,SAAO,KAAK,QAAQ,GAAG,YAAY;AACrC;","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"townhouseVersion": "0.1.0-rc5",
|
|
4
|
+
"builtAt": "2026-05-10T00:04:39.745Z",
|
|
5
|
+
"images": {
|
|
6
|
+
"townhouse-api": {
|
|
7
|
+
"name": "ghcr.io/toon-protocol/townhouse-api",
|
|
8
|
+
"tag": "0.1.0-rc5",
|
|
9
|
+
"digest": "sha256:48cb3df422019e252b7b1b5506b2fc666ea206ec57024eb68acbf781ebeb1f11"
|
|
10
|
+
},
|
|
11
|
+
"town": {
|
|
12
|
+
"name": "ghcr.io/toon-protocol/town",
|
|
13
|
+
"tag": "0.1.0-rc5",
|
|
14
|
+
"digest": "sha256:ba83fb6df536dec240f27fb7449db59e803207221c54e61ea019375ddc0461a0"
|
|
15
|
+
},
|
|
16
|
+
"mill": {
|
|
17
|
+
"name": "ghcr.io/toon-protocol/mill",
|
|
18
|
+
"tag": "0.1.0-rc5",
|
|
19
|
+
"digest": "sha256:38e8c6460a3fc562dab28284b4f51958d8417ce10a7152bbb4224fc00cc42df5"
|
|
20
|
+
},
|
|
21
|
+
"dvm": {
|
|
22
|
+
"name": "ghcr.io/toon-protocol/dvm",
|
|
23
|
+
"tag": "0.1.0-rc5",
|
|
24
|
+
"digest": "sha256:26a2aff4cb490d11f8eab9cff3272475c951e7cc87378a1af6c702b529d74e10"
|
|
25
|
+
},
|
|
26
|
+
"connector": {
|
|
27
|
+
"name": "ghcr.io/toon-protocol/connector",
|
|
28
|
+
"tag": "3.4.1",
|
|
29
|
+
"digest": "sha256:4a24ccb0997d7b025997e670546032f6a84cd18a77c490509016b85e181a344e"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|