API Reference

Sale API Reference

This page documents the public API of openzeppelin_sale for OpenZeppelin Contracts for Sui v1.x.

The package centers on prefunded_sale - the fixed-price sale object and its lifecycle - and the built-in fixed_rate_curve that prices it. Three supporting modules complete it: refund_vault (the escrow that guarantees refunds), allowlist (the typed compliance slot), and receipt (the buyer-bound claim ticket). All functions that observe time take &Clock (the shared Sui Clock singleton at 0x6).

use openzeppelin_sale::prefunded_sale;

A fixed-price, pre-funded token sale. Created as an owned value during Init, then shared on activation. The sale draws from a fixed Balance<SaleCoin> inventory and never holds a TreasuryCap.

PrefundedSale carries six type parameters - <Curve, CurveParams, SaleCoin, PaymentCoin, VestingWitness, VestingScheduleParams> - all fixed at create_sale. Signatures below abbreviate the receiver as PrefundedSale<..>; SaleCoin, PaymentCoin, CurveParams, and the vesting slots are named directly where a signature needs them.

Types

Functions

Events

Errors

Types

struct PrefundedSale<phantom Curve, CurveParams, phantom SaleCoin, phantom PaymentCoin, phantom VestingWitness, VestingScheduleParams> has key

struct

#

The sale object. Holds the inventory, collected proceeds, caps, window, phase, the paired vault's id and controller cap, the optional per-buyer contributions table, and the optional vesting schedule. key-only: created owned in Init, shared on activation, and never deleted.

VestingWitness is the drop-only schedule witness (e.g. vesting_wallet_linear::Linear); pinning it in the type is what makes an attached vesting lockup unbypassable. For a non-vesting sale the vesting slots are inert.

enum Phase has copy, drop, store

struct

#

The lifecycle phase: Init (owned, under setup), Active (shared, accepting purchases within the window), Finalized (successful close), or Cancelled (failed close). Finalized and Cancelled are terminal. Read it with phase.

struct SaleAdminCap<phantom SaleCoin, phantom PaymentCoin> has key, store

struct

#

Admin capability for a single sale, bound to it by id. Gates cancel_emergency, withdraw_proceeds, and withdraw_unsold_inventory. Losing it never strands buyer funds - all buyer-facing flows are permissionless.

struct ActivationTicket<phantom Curve>

struct

#

Witness-gated, single-use carrier for share_and_activate. Has no abilities, so it must be minted and consumed in the same PTB. Pins the sale_id and the curve's committed required_inventory.

struct Quote<phantom PaymentCoin>

struct

#

Hot-potato carrying a curve-priced quote for a single purchase: the buyer's payment Balance welded to the curve-computed allocation, pinned to a sale_id. Has no abilities, so it can only be minted by the sale's curve module (via mint_quote) and consumed by purchase, in the same PTB. Cannot be stored, copied, replayed, or transferred.

enum CancelReason has copy, drop, store

struct

#

The reason a sale was cancelled, carried by SaleCancelled: SoftCapMissed (the window closed below the minimum raise) or AdminEmergency (an admin cancelled while the sale was still open).

Functions

create_sale<Curve, CurveParams, SaleCoin, PaymentCoin, VestingWitness, VestingScheduleParams>(curve_params: CurveParams, hard_cap: u64, soft_cap: u64, opens_at_ms: u64, closes_at_ms: u64, ctx: &mut TxContext) -> (PrefundedSale<..>, SaleAdminCap<SaleCoin, PaymentCoin>)

public

#

Creates a sale in Init phase, returning it as an owned value plus its admin cap. The caller threads the sale through the setup calls below and then share_and_activate. curve_params is opaque to the sale; build it with the curve module's params constructor. soft_cap == 0 means no soft cap.

Aborts with EHardCapZero if hard_cap == 0.

Aborts with EInvalidCapsOrdering if soft_cap > hard_cap.

Aborts with EInvalidTimeRange if opens_at_ms >= closes_at_ms.

Emits SaleCreated.

deposit(sale: &mut PrefundedSale<..>, inventory: Balance<SaleCoin>)

public

#

Adds sale tokens to inventory. May be called multiple times during Init. Authority is implicit: the sale is owned.

Aborts with ENotInit if the sale is not in Init phase.

Emits InventoryDeposited.

set_per_buyer_cap(sale: &mut PrefundedSale<..>, per_buyer_cap: u64, ctx: &mut TxContext)

public

#

Configures a cumulative per-buyer payment cap, enforced inside purchase against each buyer's running total. One-shot.

