This reference describes error behavior in contracts/savings_vault/src/lib.rs.
The contract defines a custom error enum with stable numeric error codes via
the #[contracterror] attribute. See error-code-standard.md
for the complete error code standard and SDK mapping guidance.
SDK and mobile callers should use the numeric error codes for reliable error handling and display user-friendly messages based on the error category.
- Current failure: Returns
ContractError::AlreadyInitializedfrominitialize. - Meaning: The one-time initialization flag already exists.
- Likely cause: A repeated initialization, a retry after success, or the wrong contract ID.
- Caller/developer action: Do not retry. Confirm the contract ID and use the existing deployment; this is not a transient network failure.
- Current failure: Returns
ContractError::NotInitializedwhen attempting operations that require initialization. - Meaning: The contract has not been initialized.
- Likely cause: Operations called before initialization or instance storage unavailable.
- Caller/developer action: Ensure initialization succeeded before enabling vault operations.
- Current failure: Returns
ContractError::InvalidDepositAmountfromdeposit. - Meaning: The deposit amount is zero or negative.
- Likely cause: Invalid input, unit conversion, sign handling, or an empty field converted to zero.
- Caller/developer action: Require a positive
i128amount in the token's smallest unit before invoking the contract.
- Current failure: Returns
ContractError::InvalidWithdrawAmountfromwithdraw. - Meaning: The withdrawal amount is zero or negative.
- Likely cause: Invalid input or an amount-conversion bug.
- Caller/developer action: Reject non-positive amounts before submission.
- Current failure: Returns
ContractError::InvalidLockAmountfromlock_funds. - Meaning: The lock amount is zero or negative.
- Likely cause: Invalid input or an amount-conversion bug.
- Caller/developer action: Require a positive amount before submission.
- Current failure: Returns
ContractError::InvalidUnlockTimefromlock_funds. - Meaning:
unlock_timeis less than or equal to the current ledger timestamp; it must be strictly later when executed. - Likely cause: A past timestamp, seconds/milliseconds confusion, clock skew, or submission too close to the selected time.
- Caller/developer action: Send Unix time in seconds and leave a safety margin beyond the latest ledger time.
These checks use the vault's available internal balance, not the wallet balance or locked balance.
- Current failure: Returns
ContractError::InsufficientBalancefromwithdraw. - Meaning: The withdrawal exceeds the available internal balance; a missing balance is treated as zero.
- Likely cause: The request is too large, no deposit is recorded, or some balance was moved to the locked bucket.
- Caller/developer action: Refresh
get_balance(user), cap the request to that value, and explain that locked funds are unavailable.
- Current failure: Returns
ContractError::InsufficientBalanceToLockfromlock_funds. - Meaning: The lock amount exceeds the available internal balance.
- Likely cause: A stale displayed balance, an excessive request, or funds already moved to the locked bucket.
- Caller/developer action: Refresh
get_balance(user)and allow no more than the returned available amount.
- Current failure: Returns
ContractError::FundsLockedUntilMaturityfromwithdraw. - Meaning: The withdrawal amount exceeds the available balance and would require withdrawing from immature (unmatured) locked funds. This is a specific error that occurs when the user has locked funds that have not yet reached their unlock time.
- Likely cause: The user attempted to withdraw more than their available (unlocked) balance, and the shortfall would need to come from locked funds that are still immature (current_time < unlock_time).
- Caller/developer action: Check
get_balance(user)to see available funds andget_locked_balance(user)to see locked funds. Only withdraw up to the available balance. Wait for locks to mature (check withcan_withdraw(user)) before attempting to withdraw locked funds.
- Current failure: Soroban host authorization failure from
require_auth(); the contract defines this error for documentation purposes, but the actual failure comes from the Soroban host. - Meaning: Valid authorization for the required address is absent.
- Likely cause:
initializelacksadminauthorization, ordeposit,withdraw, orlock_fundslacksuserauthorization. The app may be trying to act for another address. - Caller/developer action: Build and sign with the required address. Do not retry unchanged; request the correct wallet signature.
Read-only calls (get_balance, get_locked_balance, and can_withdraw) do not
call require_auth().
Zero-duration locks: Passing unlock_time == current ledger timestamp
(a zero-second duration) is rejected with this same panic, because the check
is unlock_time <= current_time, not <. There is no way to create a lock
that is already matured at creation time; the smallest valid duration is one
second (unlock_time == current_time + 1), and funds locked that way remain
locked until the ledger timestamp advances to that value — can_withdraw
and get_balance still treat it as locked at the moment of creation.
- Current failure: Panic message from
depositandlock_funds. - Meaning: The contract is in an emergency pause state. Deposits and lock
operations are blocked. Withdrawals (
withdraw,withdraw_lock) and read-only queries remain available. - Likely cause: The admin activated a pause for an incident response.
- Caller/developer action: Check
is_paused()to confirm. If the pause has an expiry, wait for it to expire. Otherwise, the admin must callunpause()to restore normal operations. Users can still withdraw funds during a pause.
- Current failure: Panic message from
pause. - Meaning: The
duration_secsargument topause()was zero. A pause must have a non-zero duration to ensure it auto-expires. - Likely cause: Invalid input or an accidental zero value.
- Caller/developer action: Pass a positive duration in seconds (e.g., 604800 for 7 days).
- Current condition:
can_withdraw(user)returnsfalse; it does not fail. - Meaning: No locked funds exist, or the ledger timestamp is earlier than
the unlock time. At exactly the unlock timestamp it returns
true. - Likely cause: The lock has not matured or no lock exists.
- Caller/developer action: Treat
falseas normal state and disable the action. The current contract has no operation to release or withdraw locked funds;can_withdrawis only a query.
- Current failure: Panic message from
withdraw. - Meaning: The withdrawal amount exceeds the available balance and would require withdrawing from immature (unmatured) locked funds. This is a specific error that occurs when the user has locked funds that have not yet reached their unlock time.
- Likely cause: The user attempted to withdraw more than their available (unlocked) balance, and the shortfall would need to come from locked funds that are still immature (current_time < unlock_time).
- Caller/developer action: Check
get_balance(user)to see available funds andget_locked_balance(user)to see locked funds. Only withdraw up to the available balance. Wait for locks to mature (check withcan_withdraw(user)) before attempting to withdraw locked funds.
- Current failure: Reserved for future use.
- Meaning: No lock found for the specified lock ID.
- Likely cause: Invalid lock ID or lock has been consumed.
- Caller/developer action: Verify lock ID and check lock status.
Read-only calls (get_balance, get_locked_balance, get_lock, list_locks,
and can_withdraw) do not call require_auth().
- Current failure: Error or trap propagated by the configured token contract; the vault defines no wrapper error.
- Meaning: The token transfer from the vault contract to the user failed.
- Likely cause: Insufficient real token balance, an invalid or incompatible token address, token authorization failure, or token-contract rejection. An internal balance does not guarantee matching tokens are held.
- Caller/developer action: Inspect the nested token diagnostic. Verify the configured token and vault token balance; do not label this only as an internal-balance error.
All error codes defined in the ContractError enum are stable and backward compatible:
- Existing error codes will never change
- New error codes will be added within their category ranges
- Deprecated error codes will be marked in documentation but remain functional
- See error-code-standard.md for the complete standard
For SDK mapping guidance and mobile UX recommendations, see error-code-standard.md.