Invoice watch
You are creating an XCH invoice and watching the user's address for the matching deposit. When the deposit lands, you emit a paid receipt and notify the user. Two-phase skill: issue (first run) and check (subsequent runs). Read-only — no wallet, no signing.
This skill follows the conventions in this repo's SKILLS.md. Read that if anything below is unclear.
Required capabilities
- chia-explorer (
mcp__chia-explorer__*) — mandatory. - A notification MCP — pick whichever is installed. Email is the natural channel since the user usually emails the invoice to the payer.
- Bash + Read + Write — for invoice and receipt files.
Resolve inputs
You need: $address, $amount_xch, $memo, $payer, $channel.
$address— the receiving Chia address (yours). Required.$amount_xch— XCH amount due. Required. Accept either decimal XCH or mojos with amojos:prefix.$memo— short description ("Design work, May 2026"). Optional.$payer— name / handle / email of who's paying. Optional but recommended for the receipt.$channel— notification channel for the "paid" alert. Defaultauto.
Resolve: arguments → context → ask user. If $address or $amount_xch is missing after asking, emit STATUS: aborted, REASON: missing required inputs.
Validate $address (xch1/txch1 prefix). Convert $amount_xch to mojos for storage.
State and files
Per-invoice state file: ~/.chia-skills/state/invoice-watch__<sha1(address+amount+memo+created_iso)>.json.
Shape:
{
"invoice_id": "INV-2026-05-19-001",
"address": "xch1...",
"amount_mojos": 2500000000000,
"amount_xch": 2.5,
"memo": "Design work, May 2026",
"payer": "alice@example.com",
"created_iso": "2026-05-19T14:33:00Z",
"first_seen_height": 6532109,
"status": "open" | "paid" | "overpaid" | "expired",
"paid_at_iso": null,
"paid_coin_id": null,
"paid_height": null,
"paid_block_hash": null
}
Invoice files: ~/.chia-skills/invoices/<invoice_id>.md (the bill the user emails out).
Receipt files: ~/.chia-skills/receipts/<invoice_id>.md (the paid record, emitted after payment).
Issue phase (first run)
If no state file matches, this is the issue phase.
- Generate
invoice_idlikeINV-YYYY-MM-DD-NNN(sequential per day; check the invoices dir for collisions). - Capture the current chain peak as
first_seen_height. - Write the state file.
- Render the invoice markdown:
# Invoice INV-2026-05-19-001
**From:** (your name / business — pull from `$payer` context or ask)
**To:** alice@example.com
**Amount due:** 2.500 XCH (2,500,000,000,000 mojos)
**Pay to:** xch1yxqsmyuyjdlgxw4sqjg4vqlqv5ms2qzex00586nu643jqemmarwslh08yl
**Memo:** Design work, May 2026
**Issued:** 2026-05-19
The exact amount must land at the address above. Partial or over-payments
will be flagged on the receipt.
This invoice is being watched on-chain — a paid receipt will be issued
automatically when the deposit lands.
- Save to
~/.chia-skills/invoices/<invoice_id>.md. - Tell the user inline: "Invoice issued. Send this file to . I'll watch for the deposit."
- Emit
STATUS: success,ARTIFACT: <invoice file path>,PHASE: issued.
Check phase (subsequent runs)
If state file exists with status: open:
- Look up coin records created at
$addresssincefirst_seen_height. Apply the standard chia-explorer filtering. - For each coin, compare amount to
$amount_mojos:- Exact match →
paid. Recordpaid_coin_id,paid_height,paid_at_iso. Stop. - Greater than expected →
overpaid. Same fields. Still treat as paid but flag in the receipt. - Less than expected → ignore (the payer might be sending in multiple chunks; don't conclude anything yet).
- Exact match →
- If multiple matching coins land in a single check, treat the first one as the payment.
- If
now - created_iso > 30 daysand still unpaid, setstatus: expired. (Configurable later.)
If status flips to paid or overpaid:
- Render the receipt:
# Receipt for INV-2026-05-19-001
**Paid:** 2026-05-20 09:14 UTC
**From:** alice@example.com
**Amount:** 2.500 XCH (matches invoice)
**Block height:** 6,532,210
**Coin id:** 0xabc...123
Original memo: Design work, May 2026
For overpaid, add a line: Overpayment: +0.050 XCH — reach out to the payer if a refund is appropriate.
- Save to
~/.chia-skills/receipts/<invoice_id>.md. - Send a notification through the chosen channel: subject
Invoice <id> paid, body = receipt. - Update state to
paid/overpaid, atomic write. - This watch is terminal — emit
TERMINAL: truein the output.
If still open, emit STATUS: no_change, PHASE: watching.
Scheduling
This skill is one-shot. The issue phase runs once; the check phase is what gets scheduled.
scheduleskill / cron — every 5–15 minutes once the invoice is issued. Stop scheduling when the run emitsTERMINAL: true./loop— useful when the user is actively waiting for a payment.
Hard rules
- Read-only. Don't refund overpayments — that needs a wallet. Just flag them.
- Exact-amount match by default. Overpayments are flagged, underpayments ignored. Don't try to total partial payments without explicit user opt-in.
- No wallet integration. The invoice file is markdown the user emails out. No QR codes (require external libs); the address is enough.
- Expire long-unpaid invoices so the schedule doesn't run forever on dead invoices.
Recovery patterns
- Address didn't see any coins.
STATUS: no_change,PHASE: watching. - chia-explorer call fails. Don't update state.
STATUS: failed. - State file says
paidalready. EmitSTATUS: no_change,TERMINAL: true. Tell the user the schedule is stale.
Output
STATUS: success | no_change | partial | failed | aborted
PHASE: issued | watching | paid | overpaid | expired
INVOICE_ID: INV-YYYY-MM-DD-NNN
AMOUNT_XCH: <decimal>
ADDRESS: <full address>
PAID_COIN_ID: <0x...> | none
PAID_HEIGHT: <int> | none
TERMINAL: true | false
ARTIFACT: <absolute path to invoice or receipt file>
REASON: <one line, only when STATUS is failed, partial, or aborted>