Aborts with ENotInit if the sale is not in Init phase.

Aborts with EPerBuyerCapAlreadySet if a per-buyer cap is already configured.

Aborts with EPerBuyerCapZero if per_buyer_cap == 0.

Emits PerBuyerCapSet.

set_vesting_schedule_params(sale: &mut PrefundedSale<..>, params: VestingScheduleParams)

public

#

Attaches an issuer-defined vesting schedule. Once set, plain claim aborts and redemption must go through claim_into_vesting. The schedule is unbypassable - the wallet is built under the sale's pinned VestingWitness. One-shot, Init-only.

Aborts with ENotInit if the sale is not in Init phase.

Aborts with EVestingScheduleAlreadySet if a schedule is already configured.

Emits VestingScheduleParamsSet.

pair_refund_vault(sale: &mut PrefundedSale<..>, vault: &RefundVault<PaymentCoin>, vault_cap: RefundVaultCap<PaymentCoin>)

public

#

Pairs a refund vault with the sale, required before activation. The cap is consumed into the sale (never returned); from then on only the sale's gated functions drive the vault. The vault must be Active and empty.

Aborts with ENotInit if the sale is not in Init phase.

Aborts with EVaultAlreadyPaired if a vault has already been paired.

Aborts with EWrongVault if vault_cap does not control vault.

Aborts with EVaultNotActive if vault is not in the Active state.

Aborts with EVaultNotEmpty if vault holds a non-zero balance.

Emits RefundVaultPaired.

enable_allowlist(sale: &mut PrefundedSale<..>, ctx: &mut TxContext) -> AllowlistAdmin<SaleCoin>

public

#

Switches the sale into compliance-gated mode and returns the single AllowlistAdmin<SaleCoin>, to be wrapped in the consumer's compliance module. After this, every purchase must consume an AllowEntry. One-shot.

Aborts with ENotInit if the sale is not in Init phase.

Aborts with EAllowlistAlreadyEnabled if the allowlist is already enabled.

Emits AllowlistEnabled.

mint_activation_ticket<..>(sale: &PrefundedSale<..>, w: Curve, required_inventory: u64) -> ActivationTicket<Curve>

public

#

Witness-gated constructor for the ActivationTicket that share_and_activate consumes. Requires a value of type Curve, whose constructor is private to the declaring curve module, so only that module can mint a ticket for its sale. Curve modules wrap it (e.g. fixed_rate_curve::activation_ticket).

share_and_activate(sale: PrefundedSale<..>, vault: RefundVault<PaymentCoin>, ticket: ActivationTicket<Curve>, clock: &Clock)

public

#

Transitions Init -> Active and shares both the sale and its paired vault, consuming all three arguments. Taking the vault by value and sharing it here is what makes the permissionless refund guarantee structural: the paths that need &mut vault can never be bricked by a forgotten share step. Activation after closes_at_ms is rejected.

Aborts with ETicketSaleMismatch if ticket was minted for a different sale.

Aborts with ENotInit if the sale is not in Init phase.

Aborts with EVaultRequiredForActivate if no refund vault has been paired.

Aborts with EWrongVault if vault is not the one paired with this sale.

Aborts with EActivationAfterClose if now >= closes_at_ms.

Aborts with EInsufficientInventoryAtActivate if inventory < required_inventory.

Emits SaleActivated.

purchase(sale: &mut PrefundedSale<..>, quote: Quote<PaymentCoin>, allow: Option<AllowEntry<SaleCoin>>, clock: &Clock, ctx: &mut TxContext)

public

#

Buys sale tokens, delivering a Receipt<SaleCoin> to ctx.sender() and adding the payment to proceeds. Applies the quote's allocation verbatim (the curve is trusted), bounded only by unallocated inventory and overflow. The hard cap is all-or-nothing - a payment past hard_cap reverts in full. Pass Some(entry) iff the sale requires an allowlist.

Aborts with ENotActive if the sale is not in Active phase.

Aborts with EQuoteSaleMismatch if quote was minted for a different sale.

Aborts with ESaleWindowClosed if now is outside [opens_at_ms, closes_at_ms].

Aborts with EAllowlistRequired / EAllowlistNotRequired if the entry is missing when required, or supplied when not.

Aborts with allowlist::EWrongSaleId / allowlist::EWrongBuyer if the entry was issued for a different sale or buyer.

Aborts with ERaisedOverflow if raised + paid would exceed u64::MAX.

Aborts with EHardCapExceeded if raised + paid would exceed hard_cap.

