Two‑Phase Funds Transfer With Holds (Design + Implementation)
You are building an in‑memory wallet/ledger component that supports two‑phase transfers using holds. Assume the caller is authenticated and acts on behalf of the source account for transfer(). The system runs in a single process; persistence/durability is out of scope.
API to Add
-
transfer(timestamp, targetAccountId, amount) -> String
-
Places a hold on the source account’s funds and returns a transferId.
-
accept(timestamp, accountId, transferId) -> boolean
-
Finalizes the transfer to the target account; should be callable only by the target account (accountId must match the transfer’s target).
-
cancel(transferId) -> boolean
-
Cancels the transfer, releasing the hold and restoring availability to the source.
Requirements
-
Define clear semantics for:
-
Available vs. ledger balance.
-
Duplicate requests and idempotency (for transfer, accept, cancel).
-
Expired holds and how to handle them.
-
Error handling for invalid transferIds and wrong accountId in accept().
-
Atomicity across updates (accounts, holds, transfer status) with concurrency safety.
-
Data structures: Use hash maps to track all transfers and pending holds; avoid list scans.
-
Provide time and space complexity for each operation.
Notes/Assumptions to Clarify
-
Source account for transfer() is the authenticated caller; you may reference it as sourceAccountId.
-
Choose a reasonable hold TTL and define how timestamp is used (e.g., for auditing vs. TTL).
-
Amounts are positive, integer cents (or smallest currency unit).