Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Amanah

БесплатноНе проверен

MCP server that enables AI agents to propose USDC payments on the Soroban blockchain with deterministic policy enforcement and injection protection, while provi

GitHubEmbed

Описание

MCP server that enables AI agents to propose USDC payments on the Soroban blockchain with deterministic policy enforcement and injection protection, while providing payment status and attestation tools.

README

Lumo — the agent can be tricked, the money cannot

The agent can be tricked. The money cannot.

Demo video · Live site · Deck · Testnet contracts

On-device SME treasury agent for USDC on Soroban. An untrusted local LLM only reads invoices; a deterministic policy layer decides; two on-chain contracts enforce. Funds in escrow can structurally reach only the bound supplier (on a Shipped attestation) or return to the SME (on Failed, or on deadline with no attestation) — so compromising the agent cannot move money to an attacker.

Demo persona: Bu Sari, owner of Sari Craft Export, a batik exporter in Yogyakarta, Indonesia. She pays overseas fabric suppliers in USDC and wants an agent that can read an invoice and propose a payment — without ever being able to send her money somewhere an attacker chose.

Quick start

Everything below runs offline in mock mode with no keys — safe to try in under a minute.

Run the agent (CLI):

git clone https://github.com/SyaugiAlkaf/lumo && cd lumo
python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/python -m lumo.cli init                  # local DB + a seeded demo supplier
printf 'INVOICE INV-1\nFrom: CV Batik Nusantara\nAmount due: 1,250.00 USDC\n' > invoice.txt
.venv/bin/python -m lumo.cli propose invoice.txt   # -> decision + reason codes as JSON

Embed it as an SDK (5 lines):

from lumo import LumoClient

client = LumoClient()
d = client.propose(open("invoice.txt").read())
print(d.decision, d.codes)   # proposed | refused | held  +  the reason codes
# a proposal carries the exact create_intent tx plan and an intent_id you can status() later

Ship it as a microservice (Docker):

docker compose up --build    # REST API on :8788, chain adapter in safe mock mode (no keys)
curl -sX POST localhost:8788/v1/intents -H 'Content-Type: application/json' \
  -d '{"invoice":"INVOICE INV-1\nFrom: CV Batik Nusantara\nAmount due: 1,250.00 USDC"}'

Plug it into any AI agent (MCP):

python -m lumo.mcp           # stdio MCP server: propose_payment · get_status · attest

To settle on Stellar testnet, add the stellar CLI + a keystore and set LUMO_CHAIN_ADAPTER=soroban — plus LUMO_SME_SMART_ACCOUNT=<policy-account> to gate every payment on-chain through the contract's __check_auth. Full options, language bindings, and trust tiers: Integrate · docs/integration.md.

Architecture

Lumo architecture: invoice -> on-device AI (reads only) -> policy engine (guard) -> Soroban (guard) -> USDC released; a bad payment is stopped at the policy engine and again on-chain

The AI reads (untrusted); a deterministic policy decides; Soroban enforces. In code that path is lumo/security (injection scan) -> lumo/llm (mock or llama, extraction-only) -> lumo/policy (caps, allowlist) -> lumo/flow -> lumo/chain/soroban_client (stellar CLI subprocess), with lumo/db as the single audit chokepoint and two Soroban contracts as the on-chain gates: the policy-signed account (__check_auth: per-tx cap + supplier allowlist) and the attestation escrow (create_intent -> attest -> release | refund).

Trust boundary: the LLM is extraction-only and holds zero tools — it reads invoice text and returns structured fields, nothing more. Every payment decision is made by the deterministic Python policy layer (caps, supplier registry, injection scanner), and every payment is enforced twice more on-chain: the policy-signer smart account refuses an out-of-policy transaction before it is ever submitted, and the escrow can only pay the one supplier bound to that intent or refund the SME who funded it. A test suite proves that even a fully compromised LLM (one that obeys attacker text in the invoice) cannot move funds anywhere but the registered supplier or back to the SME — see tests/test_t8_injection.py.

Money truth lives on-chain; agent-brain truth (suppliers, rules, intents, audit) lives in SQLite; a request_hash (sha256 of the canonical intent JSON) binds the two and is checked chain-side before any state is written locally.

Prior art, honestly

On-chain spend caps and payee allowlists already exist — Coinbase Spend Permissions, Crossmint on Soroban, OpenZeppelin's Stellar smart accounts. Lumo does not claim to invent them. Its contribution is the combination: an attestation-gated escrow fused with the policy-signed account (others release on a human signature), aimed squarely at the compromised-agent and invoice-fraud / business-email-compromise threat that agent-payment guidance usually leaves to the operator — packaged full-stack with on-device inference for the cross-border SME vertical.