Aborts with EPerEntryCapExceeded if paid exceeds the entry's max_amount.

Aborts with EPerBuyerCapExceeded if paid exceeds the buyer's remaining per-buyer cap.

Aborts with EInsufficientInventory if allocation exceeds unallocated inventory (only reachable via a dishonest curve).

Emits Purchased.

finalize(sale: &mut PrefundedSale<..>, vault: &mut RefundVault<PaymentCoin>, clock: &Clock)

public

#

Closes the sale as a success and flips the paired vault to Closed. Permissionless. Allowed once the window has closed with the soft cap met, or as soon as the hard cap is reached (closing early). Proceeds stay in the sale for the admin to withdraw.

Aborts with ENotActive if the sale is not in Active phase.

Aborts with ESaleWindowStillOpen if the window is still open and the hard cap is not reached.

Aborts with ESoftCapNotMet if raised < soft_cap.

Aborts with EWrongVault if vault is not the one paired with this sale.

Emits SaleFinalized.

cancel_after_close(sale: &mut PrefundedSale<..>, vault: &mut RefundVault<PaymentCoin>, clock: &Clock)

public

#

Closes the sale as a soft-cap miss. Permissionless. Drains proceeds into the vault and flips it to Refunding. Allowed once the window has closed with a configured soft cap that was missed.

Aborts with ENotActive if the sale is not in Active phase.

Aborts with ESaleWindowStillOpen if the window has not yet closed.

Aborts with ESoftCapMet if no soft cap is configured or raised >= soft_cap.

Aborts with EWrongVault if vault is not the one paired with this sale.

Emits SaleCancelled.

cancel_emergency(sale: &mut PrefundedSale<..>, cap: &SaleAdminCap<SaleCoin, PaymentCoin>, vault: &mut RefundVault<PaymentCoin>, clock: &Clock)

public

#

Emergency cancellation. Admin-only. Drains proceeds into the vault and flips it to Refunding. Allowed only while the window is open and the sale has not met its goal - the guards prevent rugging a successful sale. Pre-open cancel is permitted.

Aborts with EWrongAdminCap if cap was issued for a different sale.

Aborts with ENotActive if the sale is not in Active phase.

Aborts with EEmergencyCancelAfterClose if now > closes_at_ms.

Aborts with ESaleAlreadyComplete if raised >= hard_cap.

Aborts with ESoftCapMet if a soft cap is configured and raised >= soft_cap.

Aborts with EWrongVault if vault is not the one paired with this sale.

Emits SaleCancelled.

claim(sale: &mut PrefundedSale<..>, receipt: Receipt<SaleCoin>, ctx: &mut TxContext) -> Balance<SaleCoin>

public

#

Redeems a receipt for its allocation, returned as Balance<SaleCoin> split from inventory, and destroys the receipt. A vesting sale must use claim_into_vesting instead.

Aborts with EClaimRequiresVesting if the sale has a vesting schedule attached.

Aborts with ENotFinalized if the sale is not in Finalized phase.

Aborts with EReceiptSaleMismatch if receipt was issued by a different sale.

Aborts with EBuyerOnly if ctx.sender() is not the receipt's buyer.

Emits Claimed.

claim_all(sale: &mut PrefundedSale<..>, receipts: vector<Receipt<SaleCoin>>, ctx: &mut TxContext) -> Balance<SaleCoin>

public

#

Batches claim over several receipts, summing their allocations into one balance. Aborts the whole call if any receipt is invalid.

Aborts with EClaimRequiresVesting if the sale has a vesting schedule attached.

Aborts with ENotFinalized if the sale is not in Finalized phase.

Aborts with EReceiptSaleMismatch if any receipt was issued by a different sale.

Aborts with EBuyerOnly if ctx.sender() is not the buyer of any receipt.

Emits Claimed per receipt.

claim_into_vesting(sale: &mut PrefundedSale<..>, receipt: Receipt<SaleCoin>, ctx: &mut TxContext) -> (VestingWallet<VestingWitness, VestingScheduleParams, SaleCoin>, DestroyCap)

public

#

The only redemption path for a vesting sale. Redeems a receipt into a funded VestingWallet (from openzeppelin_finance) with beneficiary forced to the buyer and the sale's fixed schedule, plus the wallet's DestroyCap. Every type argument is inferred from the sale.

Aborts with ENoVestingScheduleAttached if the sale has no vesting schedule (use claim).

Aborts with ENotFinalized if the sale is not in Finalized phase.

Aborts with EReceiptSaleMismatch if receipt was issued by a different sale.

