Smart Contract

IncrementFiStakingConnectors

A.efa9bd7d1b17f1ed.IncrementFiStakingConnectors

Valid From

122,643,867

Deployed

5d ago
Feb 22, 2026, 06:34:46 AM UTC

Dependents

23 imports
1import DeFiActions from 0x92195d814edf9cb0
2import DeFiActionsUtils from 0x92195d814edf9cb0
3import FungibleToken from 0xf233dcee88fe0abe
4import Staking from 0x1b77ba4b414de352
5import SwapConfig from 0xb78ef7afa52ff906
6import SwapInterfaces from 0xb78ef7afa52ff906
7
8/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
9/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
10/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11///
12/// IncrementFiStakingConnectors
13///
14/// DeFiActions adapter implementations for IncrementFi staking protocols. This contract provides connectors that
15/// integrate with IncrementFi's staking pools, allowing users to stake tokens and claim rewards through the
16/// DeFiActions framework.
17///
18/// The contract contains two main connector types:
19/// - PoolSink: Allows depositing tokens into IncrementFi staking pools
20/// - PoolRewardsSource: Allows claiming rewards from IncrementFi staking pools
21///
22access(all) contract IncrementFiStakingConnectors {
23    /// PoolSink
24    ///
25    /// A DeFiActions.Sink implementation that allows depositing tokens into IncrementFi staking pools.
26    /// This connector accepts tokens of a specific type and stakes them in the designated staking pool.
27    ///
28    access(all) struct PoolSink: DeFiActions.Sink {
29        /// The type of Vault this Sink accepts when performing a deposit
30        access(all) let vaultType: Type
31        /// Address of the user staking in the pool
32        access(self) let staker: Address
33        /// The unique identifier of the staking pool to deposit into
34        access(self) let pid: UInt64
35        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
36        /// specific Identifier to associated connectors on construction
37        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
38
39        /// Initializes a new PoolSink
40        ///
41        /// @param pid: The unique identifier of the staking pool to deposit into
42        /// @param staker: Address of the user staking in the pool
43        /// @param uniqueID: Optional identifier for associating connectors in a stack
44        ///
45        init(
46            pid: UInt64,
47            staker: Address,
48            uniqueID: DeFiActions.UniqueIdentifier?
49        ) {
50            let pool = IncrementFiStakingConnectors.borrowPool(pid: pid)
51                ?? panic("Pool with ID \(pid) not found or not accessible")
52
53            self.vaultType = IncrementFiStakingConnectors.tokenTypeIdentifierToVaultType(pool.getPoolInfo().acceptTokenKey)
54            self.staker = staker
55            self.pid = pid
56            self.uniqueID = uniqueID
57        }
58
59        /// Returns a list of ComponentInfo for each component in the stack
60        ///
61        /// @return a list of ComponentInfo for each inner DeFiActions component in the PoolSink
62        ///
63        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
64            return DeFiActions.ComponentInfo(
65                type: self.getType(),
66                id: self.id() ?? nil,
67                innerComponents: []
68            )
69        }
70        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
71        /// a DeFiActions stack. See DeFiActions.align() for more information.
72        ///
73        /// @return a copy of the struct's UniqueIdentifier
74        ///
75        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
76            return self.uniqueID
77        }
78
79        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
80        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
81        ///
82        /// @param id: the UniqueIdentifier to set for this component
83        ///
84        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
85            self.uniqueID = id
86        }
87
88        /// Returns the Vault type accepted by this Sink
89        ///
90        /// @return the type of Vault this Sink accepts when performing a deposit
91        ///
92        access(all) view fun getSinkType(): Type {
93            return self.vaultType
94        }
95
96        /// Returns an estimate of how much of the associated Vault can be accepted by this Sink
97        ///
98        /// @return the minimum capacity available for deposits to this Sink
99        ///
100        access(all) fun minimumCapacity(): UFix64 {
101            if let pool = IncrementFiStakingConnectors.borrowPool(pid: self.pid) {
102                // Get the staking amount for the user in the pool
103                let stakingAmount = pool.getUserInfo(address: self.staker)?.stakingAmount ?? 0.0
104                return pool.getPoolInfo().limitAmount - stakingAmount
105            }
106
107            return 0.0 // no capacity if the staking pool is not available
108        }
109
110        /// Deposits up to the Sink's capacity from the provided Vault
111        ///
112        /// @param from: The vault to withdraw tokens from for staking
113        ///
114        access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
115            let minimumCapacity = self.minimumCapacity()
116            if minimumCapacity == 0.0 {
117                return
118            }
119
120            if let pool: &{Staking.PoolPublic} = IncrementFiStakingConnectors.borrowPool(pid: self.pid) {
121                let depositAmount = from.balance < minimumCapacity
122                    ? from.balance
123                    : minimumCapacity
124
125                pool.stake(staker: self.staker, stakingToken: <- from.withdraw(amount: depositAmount))
126            }
127        }
128    }
129
130    /// PoolRewardsSource
131    ///
132    /// A DeFiActions.Source implementation that allows claiming rewards from IncrementFi staking pools.
133    /// This connector provides tokens by claiming rewards from the designated staking pool.
134    ///
135    /// NOTE: This connector assumes that the pool has only one reward token type. If the pool has multiple reward
136    /// token types, the connector will panic.
137    ///
138    access(all) struct PoolRewardsSource: DeFiActions.Source {
139        /// The type of Vault this Source provides when claiming rewards
140        access(all) let vaultType: Type
141        /// The unique identifier of the staking pool to claim rewards from
142        access(self) let pid: UInt64
143        /// Capability to access the user's staking certificate
144        access(self) let userCertificate: Capability<&Staking.UserCertificate>
145        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
146        /// specific Identifier to associated connectors on construction
147        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
148
149        /// Initializes a new PoolRewardsSource
150        ///
151        /// @param userCertificate: Capability to access the user's staking certificate
152        /// @param pid: The unique identifier of the staking pool to claim rewards from
153        /// @param vaultType: The type of Vault this Source provides when claiming rewards
154        /// @param uniqueID: Optional identifier for associating connectors in a stack
155        ///
156        init(
157            userCertificate: Capability<&Staking.UserCertificate>,
158            pid: UInt64,
159            uniqueID: DeFiActions.UniqueIdentifier?,
160        ) {
161            let pool = IncrementFiStakingConnectors.borrowPool(pid: pid)
162                ?? panic("Pool with ID \(pid) not found")
163            let rewardsInfo = pool!.getPoolInfo().rewardsInfo
164
165            assert(rewardsInfo.keys.length == 1, message: "Pool with ID \(pid) has multiple reward token types, only one is supported")
166            let rewardTokenType = rewardsInfo.keys[0]
167
168            self.pid = pid
169            self.userCertificate = userCertificate
170            self.vaultType = IncrementFiStakingConnectors.tokenTypeIdentifierToVaultType(rewardTokenType)
171            self.uniqueID = uniqueID
172        }
173
174        /// Returns a list of ComponentInfo for each component in the stack
175        ///
176        /// @return a list of ComponentInfo for each inner DeFiActions component in the PoolRewardsSource
177        ///
178        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
179            return DeFiActions.ComponentInfo(
180                type: self.getType(),
181                id: self.id() ?? nil,
182                innerComponents: []
183            )
184        }
185
186        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
187        /// a DeFiActions stack. See DeFiActions.align() for more information.
188        ///
189        /// @return a copy of the struct's UniqueIdentifier
190        ///
191        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
192            return self.uniqueID
193        }
194
195        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
196        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
197        ///
198        /// @param id: the UniqueIdentifier to set for this component
199        ///
200        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
201            self.uniqueID = id
202        }
203
204        /// Returns the Vault type provided by this Source
205        ///
206        /// @return the type of Vault this Source provides when claiming rewards
207        ///
208        access(all) view fun getSourceType(): Type {
209            return self.vaultType
210        }
211
212        /// Returns an estimate of how much of the associated rewards can be claimed from this Source
213        ///
214        /// @return the minimum amount of rewards available for claiming from this Source
215        ///
216        access(all) fun minimumAvailable(): UFix64 {
217            if let address = self.userCertificate.borrow()?.owner?.address {
218                if let pool = IncrementFiStakingConnectors.borrowPool(pid: self.pid) {
219                    // Stake an empty vault on behalf of the user to update the pool
220                    // The Staking contract does not expose any way to update the unclaimed rewards
221                    // field, so staking an empty vault is a workaround to update the unclaimed rewards
222                    let emptyVault <- DeFiActionsUtils.getEmptyVault(IncrementFiStakingConnectors.tokenTypeIdentifierToVaultType(pool.getPoolInfo().acceptTokenKey))
223                    pool.stake(staker: address, stakingToken: <- emptyVault)
224                    if let unclaimedRewards = pool.getUserInfo(address: address)?.unclaimedRewards {
225                        // Return the unclaimed rewards for the specific vault type
226                        return unclaimedRewards[SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.vaultType.identifier)] ?? 0.0
227                    }
228                }
229            }
230
231            return 0.0 // no capacity if the staking pool is not available
232        }
233
234        /// Withdraws rewards from the staking pool up to the specified maximum amount
235        /// Overflow rewards are sent to the appropriate overflow sinks if provided
236        ///
237        /// @param maxAmount: The maximum amount of rewards to claim
238        /// @return a Vault containing the claimed rewards
239        ///
240        access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
241            let minimumAvailable = self.minimumAvailable()
242            if minimumAvailable == 0.0 {
243                return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
244            }
245
246            if let pool = IncrementFiStakingConnectors.borrowPool(pid: self.pid) {
247                if let userCertificate = self.userCertificate.borrow() {
248                    let withdrawAmount = maxAmount < minimumAvailable
249                        ? maxAmount
250                        : minimumAvailable
251
252                    let rewards <- pool.claimRewards(userCertificate: userCertificate)
253                    let targetSliceType = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.vaultType.identifier)
254                    
255                    assert(rewards.keys.length <= 1, message: "Pool with ID \(self.pid) has multiple reward token types, only one is supported")
256
257                    if rewards.keys.length == 0 {
258                        destroy rewards
259                        return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
260                    }
261
262                    assert(
263                        rewards.keys[0] == targetSliceType,
264                        message: "Reward token type \(rewards.keys[0]) is not supported by this Source instance (poolID: \(self.pid), instance sourceType: \(self.vaultType.identifier)). This instance can only claim \(targetSliceType) rewards."
265                    )
266                    let reward <- rewards.remove(key: rewards.keys[0])!
267                    destroy rewards
268                    return <- reward
269                }
270            }
271
272            return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
273        }
274    }
275
276    /// Helper function to borrow a reference to the staking pool
277    ///
278    /// @return a reference to the staking pool, or nil if not available
279    ///
280    access(all) fun borrowPool(pid: UInt64): &{Staking.PoolPublic}? {
281        let poolCollectionCap = getAccount(Type<Staking>().address!).capabilities.get<&Staking.StakingPoolCollection>(Staking.CollectionPublicPath)
282        return poolCollectionCap.borrow()?.getPool(pid: pid)
283    }
284
285    /// Helper function to borrow a reference to the pair public interface
286    ///
287    /// @param pid: The pool ID to borrow the pair public interface for
288    /// @return a reference to the pair public interface
289    ///
290    access(all) fun borrowPairPublicByPid(pid: UInt64): &{SwapInterfaces.PairPublic}? {
291        let pool = IncrementFiStakingConnectors.borrowPool(pid: pid)
292        if pool == nil {
293            return nil
294        }
295
296        let pair = getAccount(IncrementFiStakingConnectors.tokenTypeIdentifierToVaultType(pool!.getPoolInfo().acceptTokenKey).address!)
297            .capabilities
298            .borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath)
299        
300        return pair
301    }
302
303    /// Helper function to convert a token type identifier to a vault type
304    /// E.g. "A.0x1234567890.USDC" -> Type("A.0x1234567890.USDC.Vault")
305    ///
306    /// @param tokenType: The token type identifier to convert to a vault type
307    /// @return the vault type
308    ///
309    access(all) fun tokenTypeIdentifierToVaultType(_ tokenType: String): Type {
310        return CompositeType(tokenType.concat(".Vault"))!
311    }
312}