Wallet Invariants
Global invariants for every Unit wallet movement. Send, Deposit, and Withdraw differ by funding source — and the rules differ with them. Display values must never become executable values.
Source of truthPercentage controls signal known internal balance
The single rule from which most others follow.
✓
SEND spends an internal Unit balance — quick-fill required.
✓
WITHDRAW spends an internal Unit-wrapped balance — quick-fill required.
✕
DEPOSIT is an external inflow — quick-fill forbidden.
Why this is the rule: the existence of percentage
controls is a SIGNAL to the user that the system knows their balance.
Showing them where the system does not know the balance teaches false
behavior, regardless of any "convenience" intent.
Send vs Deposit vs Withdraw at a glance
Same lifecycle shape. Different funding models. Different rules.
| Aspect | Send | Deposit | Withdraw | Receive | Activity | Vault |
|---|---|---|---|---|---|---|
| Funding source | Internal Unit balance | External chain (unknown) | Internal wrapped Unit balance | External (incoming) | — | Internal vault stake |
| Asset type | Internal Unit asset | External native (BTC, ETH, …) | Wrapped Unit-native (BTCU, …) | External native | — | Internal Unit asset |
| Quick-fill (25/50/75/Max) | Required | Forbidden | Required | N/A | N/A | Allowed |
| "Available balance" surface | Required | Forbidden | Required | — | — | Allowed |
| Address book / saved address | Optional (recipients) | Single-use deposit address | Required (matching chain) | N/A | N/A | — |
| Optional tx-hash submission | No | User-facing | Operator-only | — | — | — |
| Forbidden post-submit causes | USER_CANCELLED, USER_REJECTED, INSUFFICIENT_BALANCE | USER_CANCELLED, USER_REJECTED, BELOW_MINIMUM | USER_CANCELLED, USER_REJECTED, INSUFFICIENT_BALANCE | — | — | USER_CANCELLED, USER_REJECTED |
Decimal precision discipline
Four kinds of precision. Never confused. Display rounding is for reading; the executable amount preserves full asset precision.
A
Computational precision
- Full chain-defined decimals
- Used for arithmetic + storage
- Never lossy
B
Validation precision
- Asset's allowed decimal count
- Rejects over-precision input
- Never silently rounds to "valid"
C
Display precision
- Readability rounding only
- Output of
toFixed/ fiat formatter - NEVER reused as executable
D
Input precision
- What the input field accepts
- Mode-aware (token vs USD)
- Always normalized to token before validation
Rule: the executable amount sent to chain is
computed from the user's typed token quantity, parsed via
parseUnits(_, asset.decimals). It is independent of every
toFixed() in the codebase.
Per-asset native precision
| Asset | Native decimals | Display |
|---|---|---|
| BTCU / BTC | 8 | up to 8 |
| ETHU / ETH | 18 (computational) | 6–8 significant |
| BNBU / BNB | 18 (computational) | 6–8 significant |
| SOLU / SOL | 9 | up to 9 |
| TONU / TON | 9 | up to 9 |
| USDTU / USDT | 6 | up to 6 |
| USDCU / USDC | 6 | up to 6 |
| $UNITCOIN | from chain config | from chain config |
| Fiat (USDU, EURU, …) | chain config | 2 decimals |
Other tokens: precision MUST be supplied by token
metadata. If precision is unknown, executable submission is blocked —
never guessed.
No raw backend enums in user UI
Internal rejection reasons stay internal. Users see one safe sentence that tells them what they need to know.
Internal (admin / ops)
Never user-facing
UNDERPAID,OVERPAIDAMOUNT_MISMATCHWRONG_ASSET,WRONG_NETWORKTX_NOT_FOUND,TX_REUSEDDepositNotPending,TxHashAlreadySubmitted
User-facing (safe set)
From userFacingReason
- "Transaction not found on chain"
- "Transaction did not match your deposit"
- "This transaction has already been used"
- "This deposit is already confirmed."
- Fallback: "This deposit could not be verified."
Rule: the resolver
(rejectionReason, userFacingReason) → string returns
ONLY allowed strings. Tests assert that no raw enum identifier appears
in user copy.
What the user must always know
Every Unit wallet UI must continuously expose six facts.
✓
Source of funds — where the value originates.
✓
Destination of funds — where the value lands.
✓
Asset being moved — exact uppercase ticker.
✓
Value being moved — executable, not display-rounded.
✓
Network involved where applicable — exact chain.
✓
Internal vs external — and which side is which.