Limits
What are Limits
Limits are a bridge protection mechanism against large-scale attacks and exploits. They restrict daily transfer volume for each token separately.
Purpose:
- Protection against drainage attacks (mass fund withdrawal on hack)
- Limiting potential damage from exploits
- Time for responding to anomalous activity
Where Applied:
ProxyMultiVaultAlienJetton_V3contracts (alien tokens)ProxyMultiVaultNativeJetton_V3contracts (native tokens)
Limit Types
The system supports two types of daily limits for each token:
| Type | Description | Application |
|---|---|---|
| Incoming | Limit on token volume entering the network | Deposits from other networks to TVM |
| Outgoing | Limit on token volume leaving the network | Withdrawals from TVM to other networks |
Features
- Limits are set per specific token, not globally
- Each limit is optional — may not be set (
null) - If limit is not set — check is skipped, transfer proceeds without restrictions
- Limits are independent of user address — this is total daily volume for token
Data Structure
DailyLimits
struct DailyLimits {
optional(uint128) incomingLimit; // Incoming limit (null = no limit)
uint128 dailyIncomingVolume; // Current daily incoming volume
optional(uint128) outgoingLimit; // Outgoing limit (null = no limit)
uint128 dailyOutgoingVolume; // Current daily outgoing volume
uint32 dayStartTimestamp; // Current "day" start timestamp
}Limit Check Mechanism
Check Algorithm

Day Definition
Day is determined by dividing block.timestamp by 86400 (seconds per day).
- Day resets at 00:00 UTC
- On day change, volumes are reset
dayStartTimestampis updated
When Limit is Checked
| Direction | Check Moment |
|---|---|
| Incoming (deposit) | After event relay confirmation |
| Outgoing (withdraw) | When receiving tokens for burn/transfer |
Check Formula
- Incoming:
dailyIncomingVolume + amount > incomingLimit→ LimitReached - Outgoing:
dailyOutgoingVolume + amount > outgoingLimit→ LimitReached
Important
Check is > (strictly greater), not >=. If limit = 100 and current volume = 0, then transfer for 100 will pass (100 > 100 = false).
Scenarios When Limit is Reached
Outgoing — LimitReached
When outgoing limit is exceeded:
- Tokens are returned to sender
OutgoingLimitReached(token)event is emitted- Transfer is not created
Incoming — LimitReached
When incoming limit is exceeded:
- Event contract transitions to
LimitReachedstatus (code 5) limitApproveraddress is setLimitReached(approver)event is emitted- Transfer is delayed until approver decision
Actions on LimitReached (incoming)
LimitApprover can perform one of three actions:
| Action | Function | Result |
|---|---|---|
| Approve | approveLimit() | Transfer executes, tokens minted to recipient |
| Cancel | cancel() | Tokens returned to source network |
| Reject | rejectLimit() | Event rejected, funds remain in source network |
Retry Mechanism
User or initializer can call retry() for another transfer attempt (e.g., after limit reset on new day).
Limit Recovery
Limit automatically resets at 00:00 UTC the next day. Manual reset is not provided — only limit value change via setTokenDailyLimits().
Limit Management
Setting Limits
setTokenDailyLimits(_token, _incomingLimit, _outgoingLimit)Parameters:
_token— token address_incomingLimit— incoming limit (null = no limit)_outgoingLimit— outgoing limit (null = no limit)
Access rights: Contract owner only
Setting LimitApprover
setLimitApprover(approverAddress)Access rights: Contract owner only
Reading Limits
getDailyLimits(token)— returns DailyLimits structure for tokengetLimitApprover()— returns current approver address
Default Limits
By default, limits are not set (null). This means unlimited transfers until administrator explicitly sets limits.
API/Contract Reference
Proxy Contracts (Alien and Native)
| Function | Access | Description |
|---|---|---|
setTokenDailyLimits() | owner | Set/change token limits |
getDailyLimits() | public | Get current limits and volumes |
setLimitApprover() | owner | Set approver address |
getLimitApprover() | public | Get approver address |
Event Contracts
| Function | Access | Description |
|---|---|---|
approveLimit() | limitApprover | Approve transfer on LimitReached |
cancel() | limitApprover | Cancel and return funds |
rejectLimit() | limitApprover | Reject transfer |
retry() | recipient/initializer | Retry transfer attempt |
Events
| Event | When Emitted |
|---|---|
OutgoingLimitReached(address token) | When outgoing limit is exceeded |
LimitReached(address approver) | When incoming limit is exceeded |
Errors and Edge Cases
Error Codes
| Code | Constant | Description |
|---|---|---|
| 2335 | SENDER_IS_NOT_LIMIT_APPROVER | Caller is not limit approver |
| 2324 | WRONG_STATUS | Invalid status for operation |
Edge Cases
| Situation | Behavior |
|---|---|
| Limit = 0 | Any transfer > 0 will exceed limit |
| Limit = null | No restrictions |
| First transfer of day | Volume resets, then amount is added |
| Transfer exactly equals limit | Will pass (check is >, not >=) |
| limitApprover = zero address | approveLimit() will fail |
Important Notes
- Partial execution is NOT supported — transfer either passes completely or is rejected
- Limits are checked AFTER relay confirmation for incoming transfers
- Limits are checked BEFORE event creation for outgoing transfers
- On outgoing LimitReached, tokens are returned instantly, no manual intervention required