aimarket-treasury 0.1.0__tar.gz

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.
@@ -0,0 +1,11 @@
1
+ **/__pycache__/
2
+ **/*.pyc
3
+ **/.venv/
4
+ **/.pytest_cache/
5
+ **/.coverage
6
+ **/coverage/
7
+ **/node_modules/
8
+ frontend/dist/
9
+ data/
10
+ *.egg-info/
11
+ .DS_Store
@@ -0,0 +1,39 @@
1
+ # Treasury CI — unit suite. On the GitHub mirror, oracle-core + momus are vendored.
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ test:
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 15
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.12"
27
+ cache: pip
28
+ - name: Install
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ if [[ -d vendor/oracle-core && -d vendor/momus ]]; then
32
+ pip install -e vendor/oracle-core -e vendor/momus -e ".[dev]"
33
+ elif [[ -d vendor/oracle-core ]]; then
34
+ pip install -e vendor/oracle-core -e ".[dev]"
35
+ else
36
+ pip install -e ".[dev]"
37
+ fi
38
+ - name: Pytest
39
+ run: pytest -q --tb=short
@@ -0,0 +1,48 @@
1
+ name: Pages — landing
2
+
3
+ # Publish docs/landing/ → https://alexar76.github.io/treasury/
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ paths:
8
+ - docs/landing/**
9
+ - .github/workflows/pages.yml
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: pages-treasury
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ deploy:
23
+ runs-on: ubuntu-latest
24
+ environment:
25
+ name: github-pages
26
+ url: ${{ steps.deployment.outputs.page_url }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - name: Ensure Pages source is GitHub Actions
31
+ continue-on-error: true
32
+ env:
33
+ GH_TOKEN: ${{ github.token }}
34
+ run: |
35
+ gh api --method PUT "/repos/${{ github.repository }}/pages" \
36
+ -f build_type=workflow
37
+
38
+ - name: Prepare landing artifact
39
+ run: touch docs/landing/.nojekyll
40
+
41
+ - uses: actions/configure-pages@v5
42
+
43
+ - uses: actions/upload-pages-artifact@v3
44
+ with:
45
+ path: docs/landing
46
+
47
+ - id: deployment
48
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,16 @@
1
+ data/
2
+ *.key
3
+ *_signing_key
4
+ .venv/
5
+ __pycache__/
6
+ *.pyc
7
+ .pytest_cache/
8
+ .coverage
9
+ coverage/
10
+ htmlcov/
11
+ frontend/node_modules/
12
+ frontend/dist/
13
+ *.egg-info/
14
+ dist/
15
+ build/
16
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ # MOMUS Treasury — the SEPARATE payer service. Built as its own image and run as its own
2
+ # container with its own key volume, so the payout key lives nowhere MOMUS can reach it.
3
+ # Build from the MONOREPO ROOT (needs oracle-core AND momus in context):
4
+ # docker build -f treasury/Dockerfile -t momus-treasury .
5
+ FROM python:3.11-slim
6
+ WORKDIR /app
7
+
8
+ COPY oracles/core /app/core
9
+ COPY momus /app/momus
10
+ COPY treasury /app/treasury
11
+
12
+ RUN pip install --no-cache-dir -e /app/core -e /app/momus -e /app/treasury
13
+
14
+ EXPOSE 9401
15
+ CMD ["python", "-m", "treasury.service"]
@@ -0,0 +1,14 @@
1
+ # Standalone image for the GitHub mirror (oracle-core + momus vendored at publish time).
2
+ # Build from the satellite repo root:
3
+ # docker build -f Dockerfile.standalone -t ghcr.io/alexar76/treasury .
4
+ FROM python:3.11-slim
5
+ WORKDIR /app
6
+
7
+ COPY vendor/oracle-core /app/oracle-core
8
+ COPY vendor/momus /app/momus
9
+ COPY . /app/treasury
10
+
11
+ RUN pip install --no-cache-dir -e /app/oracle-core -e /app/momus -e /app/treasury
12
+
13
+ EXPOSE 9401
14
+ CMD ["python", "-m", "treasury.service"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI-Factory Project Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: aimarket-treasury
3
+ Version: 0.1.0
4
+ Summary: MOMUS Treasury — the separate payer role that releases red-team bounties only on independent verification; MOMUS finds and signs, the Treasury (its own key) pays
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aimarket-momus>=0.1
8
+ Requires-Dist: aimarket-oracle-core>=0.2
9
+ Requires-Dist: fastapi>=0.115
10
+ Requires-Dist: httpx>=0.27.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
13
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
14
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
16
+ Requires-Dist: uvicorn>=0.30; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Treasury
20
+
21
+ <!-- aicom-readme-badges -->
22
+ <p align="center">
23
+ <a href="https://github.com/alexar76/treasury/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI passing" /></a>
24
+ <a href="https://github.com/alexar76/momus"><img src="docs/badges/momus.svg" alt="Pays MOMUS findings" /></a>
25
+ <img src="docs/badges/separation.svg" alt="Duties separated" />
26
+ <img src="docs/badges/python.svg" alt="Python >=3.11" />
27
+ <img src="docs/badges/docker.svg" alt="Docker ready" />
28
+ <a href="https://github.com/alexar76/treasury/blob/main/LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
29
+ </p>
30
+ <!-- /aicom-readme-badges -->
31
+
32
+ <p align="center">
33
+ <strong>The only key that can pay a red-team bounty — and it is not the key that finds the bug.</strong>
34
+ </p>
35
+
36
+ <p align="center">
37
+ <strong><a href="https://github.com/alexar76/momus">MOMUS (the scanner)</a></strong>
38
+ ·
39
+ <strong><a href="https://github.com/alexar76/momus/blob/main/docs/uni-chain.md">Every vault transaction explained</a></strong>
40
+ ·
41
+ <strong><a href="https://github.com/alexar76/momus/blob/main/docs/first-cycle.md">The first live cycle</a></strong>
42
+ ·
43
+ <strong><a href="https://momus.modelmarket.dev/treasury/health">Live health surface</a></strong>
44
+ </p>
45
+
46
+ > 🌐 **English** · [Русский](README.ru.md) · [Español](README.es.md) · [Français](README.fr.md) · [中文](README.zh.md)
47
+
48
+ ## What it is
49
+
50
+ [MOMUS](https://github.com/alexar76/momus) is the ecosystem's red team: it probes our own services,
51
+ finds contract violations, and **Ed25519-signs** the evidence. It cannot pay itself. This service is
52
+ the other half of that sentence — **the Treasury holds the one key that can release a bounty**, and
53
+ it lives in a different process, in a different container, on a different key volume.
54
+
55
+ The split is not a stylistic preference. A scanner that held the purse could pay itself for its own
56
+ findings, so "did we find a bug" and "does anyone get money" must be decided by different principals
57
+ with different keys. `KeyRing` refuses to start at all if the scanner key equals the treasury key —
58
+ even a single-box demo cannot collapse the two roles by misconfiguration.
59
+
60
+ The Treasury also does not take MOMUS's word for anything. It receives a finding plus its verdicts
61
+ over HTTP and **re-derives the decision from scratch**: re-verifies every signature, re-checks the
62
+ independence quorum, re-checks the external-verifier requirement, recomputes the dedup identity,
63
+ re-checks the ledger — and only then signs a payout decision with its own key. There is no
64
+ "MOMUS says it is confirmed" input anywhere in the gate.
65
+
66
+ ```mermaid
67
+ flowchart LR
68
+ M["MOMUS · scanner key<br/>probe · sign Finding<br/>❌ cannot pay"]
69
+ V["Verifier(s) · own keys<br/>sign Verdict<br/>❌ cannot pay"]
70
+ T["🏦 Treasury · treasury key<br/>re-verify · authorize · release<br/>❌ cannot find or verify"]
71
+ VA[("UNI vault<br/>balance that can run out")]
72
+ M -- "Finding + Verdicts + deposit" --> T
73
+ V -- "signed Verdict" --> T
74
+ T -- "release a share" --> VA
75
+ T -. "refused / held + reasons" .-> M
76
+
77
+ classDef pay fill:#2a0e12,stroke:#ff2d55,color:#fff;
78
+ class T,VA pay;
79
+ ```
80
+
81
+ ## What it refuses, and why
82
+
83
+ Every refusal below exists because the opposite behaviour was a real way to get paid for nothing.
84
+
85
+ | It refuses | Because |
86
+ |---|---|
87
+ | **A finding whose scanner signature does not verify** | The signature is the whole claim. A tampered document — e.g. `severity` edited from `high` to `critical` after signing — is refused outright, not repaired. Covered by `test_authorize_refuses_tampered_finding`. |
88
+ | **A claimant's self-declared dedup identity** | `dedup_key` is signed *by the claimant*, so a scanner that wants to be paid twice for one bug just varies the field and the replay guard never matches. The Treasury **recomputes** the identity from the finding's content and refuses any declared mismatch. |
89
+ | **A duplicate payout for a bug already paid** | A bug pays once, ever. Only a `paid` decision consumes the dedup identity — a `held` one must stay retryable, because otherwise a temporary funding shortfall would permanently burn a legitimate bounty (a test caught exactly that once the vault could genuinely run out). |
90
+ | **A HIGH/CRITICAL finding with fewer than two distinct verifiers** | One key confirming its own finder is not verification. Strong actions need ≥2 **distinct** confirming verifier keys, none of which may be the scanner key or the treasury key. |
91
+ | **…and, for those, a quorum with no external verifier** | Distinct `did:key`s prove distinct *keys*, not distinct *parties* — one operator can hold them all. So at least one confirmation must come from a pre-registered external verifier (`MOMUS_EXTERNAL_VERIFIERS`). In production, an empty external set fails **closed**; outside production it is allowed but the decision records a warning that the payout rests on operator key custody alone. |
92
+ | **A malformed or small-order verifier key** | An Ed25519 small-order point encodes to a pubkey string that *differs* from the scanner's, so naive string inequality would count it toward the independence quorum. Nobody holds its private half. Rejected before any verdict it signed can count. |
93
+ | **A verdict that does not bind to this finding's digest** | Otherwise a verdict for one finding could be transplanted onto another. |
94
+ | **A claim with no anti-griefing deposit** | Filing a claim costs collateral, proportional to the bounty. A claim that independent verifiers **refute** forfeits the *whole* deposit — not a percentage, because bleeding it a few percent at a time makes spamming near-free. An honestly inconclusive claim is refunded, so an unreproducible-but-honest report stays cheap. |
95
+ | **A finding against the ecosystem's own security infrastructure** | A bug in the scanner, the treasury, the verifier, the gate or the escrow is the exact lever for disabling the payout controls. Those never auto-pay; they route to human review. The check is server-side against the target, never trusting the claim's own label. |
96
+ | **A write request with no client token** | See below — this one was a live vulnerability. |
97
+ | **A payout the vault cannot cover** | An unfunded treasury does not invent money. All gates passing plus an empty balance is `held`, not `paid`. |
98
+
99
+ ### The defect that made the token mandatory
100
+
101
+ The payout routes originally had **no authentication at all**. An audit agent did not theorise about
102
+ it — it *reproduced* the attack, minting a treasury-signed `paid` decision from an unprivileged
103
+ process on the shared Docker network. Signature checks prove the documents are internally
104
+ consistent; they say nothing about whether the **caller** is entitled to ask.
105
+
106
+ So `/authorize`, `/deposit`, `/explain` and the vault write routes now require a client token
107
+ (`x-treasury-client`), are rate-limited per caller, and — when an allowlist is configured — the
108
+ finding's `scanner_pubkey` must belong to a registered claimant, so a stranger's key cannot claim a
109
+ bounty even holding a valid token. In production a missing `TREASURY_CLIENT_TOKEN` returns `503`
110
+ rather than defaulting to open. `GET /health` reports `write_gated` so the posture is checkable from
111
+ outside. Read-only `/health`, `/ledger`, `/vault` and `/vault/journal` stay open on purpose: they
112
+ are the audit surface.
113
+
114
+ ## The UNI vault
115
+
116
+ The vault lives here, with the money, because a scanner holding the purse would defeat the
117
+ separation the whole design rests on.
118
+
119
+ Without a balance a simulated treasury "pays" forever: every bounty succeeds, nothing depletes, and
120
+ the simulation teaches you nothing about whether the economics work. So the vault is real
121
+ bookkeeping — it is funded, reserved against, drawn down, and **can genuinely run out**. State is
122
+ always derivable from history: the journal is append-only and replayed on start.
123
+
124
+ - **balance** — everything the vault holds.
125
+ - **reserved** — the part already promised to in-flight bounties.
126
+ - **available** = balance − reserved — what a new bounty may draw on.
127
+
128
+ There are exactly six transaction kinds, and the service reports what each one means at
129
+ `GET /vault` → `transaction_meanings`, so a line in the journal never needs interpreting:
130
+
131
+ | kind | what it means |
132
+ |---|---|
133
+ | `fund` | an operator added simulated budget — the only way money enters the vault |
134
+ | `reserve` | a bounty cleared the payout gate; its pool is set aside and no longer available |
135
+ | `release` | a contributor's share left the vault (finder / fixer / conductor) |
136
+ | `unreserve` | a reservation was cancelled without paying; the funds are available again |
137
+ | `forfeit` | a refuted claimant's deposit was taken — spam funds the honest side |
138
+ | `refund` | a claimant's deposit returned because their claim was not refuted |
139
+
140
+ Reservation is what stops two concurrent claims spending the same dollar, and a release larger than
141
+ what is reserved for that finding is refused rather than allowed to over-draw. A share the vault
142
+ cannot cover comes back as `UNI vault refused the release — insufficient available funds…`, and the
143
+ decision is `held`.
144
+
145
+ One bug worth naming: the base decision used to settle the **full** pool as the finder's share, and
146
+ then the per-role split settled the finder's 50% again — two settlement records and, once a real
147
+ vault existed, a genuine double debit. The split now decides without settling and settles each share
148
+ itself.
149
+
150
+ Full narration of a real end-to-end run, transaction by transaction:
151
+ [**uni-chain.md**](https://github.com/alexar76/momus/blob/main/docs/uni-chain.md).
152
+
153
+ ## The security budget — a rule, not an approval
154
+
155
+ A vault that can run out is honest, but then someone has to refill it, and *who decides* is a
156
+ governance question with a security answer.
157
+
158
+ The hub funds it — that is where the ecosystem's revenue lands, and security is a cost of running a
159
+ marketplace people trust, the same way fraud prevention is funded out of transaction fees. The
160
+ critical part is that the refill is a **standing rule, never a discretionary approval**: an approver
161
+ could starve the auditor exactly when the auditor finds something embarrassing, which is the same
162
+ capture the key separation exists to prevent.
163
+
164
+ - **pull, not push** — the Treasury requests a top-up when available funds fall below a threshold;
165
+ - **a standing rate** — honoured automatically up to `rate_bps` of settled invoke volume in the
166
+ period, capped by `period_cap_usd`; no approval needed inside the allowance;
167
+ - **escalate above it** — a request beyond the allowance is refused *with its arithmetic* and routed
168
+ to human governance. The auditor is never silently defunded; the funder is never silently drained;
169
+ - **fail-closed** — no allocator, or zero settled volume, and the vault simply runs out and bounties
170
+ become `held` intents. An exhausted budget is reported, never hidden;
171
+ - **honest provenance** — every allocation records whether the volume was *measured from the hub* or
172
+ *operator-declared*, so a granted top-up can never look anchored to real economic activity when it
173
+ was not.
174
+
175
+ Both branches (`granted` and `escalated`) have run live; see `POST /vault/top-up` and the
176
+ [uni-chain doc](https://github.com/alexar76/momus/blob/main/docs/uni-chain.md).
177
+
178
+ ## Settlement ladder
179
+
180
+ `UNI` (default) → `HELD` → `BASE` / `SOLANA`. The ladder only ever falls **back**, never forward
181
+ into paying.
182
+
183
+ | tier | what happens |
184
+ |---|---|
185
+ | **`UNI`** | Simulated settlement inside the universe. The whole loop runs, every share is recorded and marked `simulated: true`, the vault is really debited — and **no value moves anywhere**. |
186
+ | **`HELD`** | Crypto is on but on-chain bounty settlement was never explicitly enabled, or its config is incomplete. Decisions are recorded as intents only. |
187
+ | **`BASE` / `SOLANA`** | Real settlement, and it needs a **second, separate opt-in on top of the crypto master switch**: `AIFACTORY_CRYPTO_ENABLED=1` **and** `MOMUS_BOUNTY_ONCHAIN=1` **and** `MOMUS_BOUNTY_CHAIN` **and** a deployed `MOMUS_BOUNTY_SPLITTER` address. Anything missing or malformed lands on `HELD`. |
188
+
189
+ > ### ⚠️ Disclaimer
190
+ >
191
+ > **By default nothing is paid.** UNI figures are **simulated** bookkeeping — an amount in the
192
+ > journal is not a transfer, and no value moves.
193
+ >
194
+ > **Turning crypto on does not start paying bounties.** That is why the on-chain bounty switch is
195
+ > separate: enabling the ecosystem's crypto (channels, escrow, hub settlement) must not silently
196
+ > also start releasing red-team money. Separate risks get separate switches.
197
+ >
198
+ > **Nothing is ever auto-broadcast.** Even fully enabled, the `BASE` tier only *prepares* an
199
+ > unsigned `releaseShare(...)` call for the Treasury operator to sign and send; MOMUS never
200
+ > broadcasts its own payout. An agent able to broadcast its own payouts would defeat the separation
201
+ > of duties the whole design rests on.
202
+ >
203
+ > **A deployed contract is not an enabled payout.** `BountySplitter` is deployed on Base mainnet,
204
+ > and the default tier is still UNI.
205
+ >
206
+ > Nothing here is a financial product, an investment, or a promise of payment. The bounty schedule
207
+ > is a configurable demo parameter, not an offer.
208
+
209
+ ## API surface
210
+
211
+ | route | auth | what it does |
212
+ |---|---|---|
213
+ | `GET /health` | open | liveness, the treasury **public** key (never the private one), `write_gated`, registered claimant count, external verifier set, crypto/prod posture |
214
+ | `GET /ledger?limit=` | open | the append-only decision/claim tail — the audit surface |
215
+ | `GET /vault` | open | balance / reserved / available, the standing allocation rule, the settlement mode, and what every transaction kind means |
216
+ | `GET /vault/journal?limit=` | open | the transaction journal, each entry carrying its own plain-language meaning |
217
+ | `POST /authorize` | token | re-verify everything and return a **treasury-signed** `Decision` (`paid` / `held` / `refused`, with reasons) |
218
+ | `POST /deposit` | token | rule on a claim's deposit — refund vs forfeit |
219
+ | `POST /vault/fund` | token | operator adds simulated budget |
220
+ | `POST /vault/reserve` | token | set a bounty's pool aside before its shares are released |
221
+ | `POST /vault/top-up` | token | request a refill under the standing rule (grants inside the allowance, escalates above it) |
222
+ | `POST /explain` | token | authorize first, then narrate the finished decision — advisory only |
223
+
224
+ ### The advisory explainer is never in the money path
225
+
226
+ Money must never depend on model output, so authorization is entirely deterministic and contains no
227
+ LLM. The explainer (DeepSeek V4 Pro by default) gets exactly one job: **after** a decision has
228
+ already been made, write the audit note. It receives the finished decision — state, amount,
229
+ severity, verifier count, reasons — and never the raw finding, so there is no untrusted-content sink
230
+ to inject through. It cannot change the outcome, its output is tagged `advisory: true`, and if the
231
+ model is unconfigured or fails, a deterministic sentence is used instead. A payout never blocks on a
232
+ model.
233
+
234
+ ## Run it
235
+
236
+ Docker is the intended shape, because the separation is a property of *where the key lives*. Build
237
+ from the **monorepo root** (the image needs `oracles/core` and `momus` in context):
238
+
239
+ ```bash
240
+ docker compose -f treasury/docker-compose.yml up -d --build # → 127.0.0.1:9401
241
+ ```
242
+
243
+ Or the whole stack — MOMUS + Treasury + panel, with separate key volumes:
244
+
245
+ ```bash
246
+ docker compose -f momus/docker-compose.yml up -d --build
247
+ ```
248
+
249
+ Without Docker:
250
+
251
+ ```bash
252
+ cd treasury && pip install -e ../oracles/core -e ../momus -e ".[dev]" && python -m treasury.service
253
+ ```
254
+
255
+ **Ports:** `9401` locally · `9411` in production (on the oracle host `:9400` belongs to the oracle
256
+ family, so MOMUS shifts to `:9410` and the Treasury to `:9411`). There the Treasury binds loopback
257
+ only and sits behind the
258
+ `momus.modelmarket.dev` edge, which serves the read-only surface —
259
+ [`/treasury/health`](https://momus.modelmarket.dev/treasury/health) — and does **not** expose
260
+ `/treasury/authorize`, `/deposit` or `/vault/fund` publicly. That is asserted by the production
261
+ verification script, not just configured.
262
+
263
+ ### The env vars that matter
264
+
265
+ | variable | meaning | default |
266
+ |---|---|---|
267
+ | `TREASURY_KEY_PATH` | the treasury signing key — the one key that can release a bounty | `data/treasury_signing_key` |
268
+ | `TREASURY_CLIENT_TOKEN` | caller token for every write route; **unset in prod ⇒ `503`, fail-closed** | unset |
269
+ | `TREASURY_SCANNER_PUBKEYS` | comma-separated allowlist of claimant scanner keys | unset = any |
270
+ | `MOMUS_EXTERNAL_VERIFIERS` | pubkeys of independently-operated verifiers; required for high/critical in prod | unset |
271
+ | `TREASURY_LEDGER_PATH` | append-only decision/claim ledger | `data/bounty_ledger.jsonl` |
272
+ | `TREASURY_VAULT_PATH` | the vault's append-only journal | `<data>/uni_vault.jsonl` |
273
+ | `TREASURY_PORT` | listen port | `9401` |
274
+ | `TREASURY_WRITE_RATE_LIMIT` | per-caller rate limit on write routes | `30` |
275
+ | `TREASURY_CORS_ORIGINS` | allowed origins | `*` |
276
+ | `AIFACTORY_PROD` | arms the fail-closed branches | unset |
277
+ | `AIFACTORY_CRYPTO_ENABLED` | ecosystem-wide crypto master switch — **not** enough to pay on-chain | `0` |
278
+ | `MOMUS_BOUNTY_ONCHAIN` · `MOMUS_BOUNTY_CHAIN` · `MOMUS_BOUNTY_SPLITTER` | the separate on-chain opt-in, its chain, and the deployed splitter address | unset |
279
+ | `MOMUS_BUDGET_RATE_BPS` · `MOMUS_BUDGET_PERIOD_CAP_USD` · `MOMUS_BUDGET_THRESHOLD_USD` · `MOMUS_BUDGET_TARGET_USD` | the standing allocation rule | see [uni-chain.md](https://github.com/alexar76/momus/blob/main/docs/uni-chain.md#configuration) |
280
+ | `MOMUS_BUDGET_HUB_URL` · `MOMUS_BUDGET_DECLARED_VOLUME_USD` | measured hub volume, or the operator-declared figure used in simulation | unset · `0` |
281
+ | `TREASURY_LLM_PROVIDER` | advisory explainer only, never the payout path | `deepseek` |
282
+
283
+ Note that `TREASURY_SCANNER_KEY_PATH` is a *reference* slot, not custody: the independence check
284
+ only needs the scanner's **public** key, which travels inside each finding. The Treasury never holds
285
+ a scanner private key, and the `KeyRing` guard refuses `scanner == treasury` regardless.
286
+
287
+ ## Tests
288
+
289
+ ```bash
290
+ cd treasury && pytest -q # 5 tests
291
+ ```
292
+
293
+ The suite exercises the properties, not the plumbing: `/health` exposes the treasury public key and
294
+ nothing secret, a valid HIGH claim is **held** on an unfunded vault and only pays after the pool is
295
+ funded and reserved (with the money actually leaving the vault), a tampered finding is refused, a
296
+ refuted claim forfeits its deposit, and every decision lands in the ledger. `aimarket-momus` and
297
+ `aimarket-oracle-core` must be importable; the standalone mirror vendors both.
298
+
299
+ ## License
300
+
301
+ MIT · part of the [AICOM / AIMarket](https://magic-ai-factory.com/) ecosystem.