Smart Contract

FlowFees

A.912d5440f7e3769e.FlowFees

Deployed

3h ago
Feb 26, 2026, 04:02:35 PM UTC

Dependents

0 imports
1import FungibleToken from 0x9a0766d93b6608b7
2import FlowToken from 0x7e60df042a9c0868
3import FlowStorageFees from 0x8c5303eaa26202d6
4
5access(all) contract FlowFees {
6
7    // Event that is emitted when tokens are deposited to the fee vault
8    access(all) event TokensDeposited(amount: UFix64)
9
10    // Event that is emitted when tokens are withdrawn from the fee vault
11    access(all) event TokensWithdrawn(amount: UFix64)
12
13    // Event that is emitted when fees are deducted
14    access(all) event FeesDeducted(amount: UFix64, inclusionEffort: UFix64, executionEffort: UFix64)
15
16    // Event that is emitted when fee parameters change
17    access(all) event FeeParametersChanged(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64)
18
19    // Private vault with public deposit function
20    access(self) var vault: @FlowToken.Vault
21
22    access(all) fun deposit(from: @{FungibleToken.Vault}) {
23        let from <- from as! @FlowToken.Vault
24        let balance = from.balance
25        self.vault.deposit(from: <-from)
26        emit TokensDeposited(amount: balance)
27    }
28
29    /// Get the balance of the Fees Vault
30    access(all) fun getFeeBalance(): UFix64 {
31        return self.vault.balance
32    }
33
34    access(all) resource Administrator {
35        // withdraw
36        //
37        // Allows the administrator to withdraw tokens from the fee vault
38        access(all) fun withdrawTokensFromFeeVault(amount: UFix64): @{FungibleToken.Vault} {
39            let vault <- FlowFees.vault.withdraw(amount: amount)
40            emit TokensWithdrawn(amount: amount)
41            return <-vault
42        }
43
44        /// Allows the administrator to change all the fee parameters at once
45        access(all) fun setFeeParameters(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64) {
46            let newParameters = FeeParameters(surgeFactor: surgeFactor, inclusionEffortCost: inclusionEffortCost, executionEffortCost: executionEffortCost)
47            FlowFees.setFeeParameters(newParameters)
48        }
49
50        /// Allows the administrator to change the fee surge factor
51        access(all) fun setFeeSurgeFactor(_ surgeFactor: UFix64) {
52            let oldParameters = FlowFees.getFeeParameters()
53            let newParameters = FeeParameters(surgeFactor: surgeFactor, inclusionEffortCost: oldParameters.inclusionEffortCost, executionEffortCost: oldParameters.executionEffortCost)
54            FlowFees.setFeeParameters(newParameters)
55        }
56    }
57
58    /// A struct holding the fee parameters needed to calculate the fees
59    access(all) struct FeeParameters {
60        /// The surge factor is used to make transaction fees respond to high loads on the network
61        access(all) var surgeFactor: UFix64
62        /// The FLOW cost of one unit of inclusion effort. The FVM is responsible for metering inclusion effort.
63        access(all) var inclusionEffortCost: UFix64
64        /// The FLOW cost of one unit of execution effort. The FVM is responsible for metering execution effort.
65        access(all) var executionEffortCost: UFix64
66
67        init(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64){
68            self.surgeFactor = surgeFactor
69            self.inclusionEffortCost = inclusionEffortCost
70            self.executionEffortCost = executionEffortCost
71        }
72    }
73
74    // VerifyPayerBalanceResult is returned by the verifyPayersBalanceForTransactionExecution function
75    access(all) struct VerifyPayerBalanceResult {
76        // True if the payer has sufficient balance for the transaction execution to continue
77        access(all) let canExecuteTransaction: Bool
78        // The minimum payer balance required for the transaction execution to continue.
79        // This value is defined by verifyPayersBalanceForTransactionExecution.
80        access(all) let requiredBalance: UFix64
81        // The maximum transaction fees (inclusion fees + execution fees) the transaction can incur
82        // (if all available execution effort is used)
83        access(all) let maximumTransactionFees: UFix64
84
85        init(canExecuteTransaction: Bool, requiredBalance: UFix64,  maximumTransactionFees: UFix64){
86            self.canExecuteTransaction = canExecuteTransaction
87            self.requiredBalance = requiredBalance
88            self.maximumTransactionFees = maximumTransactionFees
89        }
90
91    }
92
93    // verifyPayersBalanceForTransactionExecution is called by the FVM before executing a transaction.
94    // It verifies that the transaction payer's balance is high enough to continue transaction execution,
95    // and returns the maximum possible transaction fees.
96    // (according to the inclusion effort and maximum execution effort of the transaction).
97    //
98    // The requiredBalance balance is defined as the minimum account balance +
99    //  maximum transaction fees (inclusion fees + execution fees at max execution effort).
100    access(all) fun verifyPayersBalanceForTransactionExecution(
101        _ payerAcct: auth(BorrowValue) &Account,
102        inclusionEffort: UFix64,
103        maxExecutionEffort: UFix64
104    ): VerifyPayerBalanceResult {
105        // Get the maximum fees required for the transaction.
106        var maxTransactionFee = self.computeFees(inclusionEffort: inclusionEffort, executionEffort: maxExecutionEffort)
107
108        // Get the minimum required payer's balance for the transaction.
109        let minimumRequiredBalance = FlowStorageFees.defaultTokenReservedBalance(payerAcct.address)
110
111        if minimumRequiredBalance == UFix64(0) {
112            // If the required balance is zero exit early.
113            return VerifyPayerBalanceResult(
114                canExecuteTransaction: true,
115                requiredBalance: minimumRequiredBalance,
116                maximumTransactionFees: maxTransactionFee
117            )
118        }
119
120        // Get the balance of the payers default vault.
121        // In the edge case where the payer doesnt have a vault, treat the balance as 0.
122        var balance = 0.0
123        if let tokenVault = payerAcct.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) {
124            balance = tokenVault.balance
125        }
126
127        return VerifyPayerBalanceResult(
128            // The transaction can be executed it the payers balance is greater than the minimum required balance.
129            canExecuteTransaction: balance >= minimumRequiredBalance,
130            requiredBalance: minimumRequiredBalance,
131            maximumTransactionFees: maxTransactionFee)
132    }
133
134    /// Called when a transaction is submitted to deduct the fee
135    /// from the AuthAccount that submitted it
136    access(all) fun deductTransactionFee(_ acct: auth(BorrowValue) &Account, inclusionEffort: UFix64, executionEffort: UFix64) {
137        var feeAmount = self.computeFees(inclusionEffort: inclusionEffort, executionEffort: executionEffort)
138
139        if feeAmount == UFix64(0) {
140            // If there are no fees to deduct, do not continue,
141            // so that there are no unnecessarily emitted events
142            return
143        }
144
145        let tokenVault = acct.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
146            ?? panic("Unable to borrow reference to the default token vault")
147
148
149        if feeAmount > tokenVault.balance {
150            // In the future this code path will never be reached,
151            // as payers that are under account minimum balance will not have their transactions included in a collection
152            //
153            // Currently this is not used to fail the transaction (as that is the responsibility of the minimum account balance logic),
154            // But is used to reduce the balance of the vault to 0.0, if the vault has less available balance than the transaction fees.
155            feeAmount = tokenVault.balance
156        }
157
158        let feeVault <- tokenVault.withdraw(amount: feeAmount)
159        self.vault.deposit(from: <-feeVault)
160
161        // The fee calculation can be reconstructed using the data from this event and the FeeParameters at the block when the event happened
162        emit FeesDeducted(amount: feeAmount, inclusionEffort: inclusionEffort, executionEffort: executionEffort)
163    }
164
165    access(all) view fun getFeeParameters(): FeeParameters {
166        return self.account.storage.copy<FeeParameters>(from: /storage/FlowTxFeeParameters) ?? panic("Error getting tx fee parameters. They need to be initialized first!")
167    }
168
169    access(self) fun setFeeParameters(_ feeParameters: FeeParameters) {
170        // empty storage before writing new FeeParameters to it
171        self.account.storage.load<FeeParameters>(from: /storage/FlowTxFeeParameters)
172        self.account.storage.save(feeParameters,to: /storage/FlowTxFeeParameters)
173        emit FeeParametersChanged(surgeFactor: feeParameters.surgeFactor, inclusionEffortCost: feeParameters.inclusionEffortCost, executionEffortCost: feeParameters.executionEffortCost)
174    }
175
176
177    // compute the transaction fees with the current fee parameters and the given inclusionEffort and executionEffort
178    access(all) view fun computeFees(inclusionEffort: UFix64, executionEffort: UFix64): UFix64 {
179        let params = self.getFeeParameters()
180
181        let totalFees = params.surgeFactor * ( inclusionEffort * params.inclusionEffortCost + executionEffort * params.executionEffortCost )
182        return totalFees
183    }
184
185    init(adminAccount: auth(SaveValue) &Account) {
186        // Create a new FlowToken Vault and save it in storage
187        self.vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
188
189        let admin <- create Administrator()
190        adminAccount.storage.save(<-admin, to: /storage/flowFeesAdmin)
191    }
192}