This document describes the four new withdrawal features implemented in Issues #565-#568.
Prevents overlapping or conflicting withdrawal schedules by validating that scheduled withdrawals don't occur within a 1-hour window of each other.
Schedules a withdrawal with conflict detection.
Parameters:
vault_id: The vault IDcaller: The vault owner (must be authenticated)timestamp: Unix timestamp for the scheduled withdrawalamount: Amount to withdraw in stroops
Returns:
Ok(())on successErr(ContractError::ConflictingWithdrawalSchedule)if overlapping with existing scheduleErr(ContractError::NotOwner)if caller is not the vault ownerErr(ContractError::AlreadyReleased)if vault is not in Locked status
Events:
WITHDRAWAL_VALIDATION_TOPIC: Emitted when a withdrawal is successfully scheduled
- Maintains a vector of
WithdrawalScheduleEntrystructs per vault - Checks for conflicts within a 1-hour (3600 second) window
- Prevents scheduling withdrawals that would overlap with existing schedules
- Stores schedules in persistent storage with TTL management
OverlappingWithdrawalSchedule = 64: Withdrawal overlaps with existing scheduleConflictingWithdrawalSchedule = 65: Withdrawal conflicts with existing schedule
Implements daily, weekly, and monthly withdrawal limits with automatic period resets. Limits are tracked per vault and reset automatically when their respective periods expire.
set_withdrawal_limits(vault_id, caller, daily_limit, weekly_limit, monthly_limit) -> Result<(), ContractError>
Configures withdrawal limits for a vault.
Parameters:
vault_id: The vault IDcaller: The vault owner (must be authenticated)daily_limit: Maximum amount withdrawable per day (in stroops)weekly_limit: Maximum amount withdrawable per week (in stroops)monthly_limit: Maximum amount withdrawable per month (in stroops)
Returns:
Ok(())on successErr(ContractError::NotOwner)if caller is not the vault owner
Events:
WITHDRAWAL_LIMIT_SET_TOPIC: Emitted when limits are configured
Retrieves the current withdrawal limits for a vault.
Returns:
Some(WithdrawalLimit)if limits are configuredNoneif no limits are set
pub struct WithdrawalLimit {
pub daily_limit: i128,
pub weekly_limit: i128,
pub monthly_limit: i128,
}pub struct WithdrawalTracker {
pub daily_withdrawn: i128,
pub daily_reset_at: u64,
pub weekly_withdrawn: i128,
pub weekly_reset_at: u64,
pub monthly_withdrawn: i128,
pub monthly_reset_at: u64,
}- Limits are checked during every
withdraw()call - Trackers automatically reset when their period expires
- Daily period: 24 hours (86,400 seconds)
- Weekly period: 7 days (604,800 seconds)
- Monthly period: 30 days (2,592,000 seconds)
- Limits are optional; if not set, no restrictions apply
DailyWithdrawalLimitExceeded = 66: Daily limit would be exceededWeeklyWithdrawalLimitExceeded = 67: Weekly limit would be exceededMonthlyWithdrawalLimitExceeded = 68: Monthly limit would be exceeded
WITHDRAWAL_LIMIT_SET_TOPIC: Emitted when limits are configuredWITHDRAWAL_LIMIT_EXCEEDED_TOPIC: Emitted when a limit is exceeded
Restricts withdrawals to only whitelisted addresses. Vault owners can add and remove addresses from the whitelist.
Adds an address to the withdrawal whitelist.
Parameters:
vault_id: The vault IDcaller: The vault owner (must be authenticated)address: The address to whitelistlabel: A descriptive label for the address (e.g., "cold_wallet")
Returns:
Ok(())on successErr(ContractError::NotOwner)if caller is not the vault owner
Events:
WHITELIST_ADDED_TOPIC: Emitted when an address is added
Removes an address from the withdrawal whitelist.
Parameters:
vault_id: The vault IDcaller: The vault owner (must be authenticated)address: The address to remove
Returns:
Ok(())on successErr(ContractError::NotOwner)if caller is not the vault owner
Events:
WHITELIST_REMOVED_TOPIC: Emitted when an address is removed
Retrieves the whitelist for a vault.
Returns:
Some(Vec<WhitelistEntry>)if whitelist existsNoneif no whitelist is configured
pub struct WhitelistEntry {
pub address: Address,
pub added_at: u64,
pub label: String,
}- If no whitelist is configured, all addresses are allowed (backward compatible)
- If a whitelist exists, only whitelisted addresses can receive withdrawals
- Whitelist entries include timestamps for audit trails
- Whitelist is stored in persistent storage with TTL management
WithdrawalDestinationNotWhitelisted = 69: Destination address is not whitelisted
WHITELIST_ADDED_TOPIC: Emitted when an address is addedWHITELIST_REMOVED_TOPIC: Emitted when an address is removedWHITELIST_VIOLATION_TOPIC: Emitted when a withdrawal to non-whitelisted address is attempted
Allows vault owners to reverse withdrawals within a grace period (24 hours by default). Reversed withdrawals restore funds to the vault.
Reverses a withdrawal within the grace period.
Parameters:
vault_id: The vault IDcaller: The vault owner (must be authenticated)withdrawal_id: The ID of the withdrawal to reverse
Returns:
Ok(())on successErr(ContractError::WithdrawalReversalGracePeriodExpired)if grace period has expiredErr(ContractError::WithdrawalAlreadyReversed)if already reversedErr(ContractError::NotOwner)if caller is not the vault owner
Events:
WITHDRAWAL_REVERSED_TOPIC: Emitted when a withdrawal is successfully reversedWITHDRAWAL_CANCELLED_TOPIC: Emitted when a withdrawal is cancelled (Issue #1134)
Retrieves a withdrawal reversal record.
Returns:
Some(WithdrawalReversal)if the record existsNoneif not found
pub struct WithdrawalReversal {
pub withdrawal_id: u64,
pub amount: i128,
pub withdrawn_at: u64,
pub grace_period_until: u64,
pub reversed: bool,
}- Every withdrawal is automatically recorded for potential reversal
- Grace period is 24 hours (86,400 seconds) from withdrawal time
- Withdrawal IDs are auto-incremented per vault
- Reversals restore funds to the vault balance
- Once reversed, a withdrawal cannot be reversed again
- Reversal records are stored in persistent storage with TTL management
WithdrawalReversalGracePeriodExpired = 70: Grace period has expiredWithdrawalAlreadyReversed = 71: Withdrawal has already been reversed
WITHDRAWAL_REVERSED_TOPIC: Emitted when a withdrawal is reversedWITHDRAWAL_CANCELLED_TOPIC: Emitted when a withdrawal is cancelled (Issue #1134)REVERSAL_GRACE_EXPIRED_TOPIC: Emitted when a grace period expires
All four features are integrated into the existing withdraw() function:
pub fn withdraw(env: Env, vault_id: u64, caller: Address, amount: i128) -> Result<(), ContractError>The withdrawal process now:
- Validates the caller is the vault owner
- Checks withdrawal approval threshold (Issue #404)
- Checks withdrawal limits (Issue #566)
- Validates whitelist (Issue #567)
- Transfers funds to the owner
- Records withdrawal for reversal (Issue #568)
- Emits withdrawal event
// Set daily limit of 10 XLM, weekly of 50 XLM, monthly of 100 XLM
client.set_withdrawal_limits(
&vault_id,
&owner,
&(10 * 10_000_000i128), // 10 XLM in stroops
&(50 * 10_000_000i128), // 50 XLM in stroops
&(100 * 10_000_000i128), // 100 XLM in stroops
)?;// Add a cold wallet to the whitelist
client.add_whitelist_address(
&vault_id,
&owner,
&cold_wallet_address,
&String::from_str(&env, "cold_storage"),
)?;
// Withdrawals can now only go to whitelisted addresses
client.withdraw(&vault_id, &owner, &amount)?;// Withdraw funds
client.withdraw(&vault_id, &owner, &amount)?;
// Within 24 hours, reverse the withdrawal
client.reverse_withdrawal(&vault_id, &owner, &0u64)?;
// Funds are restored to the vault// Schedule a withdrawal for tomorrow
let tomorrow = env.ledger().timestamp() + 86_400u64;
client.schedule_withdrawal(
&vault_id,
&owner,
&tomorrow,
&amount,
)?;-
Withdrawal Limits: Limits are per-vault and reset automatically. Owners should set appropriate limits based on their risk tolerance.
-
Whitelist: If a whitelist is configured, only whitelisted addresses can receive withdrawals. This prevents accidental transfers to wrong addresses.
-
Reversal Grace Period: The 24-hour grace period allows owners to recover from mistakes. After the grace period, reversals are no longer possible.
-
Scheduling: Scheduled withdrawals prevent overlapping transactions within a 1-hour window, reducing the risk of double-spending.
-
Authorization: All configuration functions require owner authentication via
require_auth().
- Withdrawal schedules are stored per-vault in a vector
- Withdrawal limits and trackers are stored per-vault
- Whitelist entries are stored per-vault in a vector
- Reversal records are stored with (vault_id, withdrawal_id) as key
- All storage uses TTL management to prevent bloat
- Configurable Grace Period: Allow owners to set custom reversal grace periods
- Withdrawal Notifications: Emit events for monitoring systems
- Batch Reversals: Reverse multiple withdrawals in a single transaction
- Limit Adjustments: Allow dynamic limit adjustments without resetting trackers
- Whitelist Expiry: Add expiration dates to whitelist entries