SUP · Docs

Build a policy rule

A rule is a tiny Move package that lets a wallet owner gate how a delegate spends. It plugs into SupWallet::policy using only its public interface — no core changes, no permission. (To have an agent scaffold one, use the sup-policy-rule-dev skill.)

#Two hard requirements

  1. A witness typepublic struct MyRule has drop {}. Its full name 0x<pkg>::<module>::MyRule is the rule-type: the marketplace key and the R an owner attaches with add_auth_rule<R> / add_caveat_rule<R>. Only your module can build it ⇒ unforgeable.
  2. Touch a spend only by stamping — call policy::add_auth_receipt(MyRule {}, req) (auth, OR) or policy::add_caveat_receipt(MyRule {}, req) (caveat, AND) after your condition holds. Never take a vault Coin, never call confirm_spend, never transfer funds.

#Auth or caveat?

  • Auth answers "is this the right principal?" (address / cap / ZK / contract / agent) — OR-gated.
  • Caveat answers "is this spend allowed?" (budget / recipient / time / oracle / KYC) — AND-gated.

#The safety boundary

Your rule gets &mut SpendRequest, but the only mutation exposed is stamping your own witness. You can read policy::spend_amount / spend_recipient / spend_coin / spend_wallet_id / spend_policy_version, stamp, or abort. You cannot change the amount or recipient.

#A caveat rule

module my_rule::rule;

use SupWallet::wallet::{Self, Wallet};
use SupWallet::policy::{Self, SpendRequest};

public struct MyRule has drop {}

public struct Config has key, store { id: UID, wallet_id: ID, owner: address /*, params */ }

public fun create_and_share(wallet: &Wallet, ctx: &mut TxContext) {
    // owner-gate via the PUBLIC owner accessor — no core access needed
    assert!(tx_context::sender(ctx) == wallet::owner(wallet), 0);
    transfer::share_object(Config { id: object::new(ctx), wallet_id: wallet::id(wallet), owner: wallet::owner(wallet) });
}

public fun enforce(cfg: &Config, req: &mut SpendRequest) {
    assert!(cfg.wallet_id == policy::spend_wallet_id(req), 1);
    assert!(/* your condition over spend_amount / spend_recipient / spend_coin */, 2);
    policy::add_caveat_receipt(MyRule {}, req);
}

Owner enables it once with policy::add_caveat_rule<my_rule::rule::MyRule>(&mut wallet, ctx). A spend composes it in one PTB: begin_spend → enforce → confirm_spend.

#An auth rule (bearer cap)

Mint a cap bound to policy::version(wallet); in prove, assert cap.policy_version == policy::spend_policy_version(req) then add_auth_receipt. Binding to the version means the owner's revoke_all invalidates every outstanding cap for free.

#Register it (open marketplace)

policy_rule_registry::registry::register(
    registry, package_id, rule_type, kind /* auth | caveat */, manifest_uri, manifest_hash, clock, ctx)

On-chain stores only the anchor; put docs / params schema / audits off-chain at manifest_uri (hash-pinned). Listing grants no power — only an owner attaching the rule does.

#Rule ideas — a menu to build from

A rule is ~30 lines, lists permissionlessly, and any owner can opt in — so the design space is wide open. The built-ins cover only the obvious cases; here's what's missing and worth writing. (Auth = WHO may spend, OR-gated. Caveat = is the spend ALLOWED, AND-gated.)

Auth rules — new ways to be a principal

  • zkLogin / Enoki assessor — stamp if the request carries a valid zk proof for an OAuth identity.
  • Passkey / WebAuthn cap — a cap unlocked by a device passkey signature.
  • M-of-N threshold — release only once N distinct co-signer caps have stamped (on-chain multisig).
  • NFT- or attestation-gated — whoever holds a membership NFT or a SuiNS/attestation may spend.
  • Time-locked cap — a cap that only becomes valid after a date (vesting cliff, scheduled handoff).
  • Oracle-signed geofence / KYC — stamp only with a fresh signed claim from an approved provider.

Caveat rules — new conditions on a spend

  • Time window — business hours only, or after a vesting cliff, or before an expiry.
  • Velocity / rate limit — at most N spends or X total per rolling day; a cooldown between spends.
  • Price / TWAP guard — only allow a swap-spend if the oracle price sits inside a band (anti-MEV).
  • Spend-category budget — separate envelopes (groceries vs. entertainment) each with their own cap.
  • Recipient denylist / category — the inverse of the allowlist, or "only approved protocol contracts".
  • Quorum approval — a human (or second agent) must co-stamp spends over a threshold.
  • Dead-man / inactivity gate — only releases after the owner has been silent for N days.
  • Destination memo required — enforce an invoice id / reference tag for accounting.

Each is a tiny, separately-deployed package — so one good rule (say, a velocity limiter) becomes infrastructure every Sup owner can attach.

#Reference

The reference caveat rule (public-interface only) is policy_rule_recipient_allowlist. The built-in cap_auth (auth) and sub_delegate (auth + caveat) packages are worked examples. Full design lives in the contract's DELEGATION_POLICY.md and ARCHITECTURE.md.

#Use the agent skill

This whole guide is packaged as an installable skill so your coding agent can scaffold a conforming rule (witness + enforce/prove + tests + manifest) and register it for you.

📥 View / download the skill → /llms/policy-rule-dev.md  ·  on GitHub: view · raw

Save it for your agent: curl -sL $SUP/llms/policy-rule-dev.md -o SKILL.md. To design a policy (which rules to compose) rather than write one, see the Delegation policy guide's design skill.