Skip to content

Commit 12dde78

Browse files
committed
Merge PR #2250: docs: add invoice lock time limits guide (admin merge, -X theirs)
2 parents 409bcf2 + 4d72d02 commit 12dde78

3 files changed

Lines changed: 85 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
• docs/RUNBOOK_INCIDENT_RESPONSE.md : Operator playbook for unexpected contract behavior and incident-mode recovery.
3939
• docs/INVESTOR_TIER.md : How the investor risk score, tier, and investment limit are computed — math, thresholds, and worked examples.
4040
• docs/BID_OVERBID_POLICY.md : What happens when a bid exceeds the invoice amount — rejection path and error code.
41+
• docs/QLX_INVOICE_LOCK_TIME_LIMITS.md : Contributor-facing summary of invoice lock duration, auto-release behavior, and the default grace-period path.
4142
• quicklendx-contracts/README.md : Smart contract-specific documentation.
4243
• quicklendx-contracts/docs/contracts/deterministic-time.md : Smart contract deterministic ledger time semantics.
4344
• quicklendx-backend/README.md : Backend-specific documentation.
@@ -69,7 +70,7 @@ npm run dev
6970
- `docs/RUNBOOK_INCIDENT_RESPONSE.md`: Operator playbook for unexpected contract behavior and incident-mode recovery.
7071
- `docs/INVESTOR_TIER.md`: How the investor risk score, tier, and investment limit are computed — math, thresholds, and worked examples.
7172
- `docs/KYC.md`: Business KYC vs investor KYC, what each gates.
72-
- `docs/QLX_BUSINESS_KYC_TIERS.md`: Business KYC status system — state machine, transitions, invoice limits, and enforcement points.
73+
- [`docs/QLX_INVOICE_LOCK_TIME_LIMITS.md`](docs/QLX_INVOICE_LOCK_TIME_LIMITS.md): Contributor-facing summary of invoice lock duration, auto-release behavior, and the default grace-period path.
7374
- `quicklendx-contracts/README.md`: Smart contract-specific documentation.
7475
- `quicklendx-contracts/docs/contracts/deterministic-time.md`: Smart contract deterministic ledger time semantics.
7576
- [`docs/README.md`](docs/README.md): Full documentation index — **start here**.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# QLX Invoice Lock Time Limits
2+
3+
> Audience: contributors who need the practical answer to “how long can an invoice remain locked before it is auto-released?”
4+
5+
The short version is: there is no single invoice lock timer that always auto-releases funds. QuickLendX has two lock styles, and both behave differently:
6+
7+
- **Admin freeze**: a permanent lock that does not expire on its own.
8+
- **Escrow hold after funding**: a lock that remains until an explicit release, refund, or withdrawal action.
9+
- **Default eligibility**: the closest thing to a time-based release window, but it still requires an explicit trigger.
10+
11+
## 1. Admin freeze: effectively indefinite
12+
13+
An admin can freeze an invoice with the `freeze_invoice` entrypoint. Once set, the invoice is blocked for financial writes and is treated as locked until a separate on-chain intervention changes that state.
14+
15+
### Concrete behaviour
16+
17+
- Entry point: `freeze_invoice(env, admin, invoice_id)`
18+
- Effect: the invoice gets the `InvoiceFrozen` flag.
19+
- Result: operations such as `place_bid`, `accept_bid`, `record_payment`, and `settle_invoice` fail with `InvoiceFrozen`.
20+
21+
There is no public `unfreeze_invoice` entrypoint in the contract interface. The underlying storage helper exists, but the public contract surface does not expose a time-based auto-release for this state.
22+
23+
### Practical takeaway
24+
25+
If you are tracing a lock that came from admin freeze, treat it as **indefinite until manual intervention** rather than as a countdown-based lock.
26+
27+
## 2. Escrow hold: no auto-release timer
28+
29+
When `accept_bid_and_fund` succeeds, the invoice moves into a funded/escrow-held state. The investor’s funds stay in escrow and the invoice remains effectively locked from a funds-release perspective until one of the following is called explicitly:
30+
31+
- `release_escrow_funds`
32+
- `refund_escrow_funds`
33+
- `withdraw_investment`
34+
35+
### Concrete behaviour
36+
37+
- The escrow status becomes `Held` after funding.
38+
- The funds remain locked until one of the explicit terminal actions above is invoked.
39+
- There is no built-in expiry window for this hold.
40+
41+
### Practical takeaway
42+
43+
If you are debugging an invoice that is still “locked” after funding, the answer is usually “it is waiting for an explicit release or refund path,” not “it will expire automatically after $N$ seconds.”
44+
45+
## 3. Default path: the only bounded time window
46+
47+
The closest thing to a time-based lock release is the default path. Once the invoice reaches its due date and then passes the configured grace period, the invoice becomes eligible for default handling.
48+
49+
### How the timer is resolved
50+
51+
The grace deadline is resolved in this order:
52+
53+
1. An explicit override passed to `mark_invoice_defaulted`
54+
2. The configured protocol grace period (`grace_period_seconds`)
55+
3. The default fallback of `7 days` (`604_800` seconds)
56+
57+
The implementation also bounds the maximum grace period at `30 days` (`2_592_000` seconds).
58+
59+
### Concrete example
60+
61+
Suppose an invoice is due on `2026-07-15` and the protocol uses the default grace period of `7 days`.
62+
63+
| Invoice due date | Grace period | Default becomes eligible after |
64+
|------------------|--------------|-------------------------------|
65+
| `2026-07-15` | `7 days` | `2026-07-22` |
66+
67+
At that point, anyone can call the default entrypoint to transition the invoice to a defaulted state and refund the escrow. This is still a **manual trigger**, not an automatic on-chain release.
68+
69+
## 4. Contributor guidance
70+
71+
When you are implementing or reviewing invoice-lock logic, use this rule of thumb:
72+
73+
- If the lock came from an admin freeze, assume it is **indefinite**.
74+
- If the lock came from funded escrow, assume it is **waiting for an explicit release/refund/withdraw action**.
75+
- If the invoice is past due, check the **grace period + default path** rather than looking for a general auto-release timer.
76+
77+
## Related docs
78+
79+
- [INVOICE_LOCK.md](INVOICE_LOCK.md) for the broader invoice-lock overview
80+
- [INVOICE_LIFECYCLE.md](INVOICE_LIFECYCLE.md) for the invoice state machine
81+
- [ESCROW.md](ESCROW.md) for the escrow lifecycle and terminal actions
82+
- [ERROR_CODES.md](ERROR_CODES.md) for the `InvoiceFrozen` error reference

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| [INVOICE_LIFECYCLE_DIAGRAM.md](INVOICE_LIFECYCLE_DIAGRAM.md) | Full invoice state machine diagram — all statuses, transitions, invariants, and entrypoint signatures in one page (issue #1946) |
1010
| [OFF_CHAIN_SIGNATURES.md](OFF_CHAIN_SIGNATURES.md) | Threat model and implementation notes for all off-chain signed operations: KYC payloads, cursor attestations, dispute evidence (issue #1894) |
1111
| [DEFAULT_FLOW_DIAGRAM.md](DEFAULT_FLOW_DIAGRAM.md) | State-machine diagram from invoice past-due → default → recovery; grace period, finality guards, dispute interception, and concrete timeline example |
12+
| [QLX_INVOICE_LOCK_TIME_LIMITS.md](QLX_INVOICE_LOCK_TIME_LIMITS.md) | Contributor-facing summary of the practical invoice lock time limits, auto-release behavior, and the default grace-period path |
1213
| [errors.md](contracts/errors.md) | Error code reference (stable integers) |
1314
| [events.md](contracts/events.md) | Event schema and topic constants |
1415
| [security.md](contracts/security.md) | Reentrancy guard, pause circuit breaker, access control |

0 commit comments

Comments
 (0)