Aborts with EBuyerOnly if ctx.sender() is not the receipt's buyer.

Emits Claimed.

claim_all_into_vesting(sale: &mut PrefundedSale<..>, receipts: vector<Receipt<SaleCoin>>, ctx: &mut TxContext) -> (VestingWallet<VestingWitness, VestingScheduleParams, SaleCoin>, DestroyCap)

public

#

Batch variant of claim_into_vesting: redeems several receipts into one funded VestingWallet, summing their allocations. Aborts the whole call if any receipt is invalid.

Aborts with ENoVestingScheduleAttached if the sale has no vesting schedule (use claim_all).

Aborts with ENotFinalized if the sale is not in Finalized phase.

Aborts with EReceiptSaleMismatch if any receipt was issued by a different sale.

Aborts with EBuyerOnly if ctx.sender() is not the buyer of any receipt.

Emits Claimed per receipt.

withdraw_proceeds(sale: &mut PrefundedSale<..>, cap: &SaleAdminCap<SaleCoin, PaymentCoin>) -> Balance<PaymentCoin>

public

#

Withdraws all collected proceeds. Admin-only, Finalized-only. Idempotent: a second call returns an empty balance and emits nothing.

Aborts with EWrongAdminCap if cap was issued for a different sale.

Aborts with ENotFinalized if the sale is not in Finalized phase.

Emits ProceedsWithdrawn (only when the amount is non-zero).

withdraw_unsold_inventory(sale: &mut PrefundedSale<..>, cap: &SaleAdminCap<SaleCoin, PaymentCoin>) -> Balance<SaleCoin>

public

#

Withdraws strictly the unallocated inventory (inventory - total_allocated); tokens backing outstanding receipts stay put. Admin-only, valid in Finalized or Cancelled. Idempotent.

Aborts with EWrongAdminCap if cap was issued for a different sale.

Aborts with ENotTerminal if the sale is neither Finalized nor Cancelled.

Emits InventoryWithdrawn (only when the amount is non-zero).

refund(sale: &mut PrefundedSale<..>, vault: &mut RefundVault<PaymentCoin>, receipt: Receipt<SaleCoin>, ctx: &mut TxContext) -> Balance<PaymentCoin>

public

#

Refunds a buyer's payment from the paired vault, returning exactly the receipt's paid amount and destroying the receipt. Permissionless but buyer-bound. Never depends on admin liveness.

Aborts with ENotCancelled if the sale is not in Cancelled phase.

Aborts with EReceiptSaleMismatch if receipt was issued by a different sale.

Aborts with EBuyerOnly if ctx.sender() is not the receipt's buyer.

Aborts with EWrongVault if vault is not the one paired with this sale.

Emits Refunded.

mint_quote<..>(sale: &PrefundedSale<..>, w: Curve, payment: Balance<PaymentCoin>, rate: u64) -> Quote<PaymentCoin>

public

#

Witness-gated quote constructor. The curve module calls this from its own quote(..) after running its pricing math; the allocation is payment.value() * rate. Requires a Curve value, so a caller cannot mint a quote without the declaring curve module.

Aborts with EZeroPayment if payment has zero value.

Aborts with EAllocationOverflow if payment.value() * rate would exceed u64::MAX.

phase(sale: &PrefundedSale<..>) -> Phase

public

#

The sale's current lifecycle phase.

raised(sale: &PrefundedSale<..>) -> u64

public

#

The total payment raised so far, in PaymentCoin units.

curve_params(sale: &PrefundedSale<..>) -> CurveParams

public

#

The stored curve configuration. Opaque to the sale; interpreted by the declaring curve module.

hard_cap(sale: &PrefundedSale<..>) -> u64

public

#

The configured hard cap (maximum raise), in PaymentCoin units.

soft_cap(sale: &PrefundedSale<..>) -> u64

public

#

The configured soft cap (minimum raise to finalize); 0 means none.

opens_at_ms(sale: &PrefundedSale<..>) -> u64

public

#

The start of the purchase window (ms).

closes_at_ms(sale: &PrefundedSale<..>) -> u64

public

#

The end of the purchase window (ms).

requires_allowlist(sale: &PrefundedSale<..>) -> bool

public

#

Whether purchases require an AllowEntry (allowlist mode).

vesting_schedule_params(sale: &PrefundedSale<..>) -> Option<VestingScheduleParams>

public

#

Some(params) if a vesting schedule was attached during Init, otherwise None.

inventory_total(sale: &PrefundedSale<..>) -> u64

public

#

Total inventory currently held (allocated plus unallocated).

