Repository navigation
feat(reserves): support timelocked reserve refunds and automatic settlement (#58) - #69
Open
ocakciomursel-pixel wants to merge 1 commit into
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #58
Summary
Implements end-to-end timelocked reserve refunds against the pinned
contracts/onchain/reserve.esspecification (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):ReserveBoxSpec::refund_heightto decodeR5asSType::SInt(Option<i32>, matchingselfOut.R5[Int]inreserve.es) instead ofSLong, preventing reserves with active refunds from failing box parsing during scans, and addedrefund_amount: Option<i64>fromR6(SType::SLong).initiate_refund_transaction(getVar[Byte](0) = 20,action == 2), supporting either an explicitamountorNone(defaulting to full refundable balance aboveBoxValue::SAFE_USER_MIN).cancel_refund_transaction(getVar[Byte](0) = 30,action == 3), rebuilding the reserve box candidate withoutR5orR6so non-mandatory registers remain contiguous.complete_refund_transaction(getVar[Byte](0) = 40,action == 4), enforcing(R5 + 14400) <= current_heightand capping the withdrawal atmin(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):2026-10-30-000000_create_refundscreating therefundstable (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.RefundRepositorywith atomicPending -> Executingtransitions (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 inreserve_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):TransactionService::{initiate_refund, cancel_refund, complete_refund}.refund_worker::start_refund_worker(wired intoChainCashApp::run), which polls active refunds owned by wallet public keys, automatically settles refunds onceunlock_height <= current_height, retries transient node/wallet errors on subsequent ticks, and transitions reserves fully drained toSAFE_USER_MINintofailedwith a diagnosticerror_messagerather 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 computedunlock_height,blocks_remaining, andready_to_complete(supports?reserve_id=and?status=filters).GET /api/v1/reserves/refunds/:id— retrieve a specific refund record by ID.400 Bad Request,404 Not Found,409 Conflict,500 Internal Server Error).Verification
cargo fmt --all -- --checkcargo 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 compiledreserve.escontract, testing SQLite store concurrency & mempool-lag reconciliation, worker retry/drain behavior, and Axum router endpoints)