# Minibits Ippon Ippon (一本, "one point", "decisive victory") Minibits Ippon is a minimalistic ecash and Lightning wallet implementing the Cashu protocol. It can be operated as a **REST API server** (for hosted deployments and AI agents connecting over HTTP) or as a **local CLI** (for AI agents that install and control it directly via standard I/O). > ### Try it out > A live Ippon instance is running at **ippon.minibits.cash**: > > - [Swagger UI](https://ippon.minibits.cash/v1/) — API explorer (`GET /v1/`) > - [OpenAPI 3.0 JSON](https://ippon.minibits.cash/v1/json) — raw spec (`GET /v1/json`) > - [Wallet info](https://ippon.minibits.cash/v1/info) — wallet service info (`GET /v1/info`) > - [MCP server](https://ippon.minibits.cash/mcp) — MCP server to make usage by AI agents easier (`POST /mcp`) > > Also available as Tor hidden service (requires [Tor Browser](https://www.torproject.org/) or a Tor-enabled client): > > - `eaqmg2oqhay5btz5v75bgknfb3x4q4vesfijjzvgkbgocn5fvhkntwad.onion` > > ⚠️ **This instance is provided for testing and evaluation purposes only. It is an alpha software, use it at your own risk, with small amounts only. Do not store funds you cannot afford to lose.** Ippon is designed primarily for non-human systems — especially AI agents — that require instant, automated capability to receive and send micropayments without human intervention. Minibits Ippon is intended for server-side deployment, ideally by a Cashu mint operator as a complementary service to their mint. It is therefore a fully custodial solution optimized for short-lived, single-purpose wallets with low balances. To minimize complexity and provide an extremely simple API that AI agents can easily understand and use, Ippon adheres to strict design constraints: ### Mints defined by operator, single unit Ippon supports one or more Cashu mints configured via the `MINT_URLS` environment variable (comma-separated list). All mints share a single unit (e.g. `sat`), keeping the API simple. Each wallet is bound to exactly one mint at creation time; the first listed mint is the default. Operators control which mints are accepted, keeping custody scope well-defined. ### Seed-less wallets AI agents lack reliable long-term secret storage. Ippon wallets are expected to be short-lived, hold minimal balances, and should be emptied upon session completion. Seed-based wallets introduce unnecessary complexity and operational burden that neither agents nor wallet operators really need. ### No UI The wallet exposes a very simple REST API with verbose, intuitive route and parameter names that hide ecash internals where possible. To make consuming the API by AI agents more natural, the ippon_mcp project provides an MCP server facade. Alternatively, operators can run Ippon in **CLI mode** (`INTERACTION_MODE=cli`) where the wallet is controlled through standard I/O commands — useful for AI agents that can install and spawn a local process instead of connecting to a hosted server. ### Short-term security guarantees Wallet access relies on a bare-bones access_key generated by the wallet server at creation time and later passed in the request’s Authorization header as a Bearer token. Given the short wallet lifetime and the expectation that a new wallet is created and funded for each longer session, there is no need for token refresh or revocation. To minimize necessity for AI agents to keep secrets, MCP server safeguards wallet access_key, linking it to agent's session_id. ### Arbitrary limits on wallets, balances and transactions Due to the above constraints, the primary safeguard is a combination of rate limiting and of strict limits on maximum wallet balance and payment amounts. Limits are double-tiered: - Global limits set by the wallet operator on number of created wallets per ip address, max wallet balance and max ecash or lightning payment - Optional per-wallet balance and payment limits defined at wallet creation time. This prevents funding or outgoing payments from exceeding intended task scope. ## Wallet API The API is versioned under `/v1/` and uses standard HTTP methods with JSON payloads. All authenticated endpoints require a `Bearer access_key` in the `Authorization` header (except wallet creation and public info). ### GET /v1/info Returns machine-readable information about the wallet service, including status, supported unit, mint URL, and global limits (balance/payment caps and rate limits). | Authorization | Request Type | Response Type | |---------------|--------------|---------------| | N/A | N/A | InfoResponse | **Example:** ```bash curl -X GET http://localhost:3001/v1/info ``` ### POST /v1/wallet Creates a new short-lived wallet. Optionally accepts an initial Cashu token to fund it immediately or a name for identification. | Authorization | Request Type | Response Type | |---------------|---------------------|----------------| | N/A | WalletCreateRequest | WalletResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet \ -H "Content-Type: application/json" \ -d '{"name": "agent-007-session-001", "token": "cashuBeyJ..."}' ``` ### GET /v1/wallet Retrieves details of the current wallet, including its name, unit, mint, confirmed balance, and pending balance. Any pending proofs are checked against the mint first and their state updated before the balances are returned. | Authorization | Request Type | Response Type | |------------------------|--------------|----------------| | Bearer access_key | N/A | WalletResponse | **Example:** ```bash curl -X GET http://localhost:3001/v1/wallet \ -H "Authorization: Bearer abc123_access_key" ``` ### POST /v1/wallet/deposit Requests a Lightning invoice for funding the wallet with a specified amount. The wallet automatically handles the mint quote and ecash issuance once the invoice is paid. | Authorization | Request Type | Response Type | |------------------------|--------------------------|----------------------------| | Bearer access_key | WalletDepositRequest | WalletDepositResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/deposit \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"amount": 10000, "unit": "msat"}' ``` ### GET /v1/wallet/deposit/:quote Checks the status of a previously requested deposit quote (e.g., whether the invoice was paid and ecash minted). | Authorization | Request Type | Response Type | |------------------------|--------------|----------------------------| | Bearer access_key | N/A | WalletDepositResponse | **Example:** ```bash curl -X GET http://localhost:3001/v1/wallet/deposit/quote_abc123xyz \ -H "Authorization: Bearer abc123_access_key" ``` ### POST /v1/wallet/send Generates and exports an ecash token for a specific amount (or pays a Cashu payment request if provided). The wallet handles any necessary swaps and marks proofs as pending to prevent double-spending. Potential mint fees are fully paid by the sender (including fees paid by the receiver when swapping the received token for fresh one). Optionally allows locking the token to a specific receiver's pubkey to prevent unauthorized party to steal it. | Authorization | Request Type | Response Type | |------------------------|-------------------|---------------------| | Bearer access_key | WalletSendRequest | WalletSendResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/send \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"amount": 5000, "unit": "msat", "memo": "Complete this task for me"}' ``` ### POST /v1/wallet/check Checks the current state of an exported Cashu token (e.g., whether it has been spent/swapped by the recipient). | Authorization | Request Type | Response Type | |------------------------|--------------------|----------------------| | Bearer access_key | WalletCheckRequest | WalletCheckResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/check \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"token": "cashuB..."}' ``` ### POST /v1/wallet/decode Decodes a Cashu token, Cashu payment request, or Lightning invoice and returns structured information. | Authorization | Request Type | Response Type | |------------------------|---------------------|-----------------------| | Bearer access_key | WalletDecodeRequest | WalletDecodeResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/decode \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"type": "CASHU_TOKEN_V4", "data": "cashuB..."}' ``` ### POST /v1/wallet/pay Pays an external Lightning invoice (or Lightning address) using the wallet's ecash balance. The wallet handles melt quote, fees, and returns any change. | Authorization | Request Type | Response Type | |------------------------|------------------|--------------------| | Bearer access_key | WalletPayRequest | WalletPayResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/pay \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"bolt11_request": "lnbc50u...", "amount": 5000, "unit": "msat"}' ``` ### GET /v1/wallet/pay/:quote Checks the status of a melt quote / payment operation (e.g., whether the invoice was successfully paid). | Authorization | Request Type | Response Type | |------------------------|--------------|--------------------| | Bearer access_key | N/A | WalletPayResponse | **Example:** ```bash curl -X GET http://localhost:3001/v1/wallet/pay/etkpOx_SLLNEMOX2oEyk6y1ePKVp9sdUxc3k03bI \ -H "Authorization: Bearer abc123_access_key" ``` ### POST /v1/wallet/receive Imports and processes an external Cashu token. The wallet validates token with the mint, and swaps it for a new ecash token. | Authorization | Request Type | Response Type | |------------------------|----------------------|------------------------| | Bearer access_key | WalletReceiveRequest | WalletReceiveResponse | **Example:** ```bash curl -X POST http://localhost:3001/v1/wallet/receive \ -H "Authorization: Bearer abc123_access_key" \ -H "Content-Type: application/json" \ -d '{"token": "cashuB..."}' ``` ### GET /v1/rate/:currency Returns the current fiat exchange rate for the wallet's unit (e.g., satoshis per USD). | Authorization | Request Type | Response Type | |------------------------|--------------|----------------| | Bearer access_key | N/A | RateResponse | **Example:** ```bash curl -X GET http://localhost:3001/v1/rate/USD \ -H "Authorization: Bearer abc123_access_key" ``` ## CLI Mode CLI mode is intended for **local, self-hosted use** — typically by an AI agent that installs Ippon and interacts with it via standard I/O instead of HTTP. Data is stored in a local SQLite file; no PostgreSQL or network server is required. ### How it works - `INTERACTION_MODE=cli` — starts a readline REPL instead of an HTTP server. - `DATABASE_ENGINE=sqlite` — stores data in a local file (`DATABASE_FILE_PATH`, default `~/.ippon/database.sqlite`). - **All responses are JSON lines on stdout**; the prompt (`> `) and info messages go to stderr so they don't interfere with programmatic parsing. - Wallet access keys are short 6-character alphanumeric strings displayed as `xxx-xxx` (e.g. `adg-08m`). Both formats (`adg08m` and `adg-08m`) are accepted as input. ### Quick start ```bash # 1. Install dependencies yarn install # 2. Copy and configure environment cp .env.example .env # Set DATABASE_ENGINE=sqlite, INTERACTION_MODE=cli, MINT_URLS, UNIT, limits, etc. # 3. Build yarn build # 4. Set up SQLite schema (run once, and again after switching engines) yarn db:setup # 5. Start the CLI DATABASE_ENGINE=sqlite INTERACTION_MODE=cli node dist/index.js # or, combining steps 4 and 5: yarn start:local ``` ### Commands All commands are typed at the `> ` prompt (or piped via stdin). Every response is a single JSON object on stdout. | Command | Description | |---|---| | `info` | Service info: mints, unit, global limits | | `wallet create [name] [mint_url]` | Create a new wallet; mint must be in `MINT_URLS`; prints `access_key` | | `wallet list` | List all wallets with balances | | `wallet balance` | Show wallet balance and details; auto-syncs pending proofs with the mint first | | `wallet deposit ` | Create a Lightning invoice to fund the wallet | | `wallet deposit-check ` | Check deposit status; auto-mints ecash when paid | | `wallet send [lock_pubkey]` | Export a Cashu token (optionally P2PK-locked) | | `wallet receive ` | Import a Cashu token | | `wallet pay [amount]` | Pay a Lightning invoice or Lightning address | | `wallet pay-check ` | Check payment status | | `wallet sync` | Sync pending proofs with the mint | | `decode ` | Decode a Cashu token, Cashu request, or BOLT11 invoice | | `help` | Show available commands | | `exit` | Quit | ### Example session ``` > wallet create agent-session-1 {"access_key":"adg-08m","name":"agent-session-1","mint":"https://mint.minibits.cash/Bitcoin","unit":"sat","balance":0,"pending_balance":0} > wallet adg-08m deposit 100 {"quote":"abc123...","request":"lnbc1000n...","state":"UNPAID","expiry":1234567890} > wallet adg-08m deposit-check abc123... {"quote":"abc123...","request":"lnbc1000n...","state":"PAID","expiry":1234567890} > wallet adg-08m balance {"access_key":"adg-08m","name":"agent-session-1","mint":"https://mint.minibits.cash/Bitcoin","unit":"sat","balance":100,"pending_balance":0} > wallet adg-08m pay lnbc500n... {"quote":"xyz987...","amount":50,"fee_reserve":1,"state":"PAID","payment_preimage":"...","expiry":1234567890} ``` ### Piping commands (non-interactive) Each command can be piped as a single stdin line; the process exits cleanly after completing it: ```bash # Create a wallet and capture the access key KEY=$(echo "wallet create bot" | DATABASE_ENGINE=sqlite INTERACTION_MODE=cli LOG_LEVEL=error node dist/index.js 2>/dev/null \ | node -e "process.stdin.setEncoding('utf8');let d='';process.stdin.on('data',c=>d+=c).on('end',()=>console.log(JSON.parse(d).access_key))") # Receive a token echo "wallet $KEY receive cashuB..." | DATABASE_ENGINE=sqlite INTERACTION_MODE=cli LOG_LEVEL=error node dist/index.js 2>/dev/null # Pay an invoice echo "wallet $KEY pay lnbc..." | DATABASE_ENGINE=sqlite INTERACTION_MODE=cli LOG_LEVEL=error node dist/index.js 2>/dev/null ``` ## API Reference The full interactive API reference — including all request bodies, response schemas, and enum values — is served directly by the running Ippon instance: - **Swagger UI** (interactive): `GET /v1/` - **Raw OpenAPI 3.0 JSON**: `GET /v1/json` The spec is generated at runtime from the route definitions and is always in sync with the code. All request and response TypeScript types used in the route tables above are defined in [`src/routes/routeTypes.ts`](src/routes/routeTypes.ts). > **Note on amounts:** Despite the wallet operating with a single unit, amounts are always declared together with `unit` to keep values unambiguous (`unit` must always equal the wallet's configured `MintUnit`). ## Development Minibits Ippon is written in TypeScript and runs on Node.js (v24+). It uses Fastify as the HTTP server and Prisma ORM with either PostgreSQL (API/server mode) or SQLite (CLI/local mode). ### Prerequisites **API mode (hosted)** - Node.js v24+ - PostgreSQL - A running Cashu mint **CLI mode (local)** - Node.js v24+ - A running Cashu mint (public mints work; no self-hosted infrastructure needed) ### Setup #### API mode (PostgreSQL) ```bash yarn install cp .env.example .env # Edit .env: set DATABASE_ENGINE=postgresql, DATABASE_URL, INTERACTION_MODE=api, MINT_URLS, etc. yarn db:setup # copies schema, runs prisma generate + db push yarn build yarn start:prod ``` #### CLI mode (SQLite) ```bash yarn install cp .env.example .env # Edit .env: set DATABASE_ENGINE=sqlite, INTERACTION_MODE=cli, MINT_URLS, etc. # DATABASE_FILE_PATH defaults to ~/.ippon/database.sqlite yarn db:setup # creates ~/.ippon/, copies schema, runs prisma generate + db push yarn build yarn start:local # db:setup + CLI start combined ``` > **Switching engines:** re-run `yarn db:setup` after changing `DATABASE_ENGINE`. This regenerates the Prisma client for the new engine. ### Running ```bash # Development (auto-reload, reads INTERACTION_MODE from .env) yarn start:dev # Production build yarn build # Production start (API or CLI depending on .env) yarn start:prod # One-shot local CLI (sets up SQLite and starts the REPL) yarn start:local ``` ### Testing The test suite uses [Vitest](https://vitest.dev/) and covers three layers: | File | Scope | |---|---| | `src/__tests__/nostrService.test.ts` | Unit — pubkey normalisation (npub / x-only hex / compressed hex) | | `src/__tests__/walletService.test.ts` | Unit — `WalletService` methods with Prisma and cashu-ts mocked | | `src/__tests__/publicRoutes.test.ts` | Integration — unauthenticated routes (`GET /v1/info`, `POST /v1/wallet`) | | `src/__tests__/protectedRoutes.test.ts` | Integration — all authenticated routes via Fastify `inject()` | ```bash # Run once yarn test # Watch mode yarn test:watch ``` All external I/O (Prisma, cashu-ts `Wallet`, `getEncodedTokenV4`, fetch) is mocked; no database or mint connection is required. ### Environment variables | Variable | Description | Default | |---|---|---| | `DATABASE_ENGINE` | Database backend: `postgresql` or `sqlite` | `postgresql` | | `DATABASE_URL` | PostgreSQL connection string (required when `DATABASE_ENGINE=postgresql`) | — | | `DATABASE_FILE_PATH` | SQLite file path, supports `~` expansion (used when `DATABASE_ENGINE=sqlite`) | `~/.ippon/database.sqlite` | | `INTERACTION_MODE` | Runtime mode: `api` (HTTP server) or `cli` (stdio REPL) | `api` | | `PORT` | HTTP server port (API mode only) | `3001` | | `LOG_LEVEL` | Log verbosity (`trace`, `debug`, `info`, `warn`, `error`) | `debug` | | `MINT_URLS` | Comma-separated list of supported Cashu mint URLs. First entry is the default. | — | | `UNIT` | Wallet unit (`sat` or `msat`) | `sat` | | `MAX_BALANCE` | Global max wallet balance (in unit) | `100000` | | `MAX_SEND` | Global max ecash send amount | `50000` | | `MAX_PAY` | Global max Lightning payment amount | `50000` | | `SERVICE_STATUS` | Status string returned by `/v1/info` (API mode) | `operational` | | `SERVICE_HELP` | Help URL returned by `/v1/info` (API mode) | — | | `SERVICE_TERMS` | Terms URL returned by `/v1/info` (API mode) | — | | `RATE_LIMIT_MAX` | Default max requests per window (API mode) | `100` | | `RATE_LIMIT_CREATE_WALLET_MAX` | Max wallet creations per window per IP (API mode) | `3` | | `RATE_LIMIT_WINDOW` | Rate-limit time window (API mode) | `1 minute` | All configured values are printed to **stderr** at startup regardless of mode, making it easy to verify the active configuration. ### MCP server The companion [minibits_ippon_mcp](https://github.com/minibits-cash/minibits_ippon_mcp) project provides an MCP server that wraps the wallet API for AI agent integration. It manages session lifecycle and safeguards the wallet access key so that agents never handle it directly. ## TO-DO's - [x] lock token to pubkey - [ ] pay cashu payment request - [ ] add transactions model and API