Documentation
¶
Overview ¶
Package fee is the whole native fee model of a Lux service chain: the ADMISSION policy a VM declares at boot (policy.go) and the SETTLEMENT mechanism that meters, debits and burns the admitted fee during block execution (balance.go, meter.go, settle.go).
Admission is the half every user-facing chain must declare: a Policy whose MinTxFee is > 0, or the NoUserTxPolicy sentinel for committee-only chains, checked once by Validate. Settlement is the half the 2026-05 fee audit found missing: nothing could actually METER, DEBIT, and BURN a fee during block execution the way the C-Chain EVM does (evm/core/state_transition.go buyGas: balance check -> ErrInsufficientFunds -> SubBalance). Service chains charged "fees" that were unbacked integers a caller wrote into a JSON request — never settled against real on-chain balance.
This package supplies the three pillars those chains lacked, modelled on the EVM's buyGas but for the native account model (P/X-Chain style direct usage, not EVM-gas yet — that is a later dual-metering layer that composes on top):
Balances (balance.go) — a debitable balance surface the VM can Burn from. Burn(acct, amount) is the debit: it removes funds from the payer AND reduces circulating supply (no coinbase credit) — i.e. a native burn. Credit funds an account (genesis / future treasury inflows). Ledger (ledger.go) is the canonical KV-backed implementation; any chain whose state is a luxfi/database.Database gets a working ledger with no bespoke code.
GasMeter (meter.go) — per-operation gas metering with a hard limit, mirroring the EVM gas pool (SubGas / ErrOutOfGas). A VM meters each operation's real cost against the payer's GasLimit before pricing it.
Settlement (settle.go) — Cost converts metered gas to nLUX at a price; CanPay is the read-only affordability check a block runs in Verify (so an unpayable block is never accepted — fail closed); Charge is the authoritative debit+burn a block runs in Accept. Settlement happens INSIDE consensus block processing, atomically with the operation's state effect via the VM's versiondb commit — never in a synchronous RPC.
Orthogonality. Admission and settlement are two files of one package, not one mechanism: a VM declares a Policy AND settles through a Ledger; the two compose, they do not overlap. The schedule of "which operation costs how much gas" is supplied BY THE VM (keyvm prices per cryptographic algorithm) — this package is the pure mechanism, the VM owns the values.
It lives in the chains module on purpose: the VMs are plugin binaries built without the node daemon in their dependency closure, so the fee surface they program against must live beside them.
Index ¶
- Constants
- Variables
- func CanPay(b Balances, acct Account, fee uint64) error
- func Charge(b Balances, acct Account, fee uint64) error
- func Cost(gasUsed, price Gas) (uint64, error)
- func Validate(p Policy) error
- type Account
- type Balances
- type FlatPolicy
- type Gas
- type GasMeter
- type KV
- type Ledger
- type NoUserTxPolicy
- type Policy
Constants ¶
const MinTxFeeFloor uint64 = 1_000_000
MinTxFeeFloor is the minimum tx fee, in the base unit (constants.MicroLux, 1e-6 LUX — written nLUX throughout this package), that any user-facing chain SHOULD charge. It is the lower bound used when reviewing per-VM policies; Validate does not enforce it (a VM is free to charge MORE), but a VM choosing less is flagged at review.
Known intentional exception: the X-Chain prices transactions through its own UTXO fee subsystem, NOT a FlatPolicy, and sits deliberately outside this floor — its UTXO economics are set independently of the account-model floor.
Variables ¶
var ( // ErrInsufficientFunds mirrors the EVM's error of the same intent // (core/state_transition.go buyGas): the payer cannot cover the fee. ErrInsufficientFunds = errors.New("fee: insufficient funds") // ErrBalanceOverflow is returned by Credit when an account balance or the // burned-supply counter would exceed 2^64-1 nLUX. ErrBalanceOverflow = errors.New("fee: balance overflow") )
Sentinel errors. Settlement is fail-secure: every error path denies the operation (the block fails Verify or Accept) — none silently proceeds.
var ( // ErrZeroMinFee is returned by Validate when a non-sentinel policy declares // a zero minimum fee. User-facing chains MUST charge > 0. ErrZeroMinFee = errors.New("fee policy declares zero min tx fee on a user-facing chain") // ErrWrongFeeAsset is returned by Policy.ValidateFee when the tx pays in an // asset other than the policy's FeeAssetID. ErrWrongFeeAsset = errors.New("tx pays fee in wrong asset") // ErrInsufficientFee is returned by Policy.ValidateFee when the paid amount // is below MinTxFee. ErrInsufficientFee = errors.New("tx fee below policy minimum") // ErrChainAcceptsNoUserTxs is returned by NoUserTxPolicy.ValidateFee for // any tx — committee-driven chains have no user mempool, so any arrival at // the fee gate is a wiring bug. ErrChainAcceptsNoUserTxs = errors.New("chain accepts no user-submitted txs") )
Sentinel errors returned by Policy implementations and Validate.
var ErrOutOfGas = errors.New("fee: out of gas")
ErrOutOfGas is returned by GasMeter.Consume when an operation's gas exceeds the remaining limit. It mirrors the EVM gas pool's exhaustion error and is fail-secure: the operation does not proceed.
Functions ¶
func CanPay ¶
CanPay is the read-only affordability check a block runs in Verify, for every fee-bearing transaction, BEFORE the block can be accepted. It never mutates state, so verifying a block cannot move funds; it only proves the payer could cover the fee. A block containing any unaffordable transaction fails Verify and is never accepted — fail closed.
func Charge ¶
Charge is the authoritative settlement a block runs in Accept: it debits the fee from the payer and burns it (reduces circulating supply). It is the native analogue of the EVM's buyGas SubBalance, but burning rather than crediting a coinbase. Because it writes through the VM's versiondb, the debit commits atomically with the operation it pays for. If the payer cannot cover the fee (which Verify should already have prevented), Charge returns ErrInsufficientFunds and the caller MUST abort block acceptance — a key operation never takes effect unpaid.
Types ¶
type Account ¶
Account is a fee payer identity. It is the canonical 20-byte Lux address (ids.ShortID) — the same type P/X-Chain use for UTXO owners — so balances here interoperate with existing address tooling. It is PUBLIC: an account identifier never carries secret material.
type Balances ¶
type Balances interface {
Balance(acct Account) (uint64, error)
Credit(acct Account, amount uint64) error
Burn(acct Account, amount uint64) error
Burned() (uint64, error)
}
Balances is the debitable balance surface a fee-charging VM exposes to the settler. It is the minimal contract the EVM expresses as GetBalance / SubBalance, adapted to the native account model and to BURNING (no coinbase):
- Balance reports an account's spendable nLUX.
- Credit adds nLUX (genesis seeding; future treasury/bridge inflows).
- Burn removes nLUX from the payer AND reduces circulating supply — the fee debit. It is the only spend path here; there is intentionally no account->account transfer, because service-chain fees are burned, not paid to a validator. (A treasury split, if ever wanted, is a new method, not a reinterpretation of Burn.)
- Burned reports cumulative burned supply, for audit.
Implementations MUST be atomic with respect to a single call and MUST be driven inside a transaction/versiondb whose commit is the block's commit, so a fee debit and the operation it pays for either both land or neither does.
type FlatPolicy ¶ added in v1.7.41
type FlatPolicy struct {
// Fee is the per-tx burn amount, in nLUX. MUST be > 0.
Fee uint64
// AssetID is the fee asset. For primary-network burn, use
// constants.UTXOAssetIDFor(networkID).
AssetID ids.ID
}
FlatPolicy charges a fixed fee per user tx — the canonical policy for VMs without dynamic gas pricing.
func (FlatPolicy) FeeAssetID ¶ added in v1.7.41
func (p FlatPolicy) FeeAssetID() ids.ID
FeeAssetID returns the configured fee asset.
func (FlatPolicy) MinTxFee ¶ added in v1.7.41
func (p FlatPolicy) MinTxFee() uint64
MinTxFee returns the flat fee.
func (FlatPolicy) ValidateFee ¶ added in v1.7.41
func (p FlatPolicy) ValidateFee(paid uint64, asset ids.ID) error
ValidateFee enforces the flat policy.
type Gas ¶
type Gas uint64
Gas is a unit of metered work. A VM's gas schedule assigns a Gas cost to each operation (keyvm prices per cryptographic algorithm); Cost converts Gas to nLUX at a per-unit price.
type GasMeter ¶
type GasMeter struct {
// contains filtered or unexported fields
}
GasMeter meters gas consumption against a hard limit, exactly like the EVM gas pool (SubGas / out-of-gas). A VM constructs one per fee-bearing operation with the payer's declared GasLimit, then Consumes the operation's metered cost; Consume past the limit denies the operation rather than overdraw.
func NewGasMeter ¶
NewGasMeter returns a meter with the given limit, fully unconsumed.
type KV ¶
type KV interface {
Has(key []byte) (bool, error)
Get(key []byte) ([]byte, error)
Put(key []byte, value []byte) error
}
KV is the minimal key/value surface Ledger needs. It is the read/write subset of luxfi/database.Database (satisfied by versiondb, memdb, and any backing store), declared locally so the settlement primitive does not pin a database module version — keeping it buildable beside any VM under GOWORK=off.
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger is the canonical KV-backed Balances implementation. It writes to the VM's state KV (a versiondb in production), so every Credit/Burn participates in the block's atomic commit: a fee burn and the operation it pays for land together or not at all. Balances are nLUX (1e-6 LUX), matching the node/vms/types/fee floor units.
func NewLedger ¶
NewLedger returns a Ledger over kv. kv is the VM's state database; in a block Accept it is the versiondb whose Commit the block performs.
func (*Ledger) Burn ¶
Burn debits amount nLUX from acct and reduces circulating supply by the same amount (the burned counter rises). It is the fee settlement op: it returns ErrInsufficientFunds if acct cannot cover amount, leaving state untouched.
type NoUserTxPolicy ¶ added in v1.7.41
type NoUserTxPolicy struct{}
NoUserTxPolicy is the sentinel for chains that accept no user-submitted txs (committee-driven only). Distinguishing it from "policy not set" is what lets Validate refuse zero-fee user-facing chains without false positives on the committee chains.
func (NoUserTxPolicy) FeeAssetID ¶ added in v1.7.41
func (NoUserTxPolicy) FeeAssetID() ids.ID
FeeAssetID returns ids.Empty — there is no fee asset.
func (NoUserTxPolicy) MinTxFee ¶ added in v1.7.41
func (NoUserTxPolicy) MinTxFee() uint64
MinTxFee always returns 0 — there are no user txs to charge.
func (NoUserTxPolicy) ValidateFee ¶ added in v1.7.41
func (NoUserTxPolicy) ValidateFee(uint64, ids.ID) error
ValidateFee always returns ErrChainAcceptsNoUserTxs — any caller reaching this gate is a wiring bug.
type Policy ¶ added in v1.7.41
type Policy interface {
// MinTxFee returns the minimum fee, in nLUX, that any user tx must pay.
// MUST be > 0 for user-facing VMs.
MinTxFee() uint64
// FeeAssetID returns the asset the fee is paid in. For primary-network
// burn this is constants.UTXOAssetIDFor(networkID).
FeeAssetID() ids.ID
// ValidateFee returns nil if the paid amount and asset satisfy the policy,
// else ErrWrongFeeAsset, ErrInsufficientFee or ErrChainAcceptsNoUserTxs.
ValidateFee(paidNanoLux uint64, paidAsset ids.ID) error
}
Policy is the ADMISSION half of the fee model: how a VM decides whether a user-submitted tx pays enough to enter at all. Every chain that accepts user txs MUST declare a non-nil Policy whose MinTxFee() is > 0. Chains that accept no user txs declare NoUserTxPolicy — the only legal way to opt out.
Policy is declared at boot and checked once by Validate; settlement of the admitted fee during block execution is the Ledger / Charge half of this package. The two compose and do not overlap.