Smart Contract

PPPV3

A.3ddb31a3880d1d8f.PPPV3

Deployed

1d ago
Feb 26, 2026, 10:41:48 PM UTC

Dependents

0 imports
1/**
2
3# 
4
5*/
6
7import FungibleToken from 0xf233dcee88fe0abe
8// Lending
9import LendingInterfaces from 0x2df970b6cdee5735
10import LendingConfig from 0x2df970b6cdee5735
11// Swap
12import SwapConfig from 0xb78ef7afa52ff906
13import SwapInterfaces from 0xb78ef7afa52ff906
14// Liquid Staking
15import LiquidStaking from 0xd6f80565193ad727
16// Farm
17import Staking from 0x1b77ba4b414de352
18// Oracle
19import PublicPriceOracle from 0xec67451f8a58216a
20
21import RV2 from 0x3ddb31a3880d1d8f
22
23
24pub contract PPPV3 {
25
26    //
27    access(all) var _totalSupply: UFix64
28
29    //
30    access(self) let _pointsbase: {Address: UFix64}
31
32    // 
33    access(self) let _pointsHistorySnapshot: {Address: UFix64}
34
35    // {Referrer: TotalPointsFromReferees}
36    access(self) let _totalPointsAsReferrer: {Address: UFix64}
37
38    // {Referrer: {Referee: PointsFrom}}
39    access(self) let _pointsFromReferees: {Address: {Address: UFix64}}
40
41    //
42    access(self) let _pointsAsReferee: {Address: UFix64}
43
44
45    access(self) let _userBlacklist: {Address: Bool}
46
47    // 
48    access(self) let _swapPoolWhitelist: {Address: Bool} // {PoolAddress}
49
50    /* {
51            "LendingSupply": {
52                0.0         : 0.001,  //   0.0     ~ 1000.0  -> 0.001
53                1000.0      : 0.002,  //   1000.0  ~ 10000.0 -> 0.002
54                10000.0     : 0.003   //   10000.0 ~ Max     -> 0.003
55            }
56        }
57    */
58    access(self) let _pointsRatePerDay: {String: AnyStruct}
59
60    //access(self) let _leaderBoard: [AnyStruct]
61
62    access(self) let _userStates: {Address: {String: UFix64}}
63
64    access(self) let _secondsPerDay: UFix64
65
66    // 
67    access(self) var _swapPoolAddress: Address
68    access(self) var _swapVolumeTrackingTimestamp: UFix64
69    access(self) var _swapPoolReserve0: UFix64
70    access(self) var _swapPoolReserve1: UFix64
71
72    access(self) var _topUsers: [Address]
73    
74
75    access(self) let _reservedFields: {String: AnyStruct}
76
77    /// Events
78    access(all) event PointsMinted(userAddr: Address, amount: UFix64, source: String, param: {String: String})
79    access(all) event PointsBurned(userAddr: Address, amount: UFix64, source: String, param: {String: String})
80    access(all) event StateUpdated(userAddr: Address, state: {String: UFix64})
81    access(all) event PointsRateChanged(source: String, ori: UFix64, new: UFix64)
82    access(all) event PointsTierRateChanged(source: String, ori: {UFix64: UFix64}, new: {UFix64: UFix64})
83    access(all) event TopUsersChanged(ori: [Address], new: [Address])
84
85    // 
86    access(all) view fun balanceOf(_ userAddr: Address): UFix64 {
87        return self.getBasePoints(userAddr)
88                +
89               self.getHistorySnapshotPoints(userAddr)
90                +
91               self.getPointsAsReferrer(userAddr)
92                +
93               self.getPointsAsReferee(userAddr)
94                +
95               self.calculateNewPointsSinceLastUpdate(userAddr: userAddr) as! UFix64  // accured points
96    }
97
98    // Mint Points
99    access(self) fun _mint(targetAddr: Address, amount: UFix64) {
100        if self._userBlacklist.containsKey(targetAddr) {
101            return
102        }
103
104        // mint points
105        if self._pointsbase.containsKey(targetAddr) == false {
106            self._pointsbase[targetAddr] = 0.0
107        }
108        self._pointsbase[targetAddr] = self._pointsbase[targetAddr]! + amount
109        
110        let referrerAddr: Address? = RV2.getReferrerByReferee(referee: targetAddr)
111        if referrerAddr != nil {
112            // referee boost
113            let boostAmountAsReferee = amount * self.getPointsRate_RefereeUp()
114            if boostAmountAsReferee > 0.0 {
115                if self._pointsAsReferee.containsKey(targetAddr) == false {
116                    self._pointsAsReferee[targetAddr] = boostAmountAsReferee
117                } else {
118                    self._pointsAsReferee[targetAddr] = self._pointsAsReferee[targetAddr]! + boostAmountAsReferee
119                }
120                emit PointsMinted(
121                    userAddr: targetAddr,
122                    amount: boostAmountAsReferee,
123                    source: "AsReferee",
124                    param: {}
125                )
126                self._totalSupply = self._totalSupply + boostAmountAsReferee
127            }
128
129            // referrer boost
130            let boostAmountForReferrer = amount * self.getPointsRate_ReferrerUp()
131            if boostAmountForReferrer > 0.0 && self._userBlacklist.containsKey(referrerAddr!) == false {
132                if self._totalPointsAsReferrer.containsKey(referrerAddr!) == false {
133                    self._totalPointsAsReferrer[referrerAddr!] = boostAmountForReferrer
134                } else {
135                    self._totalPointsAsReferrer[referrerAddr!] = self._totalPointsAsReferrer[referrerAddr!]! + boostAmountForReferrer
136                }
137                if self._pointsFromReferees.containsKey(referrerAddr!) == false {
138                    self._pointsFromReferees[referrerAddr!] = {}
139                }
140                if self._pointsFromReferees[referrerAddr!]!.containsKey(targetAddr) == false {
141                    self._pointsFromReferees[referrerAddr!]!.insert(key: targetAddr, boostAmountForReferrer)
142                } else {
143                    self._pointsFromReferees[referrerAddr!]!.insert(key: targetAddr, self._pointsFromReferees[referrerAddr!]![targetAddr]! + boostAmountForReferrer)
144                }
145                if self._pointsbase.containsKey(referrerAddr!) == false {
146                    self._pointsbase[referrerAddr!] = 0.0
147                }
148                emit PointsMinted(
149                    userAddr: referrerAddr!,
150                    amount: boostAmountForReferrer,
151                    source: "AsReferrer",
152                    param: {}
153                )
154                self._totalSupply = self._totalSupply + boostAmountForReferrer
155            }
156
157        }
158
159        // update total supply
160        self._totalSupply = self._totalSupply + amount;
161    }
162    
163    // 
164    access(all) view fun calculateNewPointsSinceLastUpdate(userAddr: Address): AnyStruct {
165        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
166        if lastUpdateTimestamp == 0.0 { return 0.0 }
167
168        // Lending Supply
169        let accuredLendingSupplyPoints = self.calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: userAddr)
170        // Lending Borrow
171        let accuredLendingBorrowPoints = self.calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: userAddr)
172        // stFlow Holdings
173        let accuredStFlowHoldingPoints = self.calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: userAddr)
174        // Swap LP
175        let accuredSwapLPPoints = self.calculateNewPointsSinceLastUpdate_SwapLP(userAddr: userAddr)
176        
177        let referrerAddr: Address? = RV2.getReferrerByReferee(referee: userAddr)
178        
179        return (accuredLendingSupplyPoints + accuredLendingBorrowPoints + accuredStFlowHoldingPoints + accuredSwapLPPoints) * (referrerAddr==nil? 1.0 : 1.0+self.getPointsRate_RefereeUp())
180    }
181    
182    access(all) fun updateUserState(userAddr: Address) {
183        if self._userBlacklist.containsKey(userAddr) {
184            return
185        }
186
187        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
188        let duration = getCurrentBlock().timestamp - lastUpdateTimestamp
189        let durationStr = duration.toString()
190
191        if duration > 0.0 {
192            // Lending Supply
193            let accuredLendingSupplyPoints = self.calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: userAddr)
194            if (accuredLendingSupplyPoints > 0.0) {
195                emit PointsMinted(
196                    userAddr: userAddr,
197                    amount: accuredLendingSupplyPoints,
198                    source: "LendingSupply",
199                    param: {
200                        "SupplyUsdValue": self.getUserState_LendingSupply(userAddr: userAddr).toString(),
201                        "Duration": durationStr
202                    }
203                )
204            }
205
206            // Lending Borrow
207            let accuredLendingBorrowPoints = self.calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: userAddr)
208            if (accuredLendingBorrowPoints > 0.0) {
209                emit PointsMinted(
210                    userAddr: userAddr,
211                    amount: accuredLendingBorrowPoints,
212                    source: "LendingBorrow",
213                    param: {
214                        "BorrowUsdValue": self.getUserState_LendingBorrow(userAddr: userAddr).toString(),
215                        "Duration": durationStr
216                    }
217                )
218            }
219
220            // stFlow Holding
221            let accuredStFlowHoldingPoints = self.calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: userAddr)
222            if (accuredStFlowHoldingPoints > 0.0) {
223                emit PointsMinted(
224                    userAddr: userAddr,
225                    amount: accuredStFlowHoldingPoints,
226                    source: "stFlowHolding",
227                    param: {
228                        "stFlowHoldingBalance": self.getUserState_stFlowHolding(userAddr: userAddr).toString(),
229                        "Duration": durationStr
230                    }
231                )
232            }
233
234            // Swap LP
235            let accuredSwapLPPoints = self.calculateNewPointsSinceLastUpdate_SwapLP(userAddr: userAddr)
236            if (accuredSwapLPPoints > 0.0) {
237                emit PointsMinted(
238                    userAddr: userAddr,
239                    amount: accuredSwapLPPoints,
240                    source: "SwapLP",
241                    param: {
242                        "SwapLPUsdValue": self.getUserState_SwapLPUsd(userAddr: userAddr).toString(),
243                        "Duration": durationStr
244                    }
245                )
246            }
247
248            // Mint Points
249            let accuredPointsToMint = 
250                accuredLendingSupplyPoints +
251                accuredLendingBorrowPoints +
252                accuredStFlowHoldingPoints +
253                accuredSwapLPPoints
254            if (accuredPointsToMint > 0.0) {
255                self._mint(targetAddr: userAddr, amount: accuredPointsToMint)
256            }
257        }
258
259        //
260        let states = self.fetchOnchainUserStates(userAddr: userAddr)
261        let totalSupplyAmountInUsd = states[0]
262        let totalBorrowAmountInUsd = states[1]
263        let stFlowTotalBalance = states[2]
264        let totalLpBalanceUsd = states[3]
265        let totalLpAmount = states[4]
266
267        // Update State
268        if self._userStates.containsKey(userAddr) 
269            || totalSupplyAmountInUsd > 0.0 || totalBorrowAmountInUsd > 0.0
270            || stFlowTotalBalance > 0.0
271            || totalLpBalanceUsd > 0.0
272        {
273            if self._userStates.containsKey(userAddr) == false {
274                self._userStates[userAddr] = {}
275            }
276
277            self.setUserState_LendingSupply(userAddr: userAddr, supplyAmount: totalSupplyAmountInUsd)
278            self.setUserState_LendingBorrow(userAddr: userAddr, borrowAmount: totalBorrowAmountInUsd)
279            self.setUserState_stFlowHolding(userAddr: userAddr, stFlowBalance: stFlowTotalBalance)
280            self.setUserState_SwapLPUsd(userAddr: userAddr, lpUsd: totalLpBalanceUsd)
281            self.setUserState_SwapLPAmount(userAddr: userAddr, lpAmount: totalLpAmount)
282            
283            self.setUserState_LastUpdateTimestamp(userAddr: userAddr, timestamp: getCurrentBlock().timestamp)
284
285            //
286            emit StateUpdated(userAddr: userAddr, state: self._userStates[userAddr]!)
287        }
288    }
289
290    access(all) fun beginVolumeTracking(swapPoolAddr: Address) {
291        if self._swapPoolWhitelist.containsKey(swapPoolAddr) == false { return }
292
293        let poolInfo: [AnyStruct] = getAccount(swapPoolAddr).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
294        self._swapPoolReserve0 = poolInfo[2] as! UFix64
295        self._swapPoolReserve1 = poolInfo[3] as! UFix64
296
297        self._swapVolumeTrackingTimestamp = getCurrentBlock().timestamp
298        self._swapPoolAddress = swapPoolAddr
299    }
300
301    access(all) fun endVolumeTrackingAndMintPoints(userAddr: Address) {
302        if self._swapVolumeTrackingTimestamp != getCurrentBlock().timestamp {
303            return
304        }
305        self._swapVolumeTrackingTimestamp = 0.0
306
307        if self._userBlacklist.containsKey(userAddr) {
308            return
309        }
310
311        let poolInfo: [AnyStruct] = getAccount(self._swapPoolAddress).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
312        let reserve0Token = poolInfo[0] as! String
313        let reserve1Token = poolInfo[1] as! String
314        let curReserve0 = poolInfo[2] as! UFix64
315        let curReserve1 = poolInfo[3] as! UFix64
316
317        // Add/Remove Lp won't mint any points
318        if curReserve0 >= self._swapPoolReserve0 && curReserve1 >= self._swapPoolReserve1 { return }
319        if curReserve0 <= self._swapPoolReserve0 && curReserve1 <= self._swapPoolReserve1 { return }
320        var amountIn = 0.0
321        var amountOut = 0.0
322        var tokenInKey = ""
323        var tokenOutKey = ""
324        // Swap A to B
325        if curReserve0 > self._swapPoolReserve0 && curReserve1 < self._swapPoolReserve1 {
326            amountIn = curReserve0 - self._swapPoolReserve0
327            tokenInKey = reserve0Token
328            amountOut = self._swapPoolReserve1 - curReserve1
329            tokenOutKey = reserve1Token
330        }
331        // Swap B to A
332        if curReserve0 < self._swapPoolReserve0 && curReserve1 > self._swapPoolReserve1 {
333            amountIn = curReserve1 - self._swapPoolReserve1
334            tokenInKey = reserve1Token
335            amountOut = self._swapPoolReserve0 - curReserve0
336            tokenOutKey = reserve0Token
337        }
338
339        // Cal volume
340        let usdcPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0xf5d12412c09d2470)
341        let flowPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0xe385412159992e11)
342        let stflowPrice = PublicPriceOracle.getLatestPrice(oracleAddr: 0x031dabc5ba1d2932)
343        var volumeUsd = 0.0
344        if tokenInKey == "A.b19436aae4d94622.FiatToken" { volumeUsd = amountIn * usdcPrice }
345        else if tokenInKey == "A.1654653399040a61.FlowToken" { volumeUsd = amountIn * flowPrice }
346        else if tokenInKey == "A.d6f80565193ad727.stFlowToken" { volumeUsd = amountIn * stflowPrice }
347        else if tokenOutKey == "A.b19436aae4d94622.FiatToken" { volumeUsd = amountOut * usdcPrice }
348        else if tokenOutKey == "A.1654653399040a61.FlowToken" { volumeUsd = amountOut * flowPrice }
349        else if tokenOutKey == "A.d6f80565193ad727.stFlowToken" { volumeUsd = amountOut * stflowPrice }
350
351        // Mint points
352        let mintAmountBySwapVolume = volumeUsd * self.getPointsRate_SwapVolume()
353        if mintAmountBySwapVolume > 0.0 {
354            emit PointsMinted(
355                userAddr: userAddr,
356                amount: mintAmountBySwapVolume,
357                source: "SwapVolume", 
358                param: {
359                    "TokenInKey": tokenInKey,
360                    "TokenOutKey": tokenOutKey,
361                    "AmountIn": amountIn.toString(),
362                    "AmountOut": amountOut.toString(),
363                    "VolumeUsd": volumeUsd.toString()
364                }
365            )
366            self._mint(targetAddr: userAddr, amount: mintAmountBySwapVolume)
367
368            if self._userStates.containsKey(userAddr) == false {
369                self._userStates[userAddr] = {}
370            }
371            self.setUserState_SwapVolume(userAddr: userAddr, volume: self.getUserState_SwapVolume(userAddr: userAddr) + volumeUsd)
372            emit StateUpdated(userAddr: userAddr, state: self._userStates[userAddr]!)
373        }
374    }
375
376    access(all) view fun getBasePoints(_ userAddr: Address): UFix64 {
377        return self._pointsbase.containsKey(userAddr)? self._pointsbase[userAddr]! : 0.0
378    }
379    
380    access(all) view fun getHistorySnapshotPoints(_ userAddr: Address): UFix64 {
381        return self._pointsHistorySnapshot.containsKey(userAddr)? self._pointsHistorySnapshot[userAddr]! : 0.0
382    }
383
384    access(all) view fun getPointsAsReferrer(_ userAddr: Address): UFix64 {
385        return self._totalPointsAsReferrer.containsKey(userAddr)? self._totalPointsAsReferrer[userAddr]!: 0.0
386    }
387
388    access(all) view fun getPointsAsReferee(_ userAddr: Address): UFix64 {
389        return self._pointsAsReferee.containsKey(userAddr)? self._pointsAsReferee[userAddr]!: 0.0
390    }
391
392    access(all) view fun getPointsAndTimestamp(_ userAddr: Address): [UFix64; 3] {
393        return [self.balanceOf(userAddr), self.getPointsAsReferrer(userAddr), self.getUserState_LastUpdateTimestamp(userAddr: userAddr)]
394    }
395
396    access(all) view fun getBasePointsLength(): Int {
397        return self._pointsbase.length
398    }
399
400    access(all) view fun getUserInfo(_ userAddr: Address): [UFix64] {
401        return [
402            self.balanceOf(userAddr),
403            self.getPointsAsReferrer(userAddr),
404            self.getUserState_LastUpdateTimestamp(userAddr: userAddr),
405            self.getUserState_LendingSupply(userAddr: userAddr),
406            self.getUserState_LendingBorrow(userAddr: userAddr),
407            self.getUserState_stFlowHolding(userAddr: userAddr),
408            self.getUserState_SwapLPUsd(userAddr: userAddr),
409            self.getUserState_SwapVolume(userAddr: userAddr)
410        ]
411    }
412
413    access(all) view fun getUserInfosLength(): Int {
414        return self._userStates.length
415    }
416
417    access(all) view fun getSlicedUserInfos(from: Int, to: Int): {Address: [UFix64]} {
418        let len = self._userStates.length
419        let endIndex = to > len ? len : to
420        var curIndex = from
421        let res: {Address: [UFix64]} = {}
422        while curIndex < endIndex {
423            let userAddr: Address = self._userStates.keys[curIndex]
424            res[userAddr] = self.getUserInfo(userAddr)
425            curIndex = curIndex + 1
426        }
427        return res
428    }
429
430    access(all) view fun getSlicedBalances(from: Int, to: Int): {Address: UFix64} {
431        let len = self._pointsbase.length
432        let endIndex = to > len ? len : to
433        var curIndex = from
434        let res: {Address: UFix64} = {}
435        while curIndex < endIndex {
436            let key: Address = self._pointsbase.keys[curIndex]
437            res[key] = self.balanceOf(key)
438            curIndex = curIndex + 1
439        }
440        return res
441    }
442
443    access(all) view fun getSlicedPointsAndTimestamp(from: Int, to: Int): {Address: [UFix64; 3]} {
444        let len = self._pointsbase.length
445        let endIndex = to > len ? len : to
446        var curIndex = from
447        let res: {Address: [UFix64; 3]} = {}
448        while curIndex < endIndex {
449            let key: Address = self._pointsbase.keys[curIndex]
450            res[key] = self.getPointsAndTimestamp(key)
451            curIndex = curIndex + 1
452        }
453        return res
454    }
455
456    access(all) view fun fetchOnchainUserStates(userAddr: Address): [UFix64] {
457        // Oracle Price
458        let oraclePrices: {String: UFix64} = { // OracleAddress -> Token Price
459            "Flow": PublicPriceOracle.getLatestPrice(oracleAddr: 0xe385412159992e11),
460            "stFlow": PublicPriceOracle.getLatestPrice(oracleAddr: 0x031dabc5ba1d2932),
461            "USDC": PublicPriceOracle.getLatestPrice(oracleAddr: 0xf5d12412c09d2470)
462        }
463  
464        // Lending State
465        let lendingComptrollerRef = getAccount(0xf80cb737bfe7c792).getCapability<&{LendingInterfaces.ComptrollerPublic}>(LendingConfig.ComptrollerPublicPath).borrow()!
466        let marketAddrs: [Address] = lendingComptrollerRef.getAllMarkets()
467        let lendingOracleRef = getAccount(0x72d3a05910b6ffa3).getCapability<&{LendingInterfaces.OraclePublic}>(LendingConfig.OraclePublicPath).borrow()!
468        var totalSupplyAmountInUsd = 0.0
469        var totalBorrowAmountInUsd = 0.0
470        for poolAddr in marketAddrs {
471            let poolRef = lendingComptrollerRef.getPoolPublicRef(poolAddr: poolAddr)
472            let poolOraclePrice = lendingOracleRef.getUnderlyingPrice(pool: poolAddr)
473            let res: [UInt256; 5] = poolRef.getAccountRealtimeScaled(account: userAddr)
474            let supplyAmount = SwapConfig.ScaledUInt256ToUFix64(res[0] * res[1] / SwapConfig.scaleFactor)
475            let borrowAmount = SwapConfig.ScaledUInt256ToUFix64(res[2])
476            totalSupplyAmountInUsd = totalSupplyAmountInUsd + supplyAmount * poolOraclePrice
477            totalBorrowAmountInUsd = totalBorrowAmountInUsd + borrowAmount * poolOraclePrice
478        }
479
480        // Liquid Staking State
481        // stFlow
482        var stFlowTotalBalance = 0.0
483        let stFlowVaultCap = getAccount(userAddr).getCapability<&{FungibleToken.Balance}>(/public/stFlowTokenBalance)
484        if stFlowVaultCap.check() {
485            // Prevent fake stFlow token vault
486            if stFlowVaultCap.borrow()!.getType().identifier == "A.d6f80565193ad727.stFlowToken.Vault" {
487                stFlowTotalBalance = stFlowVaultCap.borrow()!.balance
488            }
489        }
490
491        // Swap LP in Balance
492        let lpPrices: {Address: UFix64} = {}
493        var totalLpBalanceUsd = 0.0
494        var totalLpAmount = 0.0
495        let lpTokenCollectionCap = getAccount(userAddr).getCapability<&{SwapInterfaces.LpTokenCollectionPublic}>(SwapConfig.LpTokenCollectionPublicPath)
496        if lpTokenCollectionCap.check() {
497            // Prevent fake lp token vault
498            if lpTokenCollectionCap.borrow()!.getType().identifier == "A.b063c16cac85dbd1.SwapFactory.LpTokenCollection" {
499                let lpTokenCollectionRef = lpTokenCollectionCap.borrow()!
500                let liquidityPairAddrs = lpTokenCollectionRef.getAllLPTokens()
501                for pairAddr in liquidityPairAddrs {
502                    // 
503                    if self._swapPoolWhitelist.containsKey(pairAddr) == false {
504                        continue
505                    }
506
507                    var lpTokenAmount = lpTokenCollectionRef.getLpTokenBalance(pairAddr: pairAddr)
508                    let pairInfo = getAccount(pairAddr).getCapability<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair).borrow()!.getPairInfo()
509                    // Cal lp price
510                    var lpPrice = 0.0
511                    if lpPrices.containsKey(pairAddr) {
512                        lpPrice = lpPrices[pairAddr]!
513                    } else {
514                        lpPrice = self.calValidLpPrice(pairInfo: pairInfo, oraclePrices: oraclePrices)
515                        lpPrices[pairAddr] = lpPrice
516                    }
517                    if lpPrice == 0.0 || lpTokenAmount == 0.0 { continue }
518                    totalLpBalanceUsd = totalLpBalanceUsd + lpPrice * lpTokenAmount
519                    totalLpAmount = totalLpAmount + lpTokenAmount
520                }
521            }
522        }
523
524        // Swap LP in Farm & stFlow in Farm
525        let farmCollectionRef = getAccount(0x1b77ba4b414de352).getCapability<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath).borrow()!
526        let userFarmIds = Staking.getUserStakingIds(address: userAddr)
527        for farmPoolId in userFarmIds {
528            let farmPool = farmCollectionRef.getPool(pid: farmPoolId)
529            let farmPoolInfo = farmPool.getPoolInfo()
530            let userInfo = farmPool.getUserInfo(address: userAddr)!
531            if farmPoolInfo.status == "0" || farmPoolInfo.status == "1" || farmPoolInfo.status == "2" {
532                let acceptTokenKey = farmPoolInfo.acceptTokenKey
533                let acceptTokenName = acceptTokenKey.slice(from: 19, upTo: acceptTokenKey.length)
534                let userFarmAmount = userInfo.stakingAmount
535                // add stFlow holding balance
536                if acceptTokenKey == "A.d6f80565193ad727.stFlowToken" {
537                    stFlowTotalBalance = stFlowTotalBalance + userFarmAmount
538                    continue
539                }
540                if userFarmAmount == 0.0 {
541                    continue
542                }
543                // add lp holding balance
544                let swapPoolAddress = self.type2address(acceptTokenKey)
545                if acceptTokenName == "SwapPair" {
546                    let swapPoolInfo = getAccount(swapPoolAddress).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()!.getPairInfo()
547                    var lpPrice = 0.0
548                    if lpPrices.containsKey(swapPoolAddress) {
549                        lpPrice = lpPrices[swapPoolAddress]!
550                    } else {
551                        lpPrice = self.calValidLpPrice(pairInfo: swapPoolInfo, oraclePrices: oraclePrices)
552                        lpPrices[swapPoolAddress] = lpPrice
553                    }
554                    totalLpBalanceUsd = totalLpBalanceUsd + userFarmAmount * lpPrice
555                    totalLpAmount = totalLpAmount + userFarmAmount
556                }
557            }
558        }
559
560        return [
561            totalSupplyAmountInUsd,
562            totalBorrowAmountInUsd,
563            stFlowTotalBalance,
564            totalLpBalanceUsd,
565            totalLpAmount
566        ]
567    }
568
569    // Accure Lending Supply
570    access(all) view fun calculateNewPointsSinceLastUpdate_LendingSupply(userAddr: Address): UFix64 {
571        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
572        var accuredPoints = 0.0
573        if lastUpdateTimestamp == 0.0 { return 0.0 }
574        let currUpdateTimestamp = getCurrentBlock().timestamp
575        let duration = currUpdateTimestamp - lastUpdateTimestamp
576        if duration > 0.0 {
577            let supplyAmountUsd = self.getUserState_LendingSupply(userAddr: userAddr)
578            accuredPoints = supplyAmountUsd * self.getPointsRate_LendingSupply(amount: supplyAmountUsd) / self._secondsPerDay * duration
579        }
580        return accuredPoints
581    }
582
583    // Accure Lending Borrow
584    access(all) view fun calculateNewPointsSinceLastUpdate_LendingBorrow(userAddr: Address): UFix64 {
585        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
586        var accuredPoints = 0.0
587        if lastUpdateTimestamp == 0.0 { return 0.0 }
588        let currUpdateTimestamp = getCurrentBlock().timestamp
589        let duration = currUpdateTimestamp - lastUpdateTimestamp
590        if duration > 0.0 {
591            let borrowAmountUsd = self.getUserState_LendingBorrow(userAddr: userAddr)
592            accuredPoints = borrowAmountUsd * self.getPointsRate_LendingBorrow(amount: borrowAmountUsd) / self._secondsPerDay * duration
593        }
594        return accuredPoints
595    }
596
597    // Accure Liquid Staking - stFlowHolding
598    access(self) view fun calculateNewPointsSinceLastUpdate_stFlowHolding(userAddr: Address): UFix64 {
599        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
600        var accuredPoints = 0.0
601        if lastUpdateTimestamp == 0.0 { return 0.0 }
602        let currUpdateTimestamp = getCurrentBlock().timestamp
603        let duration = currUpdateTimestamp - lastUpdateTimestamp
604        if duration > 0.0 {
605            let stFlowHolding = self.getUserState_stFlowHolding(userAddr: userAddr)
606            accuredPoints = stFlowHolding * self.getPointsRate_stFlowHolding(amount: stFlowHolding) / self._secondsPerDay * duration
607        }
608        return accuredPoints
609    }
610
611    // Accure Swap LP
612    access(self) view fun calculateNewPointsSinceLastUpdate_SwapLP(userAddr: Address): UFix64 {
613        let lastUpdateTimestamp = self.getUserState_LastUpdateTimestamp(userAddr: userAddr)
614        var accuredPoints = 0.0
615        if lastUpdateTimestamp == 0.0 { return 0.0 }
616        let currUpdateTimestamp = getCurrentBlock().timestamp
617        let duration = currUpdateTimestamp - lastUpdateTimestamp
618        if duration > 0.0 {
619            let swapLP = self.getUserState_SwapLPUsd(userAddr: userAddr)
620            accuredPoints = swapLP * self.getPointsRate_SwapLP(amount: swapLP) / self._secondsPerDay * duration
621        }
622        return accuredPoints
623    }
624
625    access(all) view fun getTopUsers(): [Address] { return self._topUsers }
626
627    access(all) view fun getSwapPoolWhiltlist(): {Address: Bool} { return self._swapPoolWhitelist }
628
629    access(all) view fun getUserBlacklist(): {Address: Bool} { return self._userBlacklist }
630
631    // Get Points Rate
632    access(all) view fun getPointsRate(): {String: AnyStruct} { return self._pointsRatePerDay }
633    access(all) view fun getPointsRate_LendingSupply(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["LendingSupply"]! as! {UFix64: UFix64}) }
634    access(all) view fun getPointsRate_LendingBorrow(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["LendingBorrow"]! as! {UFix64: UFix64}) }
635    access(all) view fun getPointsRate_stFlowHolding(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["stFlowHolding"]! as! {UFix64: UFix64}) }
636    access(all) view fun getPointsRate_SwapLP(amount: UFix64): UFix64 { return self.calculateTierRateByAmount(amount: amount, tier: self._pointsRatePerDay["SwapLP"]! as! {UFix64: UFix64}) }
637    access(all) view fun getPointsRate_SwapVolume(): UFix64 { return self._pointsRatePerDay["SwapVolume"]! as! UFix64 }
638    access(all) view fun getPointsRate_ReferrerUp(): UFix64 { return self._pointsRatePerDay["ReferrerUp"]! as! UFix64 }
639    access(all) view fun getPointsRate_RefereeUp(): UFix64 { return self._pointsRatePerDay["RefereeUp"]! as! UFix64 }
640
641    // Get User State
642    access(all) view fun getUserState(userAddr: Address): {String: UFix64} { return self._userStates.containsKey(userAddr)? self._userStates[userAddr]! : {} }
643    access(all) view fun getUserState_LastUpdateTimestamp(userAddr: Address): UFix64 { return self._userStates.containsKey(userAddr)? (self._userStates[userAddr]!.containsKey("LastUpdateTimestamp")? self._userStates[userAddr]!["LastUpdateTimestamp"]! : 0.0): 0.0 }
644    access(all) view fun getUserState_LendingSupply(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("LendingTotalSupplyUsd")? self._userStates[userAddr]!["LendingTotalSupplyUsd"]! : 0.0 }
645    access(all) view fun getUserState_LendingBorrow(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("LendingTotalBorrowUsd")? self._userStates[userAddr]!["LendingTotalBorrowUsd"]! : 0.0 }
646    access(all) view fun getUserState_stFlowHolding(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("stFlowHolding")? self._userStates[userAddr]!["stFlowHolding"]! : 0.0 }
647    access(all) view fun getUserState_SwapLPUsd(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapLpUsd")? self._userStates[userAddr]!["SwapLpUsd"]! : 0.0 }
648    access(all) view fun getUserState_SwapLPAmount(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapLpAmount")? self._userStates[userAddr]!["SwapLpAmount"]! : 0.0 }
649    access(all) view fun getUserState_SwapVolume(userAddr: Address): UFix64 { return self._userStates[userAddr]!.containsKey("SwapVolumeUsd")? self._userStates[userAddr]!["SwapVolumeUsd"]! : 0.0 }
650    
651    access(self) fun setUserState_LastUpdateTimestamp(userAddr: Address, timestamp: UFix64) { self._userStates[userAddr]!.insert(key: "LastUpdateTimestamp", timestamp) }
652    access(self) fun setUserState_LendingSupply(userAddr: Address, supplyAmount: UFix64) { self._userStates[userAddr]!.insert(key: "LendingTotalSupplyUsd", supplyAmount) }
653    access(self) fun setUserState_LendingBorrow(userAddr: Address, borrowAmount: UFix64) { self._userStates[userAddr]!.insert(key: "LendingTotalBorrowUsd", borrowAmount) }
654    access(self) fun setUserState_stFlowHolding(userAddr: Address, stFlowBalance: UFix64) { self._userStates[userAddr]!.insert(key: "stFlowHolding", stFlowBalance) }
655    access(self) fun setUserState_SwapLPUsd(userAddr: Address, lpUsd: UFix64) { self._userStates[userAddr]!.insert(key: "SwapLpUsd", lpUsd) }
656    access(self) fun setUserState_SwapLPAmount(userAddr: Address, lpAmount: UFix64) { self._userStates[userAddr]!.insert(key: "SwapLpAmount", lpAmount) }
657    access(self) fun setUserState_SwapVolume(userAddr: Address, volume: UFix64) { self._userStates[userAddr]!.insert(key: "SwapVolumeUsd", volume) }
658
659    access(all) view fun calculateTierRateByAmount(amount: UFix64, tier: {UFix64: UFix64}): UFix64 {
660        var rate = 0.0
661        var maxThreshold = 0.0
662        for threshold in tier.keys {
663            if amount >= threshold && threshold >= maxThreshold {
664                rate = tier[threshold]!
665                maxThreshold = threshold
666            }
667        }
668        return rate
669    }
670
671    access(all) view fun calValidLpPrice(pairInfo: [AnyStruct], oraclePrices: {String: UFix64}): UFix64 {
672        var reserveAmount = 0.0
673        var reservePrice = 0.0
674        var lpPrice = 0.0
675        if pairInfo[0] as! String == "A.b19436aae4d94622.FiatToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["USDC"]!}
676        else if pairInfo[1] as! String == "A.b19436aae4d94622.FiatToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["USDC"]!}
677        else if pairInfo[0] as! String == "A.1654653399040a61.FlowToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["Flow"]!}
678        else if pairInfo[1] as! String == "A.1654653399040a61.FlowToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["Flow"]!}
679        else if pairInfo[0] as! String == "A.d6f80565193ad727.stFlowToken" {reserveAmount = pairInfo[2] as! UFix64; reservePrice = oraclePrices["stFlow"]!}
680        else if pairInfo[1] as! String == "A.d6f80565193ad727.stFlowToken" {reserveAmount = pairInfo[3] as! UFix64; reservePrice = oraclePrices["stFlow"]!}
681        if reservePrice > 0.0 && reserveAmount > 1000.0 {
682            lpPrice = reserveAmount * reservePrice * 2.0 / (pairInfo[5] as! UFix64)
683        }
684        return lpPrice
685    }
686
687    access(all) view fun type2address(_ type: String) : Address {
688        let address = type.slice(from: 2, upTo: 18)
689        var r: UInt64 = 0
690        var bytes = address.decodeHex()
691        while bytes.length>0{
692            r = r  + (UInt64(bytes.removeFirst()) << UInt64(bytes.length*8))
693        }
694        return Address(r)
695    }
696
697    /// Admin
698    ///
699    access(all) resource Admin {
700        // Set Points Rate
701        access(all) fun setPointsRate_stFlowHoldingTier(tierRate: {UFix64: UFix64}) {
702            emit PointsTierRateChanged(source: "stFlowHolding", ori: PPPV3._pointsRatePerDay["stFlowHolding"]! as! {UFix64: UFix64}, new: tierRate)
703            PPPV3._pointsRatePerDay["stFlowHolding"] = tierRate
704        }
705        access(all) fun setPointsRate_LendingSupplyTier(tierRate: {UFix64: UFix64}) {
706            emit PointsTierRateChanged(source: "LendingSupply", ori: PPPV3._pointsRatePerDay["LendingSupply"]! as! {UFix64: UFix64}, new: tierRate)
707            PPPV3._pointsRatePerDay["LendingSupply"] = tierRate
708        }
709        access(all) fun setPointsRate_LendingBorrowTier(tierRate: {UFix64: UFix64}) {
710            emit PointsTierRateChanged(source: "LendingBorrow", ori: PPPV3._pointsRatePerDay["LendingBorrow"]! as! {UFix64: UFix64}, new: tierRate)
711            PPPV3._pointsRatePerDay["LendingBorrow"] = tierRate
712        }
713        access(all) fun setPointsRate_SwapLPTier(tierRate: {UFix64: UFix64}) {
714            emit PointsTierRateChanged(source: "SwapLP", ori: PPPV3._pointsRatePerDay["SwapLP"]! as! {UFix64: UFix64}, new: tierRate)
715            PPPV3._pointsRatePerDay["SwapLP"] = tierRate
716        }
717        access(all) fun setPointsRate_SwapVolume(rate: UFix64) {
718            emit PointsRateChanged(source: "SwapVolume", ori: PPPV3._pointsRatePerDay["SwapVolume"]! as! UFix64, new: rate)
719            PPPV3._pointsRatePerDay["SwapVolume"] = rate
720        }
721        access(all) fun setPointsRate_ReferrerUp(rate: UFix64) {
722            emit PointsRateChanged(source: "ReferrerUp", ori: PPPV3._pointsRatePerDay["ReferrerUp"]! as! UFix64, new: rate)
723            PPPV3._pointsRatePerDay["ReferrerUp"] = rate
724        }
725        access(all) fun setPointsRate_RefereeUp(rate: UFix64) {
726            emit PointsRateChanged(source: "RefereeUp", ori: PPPV3._pointsRatePerDay["RefereeUp"]! as! UFix64, new: rate)
727            PPPV3._pointsRatePerDay["RefereeUp"] = rate
728        }
729
730        // Add Swap Pool in Whiltelist
731        access(all) fun addSwapPoolInWhiltelist(poolAddr: Address) {
732            PPPV3._swapPoolWhitelist[poolAddr] = true
733        }
734        // Remove Swap Pool in Whitelist
735        access(all) fun removeSwapPoolInWhiltelist(poolAddr: Address) {
736            PPPV3._swapPoolWhitelist.remove(key: poolAddr)
737        }
738
739        // Set history snapshot points
740        access(all) fun setHistorySnapshotPoints(userAddr: Address, newSnapshotBalance: UFix64) {
741            if PPPV3._pointsHistorySnapshot.containsKey(userAddr) == false {
742                PPPV3._pointsHistorySnapshot[userAddr] = 0.0
743            }
744            if PPPV3._pointsbase.containsKey(userAddr) == false {
745                PPPV3._pointsbase[userAddr] = 0.0
746            }
747            let preSnapshotBalance = PPPV3._pointsHistorySnapshot[userAddr]!
748            if preSnapshotBalance == newSnapshotBalance {
749                return
750            }
751            if preSnapshotBalance < newSnapshotBalance {
752                emit PointsMinted(userAddr: userAddr, amount: newSnapshotBalance - preSnapshotBalance, source: "HistorySnapshot", param: {"PreBalance": preSnapshotBalance.toString(), "NewBalance": newSnapshotBalance.toString()})
753            } else {
754                emit PointsBurned(userAddr: userAddr, amount: preSnapshotBalance - newSnapshotBalance, source: "HistorySnapshot", param: {"PreBalance": preSnapshotBalance.toString(), "NewBalance": newSnapshotBalance.toString()})
755            }
756            PPPV3._totalSupply = PPPV3._totalSupply - preSnapshotBalance + newSnapshotBalance
757            PPPV3._pointsHistorySnapshot[userAddr] = newSnapshotBalance
758        }
759
760        // Ban user
761        access(all) fun addUserBlackList(userAddr: Address) {
762            PPPV3._userBlacklist[userAddr] = true
763        }
764        access(all) fun removeUserBlackList(userAddr: Address) {
765            PPPV3._userBlacklist.remove(key: userAddr)
766        }
767        access(all) fun reconcileBasePoints(userAddr: Address, newBasePoints: UFix64) {
768            // TODO process referral points
769            if PPPV3._pointsbase.containsKey(userAddr) == false {
770                PPPV3._pointsbase[userAddr] = 0.0
771            }
772            let preBasePoints = PPPV3._pointsbase[userAddr]!
773            if preBasePoints == newBasePoints {
774                return
775            }
776            if preBasePoints < newBasePoints {
777                emit PointsMinted(userAddr: userAddr, amount: newBasePoints - preBasePoints, source: "Reconcile", param: {"PreBaseBalance": preBasePoints.toString(), "NewBaseBalance": newBasePoints.toString()})
778            } else {
779                emit PointsBurned(userAddr: userAddr, amount: preBasePoints - newBasePoints, source: "Reconcile", param: {"PreBaseBalance": preBasePoints.toString(), "NewBaseBalance": newBasePoints.toString()})
780            }
781            PPPV3._totalSupply = PPPV3._totalSupply - preBasePoints + newBasePoints
782            PPPV3._pointsbase[userAddr] = newBasePoints
783        }
784
785        access(all) fun updateTopUsers(addrs: [Address]) {
786            emit TopUsersChanged(ori: PPPV3._topUsers, new: addrs)
787            PPPV3._topUsers = addrs
788        }     
789    }
790
791    init() {
792        self._totalSupply = 0.0
793        self._secondsPerDay = 86400.0
794        self._pointsbase = {}
795        self._pointsHistorySnapshot = {}
796        self._totalPointsAsReferrer = {}
797        self._pointsFromReferees = {}
798        self._pointsAsReferee = {}
799        self._topUsers = []
800        self._pointsRatePerDay = {
801            "stFlowHolding": {
802                0.0:        1.0,
803                1000.0:     2.0,
804                10000.0:    3.0
805            },
806            "LendingSupply": {
807                0.0:        0.001,
808                1000.0:     0.002,
809                10000.0:    0.003
810            },
811            "LendingBorrow": {
812                0.0:        0.002,
813                1000.0:     0.004,
814                10000.0:    0.006
815            },
816            "SwapLP": {
817                0.0:        1.0,
818                1000.0:     2.0,
819                10000.0:    3.0
820            },
821            "SwapVolume": 5.0,
822            "ReferrerUp": 0.1,
823            "RefereeUp": 0.1
824        }
825        self._swapPoolWhitelist = {
826            0xfa82796435e15832: true, // FLOW-USDC
827            0xcc96d987317f0342: true, // FLOW-ceWETH
828            0x09c49abce2a7385c: true, // FLOW-ceWBTC
829            0x396c0cda3302d8c5: true, // FLOW-stFLOW v1
830            0xc353b9d685ec427d: true, // FLOW-stFLOW stable
831            0xa06c38beec9cf0e8: true, // FLOW-DUST
832            0xbfb26bb8adf90399: true  // FLOW-SLOPPY
833        }
834
835        self._userStates = {}
836        self._userBlacklist = {}
837
838        self._swapPoolAddress = 0x00
839        self._swapVolumeTrackingTimestamp = 0.0
840        self._swapPoolReserve0 = 0.0
841        self._swapPoolReserve1 = 0.0
842        
843
844        self._reservedFields = {}
845
846        destroy <-self.account.load<@AnyResource>(from: /storage/pointsAdmin1)
847        self.account.save(<-create Admin(), to: /storage/pointsAdmin1)
848    }
849}
850