Smart Contract

FindRelatedAccounts

A.097bafa4e0b48eef.FindRelatedAccounts

Deployed

1d ago
Feb 26, 2026, 03:12:51 AM UTC

Dependents

11 imports
1access(all) contract FindRelatedAccounts {
2    // Entitlements
3    access(all) entitlement Owner
4
5    // Paths
6    access(all) let storagePath: StoragePath
7    access(all) let publicPath: PublicPath
8
9    // Events
10    access(all) event RelatedAccount(user: Address, walletId: String, walletName: String, address: String, network: String, action: String)
11
12    access(all) struct AccountInformation{
13        access(all) let name:String
14        access(all) let address:Address?
15        access(all) let network:String //do not use enum because of contract upgrade
16        access(all) let stringAddress: String //other networks besides flow may be not support Address, duplication of flow address in string
17        access(all) let extra: {String : AnyStruct}
18
19        init(name:String, address:Address?, network:String, otherAddress:String?){
20            pre{
21                address == nil && otherAddress != nil || address != nil && otherAddress == nil : "Please put either flow address or other address for account"
22            }
23            self.name=name
24            self.address=address
25            self.network=network
26            self.stringAddress=otherAddress ?? address!.toString()
27            self.extra={}
28        }
29    }
30
31    access(all) resource interface Public{
32        access(all) fun getFlowAccounts() : {String: [Address]}
33        access(all) fun getRelatedAccounts(_ network: String) : {String : [String]}
34        access(all) fun getAllRelatedAccounts() : {String : {String : [String]}}
35        access(all) fun getAllRelatedAccountInfo() : {String : AccountInformation}
36        // verify enfun sure this wallet address exist under the network
37        access(all) fun verify(network: String, address: String) : Bool
38        // linked enfun sure this wallet is linked in both wallet with the same name (but not socially linked only)
39        // only suppfun orts flow for now
40        access(all) fun linked(name: String, network: String, address: Address) : Bool
41        access(all) fun getAccount(name: String, network: String, address: String) : AccountInformation?
42    }
43
44    /// This is just an empty resource we create in storage, you can safely send a reference to it to obtain msg.sender
45    access(all) resource Accounts: Public {
46
47        // { ETH : ETH_Blocto }
48        access(self) let networks: {String : [String]}
49        // { ETH_Blocto : [Address] }
50        access(self) let wallets : {String : [String]}
51        // { ETH_Blocto_Address : AccountInformation }
52        access(self) let accounts : {String : AccountInformation}
53
54        init() {
55            self.networks={}
56            self.wallets={}
57            self.accounts={}
58        }
59
60        access(all) fun linked(name: String, network: String, address: Address) : Bool {
61            if let cap = FindRelatedAccounts.getCapability(address) {
62                if let acct = cap.borrow()!.getAccount(name: name, network: network, address: self.owner!.address.toString()) {
63                    return true
64                }
65            }
66            return false
67        }
68
69        access(all) fun verify(network: String, address: String) : Bool {
70            if let wallets = self.networks[network] {
71                for wallet in wallets {
72                    let ws = self.wallets[wallet]!
73                    for candiidate in ws {
74                        if candiidate.toLower() == address.toLower() {
75                            return true
76                        }
77                    }
78                }
79            }
80            return false
81        }
82
83        access(all) fun getFlowAccounts() : {String: [Address]} {
84            let network = "Flow"
85            let tempItems : {String: [Address]} ={}
86            if let wallets = self.networks[network] {
87                for wallet in wallets {
88                    let ws = self.wallets[wallet]!
89                    for addr in ws {
90                        let id = wallet.concat("_").concat(addr)
91                        let info = self.accounts[id]!
92
93                        let tempArray = tempItems[wallet] ?? []
94                        tempArray.append(info.address!)
95                        tempItems[wallet] = tempArray
96                    }
97                }
98            }
99            return tempItems
100        }
101
102        access(all) fun getRelatedAccounts(_ network: String) : {String : [String]} {
103            return self.internal_getRelatedAccounts(network)[network] ?? {}
104        }
105
106        access(all) fun getAllRelatedAccounts() : {String : {String : [String]}} {
107            return self.internal_getRelatedAccounts(nil)
108        }
109
110        access(all) fun getAllRelatedAccountInfo() : {String : AccountInformation} {
111            return self.accounts
112        }
113
114        access(all) fun getAccount(name: String, network: String, address: String) : AccountInformation? {
115            let id = FindRelatedAccounts.getIdentifier(name: name, network: network, address: address)
116            return self.accounts[id]
117        }
118
119        access(contract) fun internal_getRelatedAccounts(_ network: String?) : {String : {String : [String]}} {
120
121            var isNil = network == nil
122
123            fun wanted(_ n: String) : Bool {
124                if network! == n {
125                    return true
126                }
127                return false
128            }
129
130            var tempRes : {String : {String : [String]}} = {}
131            for n in self.networks.keys {
132                if !isNil && !wanted(n) {
133                    continue
134                }
135                let tempItems : {String: [String]} = tempRes[n] ?? {}
136                if let wallets = self.networks[n] {
137                    for wallet in wallets {
138                        let ws = self.wallets[wallet]!
139                        let tempArray = tempItems[wallet] ?? []
140                        tempArray.appendAll(ws)
141                        tempItems[wallet] = tempArray
142                    }
143                }
144                tempRes[n] = tempItems
145            }
146            return tempRes
147        }
148
149        access(Owner) fun addFlowAccount(name: String, address:Address) {
150            let network = "Flow"
151            let id = FindRelatedAccounts.getIdentifier(name: name, network: network, address: address.toString())
152            if self.accounts[id] != nil {
153                return
154            }
155            self.internal_add(id: id, acct: AccountInformation(name: name, address:address, network: network, otherAddress:nil))
156            emit RelatedAccount(user: self.owner!.address, walletId: id, walletName: name, address: address.toString(), network: network, action: "add")
157        }
158
159        access(Owner) fun addRelatedAccount(name: String, network: String, address: String) {
160            let id = FindRelatedAccounts.getIdentifier(name: name, network: network, address: address)
161            if self.accounts[id] != nil {
162                return
163            }
164            self.internal_add(id: id, acct: AccountInformation(name: name, address:nil, network: network, otherAddress:address))
165            emit RelatedAccount(user: self.owner!.address, walletId: id, walletName: name, address: address, network: network, action: "add")
166        }
167
168        access(Owner) fun updateFlowAccount(name: String, oldAddress: Address, address:Address) {
169            self.removeRelatedAccount(name: name, network: "Flow", address: oldAddress.toString())
170            self.addFlowAccount(name: name, address: address)
171        }
172
173        access(Owner) fun updateRelatedAccount(name: String, network: String, oldAddress: String, address: String) {
174            self.removeRelatedAccount(name: name, network: network, address: oldAddress)
175            self.addRelatedAccount(name: name, network: network, address: address)
176        }
177
178        access(Owner) fun removeRelatedAccount(name: String, network: String, address: String) {
179            let id = FindRelatedAccounts.getIdentifier(name: name, network: network, address: address)
180            if self.accounts[id] == nil {
181                panic(network.concat(" address is not added as related account : ").concat(address))
182            }
183            self.internal_remove(id: id)
184            emit RelatedAccount(user: self.owner!.address, walletId: id, walletName: name, address: address, network: network, action: "remove")
185        }
186
187        access(contract) fun internal_add(id: String, acct: AccountInformation) {
188            let walletName = acct.network.concat("_").concat(acct.name)
189            let tempNetworks = self.networks[acct.network] ?? []
190            if !tempNetworks.contains(walletName) {
191                tempNetworks.append(walletName)
192                self.networks[acct.network] = tempNetworks
193            }
194
195            let tempWallets = self.wallets[walletName] ?? []
196            tempWallets.append(acct.stringAddress)
197            self.wallets[walletName] = tempWallets
198
199            self.accounts[id] = acct
200        }
201
202        access(contract) fun internal_remove(id: String) {
203            let acct = self.accounts.remove(key: id)!
204
205            let walletName = acct.network.concat("_").concat(acct.name)
206            let tempWallets = self.wallets[walletName]!
207            tempWallets.remove(at: tempWallets.firstIndex(of: acct.stringAddress)!)
208            if tempWallets.length > 0 {
209                self.wallets[walletName] = tempWallets
210                return
211            }
212            self.wallets.remove(key: walletName)
213
214            let tempNetwork = self.networks[acct.network]!
215            tempNetwork.remove(at: tempNetwork.firstIndex(of: walletName)!)
216            if tempNetwork.length > 0 {
217                self.networks[acct.network] = tempNetwork
218                return
219            }
220            self.networks.remove(key: acct.network)
221        }
222    }
223
224    access(all) fun createEmptyAccounts() : @Accounts{
225        return <- create Accounts()
226    }
227
228    access(all) fun getIdentifier(name: String, network: String, address: String) : String {
229        return network.concat("_").concat(name).concat("_").concat(address)
230    }
231
232    access(all) fun getCapability(_ addr: Address) : Capability<&{Public}>? {
233        let cap = getAccount(addr).capabilities.get<&{Public}>(self.publicPath)
234        if !cap.check() {
235            return nil
236        } 
237        return cap
238
239    }
240
241    access(all) fun findRelatedFlowAccounts(address:Address) : {String: [Address]} {
242        let cap = self.getCapability(address)
243        if cap == nil { 
244            return {}
245        }
246
247        return cap!.borrow()!.getFlowAccounts()
248    }
249
250    access(all) fun findRelatedAccounts(address:Address) : {String: {String: [String]}} {
251        let cap = self.getCapability(address)
252        if cap == nil {
253            return {}
254        }
255
256        return cap!.borrow()!.getAllRelatedAccounts()
257    }
258
259    init() {
260        self.storagePath = /storage/findRelatedAccounts
261        self.publicPath = /public/findRelatedAccounts
262    }
263
264}
265