Smart Contract

EVMAgent

A.d2abb5dbf5e08666.EVMAgent

Valid From

86,128,974

Deployed

3d ago
Feb 24, 2026, 11:54:28 PM UTC

Dependents

8 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# EVMAgent
5
6This contract is used to fetch the child account by verifying the signature of the EVM address.
7
8*/
9// Third-party Imports
10import FungibleToken from 0xf233dcee88fe0abe
11import FlowToken from 0x1654653399040a61
12import StringUtils from 0xa340dc0a4ec828ab
13import EVM from 0xe467b9dd11fa00df
14// Fixes Imports
15import ETHUtils from 0xd2abb5dbf5e08666
16import Fixes from 0xd2abb5dbf5e08666
17import FixesInscriptionFactory from 0xd2abb5dbf5e08666
18import FRC20FTShared from 0xd2abb5dbf5e08666
19import FRC20Indexer from 0xd2abb5dbf5e08666
20import FRC20Staking from 0xd2abb5dbf5e08666
21import FRC20AccountsPool from 0xd2abb5dbf5e08666
22
23access(all) contract EVMAgent {
24
25    access(all) entitlement Manage
26
27    /* --- Events --- */
28
29    /// Event emitted when the contract is initialized
30    access(all) event ContractInitialized()
31
32    /// Event emitted when a new agency is setup
33    access(all) event NewAgencySetup(agency: Address)
34    /// Event emitted when a new agency manager is created
35    access(all) event NewAgencyManagerCreated(forAgency: Address)
36    /// Event emitted when a new entrusted account is created.
37    access(all) event NewEntrustedAccountCreated(
38        accountKey: String,
39        evmAddress: String,
40        entrustedAccount: Address,
41        byAgency: Address,
42        initialFunding: UFix64,
43    )
44    /// Event emitted when the entrusted account is verified
45    access(all) event EntrustedAccountVerified(
46        accountKey: String,
47        evmAddress: String,
48        entrustedAccount: Address,
49        byAgency: Address,
50        message: String,
51        fee: UFix64,
52    )
53
54    /* --- Variable, Enums and Structs --- */
55
56    access(all)
57    let entrustedStatusStoragePath: StoragePath
58    access(all)
59    let entrustedStatusPublicPath: PublicPath
60    access(all)
61    let evmAgencyManagerStoragePath: StoragePath
62    access(all)
63    let evmAgencyStoragePath: StoragePath
64    access(all)
65    let evmAgencyPublicPath: PublicPath
66    access(all)
67    let evmAgencyCenterStoragePath: StoragePath
68    access(all)
69    let evmAgencyCenterPublicPath: PublicPath
70
71    /* --- Interfaces & Resources --- */
72
73    access(all) resource interface  IEntrustedStatus {
74        access(all)
75        let key: String
76
77        /// Borrow the agency capability
78        access(all)
79        view fun borrowAgency(): &Agency
80        /// Get the flow spent by the entrusted account
81        access(all)
82        view fun getFeeSpent(): UFix64
83        /// Add the spent flow fee
84        access(contract)
85        fun addSpentFlowFee(_ amount: UFix64)
86    }
87
88    /// Entrusted status resource stored in the entrusted child account
89    ///
90    access(all) resource EntrustedStatus: IEntrustedStatus {
91        access(all) let key: String
92        // Capability to the agency
93        access(self) let agency: Capability<&Agency>
94        // record the flow spent by the entrusted account
95        access(self) var feeSpent: UFix64
96
97        init(
98            key: String,
99            _ agency: Capability<&Agency>
100        ) {
101            self.key = key
102            self.agency = agency
103            self.feeSpent = 0.0
104        }
105
106        /// Borrow the agency capability
107        access(all)
108        view fun borrowAgency(): &Agency {
109            return self.agency.borrow() ?? panic("Agency not found")
110        }
111
112        /// Get the flow spent by the entrusted account
113        access(all)
114        view fun getFeeSpent(): UFix64 {
115            return self.feeSpent
116        }
117
118        /// Add the spent flow fee
119        access(contract)
120        fun addSpentFlowFee(_ amount: UFix64) {
121            self.feeSpent = self.feeSpent + amount
122        }
123    }
124
125    /// Agency manager resource
126    ///
127    access(all) resource AgencyManager {
128        // Capability to the agency
129        access(self)
130        let agency: Capability<auth(Manage) &Agency>
131
132        init(
133            _ agency: Capability<auth(Manage) &Agency>
134        ) {
135            self.agency = agency
136        }
137
138        /// Borrow the agency capability
139        access(Manage)
140        fun borrowAgency(): auth(Manage) &Agency {
141            return self.agency.borrow() ?? panic("Agency not found")
142        }
143
144        /// Withdraw the flow from the agency
145        ///
146        access(Manage)
147        fun withdraw(amt: UFix64): @FlowToken.Vault {
148            let agency = self.borrowAgency()
149            assert(
150                agency.getFlowBalance() >= amt,
151                message: "Insufficient balance"
152            )
153            return <- agency.withdraw(amt: amt)
154        }
155    }
156
157    /// Agency status
158    ///
159    access(all) struct AgencyStatus {
160        access(all) let extra: {String: AnyStruct}
161        access(all) var managingEntrustedAccounts: UInt64
162        access(all) var spentFlowAmount: UFix64
163        access(all) var earnedFlowAmount: UFix64
164
165        init() {
166            self.extra = {}
167            self.managingEntrustedAccounts = 0
168            self.spentFlowAmount = 0.0
169            self.earnedFlowAmount = 0.0
170        }
171
172        access(contract)
173        fun addSpentFlowAmount(_ amount: UFix64) {
174            self.spentFlowAmount = self.spentFlowAmount + amount
175        }
176
177        access(contract)
178        fun addEarnedFlowAmount(_ amount: UFix64) {
179            self.earnedFlowAmount = self.earnedFlowAmount + amount
180        }
181
182        access(contract)
183        fun addManagingEntrustedAccounts(_ count: UInt64) {
184            self.managingEntrustedAccounts = self.managingEntrustedAccounts + count
185        }
186
187        access(contract)
188        fun updateExtra(_ key: String, _ value: AnyStruct) {
189            self.extra[key] = value
190        }
191    }
192
193    /// Public interface to the agency
194    ///
195    access(all) resource interface AgencyPublic {
196        /// Get the owner address
197        access(all)
198        view fun getOwnerAddress(): Address {
199            return self.owner?.address ?? panic("Agency should have an owner")
200        }
201
202        /// Get the agency account
203        access(all)
204        view fun getDetails(): AgencyStatus
205
206        /// Get the balance of the flow for the agency
207        access(all)
208        view fun getFlowBalance(): UFix64
209
210        // Check if the EVM address is managed by the agency
211        access(all)
212        view fun isEVMAccountManaged(_ evmAddress: String): Bool
213
214        // Check if the social id is managed by the agency
215        access(all)
216        view fun isSocialIDManaged(_ platform: String, _ platformId: String): Bool
217
218        /// Get the account key of the entrusted account
219        access(all)
220        view fun getAccountKey(_ evmAddress: String): String
221
222        /// The agency will fund the new created entrusted account with 0.01 $FLOW
223        access(all)
224        fun createSocialEntrustedAccount(
225            platform: String,
226            platformId: String,
227            hexPublicKey: String,
228            hexSignature: String,
229            timestamp: UInt64,
230            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
231        ): @FlowToken.Vault
232
233        /// Create a new entrusted account by the agency
234        access(all)
235        fun createEntrustedAccount(
236            hexPublicKey: String,
237            hexSignature: String,
238            timestamp: UInt64,
239            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
240        ): @FlowToken.Vault
241
242        /// Verify the evm signature, if valid, borrow the reference of the entrusted account
243        ///
244        access(all)
245        fun verifyAndBorrowEntrustedAccount(
246            methodFingerprint: String,
247            params: [String],
248            hexPublicKey: String,
249            hexSignature: String,
250            timestamp: UInt64,
251        ): auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account
252    }
253
254    /// Private interface to the agency
255    ///
256    access(all) resource interface AgencyPrivate  {
257        /// Create a new agency manager
258        access(Manage)
259        fun createAgencyManager(): @AgencyManager
260
261        /// Withdraw the flow from the agency
262        access(Manage)
263        fun withdraw(amt: UFix64): @FlowToken.Vault
264    }
265
266    /// Agency resource
267    ///
268    access(all) resource Agency: AgencyPublic, AgencyPrivate {
269        access(all) let creator: Address
270        /// Current status of the agency
271        access(self)
272        let status: AgencyStatus
273        /// Key => Address
274        /// Keys format:
275        ///     EVMAddress => 0xabc...123
276        ///     SoicalHandleKey => platform:platformId
277        access(self)
278        let managedEntrustedAccounts: {String: Address}
279
280        init(
281            _ creatingIns: &Fixes.Inscription
282        ) {
283            self.creator = creatingIns.owner?.address ?? panic("Agency should have an owner")
284            self.managedEntrustedAccounts = {}
285            self.status = AgencyStatus()
286        }
287
288        /// Setup the agency
289        access(Manage)
290        fun setup(
291            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
292        ) {
293            pre {
294                self.getOwnerAddress() == acctCap.address: "Only the owner can setup the agency"
295            }
296            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
297
298            let creator = self.creator.toString()
299            assert(
300                acctsPool.getEVMAgencyAddress(creator) == nil,
301                message: "Agency already registered"
302            )
303
304            acctsPool.setupNewChildForEVMAgency(owner: creator, acctCap)
305
306            // get the agency account
307            let authAcct = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.EVMAgency, creator)
308                ?? panic("Agency account not found")
309
310            // linking capability
311            if authAcct.capabilities.get<&Agency>(EVMAgent.evmAgencyPublicPath) != nil {
312                authAcct.capabilities.unpublish(EVMAgent.evmAgencyPublicPath)
313                authAcct.capabilities.publish(
314                    authAcct.capabilities.storage.issue<&Agency>(EVMAgent.evmAgencyStoragePath),
315                    at: EVMAgent.evmAgencyPublicPath
316                )
317            }
318
319            // emit event
320            emit NewAgencySetup(agency: authAcct.address)
321        }
322
323        /** ---- Private method ---- */
324
325        access(Manage)
326        fun createAgencyManager(): @AgencyManager {
327            let cap = self._getSelfPrivCap()
328
329            emit NewAgencyManagerCreated(forAgency: self.getOwnerAddress())
330
331            return <- create AgencyManager(cap)
332        }
333
334
335        /// Withdraw the flow from the agency
336        ///
337        access(Manage)
338        fun withdraw(amt: UFix64): @FlowToken.Vault {
339            let acct = self._borrowAgencyAccount()
340            let flowVaultRef = acct.storage
341                .borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
342                ?? panic("The flow vault is not found")
343            assert(
344                flowVaultRef.balance >= amt,
345                message: "Insufficient balance"
346            )
347            let vault <- flowVaultRef.withdraw(amount: amt)
348            return <- (vault as! @FlowToken.Vault)
349        }
350
351        /* --- Public methods  --- */
352
353        // Check if the EVM address is managed by the agency
354        access(all)
355        view fun isEVMAccountManaged(_ evmAddress: String): Bool {
356            return self.managedEntrustedAccounts[evmAddress] != nil
357        }
358
359        // Check if the social id is managed by the agency
360        access(all)
361        view fun isSocialIDManaged(_ platform: String, _ platformId: String): Bool {
362            let id = platform.concat(":").concat(platformId)
363            return self.managedEntrustedAccounts[id] != nil
364        }
365
366        /// Get the account key of the entrusted account
367        access(all)
368        view fun getAccountKey(_ evmAddress: String): String {
369            let cacheKey = "AccountKey:".concat(evmAddress)
370            if let accountKey = self.status.extra[cacheKey] as! String? {
371                return accountKey
372            }
373            return evmAddress
374        }
375
376        /// Get the agency account
377        access(all)
378        view fun getDetails(): AgencyStatus {
379            return self.status
380        }
381
382        /// Get the balance of the flow for the agency
383        access(all)
384        view fun getFlowBalance(): UFix64 {
385            if let ref = getAccount(self.getOwnerAddress())
386                .capabilities.get<&{FungibleToken.Balance}>(/public/flowTokenBalance)
387                .borrow() {
388                return ref.balance
389            }
390            return 0.0
391        }
392
393        /// The agency will fund the new created entrusted account with 0.01 $FLOW
394        ///
395        access(all)
396        fun createSocialEntrustedAccount(
397            platform: String,
398            platformId: String,
399            hexPublicKey: String,
400            hexSignature: String,
401            timestamp: UInt64,
402            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
403        ): @FlowToken.Vault {
404            let socialId = EVMAgent.getSocialId(platform, platformId)
405
406            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
407            // Ensure the key is not already registered
408            let existingAddr = acctsPool.getEntrustedAccountAddress(socialId)
409            assert(
410                existingAddr == nil,
411                message: "Already registered in account pool, SocialID: ".concat(socialId)
412            )
413            assert(
414                self.managedEntrustedAccounts[socialId] == nil,
415                message: "Already registered for an agent account, SocialID: ".concat(socialId)
416            )
417
418            // Calculate the EVM address from the public key
419            let evmAddress = ETHUtils.getETHAddressFromPublicKey(hexPublicKey: hexPublicKey)
420
421            let message = "op=create-entrusted-account-by-social(String|String)"
422                .concat(",params=").concat(platform).concat("|").concat(platformId)
423                .concat(",address=").concat(evmAddress)
424                .concat(",timestamp=").concat(timestamp.toString())
425            log("Verifying message: ".concat(message))
426            let isValid = ETHUtils.verifySignature(hexPublicKey: hexPublicKey, hexSignature: hexSignature, message: message)
427            assert(
428                isValid,
429                message: "Invalid signature"
430            )
431
432            return <- self._createEntrustedAccount(accountKey: socialId, evmAddress: evmAddress, acctCap)
433        }
434
435        /// The agency will fund the new created entrusted account with 0.01 $FLOW
436        ///
437        access(all)
438        fun createEntrustedAccount(
439            hexPublicKey: String,
440            hexSignature: String,
441            timestamp: UInt64,
442            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
443        ): @FlowToken.Vault {
444            let evmAddress = ETHUtils.getETHAddressFromPublicKey(hexPublicKey: hexPublicKey)
445            assert(
446                self.managedEntrustedAccounts[evmAddress] == nil,
447                message: "EVM address already registered for an agent account"
448            )
449            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
450            // Ensure the evmAddress is not already registered
451            let existingAddr = acctsPool.getEntrustedAccountAddress(evmAddress)
452            assert(
453                existingAddr == nil,
454                message: "EVM address already registered for an agent account"
455            )
456
457            let message = "op=create-entrusted-account(),params="
458                .concat(",address=").concat(evmAddress)
459                .concat(",timestamp=").concat(timestamp.toString())
460            log("Verifying message: ".concat(message))
461            let isValid = ETHUtils.verifySignature(hexPublicKey: hexPublicKey, hexSignature: hexSignature, message: message)
462            assert(
463                isValid,
464                message: "Invalid signature"
465            )
466
467            return <- self._createEntrustedAccount(accountKey: evmAddress, evmAddress: evmAddress, acctCap)
468        }
469
470        /// Create the entrusted account
471        access(self)
472        fun _createEntrustedAccount(
473            accountKey: String,
474            evmAddress: String,
475            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
476        ): @FlowToken.Vault {
477            pre {
478                acctCap.check(): "Invalid account capability"
479            }
480            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
481            // Get the agency account
482            let agencyAcct = self._borrowAgencyAccount()
483
484            // Get the entrusted account
485            let entrustedAcct = acctCap.borrow() ?? panic("Entrusted account not found")
486            let entrustedAddress = entrustedAcct.address
487            // Reference to the flow vault of the entrusted account
488            let entrustedAcctFlowBalanceRef = entrustedAcct
489                .capabilities.get<&{FungibleToken.Balance}>(/public/flowTokenBalance)
490                .borrow()
491                ?? panic("Could not borrow Balance reference to the Vault")
492            // Reference to the recipient's receiver
493            let entrustedAcctFlowRecipientRef = entrustedAcct
494                .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
495                .borrow()
496                ?? panic("Could not borrow receiver reference to the recipient's Vault")
497
498            // Get the flow vault from the agency account
499            let flowVaultRef = agencyAcct.storage
500                .borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
501                ?? panic("The flow vault is not found")
502            let spentFlowAmt = EVMAgent.getAgencyFlowFee()
503            // Withdraw 0.01 $FLOW from agency and deposit to the new account
504            let initFlow <- flowVaultRef.withdraw(amount: spentFlowAmt)
505            // Subtract the original amount from the entrusted account, to ensure the account is not overfunded
506            let refundBalance <- initFlow.withdraw(amount: entrustedAcctFlowBalanceRef.balance)
507
508            // Deposit the init balance to the entrusted account
509            entrustedAcctFlowRecipientRef.deposit(from: <-initFlow)
510
511            // add the cap to accounts pool
512            acctsPool.setupNewChildForEntrustedAccount(key: accountKey, acctCap)
513
514            // Ensure all resources in initialized to the entrusted account
515            self._ensureEntrustedAcctResources(accountKey)
516            self.status.addManagingEntrustedAccounts(1)
517            self.status.addSpentFlowAmount(spentFlowAmt)
518
519            /// Save the entrusted account address
520            self.managedEntrustedAccounts[accountKey] = entrustedAddress
521            /// update the status
522            if accountKey != evmAddress {
523                let cacheKey = "AccountKey:".concat(evmAddress)
524                self.status.updateExtra(cacheKey, accountKey)
525            }
526
527            // emit event
528            emit NewEntrustedAccountCreated(
529                accountKey: accountKey,
530                evmAddress: evmAddress,
531                entrustedAccount: entrustedAddress,
532                byAgency: agencyAcct.address,
533                initialFunding: spentFlowAmt
534            )
535
536            // return the refund balance
537            return <- (refundBalance as! @FlowToken.Vault)
538        }
539
540        /// Verify the evm signature, if valid, borrow the reference of the entrusted account
541        /// - parameter methodFingerprint: The method fingerprint
542        /// - parameter params: The parameters for the method
543        ///
544        access(all)
545        fun verifyAndBorrowEntrustedAccount(
546            methodFingerprint: String,
547            params: [String],
548            hexPublicKey: String,
549            hexSignature: String,
550            timestamp: UInt64
551        ): auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account {
552            let message = "op=".concat(methodFingerprint)
553                .concat(",params=").concat(params.length > 0 ? StringUtils.join(params, "|") : "")
554            return self._verifyAndBorrowEntrustedAccount(
555                message: message,
556                hexPublicKey: hexPublicKey,
557                hexSignature: hexSignature,
558                timestamp: timestamp
559            )
560        }
561
562        /// Verify the evm signature, if valid, borrow the reference of the entrusted account
563        ///
564        access(self)
565        fun _verifyAndBorrowEntrustedAccount(
566            message: String,
567            hexPublicKey: String,
568            hexSignature: String,
569            timestamp: UInt64
570        ): auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account {
571            let evmAddress = ETHUtils.getETHAddressFromPublicKey(hexPublicKey: hexPublicKey)
572            let accountKey = self.getAccountKey(evmAddress)
573            assert(
574                self.managedEntrustedAccounts[accountKey] != nil,
575                message: "EVM address not registered for an agent account"
576            )
577            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
578
579            // Ensure the account is already registered
580            let entrustedAddr = acctsPool.getEntrustedAccountAddress(accountKey)
581            assert(
582                entrustedAddr != nil,
583                message: "EVM address not registered for an agent account"
584            )
585
586            let msgToVerify = message
587                .concat(",address=").concat(evmAddress)
588                .concat(",timestamp=").concat(timestamp.toString())
589
590            let isValid = ETHUtils.verifySignature(
591                hexPublicKey: hexPublicKey,
592                hexSignature: hexSignature,
593                message: msgToVerify
594            )
595            assert(isValid, message: "Invalid signature")
596
597            // Since the signature is valid, we think the transaction is valid
598            // and we can borrow the reference to the entrusted account
599            let entrustedAcct = acctsPool.borrowChildAccount(
600                type: FRC20AccountsPool.ChildAccountType.EVMEntrustedAccount,
601                accountKey
602            ) ?? panic("The staking account was not created")
603
604            // The entrusted account need to pay a fee to the agency
605            let entrustedAcctFlowVauleRef = entrustedAcct
606                .storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
607                ?? panic("The flow vault is not found")
608
609            let agencyFlowReceiptRef = self._borrowAgencyAccount()
610                .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
611                .borrow()
612                ?? panic("Could not borrow receiver reference to the recipient's Vault")
613
614            let fee = EVMAgent.getAgencyFlowFee()
615            agencyFlowReceiptRef.deposit(from: <- entrustedAcctFlowVauleRef.withdraw(amount: fee))
616
617            // update the status
618            self.status.addEarnedFlowAmount(fee)
619            // update the entrusted account status
620            if let entrustedStatus = entrustedAcct
621                .storage.borrow<&EntrustedStatus>(from: EVMAgent.entrustedStatusStoragePath) {
622                entrustedStatus.addSpentFlowFee(fee)
623            }
624
625            // emit event
626            emit EntrustedAccountVerified(
627                accountKey: accountKey,
628                evmAddress: evmAddress,
629                entrustedAccount: entrustedAcct.address,
630                byAgency: self.getOwnerAddress(),
631                message: message,
632                fee: fee
633            )
634
635            return entrustedAcct
636        }
637
638        /* --- Contract access methods  --- */
639
640        /// Ensure the resources are initialized to the entrusted account
641        ///
642        access(contract)
643        fun _ensureEntrustedAcctResources(_ key: String): Bool {
644            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
645                // try to borrow the account to check if it was created
646            let childAcctRef = acctsPool.borrowChildAccount(type: FRC20AccountsPool.ChildAccountType.EVMEntrustedAccount, key)
647                ?? panic("The staking account was not created")
648
649            var isUpdated = false
650            // The entrust account should have the following resources in the account:
651            // - EVMAgent.EntrustedStatus
652            // - EVM Resource
653
654            // create the shared store and save it in the account
655            if childAcctRef.storage.borrow<&AnyResource>(from: EVMAgent.entrustedStatusStoragePath) == nil {
656                let cap = EVMAgent.getAgencyPublicCap(self.getOwnerAddress())
657                assert(cap.check(), message: "Invalid agency capability")
658
659                let sharedStore <- create EVMAgent.EntrustedStatus(key: key, cap)
660                childAcctRef.storage.save(<- sharedStore, to: EVMAgent.entrustedStatusStoragePath)
661
662                // link the shared store to the public path
663                childAcctRef.capabilities.unpublish(EVMAgent.entrustedStatusPublicPath)
664                childAcctRef.capabilities.publish(
665                    childAcctRef.capabilities.storage.issue<&EVMAgent.EntrustedStatus>(EVMAgent.entrustedStatusStoragePath),
666                    at: EVMAgent.entrustedStatusPublicPath
667                )
668
669                isUpdated = true || isUpdated
670            }
671
672            // Create a new COA
673            let storagePath = StoragePath(identifier: "evm")!
674            let publicPath = PublicPath(identifier: "evm")!
675            if childAcctRef.storage.borrow<&AnyResource>(from: storagePath) == nil {
676                let coa <- EVM.createCadenceOwnedAccount()
677
678                // Save the COA to the new account
679                childAcctRef.storage.save<@EVM.CadenceOwnedAccount>(<-coa, to: storagePath)
680                let addressableCap = childAcctRef.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath)
681                childAcctRef.capabilities.unpublish(publicPath)
682                childAcctRef.capabilities.publish(addressableCap, at: publicPath)
683
684                isUpdated = true || isUpdated
685            }
686
687            return isUpdated
688        }
689
690        /* --- Internal access methods  --- */
691
692        /// Get the private capability of the agency
693        ///
694        access(self)
695        fun _getSelfPrivCap(): Capability<auth(Manage) &Agency> {
696            // get the agency account
697            let authAcct = self._borrowAgencyAccount()
698
699            return authAcct.capabilities.storage.issue<auth(Manage) &Agency>(EVMAgent.evmAgencyStoragePath)
700        }
701
702        access(self)
703        fun _borrowAgencyAccount(): auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account {
704            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
705            return acctsPool.borrowChildAccount(
706                type: FRC20AccountsPool.ChildAccountType.EVMAgency,
707                self.creator.toString()
708            ) ?? panic("Agency account not found")
709        }
710    }
711
712    /// Agency center public interface
713    ///
714    access(all) resource interface AgencyCenterPublic {
715        /// Get the agencies
716        access(all)
717        view fun getAgencies(): [Address]
718
719        /// Create a new agency
720        access(all)
721        fun createAgency(
722            ins: auth(Fixes.Extractable) &Fixes.Inscription,
723            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
724        ): @AgencyManager
725
726        /// Get the agency by evm address
727        access(all)
728        view fun borrowAgencyByEVMAddress(_ evmAddress: String): &Agency?
729        /// Get the agency by address
730        access(all)
731        fun pickValidAgency(): &Agency?
732    }
733
734    /// Agency center resource
735    ///
736    access(all) resource AgencyCenter: AgencyCenterPublic {
737        access(self)
738        let agencies: {Address: Bool}
739
740        init() {
741            self.agencies = {}
742        }
743
744        /// Get the agencies
745        ///
746        access(all)
747        view fun getAgencies(): [Address] {
748            return self.agencies.keys
749        }
750
751        /// Create a new agency
752        ///
753        access(all)
754        fun createAgency(
755            ins: auth(Fixes.Extractable) &Fixes.Inscription,
756            _ acctCap: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
757        ): @AgencyManager {
758            // singleton resources
759            let acctPool = FRC20AccountsPool.borrowAccountsPool()
760            let frc20Indexer = FRC20Indexer.getIndexer()
761
762            // inscription data
763            let meta = FixesInscriptionFactory.parseMetadata(ins.borrowData())
764            let op = meta["op"]?.toLower() ?? panic("The token operation is not found")
765            assert(
766                op == "create-evm-agency" || op == "create-acct-agency",
767                message: "Invalid operation"
768            )
769            let fromAddr = ins.owner?.address ?? panic("The owner address is not found")
770
771            let delegator = FRC20Staking.borrowDelegator(fromAddr)
772                ?? panic("The delegator is not found")
773            let platformStakeTick = FRC20FTShared.getPlatformStakingTickerName()
774            let stakedBalance = delegator.getStakedBalance(tick: platformStakeTick)
775            // only the delegator with enough staked balance can create the agency
776            assert(
777                stakedBalance >= 10000.0,
778                message: "The delegator should have staked enough balance"
779            )
780
781            // ensure the inscription owner is valid delegator in the FRC20StakingPool
782            let agency <- create Agency(ins)
783
784            // setup the agency
785            let acct = acctCap.borrow() ?? panic("Invalid account capability")
786            let addr = acct.address
787
788            // extract the flow from the inscriptions and deposit to the agency
789            let flowReceiverRef = Fixes.borrowFlowTokenReceiver(addr)
790                ?? panic("Could not borrow receiver reference to the recipient's Vault")
791            flowReceiverRef.deposit(from: <- ins.extract())
792
793            // save the agency
794            acct.storage.save(<- agency, to: EVMAgent.evmAgencyStoragePath)
795
796            let agencyRef = acct.storage
797                .borrow<auth(Manage) &Agency>(from: EVMAgent.evmAgencyStoragePath)
798                ?? panic("Agency not found")
799            agencyRef.setup(acctCap)
800            // agency registered
801            self.agencies[addr] = true
802
803            // create a new agency manager
804            return <- agencyRef.createAgencyManager()
805        }
806
807        /// Get the agency by evm address
808        ///
809        access(all)
810        view fun borrowAgencyByEVMAddress(_ evmAddress: String): &Agency? {
811            let acctsPool = FRC20AccountsPool.borrowAccountsPool()
812            if let addr = acctsPool.getEntrustedAccountAddress(evmAddress) {
813                if let entrustStatus = EVMAgent.borrowEntrustStatus(addr) {
814                    return entrustStatus.borrowAgency()
815                }
816            } else {
817                let allAgencies = self.getAgencies()
818                for addr in allAgencies {
819                    if let agency = EVMAgent.borrowAgency(addr) {
820                        let acctKey = agency.getAccountKey(evmAddress)
821                        if agency.isEVMAccountManaged(acctKey) {
822                            return agency
823                        }
824                    }
825                }
826            }
827            return nil
828        }
829
830        /// Get the agency by address
831        access(all)
832        fun pickValidAgency(): &Agency? {
833            let keys = self.agencies.keys
834            if keys.length == 0 {
835                return nil
836            }
837
838            let filteredAddrs: [Address] = []
839            for addr in keys {
840                // get flow balance of the agency
841                if let flowVaultRef = getAccount(addr)
842                    .capabilities.get<&{FungibleToken.Balance}>(/public/flowTokenBalance)
843                    .borrow() {
844                    // only the agency with enough balance can be picked
845                    if flowVaultRef.balance >= 0.1 {
846                        filteredAddrs.append(addr)
847                    }
848                }
849            }
850            if filteredAddrs.length == 0 {
851                return nil
852            }
853
854            let index = revertibleRandom<UInt64>(modulo: UInt64(filteredAddrs.length))
855            // borrow the agency
856            return EVMAgent.borrowAgency(filteredAddrs[index])
857        }
858    }
859
860    /* --- Public methods  --- */
861
862    access(all)
863    view fun getIdentifierPrefix(): String {
864        return "EVMAgency_".concat(self.account.address.toString())
865    }
866
867    /// Get the fee for any operation by the agency
868    ///
869    access(all)
870    view fun getAgencyFlowFee(): UFix64 {
871        return 0.01
872    }
873
874    /// Get the social id by platform + id
875    ///
876    access(all)
877    view fun getSocialId(_ platform: String, _ platformId: String): String {
878        return platform.concat(":").concat(platformId)
879    }
880
881    /// Get the capability to the entrusted status
882    ///
883    access(all)
884    view fun borrowEntrustStatus(_ addr: Address): &EntrustedStatus? {
885        return getAccount(addr)
886            .capabilities.get<&EntrustedStatus>(self.entrustedStatusPublicPath)
887            .borrow()
888    }
889
890    /// Get the capability to the agency
891    ///
892    access(all)
893    view fun getAgencyPublicCap(_ addr: Address): Capability<&Agency> {
894        return getAccount(addr)
895            .capabilities.get<&Agency>(self.evmAgencyPublicPath)
896    }
897
898    /// Borrow the reference to agency public
899    ///
900    access(all)
901    view fun borrowAgency(_ addr: Address): &Agency? {
902        return self.getAgencyPublicCap(addr).borrow()
903    }
904
905    /// Borrow the reference to agency public
906    ///
907    access(all)
908    view fun borrowAgencyByEVMPublicKey(_ hexPubKey: String): &Agency? {
909        let center = self.borrowAgencyCenter()
910        let evmAddr = ETHUtils.getETHAddressFromPublicKey(hexPublicKey: hexPubKey)
911        return center.borrowAgencyByEVMAddress(evmAddr)
912    }
913
914    /// Borrow the reference to agency center
915    ///
916    access(all)
917    view fun borrowAgencyCenter(): &AgencyCenter {
918        return getAccount(self.account.address)
919            .capabilities.get<&AgencyCenter>(self.evmAgencyCenterPublicPath)
920            .borrow() ?? panic("Agency center not found")
921    }
922
923    init() {
924        let prefix = EVMAgent.getIdentifierPrefix()
925        self.entrustedStatusStoragePath = StoragePath(identifier: prefix.concat("_entrusted_status"))!
926        self.entrustedStatusPublicPath = PublicPath(identifier: prefix.concat("_entrusted_status"))!
927
928        self.evmAgencyManagerStoragePath = StoragePath(identifier: prefix.concat("_agency_manager"))!
929
930        self.evmAgencyStoragePath = StoragePath(identifier: prefix.concat("_agency"))!
931        self.evmAgencyPublicPath = PublicPath(identifier: prefix.concat("_agency"))!
932
933        self.evmAgencyCenterStoragePath = StoragePath(identifier: prefix.concat("_center"))!
934        self.evmAgencyCenterPublicPath = PublicPath(identifier: prefix.concat("_center"))!
935
936        // Save the agency center resource
937        let center <- create AgencyCenter()
938        self.account.storage.save(<- center, to: self.evmAgencyCenterStoragePath)
939        // link the public path
940        self.account.capabilities.publish(
941            self.account.capabilities.storage.issue<&AgencyCenter>(self.evmAgencyCenterStoragePath),
942            at: self.evmAgencyCenterPublicPath
943        )
944
945        emit ContractInitialized()
946    }
947}
948