Repository layout

contracts/           Rust workspace (soroban-sdk 26, wasm32v1-none)
  escrow/             conditional-release escrow (T1-T3, T5)
  policy-account/     __check_auth policy-signer smart account (T4)
bindings/             frozen contract interface (escrow.json, policy_account.json)
lumo/               the Python agent (one package)
  llm/                extraction-only providers: mock + llama-server
  security/           injection scanner (NFKC + zero-width strip, patterns)
  policy/             deterministic evaluate() — caps, registry, injection
  db/                 SQLite schema, migrations, repo (single audit chokepoint)
  chain/              stellar CLI client, request_hash, chain-wins mapper
  anchor/             mock_anchor.py — SEP-24-shaped, zero network
  ui/                 monitoring dashboard + wired testnet tester (/testnet)
site/                 self-contained public landing page (landing_check.sh gate)
tests/                pytest: policy engine, injection, audit, db, chain client
acceptance/           acceptance.sh (gate runner) + t10_e2e.sh (local e2e)
scripts/              local_network / deploy_local / demo / deploy_testnet / testnet_serve

Contracts

lumo-escrow

Conditional-release escrow. One SME funds an Intent bound to one supplier; an admin-registered oracle attests the outcome; funds settle only along the two allowed paths.

Entrypoint Effect
__constructor(admin) stores the admin
add_oracle / remove_oracle / is_oracle admin-gated oracle registry
create_intent(sme, supplier, token, amount, request_hash, deadline) pulls amount into escrow, status Funded
attest(intent_id, oracle, kind) oracle-only, Funded-only, first-write-wins; kind ∈ {Shipped, Failed}
release(intent_id) requires a Shipped attestation; pays the bound supplier only
refund(intent_id) on Failed, or (no attestation and now ≥ deadline); pays the SME only

A Shipped attestation always beats the deadline: once shipped, refund is blocked and release stays valid.

lumo-policy-account

Deny-by-default policy-signer smart account (__check_auth). Only two functions are allowlisted (transfer, create_intent); every invocation is checked against a per-transaction cap and, for create_intent, an approved supplier set. Anything else — wrong function, over cap, unapproved supplier, bad signature — is a typed revert, never a silent pass-through.

cd contracts
cargo test --workspace         # unit + revert tests, both crates
stellar contract build         # -> target/wasm32v1-none/release/*.wasm

The Python agent

python -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python -m lumo.cli --db /tmp/lumo.db init
.venv/bin/python -m lumo.cli propose tests/fixtures/invoices/clean_in_policy.txt

propose exits 0 and prints a tx plan for an in-policy invoice, or exits 2 with refusal codes (INJECTION_SUSPECTED, OVER_TX_CAP, UNKNOWN_SUPPLIER, ...) and proposes nothing on-chain. LUMO_PROVIDER=mock (default) never touches a real model; point LUMO_PROVIDER=llama + LUMO_LLAMA_URL at a local llama-server for the real extraction path (make live-check).

Try it on Stellar testnet (web)

Two web surfaces ship with the agent:

  • Landingsite/index.html, a self-contained page (the only external request is Google Fonts). scripts/landing_check.sh asserts it stays self-contained, links the live contracts, and leaks no secret key.
  • Live testnet tester — a one-page tool that runs a real invoice against the deployed contracts and shows the actual create_intent → attest → release transactions on Stellar Expert, or refuses a poisoned / over-cap invoice with the real policy codes and zero transactions.
scripts/testnet_serve.sh     # one origin: / landing · /testnet tester · /dashboard monitor
scripts/testnet_smoke.sh     # POSTs a clean invoice, asserts a real create_intent tx hash

The tester refers to three funded testnet keystore identities (lumo-deployer, lumo-sme, lumo-supplier) by name only — it never reads, prints, or exports a secret key. Its presets (clean / over-cap / injection) are built from the live per-transaction cap and approved supplier returned by /testnet/info, so a clean invoice settles on-chain and a tampered one is refused before anything is signed. Nothing here touches mainnet or real funds.

Proposed: a clean invoice settles with a real create_intent, attest and release trail Refused: a prompt-injected invoice is blocked with zero transactions

Left — a clean invoice is escrowed, attested and released: three real testnet transactions to the approved payee.  Right — a prompt-injected invoice is refused by the policy layer (INJECTION_SUSPECTED · ADDRESS_MISMATCH) and zero transactions are signed. The agent was fooled; the money never moved.

Integrate

Deeper walkthrough with every option: docs/integration.md. Runnable versions of the snippets live in examples/.

Integrate in 5 lines

from lumo import LumoClient

