Allowances FAQ
These answers cover the allowance behavior that the module reference does not spell out: windows, timestamps, revocation, and gas. For the API and code samples, see Using Allowances.
Windows and timestamps
Are allowance timestamps in UTC?
Yes. Start and expiration times are Unix timestamps in milliseconds, the same format as the onchain Clock. Calendar rate limits use UTC dates, so a monthly window renews at 00:00 UTC on its renewal day, not at local midnight.
When does a rate-limit window start?
At the first successful spend. Issuing an allowance does not start a window. The first spend anchors the schedule, and every later window is counted from that anchor. A monthly limit first used on March 10 at 15:00 UTC renews on April 10 at 00:00 UTC, then May 10 at 00:00 UTC, and so on. A periodic limit counts exact milliseconds from the first spend instead, so a 30-day limit first used on March 10 at 15:00 UTC renews on April 9 at 15:00 UTC.
How do monthly limits handle short months?
A calendar window renews on the anchor's day of the month at 00:00 UTC. When a month has fewer days than the anchor day, the renewal moves to the last day of that month, and later months go back to the anchor day. A monthly limit first used on January 31 renews on February 28 (or February 29 in a leap year), then on March 31, then on April 30. Quarterly and yearly limits apply the same rule every 3 or 12 months.
Are windows sliding or fixed?
Fixed. The spent amount resets when a spend lands in a later window. As with any fixed window, a spender can use the full limit at the end of one window and again at the start of the next. Size the limit to allow for that.
Does unused allowance carry over?
No. Each window starts with the full limit and anything unused at the end of the window does not carry into the next one. The lifetime cap is separate and always counts total spend across all windows.
Revocation and expiration
What happens to an in-flight spend during revocation?
Revocation deletes the allowance. Sui rejects any spend that reaches the network after that, and a spend that was already in flight fails when it executes. Either way, no funds move after the revoke takes effect.
What is the difference between expiration and revocation?
An expired allowance still exists. A spend against it fails during execution, so the spender pays gas, and the object stays onchain until the funder revokes it. Revocation deletes the object, so Sui rejects later spends before execution and returns the storage rebate to the funder. Set an expiration as a backstop, and revoke when you want the allowance gone.
Spending and gas
Who pays gas for a spend?
The spender. A spending transaction is a normal transaction sent and signed by the spender, and gas comes from the spender's gas coin or SUI address balance. Sui debits the funder's balance only by the amount spent. A sponsor can pay the gas, as for any other transaction.
Can the spender take more than the declared amount?
No. The spending transaction declares an amount in advance, and that is the most it can take. The sui::allowance module checks every limit against the live allowance during execution, so a client cannot build a transaction that bypasses them.
What if the funder's balance is lower than the declared amount?
The spend fails and Sui charges nothing, neither the funder's balance nor the allowance's counters. An allowance never guarantees that funds are available, only that the spender might take them if they are.
Changing an allowance
Can you change the limits of an existing allowance?
No. There is no update function. Revoke the allowance and issue a new one with the new terms. The new allowance has a new object ID and a fresh spend counter, so share the new ID with the spender.
Can you change the spender?
Only for app-bound allowances, and only by the app. The module that defines the app type can call rotate_spender with a SettingsPermit. Direct allowances have a fixed spender. To hand a direct allowance to a different address, revoke and reissue.
Can you issue an allowance to a contract instead of an address?
Use an app-bound allowance. Your module decides whether each pull is allowed, and it can rotate the spender key at any time. The spender itself must still be an account address, not an object, because it must sign and send every spending transaction.