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.
- aimarket_treasury-0.1.0/.dockerignore +11 -0
- aimarket_treasury-0.1.0/.github/workflows/ci.yml +39 -0
- aimarket_treasury-0.1.0/.github/workflows/pages.yml +48 -0
- aimarket_treasury-0.1.0/.gitignore +16 -0
- aimarket_treasury-0.1.0/Dockerfile +15 -0
- aimarket_treasury-0.1.0/Dockerfile.standalone +14 -0
- aimarket_treasury-0.1.0/LICENSE +21 -0
- aimarket_treasury-0.1.0/PKG-INFO +301 -0
- aimarket_treasury-0.1.0/README.es.md +298 -0
- aimarket_treasury-0.1.0/README.fr.md +299 -0
- aimarket_treasury-0.1.0/README.md +283 -0
- aimarket_treasury-0.1.0/README.ru.md +298 -0
- aimarket_treasury-0.1.0/README.zh.md +261 -0
- aimarket_treasury-0.1.0/docker-compose.yml +38 -0
- aimarket_treasury-0.1.0/docs/badges/ci.svg +19 -0
- aimarket_treasury-0.1.0/docs/badges/docker.svg +19 -0
- aimarket_treasury-0.1.0/docs/badges/landing.svg +17 -0
- aimarket_treasury-0.1.0/docs/badges/license.svg +19 -0
- aimarket_treasury-0.1.0/docs/badges/momus.svg +19 -0
- aimarket_treasury-0.1.0/docs/badges/python.svg +19 -0
- aimarket_treasury-0.1.0/docs/badges/separation.svg +19 -0
- aimarket_treasury-0.1.0/docs/landing/index.html +77 -0
- aimarket_treasury-0.1.0/pyproject.toml +26 -0
- aimarket_treasury-0.1.0/tests/test_treasury_service.py +134 -0
- aimarket_treasury-0.1.0/treasury/__init__.py +14 -0
- aimarket_treasury-0.1.0/treasury/explainer.py +77 -0
- aimarket_treasury-0.1.0/treasury/service.py +263 -0
|
@@ -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,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.
|