client = LumoClient()
decision = client.propose(open("invoice.txt").read())
print(decision.decision, decision.codes)

decision.decision is proposed, refused, or held; a proposal carries the exact create_intent tx plan and an intent_id you can status() later.

Call from any language

Start the REST API (python -m lumo.api, default 127.0.0.1:8788) and use plain HTTP — the full schema is served at /v1/openapi.json:

curl -X POST http://127.0.0.1:8788/v1/intents \
  -H 'Content-Type: application/json' \
  -d '{"invoice": "INVOICE INV-2026-0042\nFrom: CV Batik Nusantara\nAmount due: 1,250.00 USDC\n"}'

Run it as a microservice (Docker)

Drop the trust layer into any stack as a container:

docker compose up --build      # REST API on http://127.0.0.1:8788
# or:
docker build -t lumo . && docker run -p 8788:8788 lumo

The image ships the REST API plus the deterministic guard chain (injection scan, per-tx cap, supplier allowlist, attestation gating) with the chain adapter in mock mode — no keys, safe to publish. Callers get the payment decision as a service and wire their own chain/signer. For real on-chain settlement, extend the image with the stellar CLI and a mounted keystore, then set LUMO_CHAIN_ADAPTER=soroban (see the Dockerfile header).

Use from any AI agent

python -m lumo.mcp is an MCP server over stdio exposing three tools: lumo.propose_payment, lumo.get_status, lumo.attest. Point any MCP-capable agent at that command and it can propose payments — while every cap, registry, and injection guard still decides, not the agent.

Target any chain

Settlement is behind ChainAdapter / AnchorAdapter / AttestationSource seams, selected by config:

Seam Config key Live Roadmap
Chain chain_adapter soroban (stellar CLI), mock evm (x402)
Anchor off-ramp anchor_adapter mock (SEP-24-shaped, zero network) gcash, pdax
Oracle oracle_adapter "" (single local), local (signer set) shipment_api

Monitor it

Every decision, guard trip, and state change emits an event through one bus (monitoring = true, on by default):

  • SDK: client.on_event(print) · client.metrics()
  • REST: GET /v1/metrics (counters + gauges), POST /v1/webhooks registers a URL that receives every event as JSON
  • Dashboard: the read-only monitoring UI at http://127.0.0.1:8787/dashboard shows the intent timeline and live metrics (the same server serves the landing at / and the testnet tester at /testnet)

Pick a trust tier

Config.profile(name) returns a preset guard chain; everything else stays at safe defaults and any field can be overridden per call:

Profile Guards on Extras
strict injection · policy · signer · attestation · k-of-n · cosign · proof-of-compute k_of_n = 3, cosign above 100 USDC
balanced injection · policy · signer · attestation single oracle, no cosign
fast injection · policy propose/refuse only, no release guards
client = LumoClient(Config.profile("balanced"))

Local demo

Requires Docker, the stellar CLI (major version pinned in acceptance/lib.sh), and the Rust wasm32v1-none target. Everything below runs on the local Stellar quickstart container — no testnet, no real funds.

scripts/demo.sh

This is a narrated, timed walkthrough of the whole spine as Bu Sari would see it:

  1. Seed — deploys the escrow + policy-account, registers the oracle, binds her suppliers, and starts a read-only UI at http://127.0.0.1:8787.
  2. Injected refusal — a fake "our payment address has changed" email is proposed as an invoice. The injection scanner and policy layer refuse it before any transaction is proposed — exit code 2, no chain call.
  3. In-policy escrow — a legitimate invoice is proposed and escrowed on-chain, funds locked and structurally bound to that one supplier.
  4. Attestation + release — the oracle attests Shipped; the escrow releases to the supplier and nowhere else.
  5. MOCK cash-outlumo/anchor/mock_anchor.py records a MOCK-<ulid> receipt. This is a stand-in for a real SEP-24 anchor off-ramp (structurally zero network calls) — never a real payout.
  6. Failure path — a second order is left unshipped past its deadline (a real few-second wait, no ledger time-travel) and refunds Bu Sari; the supplier never touches those funds.

Pace between steps is LUMO_DEMO_PACE seconds (default 2). The UI stays up after the walkthrough finishes — Ctrl+C to stop it, then scripts/local_network.sh down.

Acceptance gates

Each phase gate is one runnable command; acceptance/acceptance.sh (no flags) re-runs all of them and requires the full T1–T10 matrix green with no regression.

acceptance/acceptance.sh --gate P0   # T1-T3, T5 — escrow contract
acceptance/acceptance.sh --gate P1   # T4        — policy-signer __check_auth
acceptance/acceptance.sh --gate P2   # T6-T9     — policy engine, injection, audit (pytest)
acceptance/acceptance.sh --gate P3   # T10       — local e2e: happy + failure + MOCK cash-out
acceptance/acceptance.sh             # full re-run, T1-T10

