Smart Contract

FRC20StakingForwarder

A.d2abb5dbf5e08666.FRC20StakingForwarder

Valid From

86,128,757

Deployed

3d ago
Feb 24, 2026, 11:54:28 PM UTC

Dependents

0 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FRC20StakingForwarder
5
6The FRC20StakingForwarder contract is a forwarder contract that forwards the Fungible Token to the FRC20Staking Pool.
7
8*/
9import FungibleToken from 0xf233dcee88fe0abe
10import FlowToken from 0x1654653399040a61
11import Fixes from 0xd2abb5dbf5e08666
12import FRC20FTShared from 0xd2abb5dbf5e08666
13import FRC20Staking from 0xd2abb5dbf5e08666
14
15access(all) contract FRC20StakingForwarder {
16    /* --- Events --- */
17
18    // Event that is emitted when tokens are deposited to the target receiver
19    access(all) event ForwardedDeposit(amount: UFix64, from: Address?)
20
21    /* --- Variable, Enums and Structs --- */
22    access(all)
23    let StakingForwarderStoragePath: StoragePath
24    access(all)
25    let StakingForwarderPublicPath: PublicPath
26
27    /* --- Interfaces & Resources --- */
28
29    access(all) resource interface ForwarderPublic {
30        /// Helper function to check whether set `recipient` capability
31        /// is not latent or the capability tied to a type is valid.
32        access(all)
33        view fun check(): Bool
34
35        /// Gets the fallback receiver assigned to the account
36        access(all)
37        view fun fallbackBorrow(): &{FungibleToken.Receiver}?
38    }
39
40    access(all) resource Forwarder: FungibleToken.Receiver, ForwarderPublic {
41        /// The capability of staking pool
42        access(self) let pool: Capability<&FRC20Staking.Pool>
43
44        init(_ poolAddr: Address) {
45            post {
46                self.pool.check(): "Pool Capability is not valid"
47            }
48            self.pool = FRC20Staking.getPoolCap(poolAddr)
49        }
50
51        /// Helper function to check whether set `pool` capability
52        /// is not latent or the capability tied to a type is valid.
53        access(all)
54        view fun check(): Bool {
55            return self.pool.check()
56        }
57
58        /// Gets the fallback receiver assigned to the account
59        ///
60         view access(all) fun fallbackBorrow(): &{FungibleToken.Receiver}? {
61            let ownerAddress = self.owner?.address ?? panic("No owner set")
62            let cap = getAccount(ownerAddress)
63                .capabilities.get<&{FungibleToken.Receiver}>(Fixes.getFallbackFlowTokenPublicPath())
64            return cap.check() ? cap.borrow() : nil
65        }
66
67        /// A getter function that returns the token types supported by this resource,
68        /// which can be deposited using the 'deposit' function.
69        ///
70        /// @return Array of FT types that can be deposited.
71        access(all)
72        view fun getSupportedVaultTypes(): {Type: Bool} {
73            pre {
74                self.check(): "Forwarder capability is not valid"
75            }
76            let supportedVaults: {Type: Bool} = {}
77
78            let poolRef = self.pool.borrow()
79                ?? panic("Could not borrow pool reference")
80            let rewardTicks = poolRef.getRewardNames()
81            for rewardTick in rewardTicks {
82                if let rewardVault = poolRef.getRewardDetails(rewardTick) {
83                    if rewardVault.rewardVaultType != nil {
84                        supportedVaults[rewardVault.rewardVaultType!] = true
85                    }
86                }
87            }
88            return supportedVaults
89        }
90
91        /// Returns whether or not the given type is accepted by the Receiver
92        /// A vault that can accept any type should just return true by default
93        access(all)
94        view fun isSupportedVaultType(type: Type): Bool {
95            pre {
96                self.check(): "Forwarder capability is not valid"
97            }
98            let supportedVaults = self.getSupportedVaultTypes()
99            return supportedVaults[type] ?? false
100        }
101
102        // deposit
103        //
104        // Function that takes a Vault object as an argument and forwards
105        // it to the recipient's Vault using the stored reference
106        //
107        access(all)
108        fun deposit(from: @{FungibleToken.Vault}) {
109            let poolRef = self.pool.borrow()
110                ?? panic("Could not borrow pool reference")
111
112            let forwarderAddr = self.owner?.address ?? panic("No owner set in Staking Forwarder")
113            let balance = from.balance
114            let rewardType = from.getType()
115
116            let poolDetails = poolRef.getDetails()
117            let totalStakedAmount = poolDetails.totalStaked
118
119            var rewardStrategyRef: &FRC20Staking.RewardStrategy? = poolRef.borrowRewardStrategy(rewardType.identifier)
120            if rewardStrategyRef == nil && from.isInstance(Type<@FlowToken.Vault>()) {
121                rewardStrategyRef = poolRef.borrowRewardStrategy("")
122            }
123
124            let fallbackReceiver = self.fallbackBorrow()
125                ?? panic("No fallback receiver set in Staking Forwarder")
126
127            let yieldValue = totalStakedAmount > 0.0 ? balance / totalStakedAmount : 0.0
128            // If the yield value is 0 or the reward strategy is not found
129            if yieldValue == 0.0 || rewardStrategyRef == nil {
130                fallbackReceiver.deposit(from: <- from)
131            } else {
132                // Forward the tokens to staking pool
133                let change <- FRC20FTShared.wrapFungibleVaultChange(
134                    ftVault: <- from,
135                    from: forwarderAddr,
136                )
137                rewardStrategyRef!.addIncome(income: <- change)
138                emit ForwardedDeposit(amount: balance, from: forwarderAddr)
139            }
140        }
141    }
142
143    // createNewForwarder creates a new Forwarder reference with the provided recipient
144    //
145    access(all)
146    fun createNewForwarder(_ poolAddr: Address): @Forwarder {
147        return <-create Forwarder(poolAddr)
148    }
149
150    init() {
151        let identifier = "FRC20StakingForwarder_".concat(self.account.address.toString())
152        self.StakingForwarderStoragePath = StoragePath(identifier: identifier)!
153        self.StakingForwarderPublicPath = PublicPath(identifier: identifier)!
154    }
155}
156