total_allocated(sale: &PrefundedSale<..>) -> u64

public

#

Sale tokens promised to outstanding (unredeemed) receipts.

inventory_remaining(sale: &PrefundedSale<..>) -> u64

public

#

Unallocated inventory: inventory_total - total_allocated.

proceeds_amount(sale: &PrefundedSale<..>) -> u64

public

#

Payment currently held as proceeds (before withdrawal or cancel).

is_open(sale: &PrefundedSale<..>, clock: &Clock) -> bool

public

#

Whether a purchase would pass its phase and window checks right now.

has_reached_soft_cap(sale: &PrefundedSale<..>) -> bool

public

#

Whether raised >= soft_cap. Always true when no soft cap is configured.

has_reached_hard_cap(sale: &PrefundedSale<..>) -> bool

public

#

Whether raised >= hard_cap (sold out and able to finalize early).

cap_sale_id(c: &SaleAdminCap<SaleCoin, PaymentCoin>) -> ID

public

#

The id of the sale this admin cap controls.

sale_id(q: &Quote<PaymentCoin>) -> ID

public

#

The id of the sale this quote was minted for.

payment(q: &Quote<PaymentCoin>) -> &Balance<PaymentCoin>

public

#

The payment balance carried by this quote.

allocation(q: &Quote<PaymentCoin>) -> u64

public

#

The curve-computed allocation this quote will deliver on purchase.

Events

Every event is stamped with sale_id. Payloads are phantom-typed on SaleCoin and PaymentCoin so the event type identifies the coins involved.

SaleCreated(sale_id, hard_cap, soft_cap, opens_at_ms, closes_at_ms, curve_params)

event

#

Emitted by create_sale.

InventoryDeposited(sale_id, amount, new_inventory)

event

#

Emitted by deposit when sale tokens are added to inventory.

PerBuyerCapSet(sale_id, cap)

event

#

Emitted by set_per_buyer_cap.

VestingScheduleParamsSet(sale_id, params)

event

#

Emitted by set_vesting_schedule_params.

RefundVaultPaired(sale_id, vault_id)

event

#

Emitted by pair_refund_vault.

AllowlistEnabled(sale_id, allowlist_admin_id)

event

#

Emitted by enable_allowlist.

SaleActivated(sale_id, activated_at_ms)

event

#

Emitted by share_and_activate when the sale goes live.

Purchased(sale_id, buyer, receipt_id, paid, allocation, raised_after, purchased_at_ms)

event

#

Emitted by purchase for each successful buy.

SaleFinalized(sale_id, raised, closed_at_ms)

event

#

Emitted by finalize when the sale closes successfully.

SaleCancelled(sale_id, raised, reason, closed_at_ms)

event

#

Emitted by cancel_after_close and cancel_emergency. reason is a CancelReason.

Claimed(sale_id, buyer, receipt_id, amount)

event

#

Emitted for each receipt redeemed through claim / claim_all and the vesting variants.

Refunded(sale_id, buyer, receipt_id, amount)

event

#

Emitted by refund when a buyer recovers their payment.

ProceedsWithdrawn(sale_id, amount)

event

#

Emitted by withdraw_proceeds.

InventoryWithdrawn(sale_id, amount)

event

#

Emitted by withdraw_unsold_inventory.

Errors

EWrongAdminCap (code 0)

error

#

The supplied SaleAdminCap was issued for a different sale.

EBuyerOnly (code 1)

error

#

A redemption path was called by an address other than the receipt's buyer.

EEmergencyCancelAfterClose (code 2)

error

#

cancel_emergency was called after the window closed; use cancel_after_close.

EInvalidTimeRange (code 3)

error

#

create_sale was given opens_at_ms >= closes_at_ms.

ESaleWindowClosed (code 4)

error

#

A purchase was attempted outside [opens_at_ms, closes_at_ms].

ESaleWindowStillOpen (code 5)

error

#

A close was attempted while the window is still open and the hard cap is not reached.

EActivationAfterClose (code 6)

error

#

share_and_activate was called after closes_at_ms had already passed.

EHardCapZero (code 7)

error

#

create_sale was given hard_cap == 0.

EInvalidCapsOrdering (code 8)

error

#

create_sale was given soft_cap > hard_cap.

EZeroPayment (code 9)

error

#

A quote was requested for a zero-value payment.

ERaisedOverflow (code 10)

error

#

A purchase would push raised + paid past u64::MAX.

EHardCapExceeded (code 11)

error

#

A purchase would push raised past hard_cap. The cap is all-or-nothing.

