Smart Contract
EVMAgent
A.d2abb5dbf5e08666.EVMAgent
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