Smart Contract

ToucansUtils

A.577a3c409c5dcb5e.ToucansUtils

Deployed

1d ago
Feb 26, 2026, 10:26:44 PM UTC

Dependents

0 imports
1import Crypto
2import FungibleToken from 0xf233dcee88fe0abe
3import NFTCatalog from 0x49a7cda3a1eecc29
4import NonFungibleToken from 0x1d7e57aa55817448
5import FIND from 0x097bafa4e0b48eef
6import EmeraldIdentity from 0x39e42c67cc851cfb
7import SwapInterfaces from 0xb78ef7afa52ff906
8import LiquidStaking from 0xd6f80565193ad727
9import FlowToken from 0x1654653399040a61
10import stFlowToken from 0xd6f80565193ad727
11
12access(all) contract ToucansUtils {
13  access(all) fun ownsNFTFromCatalogCollectionIdentifier(collectionIdentifier: String, user: Address): Bool {
14    if let entry: NFTCatalog.NFTCatalogMetadata = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier) {
15      let publicPath: PublicPath = entry.collectionData.publicPath
16      let contractAddressToString: String = entry.contractAddress.toString()
17      let constructedIdentifier: String = "A.".concat(contractAddressToString.slice(from: 2, upTo: contractAddressToString.length)).concat(".").concat(entry.contractName).concat(".Collection")
18
19      var addresses: [Address] = [user]
20      if let discordID: String = EmeraldIdentity.getDiscordFromAccount(account: user) {
21        addresses = EmeraldIdentity.getEmeraldIDs(discordID: discordID).values
22      }
23      assert(addresses.contains(user), message: "Should always be true. Just making sure so the user doesn't get punished accidentally ;)")
24      for address in addresses {
25        if let collection: &{NonFungibleToken.CollectionPublic} = getAccount(address).capabilities.borrow<&{NonFungibleToken.CollectionPublic}>(publicPath) {
26          let identifier: String = collection.getType().identifier
27          if identifier == constructedIdentifier && collection.getIDs().length > 0 {
28            return true
29          }
30        }
31      }
32    }
33    
34    return false
35  }
36
37  access(all) fun depositTokensToAccount(funds: @{FungibleToken.Vault}, to: Address, publicPath: PublicPath) {
38    let vault = getAccount(to).capabilities.borrow<&{FungibleToken.Receiver}>(publicPath) 
39              ?? panic("Account does not have a proper Vault set up.")
40    vault.deposit(from: <- funds)
41  }
42
43  access(all) fun rangeFunc(_ start: Int, _ end: Int, _ f : (fun (Int): Void) ) {
44    var current = start
45    while current < end{
46        f(current)
47        current = current + 1
48    }
49  } 
50
51  access(all) fun range(_ start: Int, _ end: Int): [Int]{
52    var res:[Int] = []
53    self.rangeFunc(start, end, fun (i:Int){
54        res.append(i)
55    })
56    return res
57  }
58
59  access(all) fun index(_ s : String, _ substr : String, _ startIndex: Int): Int?{
60    for i in self.range(startIndex,s.length-substr.length+1){
61        if s[i]==substr[0] && s.slice(from:i, upTo:i+substr.length) == substr{
62            return i
63        }
64    }
65    return nil
66  }
67
68  access(all) fun getFind(_ address: Address): String {
69    if let name = FIND.reverseLookup(address) {
70      return name.concat(".find")
71    }
72    return address.toString()
73  }
74
75  access(all) fun fixToReadableString(num: UFix64): String {
76    let numToString: String = num.toString()
77    let indexOfDot: Int = ToucansUtils.index(numToString, ".", 1)!
78    return numToString.slice(from: 0, upTo: indexOfDot + 3)
79  }
80
81  // stringAddress DOES NOT include the `0x`
82  access(all) fun stringToAddress(stringAddress: String): Address {
83    var r: UInt64 = 0
84    var bytes: [UInt8] = stringAddress.decodeHex()
85
86    while bytes.length > 0 {
87      r = r + (UInt64(bytes.removeFirst()) << UInt64(bytes.length * 8))
88    }
89
90    return Address(r)
91  }
92
93  // returns:
94  // [address, contractname]
95  access(all) fun getAddressAndContractNameFromCollectionIdentifier(identifier: String): [AnyStruct] {
96    let address: Address = self.stringToAddress(stringAddress: identifier.slice(from: 2, upTo: 18))
97    let contractName: String = identifier.slice(from: 19, upTo: identifier.length - 11)
98    return [address, contractName]
99  }
100
101  access(all) fun getEstimatedOut(amountIn: UFix64, tokenInKey: String): UFix64 {
102    // normal xyk pool
103    let poolCapV1 = getAccount(0x396c0cda3302d8c5).capabilities.borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)!
104    // stableswap pool with most liquidity
105    let poolCapStable = getAccount(0xc353b9d685ec427d).capabilities.borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)!
106    
107    let estimatedSwapOutV1 = poolCapV1.getAmountOut(amountIn: amountIn, tokenInKey: tokenInKey)
108    let estimatedSwapOutStable = poolCapStable.getAmountOut(amountIn: amountIn, tokenInKey: tokenInKey)
109    let estimatedSwapOut = (estimatedSwapOutStable > estimatedSwapOutV1) ? estimatedSwapOutStable : estimatedSwapOutV1
110
111    if tokenInKey == "A.1654653399040a61.FlowToken" {
112      let estimatedStakeOut = LiquidStaking.calcStFlowFromFlow(flowAmount: amountIn)
113      return (estimatedSwapOut > estimatedStakeOut) ? estimatedSwapOut : estimatedStakeOut
114    }
115
116    return estimatedSwapOut
117  }
118
119  access(all) fun swapTokensWithPotentialStake(inVault: @{FungibleToken.Vault}, tokenInKey: String): @{FungibleToken.Vault} {
120    let amountIn = inVault.balance
121    // normal xyk pool
122    let poolCapV1 = getAccount(0x396c0cda3302d8c5).capabilities.borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)!
123    let estimatedSwapOutV1 = poolCapV1.getAmountOut(amountIn: amountIn, tokenInKey: tokenInKey)
124    // stableswap pool with most liquidity
125    let poolCapStable = getAccount(0xc353b9d685ec427d).capabilities.borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)!
126    let estimatedSwapOutStable = poolCapStable.getAmountOut(amountIn: amountIn, tokenInKey: tokenInKey)
127
128    let estimatedSwapPoolCap = (estimatedSwapOutStable > estimatedSwapOutV1) ? poolCapStable : poolCapV1
129    
130    let estimatedSwapOut = (estimatedSwapOutStable > estimatedSwapOutV1) ? estimatedSwapOutStable : estimatedSwapOutV1
131    let estimatedStakeOut = LiquidStaking.calcStFlowFromFlow(flowAmount: amountIn)
132
133    if tokenInKey == "A.1654653399040a61.FlowToken" && estimatedStakeOut > estimatedSwapOut {
134      return <- LiquidStaking.stake(flowVault: <- (inVault as! @FlowToken.Vault))
135    }
136
137    return <- estimatedSwapPoolCap.swap(vaultIn: <- inVault, exactAmountOut: nil)
138  }
139
140  access(all) fun getNFTCatalogCollectionIdentifierFromCollectionIdentifier(collectionIdentifier: String): String {
141    let nftTypeIdentifier: String = collectionIdentifier.slice(from: 0, upTo: collectionIdentifier.length - 10).concat("NFT")
142    let collectionsForType: {String: Bool} = NFTCatalog.getCollectionsForType(nftTypeIdentifier: nftTypeIdentifier) ?? panic("This collection is not supported in the NFTCatalog.")
143    let collectionIdentifier: String = collectionsForType.keys[0]
144    return collectionIdentifier
145  }
146}