Skip to content

feat(reserves): support timelocked reserve refunds and automatic settlement (#58) - #69

Open
ocakciomursel-pixel wants to merge 1 commit into
BetterMoneyLabs:masterfrom
ocakciomursel-pixel:feat/support-refunds-58
Open

ocakciomursel-pixel wants to merge 1 commit into
BetterMoneyLabs:masterfrom
ocakciomursel-pixel:feat/support-refunds-58

Conversation

@ocakciomursel-pixel

Copy link
Copy Markdown

Resolves #58

Summary

Implements end-to-end timelocked reserve refunds against the pinned contracts/onchain/reserve.es specification (ac99a4f): API endpoints to initiate, cancel, and manually complete refunds, automatic background settlement once the 14,400-block (~20-day) notification period elapses, and query endpoints for past and current refund history and live timelock status.

Key Changes

  • Off-chain Reserve Box Parsing & Transaction Builders (chaincash_offchain):

    • Fixed ReserveBoxSpec::refund_height to decode R5 as SType::SInt (Option<i32>, matching selfOut.R5[Int] in reserve.es) instead of SLong, preventing reserves with active refunds from failing box parsing during scans, and added refund_amount: Option<i64> from R6 (SType::SLong).
    • Added initiate_refund_transaction (getVar[Byte](0) = 20, action == 2), supporting either an explicit amount or None (defaulting to full refundable balance above BoxValue::SAFE_USER_MIN).
    • Added cancel_refund_transaction (getVar[Byte](0) = 30, action == 3), rebuilding the reserve box candidate without R5 or R6 so non-mandatory registers remain contiguous.
    • Added complete_refund_transaction (getVar[Byte](0) = 40, action == 4), enforcing (R5 + 14400) <= current_height and capping the withdrawal at min(R6, value - SAFE_USER_MIN) so reserves partially redeemed during the 20-day waiting window still settle cleanly while keeping the reserve NFT alive.
  • Persistent Refund History & Scanner Reconciliation (chaincash_store):

    • Added migration 2026-10-30-000000_create_refunds creating the refunds table (pending, executing, completed, cancelled, failed) with a partial unique index (refunds_active_reserve_idx) enforcing at most one active refund per reserve while preserving full historical records across spent reserve boxes.
    • Implemented RefundRepository with atomic Pending -> Executing transitions (try_begin_execution), startup crash recovery (recover_interrupted_executions), external on-chain initiation/settlement reconciliation (sync_scanned_reserve), and mempool-lag protection (is_box_already_settled + unconfirmed replacement box preservation in reserve_scanner) so confirmed-only scans do not resurrect or double-spend recently settled boxes before block confirmation.
  • Transaction Service & Automatic Settlement Worker (chaincash_services, chaincash_app):

    • Added TransactionService::{initiate_refund, cancel_refund, complete_refund}.
    • Added refund_worker::start_refund_worker (wired into ChainCashApp::run), which polls active refunds owned by wallet public keys, automatically settles refunds once unlock_height <= current_height, retries transient node/wallet errors on subsequent ticks, and transitions reserves fully drained to SAFE_USER_MIN into failed with a diagnostic error_message rather than retrying indefinitely.
  • HTTP REST Endpoints (chaincash_server):

    • POST /api/v1/reserves/refund — initiate a refund ({ "reserve_id", "amount"?: number }).
    • POST /api/v1/reserves/refund/cancel — cancel an active pending refund ({ "reserve_id" }).
    • POST /api/v1/reserves/refund/complete — manually complete an unlocked refund ({ "reserve_id" }).
    • GET /api/v1/reserves/refunds — list past and current refunds with computed unlock_height, blocks_remaining, and ready_to_complete (supports ?reserve_id= and ?status= filters).
    • GET /api/v1/reserves/refunds/:id — retrieve a specific refund record by ID.
    • Mapped domain errors to appropriate HTTP status codes (400 Bad Request, 404 Not Found, 409 Conflict, 500 Internal Server Error).

Verification

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features (zero warnings in new/modified code)
  • cargo test --workspace (38/38 passing, including 14 new unit/integration tests signing every refund action against the compiled reserve.es contract, testing SQLite store concurrency & mempool-lag reconciliation, worker retry/drain behavior, and Axum router endpoints)

…lement (BetterMoneyLabs#58)

- Fix ReserveBoxSpec R5 register parsing (Int/i32 per reserve.es) and parse R6 (Long/i64 refund limit)
- Implement initiate_refund_transaction (action 2), cancel_refund_transaction (action 3), and complete_refund_transaction (action 4) with 14,400-block timelock and balance cap
- Add SQLite refunds table, migration, and RefundRepository with atomic execution locking and scanner/mempool reconciliation
- Add background refund settlement worker in chaincash_services and wire into ChainCashApp::run
- Add REST endpoints POST /api/v1/reserves/refund, /refund/cancel, /refund/complete, GET /api/v1/reserves/refunds, and GET /api/v1/reserves/refunds/:id
- Add contract-verified offchain, store, worker, and HTTP API tests and update README.md and docs/MODEL.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support refunds

1 participant