diff --git a/Cargo.lock b/Cargo.lock index 87789b5..a61bcd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.0" @@ -113,9 +104,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "argon2" @@ -215,21 +206,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base58ck" version = "0.1.0" @@ -266,11 +242,11 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bip39" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.14.0", "rand 0.8.5", "rand_core 0.6.4", "serde", @@ -295,12 +271,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -341,16 +311,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitcoin_hashes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" -dependencies = [ - "bitcoin-internals 0.2.0", - "hex-conservative 0.1.2", -] - [[package]] name = "bitcoin_hashes" version = "0.14.0" @@ -495,9 +455,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -519,9 +479,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.46" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -529,9 +489,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.46" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -541,9 +501,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -565,9 +525,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "comfy-table" -version = "7.2.1" +version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ "crossterm", "unicode-segmentation", @@ -730,7 +690,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.1", ] [[package]] @@ -1013,12 +973,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.3" @@ -1075,12 +1029,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" - [[package]] name = "hex-conservative" version = "0.2.1" @@ -1433,17 +1381,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.10.1" @@ -1537,9 +1474,9 @@ dependencies = [ [[package]] name = "lightning-invoice" -version = "0.33.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11209f386879b97198b2bfc9e9c1e5d42870825c6bd4376f17f95357244d6600" +checksum = "b85e5e14bcdb30d746e9785b04f27938292e8944f78f26517e01e91691f6b3f2" dependencies = [ "bech32", "bitcoin", @@ -1548,9 +1485,9 @@ dependencies = [ [[package]] name = "lightning-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cd84d4e71472035903e43caded8ecc123066ce466329ccd5ae537a8d5488c7" +checksum = "5681708d3075bdff3a1b4daa400590e2703e7871bdc14e94ee7334fb6314ae40" dependencies = [ "bitcoin", ] @@ -1604,9 +1541,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -1686,9 +1623,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.6.56" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d9108f750c13c52c13c8cc1c447022f404222fa7cd06e576f398c38624bb55" +checksum = "74463dd48afde7c05294a01ac8eb8051d8b75186afd709dba6a67826fc310651" dependencies = [ "argon2", "base64 0.22.1", @@ -1824,15 +1761,6 @@ dependencies = [ "libm", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -2055,7 +1983,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2289,12 +2217,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -2481,9 +2403,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -2491,18 +2413,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2511,14 +2433,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2978,29 +2901,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.1", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -3318,23 +3238,23 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.2", "js-sys", "rand 0.9.2", - "serde", + "serde_core", "uuid-macro-internal", "wasm-bindgen", ] [[package]] name = "uuid-macro-internal" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f" +checksum = "39d11901c36b3650df7acb0f9ebe624f35b5ac4e1922ecd3c57f444648429594" dependencies = [ "proc-macro2", "quote", @@ -3581,6 +3501,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3825,3 +3754,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/Cargo.toml b/Cargo.toml index e16eeb0..72ecab4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,32 +20,32 @@ name = "mostro-cli" path = "src/main.rs" [dependencies] -anyhow = "1.0.99" -clap = { version = "4.5.46", features = ["derive"] } +anyhow = "1.0.100" +clap = { version = "4.5.54", features = ["derive"] } nostr-sdk = { version = "0.43.0", features = ["nip06", "nip44", "nip59"] } -serde = "1.0.219" -serde_json = "1.0.143" -tokio = { version = "1.47.1", features = ["full"] } -comfy-table = "7.2.1" -chrono = "0.4.42" -log = "0.4.28" +serde = "1.0.228" +serde_json = "1.0.149" +tokio = { version = "1.49.0", features = ["full"] } +comfy-table = "7.2.2" +chrono = "0.4.43" +log = "0.4.29" futures = "0.3" -uuid = { version = "1.18.1", features = [ +uuid = { version = "1.19.0", features = [ "v4", "fast-rng", "macro-diagnostics", "serde", ] } -lightning-invoice = { version = "0.33.2", features = ["std"] } +lightning-invoice = { version = "0.34.0", features = ["std"] } reqwest = { version = "0.12.23", default-features = false, features = [ "json", "rustls-tls", ] } -mostro-core = "0.6.56" +mostro-core = "0.7.0" lnurl-rs = { version = "0.9.0", default-features = false, features = ["ureq"] } pretty_env_logger = "0.5.0" sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio-rustls"] } -bip39 = { version = "2.2.0", features = ["rand"] } +bip39 = { version = "2.2.2", features = ["rand"] } dirs = "6.0.0" [package.metadata.release] diff --git a/docs/CODING_STANDARDS.md b/docs/CODING_STANDARDS.md new file mode 100644 index 0000000..43871bd --- /dev/null +++ b/docs/CODING_STANDARDS.md @@ -0,0 +1,126 @@ +# Coding Standards + +This document outlines the coding standards and best practices for the Mostro CLI project. These guidelines ensure code quality, maintainability, and consistency across the codebase. + +## Core Principles + +### 1. Readability and Reuse + +**Priority**: Code should be written for humans first, machines second. + +- **Clear naming**: Use descriptive names for functions, variables, and modules (e.g., `parse_dm_events` vs `pde`). +- **Function reuse**: Extract common logic into reusable functions. Place shared utilities in appropriate modules (`src/util/`, `src/parser/`, etc.). +- **Module organization**: Group related functionality together (CLI commands in `src/cli/`, Protocol parsing in `src/parser/`, Utilities in `src/util/`). + +### 2. Avoid Code Duplication (DRY Principle) + +**Don't Repeat Yourself**: If the same logic appears in multiple places, extract it. + +- **Extract common patterns**: Create helper functions for repeated operations like DM sending. +- **Centralize constants**: Import from `mostro-core::prelude` instead of hardcoding values. + +### 3. Simplicity + +**Keep It Simple**: Prefer straightforward solutions over clever ones. + +- **Avoid premature optimization**: Write clear code first, optimize only when needed. +- **Prefer explicit over implicit**: Use `Option` and `Result` types explicitly rather than hiding errors with `unwrap()`. + +### 4. Function Length Limit + +**Maximum 300 lines per function**: If a function exceeds this limit, split it into smaller, single-responsibility functions. + +## Rust-Specific Guidelines + +### Error Handling + +- **Use `Result`**: Functions that can fail should return `Result`. +- **Use `anyhow::Result`**: For application-level errors, use `anyhow::Result`. +- **Propagate errors**: Use the `?` operator to propagate errors up the call stack. +- **Add context**: Use `.context("...")` from `anyhow` to add meaningful error messages. + +### Type Safety + +- **Use strong types**: Prefer newtypes or enums (`Action`, `Status`) over primitive types. +- **Leverage enums**: Use enums for state machines and role management. + +### Async/Await + +- **Prefer async/await**: Use `async fn` for I/O and network operations. +- **Handle timeouts**: Use `tokio::time::timeout` for network operations. + +### Documentation + +- **Document public APIs**: Use `///` doc comments for public functions and types. +- **Explain "why"**: Document the reasoning behind complex logic, not just "what". +- **Markdown standards**: All markdown documentation must pass linter checks with no errors. + - Run markdown linters to catch formatting issues (MD040, MD060, MD022, MD032, etc.). + - Fix all linter errors before committing documentation changes. + - Use proper table formatting with spaces around pipes. + - Specify language for all fenced code blocks. + - Add blank lines around headings and lists. + +## Nostr and Mostro-Specific Guidelines + +### Event Kinds + +- **Use constants**: Always use constants from `mostro-core::prelude` (e.g., `NOSTR_ORDER_EVENT_KIND`). +- **Never hardcode**: Avoid hardcoding event kind numbers like 38383. + +### Message Handling + +- **Parse DMs consistently**: Use `parse_dm_events` for all DM parsing. +- **Support multiple message types**: Handle both GiftWrap (NIP-59) and PrivateDirectMessage (NIP-17). + +### Key Management + +- **Identity vs Trade Keys**: + - **Identity keys** (index 0): User's main identity, used for signing. + - **Trade keys** (index 1+): Ephemeral keys for each trade, ensuring privacy. + +## Code Organization Patterns + +### Module Structure + +```text +src/ +├── main.rs # Entry point +├── cli.rs # CLI definitions and Context +├── db.rs # Database models (User, Order) +├── cli/ # CLI command implementations +├── parser/ # Event parsing and display +└── util/ # Core utilities (events, messaging, net) +``` + +### Re-export Pattern + +Use `mod.rs` files to re-export commonly used items from submodules to keep imports clean. + +## Database Patterns + +- **User Model**: Use chainable setters and the `.save()` pattern. +- **Order Management**: Use `Order::new()` to handle both insertion and updates. + +## CLI Command Pattern + +All CLI commands follow a standard flow: + +1. Validate inputs. +2. Get required keys (Identity/Trade). +3. Build the Mostro message. +4. Send to Mostro (NIP-59). +5. Wait for response (Subscription). +6. Parse and display results. + +## Summary Checklist + +- [ ] Code is readable and well-named. +- [ ] No code duplication (DRY). +- [ ] Functions are under 300 lines. +- [ ] Errors are handled properly (`Result`, `?`). +- [ ] Event kinds use constants from `mostro-core`. +- [ ] Both GiftWrap and PrivateDirectMessage are supported. +- [ ] Public APIs are documented. +- [ ] All markdown documentation passes linter checks with no errors. +- [ ] Code passes `cargo fmt` and `cargo clippy`. +- [ ] Tests pass (`cargo test`). diff --git a/docs/CREATING_ORDERS.md b/docs/CREATING_ORDERS.md new file mode 100644 index 0000000..f840a90 --- /dev/null +++ b/docs/CREATING_ORDERS.md @@ -0,0 +1,73 @@ +# Creating Orders in Mostro CLI + +This document explains how to create new buy and sell orders using Mostro CLI. + +## Overview + +Creating an order involves: + +1. Specifying order parameters (type, amount, currency, etc.) +2. Building a Mostro message +3. Sending it to the Mostro coordinator via Nostr +4. Receiving confirmation +5. Waiting for someone to take the order + +## Order Types + +### Sell Order (Maker sells Bitcoin) + +User wants to **sell Bitcoin** for fiat currency. + +```bash +mostro-cli neworder -k sell -c USD -f 100 -a 50000 -m "PayPal" +``` + +### Buy Order (Maker buys Bitcoin) + +User wants to **buy Bitcoin** with fiat currency. + +```bash +mostro-cli neworder -k buy -c EUR -f 200 -m "Bank Transfer" -i lnbc... +``` + +## Order Parameters + +### Required Parameters + +| Parameter | Flag | Description | Example | +| ----------- | ------ | ------------- | --------- | +| Kind | `-k`, `--kind` | "buy" or "sell" | `sell` | +| Fiat Code | `-c`, `--fiat-code` | Currency code | `USD`, `EUR`, `ARS` | +| Fiat Amount | `-f`, `--fiat-amount` | Amount in fiat | `100` or `100-500` (range) | +| Payment Method | `-m`, `--payment-method` | How to pay | `"PayPal"`, `"Bank Transfer"` | + +### Optional Parameters + +| Parameter | Flag | Description | Default | +| ----------- | ------ | ------------- | --------- | +| Amount | `-a`, `--amount` | Bitcoin in sats | 0 (market price) | +| Premium | `-p`, `--premium` | Price premium % | 0 | +| Invoice | `-i`, `--invoice` | Lightning invoice (buy orders) | None | +| Expiration Days | `-e`, `--expiration-days` | Days until expired | 0 | + +## Order Examples + +### 1. Simple Sell Order (Market Price) + +```bash +mostro-cli neworder -k sell -c USD -f 100 -m "PayPal" +``` + +### 2. Range Order (Flexible Amount) + +```bash +mostro-cli neworder -k sell -c USD -f 100-500 -m "PayPal,Venmo" +``` + +### 3. Buy Order with Lightning Invoice + +```bash +mostro-cli neworder -k buy -c USD -f 50 -i lnbc500u1p3.... -m "Cash App" +``` + +For technical details on the code flow and message structures, see [ORDER_FLOW_TECHNICAL.md](./ORDER_FLOW_TECHNICAL.md). diff --git a/docs/DATABASE_SCHEMA.md b/docs/DATABASE_SCHEMA.md new file mode 100644 index 0000000..688b9c3 --- /dev/null +++ b/docs/DATABASE_SCHEMA.md @@ -0,0 +1,43 @@ +# Database Schema + +Mostro CLI uses a local SQLite database (`mcli.db`) to store user identity and trade history. + +## Table: `users` + +Stores the BIP-39 mnemonic and identity information. + +| Column | Type | Description | +| -------- | ------ | ------------- | +| `i0_pubkey` | `CHAR(64)` | Primary Key. The user's identity pubkey. | +| `mnemonic` | `TEXT` | The 12-word seed phrase. | +| `last_trade_index` | `INTEGER` | The last derived trade key index. | +| `created_at` | `INTEGER` | Timestamp of creation. | + +## Table: `orders` + +Stores details of orders created or taken by the user. + +| Column | Type | Description | +| -------- | ------ | ------------- | +| `id` | `TEXT` | Primary Key. Order UUID. | +| `kind` | `TEXT` | "buy" or "sell". | +| `status` | `TEXT` | Current status (pending, active, etc.). | +| `amount` | `INTEGER` | Satoshis amount. | +| `min_amount` | `INTEGER` | Minimum satoshis for range orders. | +| `max_amount` | `INTEGER` | Maximum satoshis for range orders. | +| `fiat_code` | `TEXT` | Fiat currency code (e.g., "USD"). | +| `fiat_amount` | `INTEGER` | Fiat units. | +| `payment_method` | `TEXT` | Payment method name. | +| `premium` | `INTEGER` | Premium percentage (basis points). | +| `trade_keys` | `TEXT` | Hex-encoded private key for this trade. | +| `counterparty_pubkey` | `TEXT` | Pubkey of the other party in the trade. | +| `is_mine` | `BOOLEAN` | True if the user created the order. | +| `buyer_invoice` | `TEXT` | Lightning invoice for the buyer. | +| `request_id` | `INTEGER` | Request ID for tracking messages. | +| `created_at` | `INTEGER` | Creation timestamp. | +| `expires_at` | `INTEGER` | Expiration timestamp. | + +## Implementation Reference + +- `src/db.rs`: Contains the `User` and `Order` structs and SQL queries. +- `src/util/storage.rs`: Helper functions for database interaction. diff --git a/docs/DISPUTE_MANAGEMENT.md b/docs/DISPUTE_MANAGEMENT.md new file mode 100644 index 0000000..b338121 --- /dev/null +++ b/docs/DISPUTE_MANAGEMENT.md @@ -0,0 +1,53 @@ +# Dispute Management + +This document covers how disputes are handled in Mostro CLI by both users and administrators. + +## User Dispute Flow + +When a trade goes wrong (e.g., fiat sent but Bitcoin not released), either party can initiate a dispute. + +### Initiate a Dispute + +```bash +mostro-cli dispute -o +``` + +Mostro changes the order status to `Dispute`. This prevents any further automated transitions and flags the trade for manual intervention. + +## Admin/Solver Flow + +Admins or designated solvers use special commands to resolve conflicts. These commands require the `ADMIN_NSEC` environment variable to be set. + +### 1. List Active Disputes + +```bash +mostro-cli listdisputes +``` + +### 2. Take a Dispute + +Before resolving, an admin must "take" the dispute to indicate they are handling it. + +```bash +mostro-cli admtakedispute -d +``` + +### 3. Settle (Pay Buyer) + +If the buyer proved they sent fiat, the admin settles the hold invoice to pay the buyer. + +```bash +mostro-cli admsettle -o +``` + +### 4. Cancel (Refund Seller) + +If the buyer failed to pay, the admin cancels the order to refund the locked Bitcoin to the seller. + +```bash +mostro-cli admcancel -o +``` + +## Security + +Admin commands are gated by public key verification on the Mostro coordinator side. The CLI must sign these messages with the private key corresponding to a registered admin pubkey. diff --git a/docs/KEY_IMPLEMENTATION.md b/docs/KEY_IMPLEMENTATION.md new file mode 100644 index 0000000..55108ec --- /dev/null +++ b/docs/KEY_IMPLEMENTATION.md @@ -0,0 +1,98 @@ +# Key Management Implementation + +This document provides technical details, code examples, and security practices for Mostro CLI key management. + +## Database Storage + +### User Table + +Stores the root secret and identity info. + +```sql +CREATE TABLE users ( + i0_pubkey char(64) PRIMARY KEY, -- Identity public key (hex) + mnemonic TEXT, -- BIP-39 mnemonic + last_trade_index INTEGER, -- Last used trade index + created_at INTEGER +); +``` + +### Order Table + +Stores trade-specific keys. + +```sql +CREATE TABLE orders ( + id TEXT PRIMARY KEY, + trade_keys TEXT, -- Private key for this trade (hex) + -- ... other fields +); +``` + +## Implementation Details + +### Deriving Identity Keys + +```rust +pub async fn get_identity_keys(pool: &SqlitePool) -> Result { + let user = User::get(pool).await?; + let account = NOSTR_ORDER_EVENT_KIND as u32; // 38383 + + Keys::from_mnemonic_advanced( + &user.mnemonic, + None, + Some(account), + Some(0), + Some(0) // Identity is always index 0 + ) +} +``` + +### Deriving Trade Keys + +```rust +pub async fn get_trade_keys(pool: &SqlitePool, index: i64) -> Result { + let user = User::get(pool).await?; + let account = NOSTR_ORDER_EVENT_KIND as u32; + + Keys::from_mnemonic_advanced( + &user.mnemonic, + None, + Some(account), + Some(0), + Some(index as u32) // Incremental index 1, 2, 3... + ) +} +``` + +## Security Best Practices + +### DO ✅ + +- **Use unique keys**: Always use `get_next_trade_keys()` for new orders. +- **Sign with identity**: Prove authenticity via NIP-59 seal (encrypted, never publicly revealed) while maintaining sender privacy. +- **Update indices**: Ensure `last_trade_index` is updated after successful creation. + +### DON'T ❌ + +- **Reuse keys**: Never use the same trade key for two different orders. +- **Author with identity**: Never set the `pubkey` of a public event to your identity key. +- **Lose mnemonic**: Keys cannot be recovered without the seed phrase. + +## Key Recovery + +If the local database is lost but the mnemonic is saved: + +1. **Identity**: Re-deriving index 0 restores the original `npub`. +2. **Trade History**: Re-deriving indices 1, 2, 3... restores access to trade messages. +3. **Session Sync**: Use `mostro-cli restore` to fetch active orders and their associated trade indices from the Mostro coordinator. + +## Troubleshooting + +### "Cannot decrypt message" + +Usually means the CLI is trying to use the wrong trade key. Ensure you are loading the key associated with the specific `order_id` from the database. + +### "Trade index mismatch" + +Occurs when the local database index is behind Mostro's records. Run `mostro-cli restore` to synchronize. diff --git a/docs/KEY_MANAGEMENT.md b/docs/KEY_MANAGEMENT.md new file mode 100644 index 0000000..e2d6781 --- /dev/null +++ b/docs/KEY_MANAGEMENT.md @@ -0,0 +1,97 @@ +# Key Management in Mostro CLI + +This document explains how Mostro CLI manages cryptographic keys for identity and trading. + +## Overview + +Mostro CLI uses **hierarchical deterministic (HD) keys** following the BIP-32/44 standards. This allows deriving multiple keys from a single mnemonic seed phrase while maintaining user privacy across trades. + +## Key Hierarchy + +```text +Master Seed (BIP-39 mnemonic - 12 words) + │ + └─ m/44'/1237'/38383'/0/ + ├─ 0 → Identity Key (permanent, for signing) + ├─ 1 → Trade Key #1 (ephemeral, for first trade) + ├─ 2 → Trade Key #2 (ephemeral, for second trade) + ├─ 3 → Trade Key #3 (ephemeral, for third trade) + └─ n → Trade Key #n (ephemeral, for nth trade) +``` + +### Path Components + +- **Purpose**: `44'` - BIP-44 standard +- **Coin Type**: `1237'` - Nostr coin type +- **Account**: `38383'` - Mostro order event kind (`NOSTR_ORDER_EVENT_KIND`) +- **Change**: `0` - External chain +- **Index**: `0` for identity, `1+` for trades + +## Identity Keys vs Trade Keys + +### Identity Keys (Index 0) + +**Purpose**: Represents the user's persistent identity. + +**Characteristics**: + +- **Permanent**: Never changes, created once at initialization. +- **Used for**: Signing messages to prove authenticity to Mostro. +- **Not used as**: Event author (for privacy). +- **Stored as**: Public key in database (`i0_pubkey`). + +### Trade Keys (Index 1, 2, 3, ...) + +**Purpose**: Ephemeral keys used for each individual trade to maintain privacy. + +**Characteristics**: + +- **Ephemeral**: New key for each trade. +- **Used for**: + - Authoring Nostr events (as the sender). + - Receiving encrypted messages. + - Trade-specific communications. +- **Privacy**: Counterparty cannot link trades together. +- **Stored**: Private key stored with each order in database. + +## Why Two Types of Keys? + +### Privacy Through Separation + +Without trade keys, all orders would be linked to one identity: + +```text +❌ Without trade keys (bad for privacy): +Order #1 → User's Identity Key (npub1abc...) +Order #2 → User's Identity Key (npub1abc...) +Order #3 → User's Identity Key (npub1abc...) + +Result: Anyone can see all orders from same user! +``` + +With trade keys, each order appears independent: + +```text +✅ With trade keys (good for privacy): +Order #1 → Trade Key #1 (npub1xyz...) + signed by identity +Order #2 → Trade Key #2 (npub1def...) + signed by identity +Order #3 → Trade Key #3 (npub1ghi...) + signed by identity + +Result: Orders appear unrelated! Privacy maintained. +``` + +### Authenticity Through Signing + +The identity key signs messages to prove they're from the real user. Mostro can verify: + +1. ✅ Message came from a legitimate user (signature verification). +2. ✅ User's reputation/history (identity-based). +3. ✅ Each trade maintains privacy (separate trade keys). + +## Key Usage Patterns + +- **New Order**: Generate new trade key using `get_next_trade_keys()`. +- **Existing Order**: Retrieve stored trade key from the local database. +- **Always Sign**: Use identity key for the `Message::sign` payload. + +For implementation details and code examples, see [KEY_IMPLEMENTATION.md](./KEY_IMPLEMENTATION.md). diff --git a/docs/MESSAGE_PROTOCOL.md b/docs/MESSAGE_PROTOCOL.md new file mode 100644 index 0000000..4041c28 --- /dev/null +++ b/docs/MESSAGE_PROTOCOL.md @@ -0,0 +1,37 @@ +# Message Protocol and Communication + +This document explains how Mostro CLI communicates privately and securely using Nostr protocols. + +## Encryption Standards + +Mostro CLI implements two major Nostr Improvement Proposals (NIPs) for communication: + +### 1. NIP-59: Gift Wrap + +Used for all communications between the CLI and the Mostro coordinator. + +- **Privacy**: Hides the sender's identity from relays. +- **Security**: Provides forward secrecy by using ephemeral keys for the wrapper. +- **Content**: The inner "rumor" event contains the actual Mostro message. + +### 2. NIP-17: Private Direct Messages + +Used for encrypted peer-to-peer communication within the Mostro ecosystem. + +- **Encryption**: Uses NIP-44 v2 encryption. +- **Conversation Keys**: Derived from the sender's secret key and recipient's public key. + +## Communication Flow + +### Request-Response Pattern + +1. **Subscribe**: The CLI subscribes to the trade key's pubkey on Nostr relays. +2. **Send**: The CLI sends a Gift Wrapped message to Mostro. +3. **Listen**: The CLI waits for an incoming Gift Wrapped event from Mostro. +4. **Unwrap**: The CLI decrypts the Gift Wrap and rumor to extract the message. + +## Code Reference + +- `src/util/messaging.rs`: Logic for wrapping and unwrapping events. +- `src/parser/dms.rs`: Logic for parsing decrypted Mostro messages. +- `src/util/events.rs`: Logic for creating Nostr filters. diff --git a/docs/ORDER_FLOW_TECHNICAL.md b/docs/ORDER_FLOW_TECHNICAL.md new file mode 100644 index 0000000..06d21de --- /dev/null +++ b/docs/ORDER_FLOW_TECHNICAL.md @@ -0,0 +1,63 @@ +# Order Creation: Technical Details + +This document covers the internal implementation, code flow, and Nostr message structures for creating orders. + +## Code Flow Implementation + +The core logic resides in `src/cli/new_order.rs`. Here is the step-by-step process: + +1. **Validation**: Check if the fiat currency is supported (if market price is used) via external APIs (e.g., Yadio). +2. **Key Derivation**: + - Fetch identity keys for signing messages. + - Generate **new** trade keys for this specific order using `User::get_next_trade_keys()`. +3. **Message Construction**: Build a `Message::Order` with `Action::NewOrder`. +4. **Encryption (NIP-59)**: Wrap the message in a Gift Wrap event to ensure the sender's identity is hidden from relays. +5. **Nostr Transmission**: Send the event to relays and subscribe to the trade key's pubkey to wait for Mostro's response. +6. **Local Persistence**: Save the order and its private trade key to the local SQLite database. + +### Core Message Structure +```json +{ + "order": { + "version": 1, + "id": "eb5740f6-e584-46c5-953a-29bc3eb818f0", + "request_id": 123456, + "trade_index": 5, + "action": "new-order", + "payload": { + "order": { + "id": "eb5740f6-e584-46c5-953a-29bc3eb818f0", + "kind": "sell", + "status": "pending", + "amount": 50000, + "fiat_code": "USD", + "fiat_amount": 100, + "payment_method": "PayPal" + } + } + } +} +``` + +## Nostr Event Types + +Mostro uses two types of events during creation: + +### 1. NIP-59 Gift Wrap (Private) +Used for the initial communication between the CLI and Mostro coordinator. It provides forward secrecy and protects sender metadata. + +### 2. NIP-33 Parameterized Replaceable Event (Public) +Mostro publishes the order publicly using `kind: 38383` (defined by `NOSTR_ORDER_EVENT_KIND` in `mostro-core`). This event contains all order details in tags: +- `d`: Order ID (unique identifier) +- `k`: Kind (buy/sell) +- `s`: Status (pending) +- `f`: Fiat code +- `amt`: Satoshis +- `fa`: Fiat amount +- `y`: "mostro" (application identifier) +- `z`: "order" (entity identifier) + +## Implementation Reference +- `src/cli/new_order.rs`: Main command handler. +- `src/util/messaging.rs`: Logic for Gift Wrap and encrypted DM sending. +- `src/db.rs`: Methods for saving order state and managing trade indices. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..5e25cb0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,26 @@ +# Mostro CLI Documentation + +Welcome to the Mostro CLI developer documentation. This directory contains detailed guides on the architecture, implementation, and protocols used in the project. + +## Document Hub + +### 🔑 Key Management +- [KEY_MANAGEMENT.md](./KEY_MANAGEMENT.md): Overview of identity vs trade keys. +- [KEY_IMPLEMENTATION.md](./KEY_IMPLEMENTATION.md): Technical details on derivation and storage. + +### 📈 Order Handling +- [CREATING_ORDERS.md](./CREATING_ORDERS.md): How to create buy/sell orders. +- [ORDER_FLOW_TECHNICAL.md](./ORDER_FLOW_TECHNICAL.md): Technical flow and message structures. +- [TAKING_ORDERS.md](./TAKING_ORDERS.md): How to take existing orders. +- [TRADE_LIFECYCLE.md](./TRADE_LIFECYCLE.md): Step-by-step trade flow and status transitions. + +### ⚖️ Disputes and Admin +- [DISPUTE_MANAGEMENT.md](./DISPUTE_MANAGEMENT.md): User and admin dispute resolution. + +### ⚙️ Technical Core +- [MESSAGE_PROTOCOL.md](./MESSAGE_PROTOCOL.md): NIP-59/17 and communication logic. +- [DATABASE_SCHEMA.md](./DATABASE_SCHEMA.md): SQLite table structures. +- [CODING_STANDARDS.md](./CODING_STANDARDS.md): Project coding guidelines. + +--- +*For general installation and usage instructions, see the root [README.md](../README.md).* diff --git a/docs/TAKING_ORDERS.md b/docs/TAKING_ORDERS.md new file mode 100644 index 0000000..5cb3830 --- /dev/null +++ b/docs/TAKING_ORDERS.md @@ -0,0 +1,58 @@ +# Taking Orders in Mostro CLI + +This document explains how to take existing orders from the Mostro order book. + +## Overview + +Taking an order means accepting someone else's offer to buy or sell Bitcoin. When you take an order: +1. You become the **taker** (they are the **maker**). +2. The order moves from `Pending` to `Active`. +3. You and the maker are matched for a trade. +4. The trade process begins based on the order type. + +## Order Types from Taker's Perspective + +### Taking a Sell Order (TakeSell) +**Scenario**: Someone wants to sell Bitcoin, you want to buy it. +```bash +mostro-cli takesell -o -i -a +``` +- **Your role**: Buyer. +- **You provide**: Lightning invoice to receive Bitcoin. +- **You send**: Fiat payment to the seller. + +### Taking a Buy Order (TakeBuy) +**Scenario**: Someone wants to buy Bitcoin, you want to sell it. +```bash +mostro-cli takebuy -o -a +``` +- **Your role**: Seller. +- **You provide**: Bitcoin by paying a hold invoice. +- **You receive**: Fiat payment from the buyer. + +## Commands + +### List Available Orders +Before taking an order, find one that suits your needs: +```bash +mostro-cli listorders --status pending +``` + +### Take Sell Order (Buy Bitcoin) +```bash +mostro-cli takesell -o eb5740f6-e584-46c5-953a-29bc3eb818f0 -i lnbc500u1... +``` + +### Take Buy Order (Sell Bitcoin) +```bash +mostro-cli takebuy -o eb5740f6-e584-46c5-953a-29bc3eb818f0 +``` + +## Implementation Highlights +The take logic is handled in `src/cli/take_order.rs`. It follows this pattern: +1. Derive **new** trade keys for the taker. +2. Send a `TakeSell` or `TakeBuy` message to Mostro. +3. Wait for Mostro's confirmation (and hold invoice if selling). +4. Save the order state locally. + +For detailed trade flows and status explanations, see [TRADE_LIFECYCLE.md](./TRADE_LIFECYCLE.md). diff --git a/docs/TRADE_LIFECYCLE.md b/docs/TRADE_LIFECYCLE.md new file mode 100644 index 0000000..820f3c6 --- /dev/null +++ b/docs/TRADE_LIFECYCLE.md @@ -0,0 +1,62 @@ +# Trade Lifecycle and Status Flow + +This document describes the complete lifecycle of a Mostro trade, from matching to completion. + +## Order Status Transitions + +``` +┌─────────────┐ +│ PENDING │ ← Order created by maker +└──────┬──────┘ + │ Someone takes the order + ↓ +┌─────────────┐ +│ ACTIVE │ ← Maker and taker matched +└──────┬──────┘ + │ Seller pays hold invoice + ↓ +┌─────────────┐ +│ WAITING_ │ +│ BUYER_ │ ← Waiting for buyer's invoice (if not provided) +│ INVOICE │ +└──────┬──────┘ + │ Buyer adds invoice + ↓ +┌─────────────┐ +│ WAITING_ │ +│ PAYMENT │ ← Waiting for fiat payment +└──────┬──────┘ + │ Buyer sends fiat + ↓ +┌─────────────┐ +│ FIAT_SENT │ ← Fiat payment claimed by buyer +└──────┬──────┘ + │ Seller confirms & releases + ↓ +┌─────────────┐ +│ SUCCESS │ ← Trade completed! +└─────────────┘ +``` + +## Detailed Example: Alice Buys from Bob + +1. **Take**: Alice takes Bob's sell order using `takesell`. +2. **Lock**: Bob receives a `pay-invoice` message. He pays the hold invoice to lock his Bitcoin. +3. **Pay**: Alice sees the status change to `WaitingPayment`. She sends fiat to Bob via the agreed method (e.g., PayPal). +4. **Confirm**: Alice runs `mostro-cli fiatsent`. +5. **Release**: Bob confirms the fiat arrived and runs `mostro-cli release`. +6. **Done**: Alice receives her Bitcoin automatically. + +## Error States + +- **Canceled**: One party canceled the order (only possible in early stages). +- **Dispute**: A party disagreed with the trade progress. See [DISPUTE_MANAGEMENT.md](./DISPUTE_MANAGEMENT.md). +- **Expired**: The order wasn't taken or advanced within the required timeframe. + +## Common Troubleshooting + +### "Invoice already used" +Generate a fresh invoice in your wallet. Mostro requires a unique payment hash for every trade. + +### "No response from Mostro" +Check your relay connections or verify if the Mostro coordinator is online. Use `--verbose` to see network logs. diff --git a/src/db.rs b/src/db.rs index 1bdcc0e..ed82479 100644 --- a/src/db.rs +++ b/src/db.rs @@ -138,7 +138,7 @@ pub struct User { impl User { pub async fn new(mnemonic: String, pool: &SqlitePool) -> Result { let mut user = User::default(); - let account = NOSTR_REPLACEABLE_EVENT_KIND as u32; + let account = NOSTR_ORDER_EVENT_KIND as u32; let i0_keys = Keys::from_mnemonic_advanced(&mnemonic, None, Some(account), Some(0), Some(0))?; user.i0_pubkey = i0_keys.public_key().to_string(); @@ -216,7 +216,7 @@ impl User { pub async fn get_identity_keys(pool: &SqlitePool) -> Result { let user = User::get(pool).await?; - let account = NOSTR_REPLACEABLE_EVENT_KIND as u32; + let account = NOSTR_ORDER_EVENT_KIND as u32; let keys = Keys::from_mnemonic_advanced(&user.mnemonic, None, Some(account), Some(0), Some(0))?; @@ -235,7 +235,7 @@ impl User { return Err(anyhow::anyhow!("Trade index cannot be negative")); } let user = User::get(pool).await?; - let account = NOSTR_REPLACEABLE_EVENT_KIND as u32; + let account = NOSTR_ORDER_EVENT_KIND as u32; let keys = Keys::from_mnemonic_advanced( &user.mnemonic, None, diff --git a/src/util/events.rs b/src/util/events.rs index e31587b..fbdac59 100644 --- a/src/util/events.rs +++ b/src/util/events.rs @@ -19,7 +19,7 @@ fn create_fake_timestamp() -> Result { Ok(Timestamp::from(fake_since_time)) } -fn create_seven_days_filter(letter: Alphabet, value: String, pubkey: PublicKey) -> Result { +fn create_seven_days_filter(kind: u16, pubkey: PublicKey) -> Result { let since_time = chrono::Utc::now() .checked_sub_signed(chrono::Duration::days(7)) .ok_or(anyhow::anyhow!("Failed to get since days ago"))? @@ -29,8 +29,7 @@ fn create_seven_days_filter(letter: Alphabet, value: String, pubkey: PublicKey) .author(pubkey) .limit(50) .since(timestamp) - .custom_tag(SingleLetterTag::lowercase(letter), value) - .kind(nostr_sdk::Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND))) + .kind(nostr_sdk::Kind::Custom(kind))) } pub fn create_filter( @@ -39,8 +38,8 @@ pub fn create_filter( since: Option<&i64>, ) -> Result { match list_kind { - ListKind::Orders => create_seven_days_filter(Alphabet::Z, "order".to_string(), pubkey), - ListKind::Disputes => create_seven_days_filter(Alphabet::Z, "dispute".to_string(), pubkey), + ListKind::Orders => create_seven_days_filter(NOSTR_ORDER_EVENT_KIND, pubkey), + ListKind::Disputes => create_seven_days_filter(NOSTR_DISPUTE_EVENT_KIND, pubkey), ListKind::DirectMessagesAdmin | ListKind::DirectMessagesUser => { let fake_timestamp = create_fake_timestamp()?; Ok(Filter::new()