make live-check runs the real-model extraction test against a local llama-server; it is a human-triggered exit check, never part of a gate.

Testnet deployment

Deployed to the Stellar testnet (Test SDF Network ; September 2015) — a valueless public test network. No mainnet asset is referenced and no real funds move; the cash-out anchor stays structurally mocked (lumo/anchor/mock_anchor.py).

Artifact Value
Network testnet
lumo-escrow CARKYFTVFVUX2Y3OZJUPYBBZKTVVIHC3APSFAQOVL6DGKWU6D6ZGJJMK
lumo-policy-account CD2EIG3V4TBGHSGLZYCIZRHVFVQFUA3NL2KG7SZFF3SIEGL7MMV4PF5L
Escrow admin + oracle GBPSOKJDBP5REZBCL6TWAXMU6CEWV5YMU3PMQUSDRGJM6K77TZHGGEEY
Policy owner (SME ed25519, hex) 12265b095264b6a939bac5e35e7144fd0c3c8de5d44336e8799e4e0a9edf164b
Policy per-tx cap 20000000000 stroops (2,000 test USDC)
Test USDC SAC CDWS5VFOIDNU7X3O4CXNF2I5TMGT5RKLB4GDHU24VOO7FRGGI3XYTQC7

The USDC used here is a self-issued testnet asset (USDC:GC5U5EI2…), never a mainnet asset. One full end-to-end smoke ran on-chain — create_intentattest(Shipped)release — pulling 100 test USDC into escrow and settling it to the bound supplier only:

Step Transaction
add_oracle fe803970…
create_intent 88b5b770…
attest(Shipped) e948634a…
release 0b5d14a5…

The live tester (scripts/testnet_serve.sh) produces a fresh trail like this on every clean run, and returns an empty transaction list on every refusal.

The policy-account is a custom smart account: its set_escrow / add_supplier config calls are authorized by the SME owner's ed25519 signature through __check_auth, on-chain — the CLI cannot forge that signature, so the account was configured with an owner-signed authorization entry:

Step Transaction
set_escrow (bind the one fundable escrow) bca818b7…
add_supplier (approve the payee) 9bd11a5f…

Every payment flows through the smart account

create_intent is routed through the policy-account, not a bare keypair. The escrow's create_intent calls sme.require_auth() and transfers the funds from the sme, so with sme = the policy-account both the intent and its funding transfer pass through __check_auth — the on-chain per-tx cap, approved-supplier allowlist, and recipient binding gate every real payment. The owner's ed25519 key signs the authorization; a compromised agent cannot forge it. This is LUMO_SME_SMART_ACCOUNT in scripts/testnet_serve.sh (lumo/chain/smart_account.py), enforced live on-chain:

Path Transaction
approved supplier, in-cap → __check_auth passes, funds settle 42b9c73f…
unapproved supplier / over-cap / wrong recipient → rejected on-chain see the verifier below + 928eb96d…

The money cannot be tricked — verify it yourself

scripts/verify_policy_enforcement.py signs a genuine owner authorization and asks the live deployed account to authorize three transfers, running each through enforce-mode simulation so the RPC actually invokes __check_auth:

[ok] wrong recipient, in-cap  -> RecipientNotAllowed   (money can't be redirected)
[ok] bound escrow, over cap   -> OverCap                (money can't exceed the cap)
[ok] bound escrow, in-cap     -> AUTHORIZED             (the legitimate payment)

Even a correctly-signed instruction cannot move money outside policy — the enforcement lives in the deployed contract, not the agent. Reproduce with:

pip install -e .
export SME_SECRET=$(stellar keys show lumo-sme)
export DEPLOYER_SECRET=$(stellar keys show lumo-deployer)
python scripts/verify_policy_enforcement.py

Re-deploying is a deliberate, human-run action — scripts/deploy_testnet.sh prints the checklist and exits 1; no gate, script, or Makefile target automates it.

Out of scope

Real anchor/off-ramp integration, mainnet deployment, and KYC/licensing are outside this build — see scripts/deploy_testnet.sh for what a real (mainnet) deploy would require.

from github.com/SyaugiAlkaf/lumo

Установка Amanah

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/SyaugiAlkaf/lumo

FAQ

Amanah MCP бесплатный?

Да, Amanah MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Amanah?

Нет, Amanah работает без API-ключей и переменных окружения.

Amanah — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Amanah в Claude Desktop, Claude Code или Cursor?

Открой Amanah на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Amanah with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории finance