EInsufficientInventoryAtActivate (code 12)

error

#

At activation, inventory did not cover the backing the curve's ActivationTicket requires.

EAllocationOverflow (code 13)

error

#

A quote's allocation (paid * rate) would exceed u64::MAX.

EInsufficientInventory (code 14)

error

#

A purchase's allocation exceeded unallocated inventory. Only reachable via a dishonest curve.

EPerBuyerCapExceeded (code 15)

error

#

A purchase would push the buyer's cumulative payment past the per-buyer cap.

EPerEntryCapExceeded (code 16)

error

#

A purchase's payment exceeded the consumed AllowEntry's max_amount.

ESoftCapNotMet (code 17)

error

#

finalize was called with raised < soft_cap.

ESoftCapMet (code 18)

error

#

A cancel was attempted on a sale that has met its goal, or has no soft cap set.

ESaleAlreadyComplete (code 19)

error

#

cancel_emergency was called on a sold-out sale (raised >= hard_cap); it must finalize.

EAllowlistRequired (code 20)

error

#

The sale requires an AllowEntry but purchase was called without one.

EAllowlistNotRequired (code 21)

error

#

The sale does not require an AllowEntry but purchase was given one.

EAllowlistAlreadyEnabled (code 22)

error

#

enable_allowlist was called a second time on the same sale.

EVaultAlreadyPaired (code 23)

error

#

pair_refund_vault was called after a vault had already been paired.

EVaultRequiredForActivate (code 24)

error

#

share_and_activate was called before a refund vault was paired.

EWrongVault (code 25)

error

#

The vault passed to a sale operation is not the one paired with this sale.

EVaultNotActive (code 26)

error

#

The vault offered to pair_refund_vault was not in the Active state.

EVaultNotEmpty (code 27)

error

#

The vault offered to pair_refund_vault held a non-zero balance.

EReceiptSaleMismatch (code 28)

error

#

A receipt passed to claim / refund was issued by a different sale.

EQuoteSaleMismatch (code 29)

error

#

A quote passed to purchase was minted for a different sale.

ETicketSaleMismatch (code 30)

error

#

An activation ticket passed to share_and_activate was minted for a different sale.

EPerBuyerCapAlreadySet (code 31)

error

#

set_per_buyer_cap was called a second time on the same sale.

EPerBuyerCapZero (code 32)

error

#

set_per_buyer_cap was given 0, which would block every buyer.

EVestingScheduleAlreadySet (code 33)

error

#

set_vesting_schedule_params was called a second time on the same sale.

EClaimRequiresVesting (code 34)

error

#

Plain claim was called on a vesting sale; redeem via claim_into_vesting.

ENoVestingScheduleAttached (code 35)

error

#

claim_into_vesting was called on a sale with no vesting schedule; use claim.

ENotInit (code 36)

error

#

A setup operation required the Init phase but the sale was past it.

ENotActive (code 37)

error

#

An operation required the Active phase: the sale was not yet activated, or has already closed.

ENotFinalized (code 38)

error

#

An operation required the Finalized phase (e.g. a claim before finalize).

ENotCancelled (code 39)

error

#

An operation required the Cancelled phase (e.g. a refund before cancel).

ENotTerminal (code 40)

error

#

An operation required a terminal phase (Finalized or Cancelled).

use openzeppelin_sale::fixed_rate_curve;

The built-in pricing curve: allocation = paid * rate, fixed for the whole sale. Declares the FixedRateCurve witness and its Params, and the integrator API around them. Because the witness constructor is module-private, only this module can mint a Quote or ActivationTicket for a FixedRateCurve sale - so such a sale is un-priceable by any other code. The params constructor is the seam a protocol driving prefunded_sale::create_sale directly uses to build the config.

Types

Functions

Errors

Types

struct FixedRateCurve has drop

struct

#

The curve witness. Field-less, drop-only, with a module-private constructor, so no other module can mint a Quote<FixedRateCurve> or activation ticket. This is the security boundary that makes the curve first-party and trusted.

struct Params has copy, drop, store

struct

#

The fixed-rate parameters stored on the sale via curve_params: rate, the sale tokens (smallest units) allocated per 1 payment-coin smallest unit. Obtain a validated value via params.

Functions

params(rate: u64) -> Params

public

#

Builds a validated Params. The only way to obtain a Params outside this module, so a protocol driving create_sale directly can build the config itself.

Aborts with ERateZero if rate == 0.

activation_ticket<..>(sale: &PrefundedSale<FixedRateCurve, Params, ..>) -> ActivationTicket<FixedRateCurve>

