Smart Contract

IncrementFarmingStrategy

A.79f5b5b0f95a160b.IncrementFarmingStrategy

Valid From

128,855,921

Deployed

1w ago
Feb 20, 2026, 08:46:22 AM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4import Staking from 0x1b77ba4b414de352
5
6/// Increment Fi LP farming strategy
7/// Stake LP tokens in farming pools to earn rewards
8access(all) contract IncrementFarmingStrategy {
9    
10    // ====================================================================
11    // EVENTS
12    // ====================================================================
13    access(all) event FarmDeposited(poolId: UInt64, amount: UFix64, tokenKey: String)
14    access(all) event FarmWithdrawn(poolId: UInt64, amount: UFix64)
15    access(all) event RewardsClaimed(poolId: UInt64, rewards: {String: UFix64})
16    access(all) event StrategyExecuted(poolId: UInt64, amount: UFix64)
17    
18    // ====================================================================
19    // STATE
20    // ====================================================================
21    access(self) var totalDeposited: {UInt64: UFix64}
22    access(self) var totalRewardsClaimed: {String: UFix64}
23    
24    // ====================================================================
25    // STRATEGY RESOURCE
26    // ====================================================================
27    access(all) resource Strategy {
28        access(self) let flowVault: @FlowToken.Vault
29        access(self) let stFlowVault: @stFlowToken.Vault
30        access(self) let activePositions: {UInt64: Bool}
31        access(self) let userCertificate: @Staking.UserCertificate
32        
33        init() {
34            self.flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
35            self.stFlowVault <- stFlowToken.createEmptyVault(vaultType: Type<@stFlowToken.Vault>()) as! @stFlowToken.Vault
36            self.activePositions = {}
37            self.userCertificate <- Staking.setupUser()
38        }
39        
40        /// Execute farming: deposit into specified pool
41        access(all) fun executeStrategy(poolId: UInt64, from: @{FungibleToken.Vault}): Bool {
42            pre {
43                from.balance > 0.0: "Cannot deposit zero"
44            }
45            
46            let amount = from.balance
47            
48            // Get staking pool collection
49            let stakingCollection = getAccount(0x1b77ba4b414de352)
50                .capabilities
51                .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
52                ?? panic("Cannot access staking collection")
53            
54            // Get specific pool
55            let poolRef = stakingCollection.getPool(pid: poolId)
56            let poolInfo = poolRef.getPoolInfo()
57            
58            // Verify pool is active (status "1" = RUNNING)
59            assert(poolInfo.status == "1", message: "Pool is not active")
60            
61            // Stake tokens - signature: stake(staker: Address, stakingToken: @{FungibleToken.Vault})
62            poolRef.stake(staker: self.owner!.address, stakingToken: <-from)
63            
64            // Track position
65            self.activePositions[poolId] = true
66            
67            if IncrementFarmingStrategy.totalDeposited[poolId] == nil {
68                IncrementFarmingStrategy.totalDeposited[poolId] = 0.0
69            }
70            IncrementFarmingStrategy.totalDeposited[poolId] = IncrementFarmingStrategy.totalDeposited[poolId]! + amount
71            
72            emit FarmDeposited(poolId: poolId, amount: amount, tokenKey: poolInfo.acceptTokenKey)
73            emit StrategyExecuted(poolId: poolId, amount: amount)
74            
75            return true
76        }
77        
78        /// Harvest rewards from a farming pool
79        access(all) fun harvestPool(poolId: UInt64): @{String: {FungibleToken.Vault}} {
80            pre {
81                self.activePositions[poolId] ?? false: "No active position in this pool"
82            }
83            
84            let stakingCollection = getAccount(0x1b77ba4b414de352)
85                .capabilities
86                .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
87                ?? panic("Cannot access staking collection")
88            
89            let poolRef = stakingCollection.getPool(pid: poolId)
90            
91            // Get user info to see unclaimed rewards
92            let userInfo = poolRef.getUserInfo(address: self.owner!.address)
93            
94            if userInfo != nil {
95                let unclaimedRewards = userInfo!.unclaimedRewards
96                
97                // Claim rewards - signature: claimRewards(userCertificate: &{IdentityCertificate})
98                let rewardVaults <- poolRef.claimRewards(
99                    userCertificate: &self.userCertificate as &{Staking.IdentityCertificate}
100                )
101                
102                // Track claimed rewards
103                for tokenKey in unclaimedRewards.keys {
104                    let rewardAmount = unclaimedRewards[tokenKey]!
105                    
106                    if IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] == nil {
107                        IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] = 0.0
108                    }
109                    IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] = 
110                        IncrementFarmingStrategy.totalRewardsClaimed[tokenKey]! + rewardAmount
111                }
112                
113                emit RewardsClaimed(poolId: poolId, rewards: unclaimedRewards)
114                
115                return <- rewardVaults
116            } else {
117                // Return empty dictionary if no user info
118                let emptyVaults: @{String: {FungibleToken.Vault}} <- {}
119                return <- emptyVaults
120            }
121        }
122        
123        /// Harvest all active positions
124        access(all) fun harvestAll(): @{String: {FungibleToken.Vault}} {
125            var allRewards: @{String: {FungibleToken.Vault}} <- {}
126            
127            for poolId in self.activePositions.keys {
128                if self.activePositions[poolId]! {
129                    let harvested <- self.harvestPool(poolId: poolId)
130                    
131                    // Merge harvested vaults into allRewards
132                    for tokenKey in harvested.keys {
133                        let rewardVault <- harvested.remove(key: tokenKey)!
134                        
135                        if allRewards.containsKey(tokenKey) {
136                            let existingVault = &allRewards[tokenKey] as &{FungibleToken.Vault}?
137                            existingVault!.deposit(from: <-rewardVault)
138                        } else {
139                            allRewards[tokenKey] <-! rewardVault
140                        }
141                    }
142                    
143                    destroy harvested
144                }
145            }
146            
147            return <- allRewards
148        }
149        
150        /// Withdraw from a farming pool
151        access(all) fun withdrawFromPool(poolId: UInt64, amount: UFix64): @{FungibleToken.Vault} {
152            pre {
153                self.activePositions[poolId] ?? false: "No active position in this pool"
154                amount > 0.0: "Amount must be positive"
155            }
156            
157            let stakingCollection = getAccount(0x1b77ba4b414de352)
158                .capabilities
159                .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
160                ?? panic("Cannot access staking collection")
161            
162            let poolRef = stakingCollection.getPool(pid: poolId)
163            
164            // Unstake tokens - signature: unstake(userCertificate: &{IdentityCertificate}, amount: UFix64)
165            let withdrawnVault <- poolRef.unstake(
166                userCertificate: &self.userCertificate as &{Staking.IdentityCertificate},
167                amount: amount
168            )
169            
170            IncrementFarmingStrategy.totalDeposited[poolId] = IncrementFarmingStrategy.totalDeposited[poolId]! - amount
171            
172            if IncrementFarmingStrategy.totalDeposited[poolId]! <= 0.0 {
173                self.activePositions[poolId] = false
174            }
175            
176            emit FarmWithdrawn(poolId: poolId, amount: amount)
177            
178            return <- withdrawnVault
179        }
180        
181        /// Emergency exit - withdraw all from all pools
182        access(all) fun emergencyExit(): @{String: {FungibleToken.Vault}} {
183            var allWithdrawn: @{String: {FungibleToken.Vault}} <- {}
184            
185            let stakingCollection = getAccount(0x1b77ba4b414de352)
186                .capabilities
187                .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
188                ?? panic("Cannot access staking collection")
189            
190            for poolId in self.activePositions.keys {
191                if self.activePositions[poolId]! {
192                    let poolRef = stakingCollection.getPool(pid: poolId)
193                    let userInfo = poolRef.getUserInfo(address: self.owner!.address)
194                    
195                    if userInfo != nil && userInfo!.stakingAmount > 0.0 {
196                        let withdrawn <- poolRef.unstake(
197                            userCertificate: &self.userCertificate as &{Staking.IdentityCertificate},
198                            amount: userInfo!.stakingAmount
199                        )
200                        
201                        let tokenKey = poolRef.getPoolInfo().acceptTokenKey
202                        
203                        if allWithdrawn.containsKey(tokenKey) {
204                            let existingVault = &allWithdrawn[tokenKey] as &{FungibleToken.Vault}?
205                            existingVault!.deposit(from: <-withdrawn)
206                        } else {
207                            allWithdrawn[tokenKey] <-! withdrawn
208                        }
209                        
210                        self.activePositions[poolId] = false
211                    }
212                }
213            }
214            
215            return <- allWithdrawn
216        }
217        
218        /// Get positions info
219        access(all) fun getPositions(): {UInt64: {String: UFix64}} {
220            let positions: {UInt64: {String: UFix64}} = {}
221            
222            let stakingCollection = getAccount(0x1b77ba4b414de352)
223                .capabilities
224                .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
225                ?? panic("Cannot access staking collection")
226            
227            for poolId in self.activePositions.keys {
228                if self.activePositions[poolId]! {
229                    let poolRef = stakingCollection.getPool(pid: poolId)
230                    let userInfo = poolRef.getUserInfo(address: self.owner!.address)
231                    
232                    if userInfo != nil {
233                        positions[poolId] = {
234                            "stakingAmount": userInfo!.stakingAmount,
235                            "isBlocked": userInfo!.isBlocked ? 1.0 : 0.0
236                        }
237                    }
238                }
239            }
240            
241            return positions
242        }
243        
244        access(all) fun getBalances(): {String: UFix64} {
245            return {
246                "flow": self.flowVault.balance,
247                "stflow": self.stFlowVault.balance
248            }
249        }
250    }
251    
252    // ====================================================================
253    // CONTRACT FUNCTIONS
254    // ====================================================================
255    access(all) fun createStrategy(): @Strategy {
256        return <- create Strategy()
257    }
258    
259    access(all) fun getMetrics(): {String: AnyStruct} {
260        return {
261            "totalDepositedByPool": self.totalDeposited,
262            "totalRewardsClaimed": self.totalRewardsClaimed
263        }
264    }
265    
266    /// Query available farming pools
267    access(all) fun getActivePools(): [{String: AnyStruct}] {
268        let pools: [{String: AnyStruct}] = []
269        
270        let stakingCollection = getAccount(0x1b77ba4b414de352)
271            .capabilities
272            .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
273        
274        if stakingCollection != nil {
275            let poolCount = stakingCollection!.getCollectionLength()
276            var i: UInt64 = 0
277            
278            while i < UInt64(poolCount) && i < 20 {
279                let poolRef = stakingCollection!.getPool(pid: i)
280                let poolInfo = poolRef.getPoolInfo()
281                
282                if poolInfo.status == "1" {
283                    pools.append({
284                        "pid": poolInfo.pid,
285                        "acceptTokenKey": poolInfo.acceptTokenKey,
286                        "totalStaking": poolInfo.totalStaking,
287                        "limitAmount": poolInfo.limitAmount,
288                        "status": poolInfo.status
289                    })
290                }
291                
292                i = i + 1
293            }
294        }
295        
296        return pools
297    }
298    
299    // ====================================================================
300    // INITIALIZATION
301    // ====================================================================
302    init() {
303        self.totalDeposited = {}
304        self.totalRewardsClaimed = {}
305    }
306}