Smart Contract

PPPV2

A.3ddb31a3880d1d8f.PPPV2

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