public

#

Mints the ActivationTicket that share_and_activate consumes, committing the inventory backing this curve requires: hard_cap * rate.

Aborts with ERequiredInventoryOverflow if hard_cap * rate would exceed u64::MAX.

quote<..>(sale: &PrefundedSale<FixedRateCurve, Params, ..>, balance: Balance<PaymentCoin>) -> Quote<PaymentCoin>

public

#

Mints a Quote for a buyer's payment balance, allocating balance.value() * rate. The Quote carries the balance through to purchase, welding pricing and funds together for one PTB.

Aborts with prefunded_sale::EZeroPayment if balance has zero value.

Aborts with prefunded_sale::EAllocationOverflow if balance.value() * rate would exceed u64::MAX.

rate<..>(sale: &PrefundedSale<FixedRateCurve, Params, ..>) -> u64

public

#

The configured fixed rate: sale tokens allocated per 1 payment-coin unit.

Errors

ERateZero (code 0)

error

#

params was called with rate == 0, which allocates nothing for any payment.

ERequiredInventoryOverflow (code 1)

error

#

hard_cap * rate would exceed u64::MAX, so the required inventory backing cannot be represented.

use openzeppelin_sale::refund_vault;

A generic refundable escrow over Balance<P>, with no knowledge of sales - usable standalone. Every mutation requires the matching RefundVaultCap<P>. State transitions one way: Active -> Refunding (depositors claim individually) or Active -> Closed (the controller withdraws all). When paired with a sale, pair_refund_vault consumes the cap into the sale, and from then on only the sale's gated functions drive the vault.

Types

Functions

Events

Errors

Types

struct RefundVault<phantom P> has key

struct

#

A refundable escrow holding a locked Balance<P> and a lifecycle state. key-only; share it with share.

struct RefundVaultCap<phantom P> has key, store

struct

#

Controller capability, bound to a vault by id and phantom-typed on P so it cannot be paired with a vault or sale of a different payment coin.

enum VaultState has copy, drop, store

struct

#

The vault's lifecycle state: Active (accepting deposits), Refunding (per-amount releases), or Closed (a single full withdrawal). Transitions are one-way from Active.

Functions

new<P>(ctx: &mut TxContext) -> (RefundVault<P>, RefundVaultCap<P>)

public

#

Creates a fresh, empty vault in Active state and its controller cap. The typical paired-sale flow is new -> pair with a sale -> the sale shares it on activation.

Emits RefundVaultCreated.

share<P>(vault: RefundVault<P>)

public

#

Shares a vault. Provided because RefundVault<P> is key-only, so external modules cannot share it directly.

deposit<P>(vault: &mut RefundVault<P>, cap: &RefundVaultCap<P>, funds: Balance<P>)

public

#

Adds funds to the locked balance. A zero-value deposit is a no-op. Vault must be Active.

Aborts with EWrongVaultCap if cap does not control vault.

Aborts with ENotActiveState if vault is not Active.

Emits VaultDeposit (only when the amount is non-zero).

flip_to_refunding<P>(vault: &mut RefundVault<P>, cap: &RefundVaultCap<P>)

public

#

Transitions Active -> Refunding, enabling per-amount releases.

Aborts with EWrongVaultCap if cap does not control vault.

Aborts with ENotActiveState if vault is not Active.

Emits VaultStateChanged.

flip_to_closed<P>(vault: &mut RefundVault<P>, cap: &RefundVaultCap<P>)

public

#

Transitions Active -> Closed, enabling withdraw_all.

Aborts with EWrongVaultCap if cap does not control vault.

Aborts with ENotActiveState if vault is not Active.

Emits VaultStateChanged.

release_balance<P>(vault: &mut RefundVault<P>, cap: &RefundVaultCap<P>, amount: u64) -> Balance<P>

public

#

Releases a specific amount from the locked balance. Vault must be Refunding.

Aborts with EWrongVaultCap if cap does not control vault.

Aborts with ENotRefundingState if vault is not Refunding.

Aborts with EInsufficientLocked if amount exceeds the locked balance.

Emits VaultRelease.

withdraw_all<P>(vault: &mut RefundVault<P>, cap: &RefundVaultCap<P>) -> Balance<P>

public

#

Withdraws the entire locked balance. Vault must be Closed.

Aborts with EWrongVaultCap if cap does not control vault.

Aborts with ENotClosedState if vault is not Closed.

Emits VaultRelease.

state<P>(vault: &RefundVault<P>) -> VaultState

public

#

