|
| 1 | +# Multi-Lock Vault Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The multi-lock list displays all time-locked deposits associated with the user's vault account. Each lock represents an independent deposit with its own amount, unlock date, and status. This aligns with the future Soroban contract direction supporting multiple locks per user. |
| 6 | + |
| 7 | +## Component: `MultiLockList` |
| 8 | + |
| 9 | +**Location:** `src/components/MultiLockList.tsx` |
| 10 | + |
| 11 | +### Props |
| 12 | + |
| 13 | +| Prop | Type | Description | |
| 14 | +|---|---|---| |
| 15 | +| `locks` | `VaultLock[]` | Array of lock objects to render | |
| 16 | +| `isLoading` | `boolean` | Shows an `ActivityIndicator` spinner | |
| 17 | +| `error` | `string \| null` | Displays error message with retry button | |
| 18 | +| `onWithdraw` | `(lock: VaultLock) => void` | Called when user taps withdraw on a matured lock | |
| 19 | +| `onRetry` | `() => void` | Called when user taps retry after an error | |
| 20 | +| `isWithdrawing` | `boolean` | Disables withdraw buttons during submission | |
| 21 | + |
| 22 | +### States Handled |
| 23 | + |
| 24 | +| State | Visual | |
| 25 | +|---|---| |
| 26 | +| **Loading** | Spinner + "Loading locks…" | |
| 27 | +| **Error** | Error icon + message + retry button | |
| 28 | +| **Empty** | Lock icon + "No Locks Yet" + guidance text | |
| 29 | +| **Populated** | List of lock cards | |
| 30 | + |
| 31 | +### Lock Status Display |
| 32 | + |
| 33 | +| Status | Icon | Color | Action | |
| 34 | +|---|---|---|---| |
| 35 | +| `locked` | `Clock` | Warning (amber) | None (immature) | |
| 36 | +| `matured` | `Unlock` | Success (green) | "Withdraw" button | |
| 37 | +| `withdrawn` | `CheckCircle2` | Muted (grey) | None (completed) | |
| 38 | + |
| 39 | +## Type: `VaultLock` |
| 40 | + |
| 41 | +**Location:** `src/types/vault.ts` |
| 42 | + |
| 43 | +```ts |
| 44 | +interface VaultLock { |
| 45 | + id: string; // Unique lock identifier |
| 46 | + amount: string; // XLM amount, 7-decimal string (e.g. "500.0000000") |
| 47 | + unlockDate: string; // ISO-8601 date when lock matures |
| 48 | + status: LockStatus; // 'locked' | 'matured' | 'withdrawn' |
| 49 | + txHash?: string; // Optional creation transaction hash |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## Placeholder Data |
| 54 | + |
| 55 | +**Location:** `tests/fixtures/vault.ts` |
| 56 | + |
| 57 | +Until the Soroban contract's multi-lock interface is ready, the vault screen uses hardcoded fixtures: |
| 58 | + |
| 59 | +```ts |
| 60 | +vaultLockFixtures.all // 3 locks: locked, matured, withdrawn |
| 61 | +vaultLockFixtures.locked // 2 immature locks |
| 62 | +vaultLockFixtures.matured // 2 matured locks |
| 63 | +vaultLockFixtures.empty // [] |
| 64 | +``` |
| 65 | + |
| 66 | +The unlock dates are computed dynamically (e.g., 30 days from now, 7 days ago) so the fixtures always show a realistic mix of statuses. |
| 67 | + |
| 68 | +## Integration Roadmap |
| 69 | + |
| 70 | +1. **Done** — `MultiLockList` component with loading/empty/error states |
| 71 | +2. **Done** — Placeholder fixtures for UI development |
| 72 | +3. **TODO** — Add `fetchLocks(publicKey)` to `src/services/vault.ts` that calls the Soroban contract's lock registry |
| 73 | +4. **TODO** — Add lock state to `src/store/vaultStore.ts` (Zustand slice) |
| 74 | +5. **TODO** — Implement `withdrawLock(secretKey, lockId)` contract call |
| 75 | +6. **TODO** — Replace fixture data in `app/(tabs)/vault.tsx` with real store data |
| 76 | + |
| 77 | +## Contract Interface (Expected) |
| 78 | + |
| 79 | +The Soroban vault contract is expected to expose: |
| 80 | + |
| 81 | +``` |
| 82 | +get_locks(owner: Address) -> Vec<Lock> |
| 83 | +withdraw_lock(owner: Address, lock_index: u32) -> i128 |
| 84 | +``` |
| 85 | + |
| 86 | +Where `Lock` is a struct containing `amount: i128`, `unlock_time: u64`, and `withdrawn: bool`. |
0 commit comments