Module sui::allowance
Native allowances enable delegated, bounded, revocable spending from an address's live balance.
A transaction declares its funding source as a (funder, allowance) pair. Signing verifies that source against the shared Allowance and hands the transaction an AllowanceWithdrawal.
All policy checks (rate limits, lifetime caps, expiry) are enforced by this module.
use std::address;
use std::ascii;
use std::bcs;
use std::internal;
use std::option;
use std::string;
use std::type_name;
use std::u128;
use std::u64;
use std::vector;
use sui::accumulator;
use sui::address;
use sui::balance;
use sui::clock;
use sui::dynamic_field;
use sui::funds_accumulator;
use sui::hex;
use sui::object;
use sui::party;
use sui::protocol_config;
use sui::transfer;
use sui::tx_context;
use sui::vec_map;
Struct AllowanceWithdrawal
Created via a PTB Argument.
The inner Withdrawal is contained within this module and cannot be accessed directly.
An allowance's limits can never be charged without the funds actually moving.
public struct AllowanceWithdrawal<phantom T: store> has drop
Fields
- allowance: sui::object::ID
- is_sponsor: bool
- Today is always false. Opens the door for future ctx.sponsor() based allowances.
- inner: sui::funds_accumulator::Withdrawal<T>
Struct Allowance
Enables withdrawing T from the funder's balance, within this allowance's limits.
Always kept as a shared object.
public struct Allowance<phantom T> has key
Fields
- id: sui::object::UID
- settings: sui::allowance::Settings
- current_spend: u256
- Total cumulative spend from this allowance.
Struct Settings
Configuration of the Allowance, held by Allowance and AllowanceProposal
public struct Settings has drop, store
Fields
- funder: address
- The address whose balance is debited by spends against this allowance.
- spender: std::option::Option<address>
-
The spender of the allowance.
While it is currently always set, in the future this may become optional to allow for keyless app-bound withdrawals. - app: std::option::Option<std::type_name::TypeName>
- When set, requires the app's SpendPermit to spend, and only that app can rotate the spender or issue the allowance in the first place.
- lifetime_cap: std::option::Option<u256>
-
An optional lifetime cap on withdrawals using this allowance. Inclusive.
Amounts are u256, matching Withdrawal.limit. - start_timestamp_ms: std::option::Option<u64>
- Optional activation time, in milliseconds. Inclusive.
- expiration_timestamp_ms: std::option::Option<u64>
- Optional expiration time, in milliseconds. Exclusive.
- rate_limit: std::option::Option<sui::allowance::RateLimit>
- An optional recurring limit, applied on top of lifetime_cap. At least one of the two must be set.
- name: std::string::String
- A label for off-chain use, never read by any check. At most 128 bytes.
Struct AllowanceCap
Revocation for an allowance, sent to the funder at issuance (soulbound).
Also used for discoverability (funder -> allowances).
Created with the allowance and destroyed with it by revoke.
public struct AllowanceCap<phantom T> has key
Fields
Struct AllowanceProposal
A proposal that can only be issued by app A.
public struct AllowanceProposal<phantom T> has drop
Fields
Struct SpendPermit
A SpendPermit<A> authorizes a single spend against an allowance bound to A. It is issued from an internal::Permit<A>, allowing the module that defines A to gate every withdrawal on its own logic.
public struct SpendPermit<phantom A> has drop
Struct SettingsPermit
A SettingsPermit<A> authorizes changing the configuration of an allowance bound to A: issuing one, or rotating its spender. It is issued from an internal::Permit<A>.
Kept distinct from SpendPermit so an app can hand out the right to spend without also handing out the right to reconfigure, and vice versa.
public struct SettingsPermit<phantom A> has drop
Enum RateLimit
A recurring cap on withdrawals, applied on top of any lifetime cap.
An enum so other mechanics, like sliding windows, can be added as variants.
public enum RateLimit has copy, drop, store
Variants
- Variant Windowed
- At most limit per window. Windows only roll forward.
- limit: u256
- The most that can be spent within a window. Inclusive.
- spent: u256
- Amount spent so far within the current window.
- anchor_ms: std::option::Option<u64>
- Start of the first window, stamped by the first successful charge.
- index: u64
-
Which window spent is accumulated in, numbered from the anchor (0 = first).
A spend landing in a later one resets spent. - window: sui::allowance::Window
- The defining period of time for this rate limit.
Enum Window
The defining time period for a RateLimit::Windowed.
public enum Window has copy, drop, store
Variants
- Variant PeriodicMs
- Windows of exactly this many milliseconds.
- 0: u64
- Variant CalendarMonths
- Windows of this many civil (UTC) months.
- 0: u8
Constants
#[error]
const ENotSpender: vector<u8> = b"Transaction sender is not this allowance's spender";
#[error]
const EExceedsLifetimeCap: vector<u8> = b"Spend would exceed the lifetime cap";
#[error]
const EExceedsRateLimit: vector<u8> = b"Spend would exceed the current rate-limit window";
#[error]
const EWrongAllowance: vector<u8> = b"Withdrawal was issued for a different allowance";
#[error]
const EBadRateLimit: vector<u8> = b"Rate limit needs a positive period and limit";
#[error]
const ENotStarted: vector<u8> = b"Allowance is not active yet; it has a future start timestamp";
#[error]
const EHasApp: vector<u8> = b"App-bound allowance: spend through app_balance_spend";
#[error]
const EWrongFunder: vector<u8> = b"Withdrawal debits a different address than this allowance's funder";
#[error]
const ENameTooLong: vector<u8> = b"Name exceeds the 128-byte limit";
#[error]
const EZeroLifetimeCap: vector<u8> = b"Lifetime cap must be greater than zero";
#[error]
const EBadTimeWindow: vector<u8> = b"Expiration must be after the start time";
#[error]
const ENoExpiration: vector<u8> = b"Allowance must have an expiration or a rate limit";
#[error]
const ENotEnabled: vector<u8> = b"Allowances are not enabled";
#[error]
const ESponsorWithdrawalNotEnabled: vector<u8> = b"Sponsor allowance withdrawals are not enabled";
const MAX_NAME_LENGTH: u64 = 128;
const MS_PER_DAY: u64 = 86400000;
Function spend_permit
Issues a SpendPermit<A> from the privileged internal::Permit<A>.
public fun spend_permit<A>(_: std::internal::Permit<A>): sui::allowance::SpendPermit<A>
Function settings_permit
Issues a SettingsPermit<A> from the privileged internal::Permit<A>.
public fun settings_permit<A>(_: std::internal::Permit<A>): sui::allowance::SettingsPermit<A>
Function periodic_rate_limit
At most limit per period_ms, counted from the first charge.
public fun periodic_rate_limit(period_ms: u64, limit: u256): sui::allowance::RateLimit
Function calendar_rate_limit
At most limit per months civil (UTC) months, counted from the first charge. Windows renew
on the anchor's day-of-month at 00:00 UTC, clamped to shorter months (a Jan 31 anchor renews.
Feb 28, then Mar 31).
public fun calendar_rate_limit(months: u8, limit: u256): sui::allowance::RateLimit
Function monthly_rate_limit
public fun monthly_rate_limit(limit: u256): sui::allowance::RateLimit
Function quarterly_rate_limit
public fun quarterly_rate_limit(limit: u256): sui::allowance::RateLimit
Function yearly_rate_limit
public fun yearly_rate_limit(limit: u256): sui::allowance::RateLimit
Function new
Issues an allowance funded by the sender, sharing it and sending its AllowanceCap to the sender. Creation is entry so contracts cannot create allowances implicitly.
entry fun new<T>(name: std::string::String, spender: address, lifetime_cap: std::option::Option<u256>, start_timestamp_ms: std::option::Option<u64>, expiration_timestamp_ms: std::option::Option<u64>, rate_limit: std::option::Option<sui::allowance::RateLimit>, ctx: &mut sui::tx_context::TxContext)
Function propose_for_app
Returns an AllowanceProposal for an allowance bound to the controlling app A, funded by the sender.
Unlike new, this creates no allowance on its own. A's module must accept the proposal via issue, giving the app a say in every allowance that names it.
entry fun propose_for_app<T, A>(name: std::string::String, spender: address, lifetime_cap: std::option::Option<u256>, start_timestamp_ms: std::option::Option<u64>, expiration_timestamp_ms: std::option::Option<u64>, rate_limit: std::option::Option<sui::allowance::RateLimit>, ctx: &sui::tx_context::TxContext): sui::allowance::AllowanceProposal<T>
Function issue
Issues the proposed allowance on A's behalf, creating and sharing it.
public fun issue<T, A>(proposal: sui::allowance::AllowanceProposal<T>, _: sui::allowance::SettingsPermit<A>, ctx: &mut sui::tx_context::TxContext)
Function balance_spend
Signer path: the tx sender must be the spender.
public fun balance_spend<C>(self: &mut sui::allowance::Allowance<sui::balance::Balance<C>>, w: sui::allowance::AllowanceWithdrawal<sui::balance::Balance<C>>, clock: &sui::clock::Clock, ctx: &sui::tx_context::TxContext): sui::balance::Balance<C>
Function app_balance_spend
App path: requires a SpendPermit<A> matching the allowance's app. The tx must still come from the spender.
public fun app_balance_spend<C, A>(self: &mut sui::allowance::Allowance<sui::balance::Balance<C>>, _: sui::allowance::SpendPermit<A>, w: sui::allowance::AllowanceWithdrawal<sui::balance::Balance<C>>, clock: &sui::clock::Clock, ctx: &sui::tx_context::TxContext): sui::balance::Balance<C>
Function revoke
Revokes an allowance, removing the ability to spend.
public fun revoke<T>(self: sui::allowance::AllowanceCap<T>, allowance: sui::allowance::Allowance<T>)
Function rotate_spender
Rotates the spender key without the funder reissuing the allowance.
App-only: for an app-bound allowance the app dictates who the spender is. Non-app allowances rotate through address aliases instead.
public fun rotate_spender<T, A>(self: &mut sui::allowance::Allowance<T>, _: sui::allowance::SettingsPermit<A>, new_spender: address)
Function allowance_settings
public fun allowance_settings<T>(self: &sui::allowance::Allowance<T>): &sui::allowance::Settings
Function allowance_current_spend
public fun allowance_current_spend<T>(self: &sui::allowance::Allowance<T>): u256
Function allowance_cap_allowance
public fun allowance_cap_allowance<T>(self: &sui::allowance::AllowanceCap<T>): sui::object::ID
Function allowance_proposal_settings
public fun allowance_proposal_settings<T>(self: &sui::allowance::AllowanceProposal<T>): &sui::allowance::Settings
Function funder
public fun funder(self: &sui::allowance::Settings): address
Function spender
public fun spender(self: &sui::allowance::Settings): std::option::Option<address>
Function app
public fun app(self: &sui::allowance::Settings): std::option::Option<std::type_name::TypeName>
Function lifetime_cap
public fun lifetime_cap(self: &sui::allowance::Settings): std::option::Option<u256>
Function start_timestamp_ms
public fun start_timestamp_ms(self: &sui::allowance::Settings): std::option::Option<u64>
Function expiration_timestamp_ms
public fun expiration_timestamp_ms(self: &sui::allowance::Settings): std::option::Option<u64>
Function rate_limit
public fun rate_limit(self: &sui::allowance::Settings): std::option::Option<sui::allowance::RateLimit>
Function name
public fun name(self: &sui::allowance::Settings): &std::string::String
Function rate_limit_limit
public fun rate_limit_limit(self: &sui::allowance::RateLimit): u256
Function rate_limit_spent
public fun rate_limit_spent(self: &sui::allowance::RateLimit): u256
Function rate_limit_window
public fun rate_limit_window(self: &sui::allowance::RateLimit): sui::allowance::Window
Function civil_from_ms
Civil (year, month, day) in UTC, month and day 1-based. This is Howard Hinnant's civil_from_days, where the derivation of every constant here is documented: https://howardhinnant.github.io/date_algorithms.html#civil_from_days
The unsigned-only form of the algorithm. Chain timestamps are never pre-epoch.
public(package) fun civil_from_ms(timestamp_ms: u64): (u64, u64, u64)
Function days_in_month
Companion to civil_from_ms, following the same reference: https://howardhinnant.github.io/date_algorithms.html#last_day_of_month
public(package) fun days_in_month(year: u64, month: u64): u64
Function assert_app
fun assert_app<T, A>(self: &sui::allowance::Allowance<T>)
Function consume
Central logic for policy checks and accounting, including the check of the spender. NB: Any app checks must be done beforehand by the caller.
fun consume<T: store>(self: &mut sui::allowance::Allowance<T>, w: sui::allowance::AllowanceWithdrawal<T>, clock: &sui::clock::Clock, ctx: &sui::tx_context::TxContext): sui::funds_accumulator::Withdrawal<T>
Function new_rate_limit
fun new_rate_limit(limit: u256, window: sui::allowance::Window): sui::allowance::RateLimit
Function charge
Records amount against the limit, aborting if it does not fit.
fun charge(self: &mut sui::allowance::RateLimit, amount: u256, now_ms: u64)
Function index_at
Which window now_ms falls in, numbered from anchor_ms (0 = first).
fun index_at(self: &sui::allowance::Window, anchor_ms: u64, now_ms: u64): u64
Function elapsed_windows
Which months-month window now_ms falls in, counting from anchor_ms (0 = the window the anchor itself is in).
fun elapsed_windows(anchor_ms: u64, now_ms: u64, months: u8): u64
Function is_leap_year
fun is_leap_year(year: u64): bool
Function new_settings
Builds and validates settings. Rejects allowances that are unbounded in amount or in time.
fun new_settings(funder: address, spender: address, app: std::option::Option<std::type_name::TypeName>, lifetime_cap: std::option::Option<u256>, start_timestamp_ms: std::option::Option<u64>, expiration_timestamp_ms: std::option::Option<u64>, rate_limit: std::option::Option<sui::allowance::RateLimit>, name: std::string::String): sui::allowance::Settings
Function share_new
fun share_new<T>(settings: sui::allowance::Settings, ctx: &mut sui::tx_context::TxContext)