The vault's current state.

value<P>(vault: &RefundVault<P>) -> u64

public

#

The locked balance amount.

cap_vault_id<P>(cap: &RefundVaultCap<P>) -> ID

public

#

The id of the vault this cap controls.

is_active<P>(vault: &RefundVault<P>) -> bool

public

#

True if the vault is Active.

is_refunding<P>(vault: &RefundVault<P>) -> bool

public

#

True if the vault is Refunding.

is_closed<P>(vault: &RefundVault<P>) -> bool

public

#

True if the vault is Closed.

Events

Every event is stamped with vault_id.

RefundVaultCreated(vault_id)

event

#

Emitted by new.

VaultDeposit(vault_id, amount, locked_after)

event

#

Emitted by deposit when funds are added.

VaultStateChanged(vault_id, old_state, new_state)

event

#

Emitted by flip_to_refunding and flip_to_closed.

VaultRelease(vault_id, amount, locked_after)

event

#

Emitted by release_balance and withdraw_all when funds leave the vault.

Errors

ENotActiveState (code 0)

error

#

A state-gated operation (deposit, flip_to_*) required the Active state.

ENotRefundingState (code 1)

error

#

release_balance was called while the vault was not Refunding.

ENotClosedState (code 2)

error

#

withdraw_all was called while the vault was not Closed.

EWrongVaultCap (code 3)

error

#

The supplied cap does not control this vault.

EInsufficientLocked (code 4)

error

#

A release requested more than the vault's locked balance.

use openzeppelin_sale::allowlist;

A typed slot for compliance hooks. The library ships no verification logic; it defines two types your compliance module wires its own scheme against: AllowlistAdmin<S>, the owned authority to approve buyers (issued once by enable_allowlist), and AllowEntry<S>, a single-use, ability-less compliance ticket your module mints per verified buyer and purchase consumes in the same PTB.

Types

Functions

Errors

Types

struct AllowEntry<phantom S>

struct

#

A single-use compliance ticket. No abilities, so it cannot be stored, copied, replayed, or transferred - it must be minted and consumed in the same transaction. Carries the sale_id, the approved buyer, and a per-entry max_amount (0 = no per-entry cap).

struct AllowlistAdmin<phantom S> has key, store

struct

#

The authority to mint AllowEntry<S> for a specific sale. Owned and transferable so the consumer can wrap it in an access-controlled compliance module. Losing it bricks the allowlist sale.

Functions

new_entry<S>(admin: &AllowlistAdmin<S>, buyer: address, max_amount: u64) -> AllowEntry<S>

public

#

Mints a fresh entry, called by the compliance module after running its verification. max_amount is per-entry, not cumulative (0 = no per-entry cap); for a cumulative bound use the sale's set_per_buyer_cap.

admin_sale_id<S>(admin: &AllowlistAdmin<S>) -> ID

public

#

The id of the sale this admin gates entries for.

Errors

EWrongSaleId (code 0)

error

#

The consumed AllowEntry was issued for a different sale than the one consuming it.

EWrongBuyer (code 1)

error

#

The consumed AllowEntry's buyer does not match the transaction sender.

use openzeppelin_sale::receipt;

The per-buyer claim ticket issued by every sale flavor. Records one purchase - the issuing sale, buyer, amount paid, token allocation, and purchase time - and is the object the buyer later redeems via claim or refund. Construction, delivery, and consumption are package-internal; integrators only read it through the getters below.

Receipt<S> has the key ability only (no store), so it cannot be transferred with public_transfer, wrapped, or stored as a field elsewhere. The single transfer path is delivery to the buyer at purchase time, and claim / refund assert the sender is the recorded buyer - no wallet rotation, and KYC at purchase carries through to distribution.

Types

Functions

Types

struct Receipt<phantom S> has key

struct

#

The non-transferable, buyer-bound claim ticket. key-only. One receipt per purchase; a buyer with several purchases holds several receipts.

Functions

sale_id<S>(r: &Receipt<S>) -> ID

public

#

The id of the sale that issued this receipt.

buyer<S>(r: &Receipt<S>) -> address

public

#

The address that purchased, and the only address that may redeem this receipt.

paid<S>(r: &Receipt<S>) -> u64

public

#

The payment amount backing this receipt. A refund pays back exactly this.

allocation<S>(r: &Receipt<S>) -> u64

public

#

The sale-token allocation promised by this receipt. A claim returns exactly this.

purchased_at_ms<S>(r: &Receipt<S>) -> u64

public

#

The timestamp (ms) at which the purchase happened.