{"skill":{"slug":"orange-wallet","displayName":"Orange Wallet","summary":"Command-line Lightning wallet for AI agents with graduated custody, enabling instant trusted payments and self-custodial channels via JSON shell commands.","description":"# orange — Lightning Wallet for AI Agents\n\n> [!WARNING]\n> Alpha software. This project was largely vibe-coded and likely contains flaws. Do not use it for large sums of money.\n\n`orange` is a CLI for the Orange SDK, a graduated-custody Lightning wallet. It gives any AI agent its own Lightning wallet through simple shell commands that output JSON.\n\nGraduated custody means funds start in a trusted Spark backend for instant, low-cost transactions, then automatically move to self-custodial Lightning channels as the balance grows.\n\n## Setup\n\n### Prerequisites\n\n**Rust** — Install via [rustup](https://rustup.rs/):\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n**protoc** (Protocol Buffers compiler) — Required by the Spark SDK dependency:\n\n```sh\n# Ubuntu/Debian\nsudo apt install -y protobuf-compiler\n\n# macOS\nbrew install protobuf\n\n# Arch\nsudo pacman -S protobuf\n```\n\n### Install\n\n```sh\ngit clone https://github.com/benthecarman/orange-skill.git\ncd orange-skill\ncargo install --path .\n```\n\n### Configure\n\n```sh\ncp config.toml.example config.toml\n```\n\nEdit `config.toml`. You need:\n- A **storage path** — where wallet data (SQLite DB, seed, logs) will be stored\n- A **chain source** — Esplora, Electrum, or Bitcoin Core RPC\n- An **LSP** — Lightning Service Provider for channel management\n\nA wallet seed is generated automatically on first run and saved to `{storage_path}/seed`. Back up this file — it's the only way to recover your wallet.\n\nThe defaults in `config.toml.example` are configured for Bitcoin mainnet:\n\n```toml\nnetwork = \"bitcoin\"\nstorage_path = \"~/.orange\"\n\n[chain_source]\ntype = \"esplora\"\nurl = \"https://blockstream.info/api\"\n\n[lsp]\naddress = \"69.59.18.144:9735\"\nnode_id = \"021deaa26ce6bb7cc63bd30e83a2bba1c0368269fa3bb9b616a24f40d941ac7d32\"\n\n[spark]\nsync_interval_secs = 60\nprefer_spark_over_lightning = false\n# lnurl_domain = \"breez.tips\"\n```\n\nPass the config path with `--config` (defaults to `config.toml` in the current directory):\n\n```\norange --config /path/to/config.toml <command>\n```\n\n### Start the daemon and receive your first payment\n\n```sh\n# 1. Start the daemon (with or without webhooks)\norange daemon --webhook https://your-app.example.com/payments\n\n# 2. In another terminal, generate an invoice\norange receive --amount 1000\n# Share the returned invoice or full_uri with the sender\n\n# 3. When the payment arrives, your webhook receives a payment_received event\n#    Or poll it manually:\norange get-event        # see the event\norange event-handled    # acknowledge it\n\n# 4. Check your balance\norange balance\n```\n\n## Running the Daemon\n\nThe daemon is the primary way to run orange. It keeps the wallet online and connected to the Lightning network.\n\n```\norange daemon [--webhook <url> ...]\n```\n\n### With webhooks (push model)\n\nWhen webhooks are configured, the daemon POSTs each event as JSON to every webhook URL in parallel and automatically marks events as handled.\n\n```sh\norange daemon \\\n  --webhook \"https://your-app.example.com/payments|your-secret-token\" \\\n  --webhook \"https://chat.example.com/notify\"\n```\n\nEach `--webhook` value is a URL, optionally followed by `|token`. When a token is provided, it's sent as `Authorization: Bearer <token>` in the POST header so your endpoint can verify requests are authentic. Each webhook can have its own token (or none).\n\nYour webhook endpoint should:\n\n- Accept `POST` requests with `Content-Type: application/json`\n- Verify the `Authorization: Bearer <token>` header if a token is configured\n- Return any 2xx status code to acknowledge receipt\n- Respond quickly — the daemon fires webhooks in parallel and won't block on slow responses, but non-2xx status codes and connection errors are logged to stderr\n- Transient failures (connection errors and 5xx responses) are retried up to 3 times with exponential backoff (1s, 2s, 4s). Client errors (4xx) are not retried. If all retries are exhausted, the event is still marked as handled and will not be re-delivered\n\nFor a complete example of building a webstore that accepts Lightning payments using webhooks and LNURL-pay, see [docs/agent-payment-flows.md](docs/agent-payment-flows.md).\n\n### Without webhooks (pull model)\n\nWhen no webhooks are configured, the daemon keeps the wallet online but does not auto-acknowledge events. Events queue up in the SDK's persistent event queue and are consumed via `get-event` and `event-handled` from a separate terminal.\n\n```sh\n# Terminal 1: keep wallet online\norange daemon\n\n# Terminal 2: pull events\norange get-event        # returns next pending event or null\norange event-handled    # ack it, advancing the queue\n```\n\n### Event Types\n\nEvery event includes a `type` and `timestamp` field. Example payload:\n\n```json\n{\n  \"type\": \"payment_received\",\n  \"timestamp\": 1700000000,\n  \"payment_id\": \"SC-abcd1234...\",\n  \"payment_hash\": \"...\",\n  \"amount_msat\": 50000000,\n  \"amount_sats\": 50000,\n  \"custom_records_count\": 0,\n  \"lsp_fee_msats\": null\n}\n```\n\n| Type | Description | Key Fields |\n|---|---|---|\n| `payment_successful` | Outgoing payment completed | `payment_id`, `payment_hash`, `payment_preimage`, `fee_paid_msat` |\n| `payment_failed` | Outgoing payment failed | `payment_id`, `payment_hash`, `reason` |\n| `payment_received` | Incoming Lightning payment | `payment_id`, `payment_hash`, `amount_msat`, `amount_sats`, `lsp_fee_msats` |\n| `onchain_payment_received` | Incoming on-chain payment | `payment_id`, `txid`, `amount_sat`, `status` |\n| `channel_opened` | Channel is ready | `channel_id`, `counterparty_node_id`, `funding_txo` |\n| `channel_closed` | Channel was closed | `channel_id`, `counterparty_node_id`, `reason` |\n| `rebalance_initiated` | Trusted-to-Lightning rebalance started | `trigger_payment_id`, `amount_msat` |\n| `rebalance_successful` | Rebalance completed | `trigger_payment_id`, `amount_msat`, `fee_msat` |\n| `splice_pending` | Splice initiated, waiting to confirm | `channel_id`, `counterparty_node_id`, `new_funding_txo` |\n\n## Event Commands\n\n### get-event\n\nGet the next pending event from the wallet's event queue. Returns the event without acknowledging it — call `event-handled` after processing.\n\n```\norange get-event\n```\n\nReturns the event JSON if one is pending:\n\n```json\n{\n  \"type\": \"payment_received\",\n  \"timestamp\": 1700000000,\n  \"payment_id\": \"SC-abcd1234...\",\n  ...\n}\n```\n\nReturns `null` if the queue is empty:\n\n```json\n{\n  \"event\": null\n}\n```\n\n### event-handled\n\nMark the current event as handled, removing it from the queue and advancing to the next event.\n\n```\norange event-handled\n```\n\n```json\n{\n  \"ok\": true\n}\n```\n\nCall this after you have fully processed the event returned by `get-event`. Do not call this if `get-event` returned `null`.\n\n## One-Shot Commands\n\nThese commands perform a single action and exit. They can be run while the daemon is active to interact with the wallet (send payments, check balance, generate invoices, etc.).\n\n### balance\n\nGet wallet balance in satoshis.\n\n```\norange balance\n```\n\n```json\n{\n  \"trusted_sats\": 50000,\n  \"lightning_sats\": 100000,\n  \"pending_sats\": 0,\n  \"available_sats\": 150000\n}\n```\n\n- `trusted_sats` — balance held in Spark trusted backend\n- `lightning_sats` — balance in Lightning channels (self-custodial)\n- `pending_sats` — in-flight or unconfirmed balance\n- `available_sats` — total spendable (trusted + lightning)\n\n### receive\n\nGenerate a single-use BIP21 URI with a BOLT11 invoice for receiving payment.\n\n```\norange receive [--amount <sats>]\n```\n\n```json\n{\n  \"invoice\": \"lnbc500u1p...\",\n  \"address\": \"bc1q...\",\n  \"amount_sats\": 50000,\n  \"full_uri\": \"bitcoin:bc1q...?lightning=lnbc500u1p...\",\n  \"from_trusted\": false\n}\n```\n\n- `--amount` — optional amount in satoshis\n- `address` — may be `null` if no on-chain address is available\n- `from_trusted` — whether this will be received into Spark trusted balance\n\n### receive-offer\n\nGet a reusable BOLT12 offer for receiving payments. Can be shared and paid multiple times.\n\n```\norange receive-offer\n```\n\n```json\n{\n  \"offer\": \"lno1q...\"\n}\n```\n\n### send\n\nSend a payment to a lightning invoice, on-chain address, or BOLT12 offer.\n\n```\norange send <payment> [--amount <sats>]\n```\n\n- `payment` — BOLT11 invoice, BOLT12 offer, on-chain address, or BIP21 URI\n- `--amount` — required for on-chain addresses and amountless offers\n\n```json\n{\n  \"payment_id\": \"abcd1234...\",\n  \"amount_sats\": 1000,\n  \"status\": \"initiated\"\n}\n```\n\n### parse\n\nParse a payment string and return its details.\n\n```\norange parse <payment>\n```\n\n```json\n{\n  \"parsed\": \"PaymentInstructions { ... }\"\n}\n```\n\n### transactions\n\nList transaction history.\n\n```\norange transactions\n```\n\n```json\n{\n  \"count\": 2,\n  \"transactions\": [\n    {\n      \"id\": \"txid123...\",\n      \"status\": \"Completed\",\n      \"outbound\": false,\n      \"amount_sats\": 50000,\n      \"fee_sats\": 100,\n      \"payment_type\": \"Lightning\",\n      \"timestamp\": 1700000000\n    }\n  ]\n}\n```\n\n### channels\n\nList lightning channels.\n\n```\norange channels\n```\n\n```json\n{\n  \"count\": 1,\n  \"channels\": [\n    {\n      \"channel_id\": \"ch123...\",\n      \"counterparty_node_id\": \"02abc...\",\n      \"funding_txo\": \"txid:0\",\n      \"is_channel_ready\": true,\n      \"is_usable\": true,\n      \"inbound_capacity_sats\": 500000,\n      \"outbound_capacity_sats\": 100000,\n      \"channel_value_sats\": 600000\n    }\n  ]\n}\n```\n\n### info\n\nGet wallet and node information.\n\n```\norange info\n```\n\n```json\n{\n  \"node_id\": \"02def...\",\n  \"lsp_connected\": true,\n  \"tunables\": {\n    \"trusted_balance_limit_sats\": 100000,\n    \"rebalance_min_sats\": 10000,\n    \"onchain_receive_threshold_sats\": 50000,\n    \"enable_amountless_receive_on_chain\": false\n  }\n}\n```\n\n### estimate-fee\n\nEstimate the fee for a payment.\n\n```\norange estimate-fee <payment>\n```\n\n```json\n{\n  \"estimated_fee_sats\": 150\n}\n```\n\n### lightning-address\n\nGet the wallet's lightning address, if one has been registered.\n\n```\norange lightning-address\n```\n\n```json\n{\n  \"lightning_address\": \"alice@breez.tips\"\n}\n```\n\nReturns `null` if no lightning address is registered.\n\n### register-lightning-address\n\nRegister a lightning address for this wallet. The address will be `<name>@<lnurl_domain>` (default domain: `breez.tips`).\n\n```\norange register-lightning-address <name>\n```\n\n```json\n{\n  \"registered\": true,\n  \"lightning_address\": \"alice@breez.tips\"\n}\n```\n\nOnce registered, anyone can pay you using the lightning address. The domain is configured via `lnurl_domain` in the `[spark]` config section.\n\n## Error Format\n\nAll errors are returned as JSON to stdout with a non-zero exit code:\n\n```json\n{\n  \"error\": \"Failed to get balance: ...\"\n}\n```\n","tags":{"latest":"0.1.0"},"stats":{"comments":0,"downloads":625,"installsAllTime":0,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1773714276998,"updatedAt":1779078340872},"latestVersion":{"version":"0.1.0","createdAt":1773714276998,"changelog":"Initial release — basic self-custodial lightning wallet","license":"MIT-0"},"metadata":null,"owner":{"handle":"benthecarman","userId":"s17cevs94yrrf8qyasdfh4wfvx885vwk","displayName":"benthecarman","image":"https://avatars.githubusercontent.com/u/15256660?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780089922817}}