From affe6454ae78c4906d3fd82a09f0a4522b88cf06 Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 26 May 2025 12:00:16 +0800 Subject: [PATCH 1/2] feat: child accessible tokens and collections --- Sources/Cadence/Cadence+Child.swift | 18 +++- .../Child/get_accessible_coin_info.cdc | 92 +++++++++++++++++++ .../Child/get_child_account_allow_types.cdc | 54 +++++++++++ Tests/AddressRegistorTests.swift | 13 +++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 Sources/Cadence/CommonCadence/Child/get_accessible_coin_info.cdc create mode 100644 Sources/Cadence/CommonCadence/Child/get_child_account_allow_types.cdc diff --git a/Sources/Cadence/Cadence+Child.swift b/Sources/Cadence/Cadence+Child.swift index 2b7ac29..d01802e 100644 --- a/Sources/Cadence/Cadence+Child.swift +++ b/Sources/Cadence/Cadence+Child.swift @@ -12,7 +12,8 @@ extension CadenceLoader.Category { public enum Child: String, CaseIterable, CadenceLoaderProtocol { case getChildAddress = "get_child_addresses" case getChildAccountMeta = "get_child_account_meta" - + case getAccessibleCoinInfo = "get_accessible_coin_info" + case getChildAccountAllowTypes = "get_child_account_allow_types" var filename: String { rawValue } @@ -43,6 +44,21 @@ public extension Flow { ).decode() } + func getChildAccessibleToken(address: Flow.Address, parentAddress: Flow.Address) async throws -> [String: UInt64] { + let script = try CadenceLoader.load(CadenceLoader.Category.Child.getAccessibleCoinInfo) + return try await executeScriptAtLatestBlock( + script: .init(text: script), + arguments: [.address(parentAddress), .address(address)]) + .decode() + } + + func getChildAccessibleCollection(address: Flow.Address, parentAddress: Flow.Address) async throws -> [String] { + let script = try CadenceLoader.load(CadenceLoader.Category.Child.getChildAccountAllowTypes) + return try await executeScriptAtLatestBlock( + script: .init(text: script), + arguments: [.address(parentAddress), .address(address)]) + .decode() + } } extension CadenceLoader.Category.Child { diff --git a/Sources/Cadence/CommonCadence/Child/get_accessible_coin_info.cdc b/Sources/Cadence/CommonCadence/Child/get_accessible_coin_info.cdc new file mode 100644 index 0000000..99d976b --- /dev/null +++ b/Sources/Cadence/CommonCadence/Child/get_accessible_coin_info.cdc @@ -0,0 +1,92 @@ +import HybridCustody from 0xHybridCustody +import MetadataViews from 0xMetadataViews +import FungibleToken from 0xFungibleToken +import NonFungibleToken from 0xNonFungibleToken + + +access(all) struct TokenInfo { +access(all) let id: String +access(all) let balance: UFix64 + +init(id: String, balance: UFix64) { + self.id = id + self.balance = balance +} +} + +access(all) fun main(parent: Address, childAddress: Address): [TokenInfo] { + let manager = getAuthAccount(parent).storage.borrow(from: HybridCustody.ManagerStoragePath) ?? panic ("manager does not exist") + + var typeIdsWithProvider: {Address: [String]} = {} + + var coinInfoList: [TokenInfo] = [] + let providerType = Type>() + let vaultType: Type = Type<@{FungibleToken.Vault}>() + + // Iterate through child accounts + + let acct = getAuthAccount (childAddress) + let foundTypes: [String] = [] + let vaultBalances: {String: UFix64} = {} + let childAcct = manager.borrowAccount(addr: childAddress) ?? panic("child account not found") + // get all private paths + acct.storage.forEachStored(fun (path: StoragePath, type: Type): Bool { + // Check which private paths have NFT Provider AND can be borrowed + if !type.isSubtype(of: providerType){ + return true + } + + let controllers = acct.capabilities.storage.getControllers(forPath: path) + + // let providerCap = cap as! Capability<&{FungibleToken.Provider}> + + for c in controllers { + if !c.borrowType.isSubtype(of: providerType) { + continue + } + + if let cap = childAcct.getCapability(controllerID: c.capabilityID, type: providerType) { + let providerCap = cap as! Capability<&{NonFungibleToken.Provider}> + + if !providerCap.check(){ + continue + } + foundTypes.append(cap.borrow<&AnyResource>()!.getType().identifier) + } + } + return true + }) + typeIdsWithProvider[childAddress] = foundTypes + + + acct.storage.forEachStored(fun (path: StoragePath, type: Type): Bool { + + if typeIdsWithProvider[childAddress] == nil { + return true + } + + for key in typeIdsWithProvider.keys { + for idx, value in typeIdsWithProvider[key]! { + let value = typeIdsWithProvider[key]! + + if value[idx] != type.identifier { + continue + } else { + if type.isInstance(vaultType) { + continue + } + if let vault = acct.storage.borrow<&{FungibleToken.Balance}>(from: path) { + // Iterate over IDs & resolve the view + coinInfoList.append( + TokenInfo(id: type.identifier, balance: vault.balance)) + } + continue + } + } + } + return true + }) + + + return coinInfoList +} diff --git a/Sources/Cadence/CommonCadence/Child/get_child_account_allow_types.cdc b/Sources/Cadence/CommonCadence/Child/get_child_account_allow_types.cdc new file mode 100644 index 0000000..32d6df6 --- /dev/null +++ b/Sources/Cadence/CommonCadence/Child/get_child_account_allow_types.cdc @@ -0,0 +1,54 @@ +import HybridCustody from 0xHybridCustody +import NonFungibleToken from 0xNonFungibleToken +import FungibleToken from 0xFungibleToken + + +// This script iterates through a parent's child accounts, +// identifies private paths with an accessible NonFungibleToken.Provider, and returns the corresponding typeIds +access(all) fun main(addr: Address, child: Address): [String]? { + let account = getAuthAccount(addr) + let manager = getAuthAccount(addr).storage.borrow(from: HybridCustody.ManagerStoragePath) ?? panic ("manager does not exist") + + + + let nftProviderType = Type() + let ftProviderType = Type() + + // Iterate through child accounts + let addr = getAuthAccount(child) + let foundTypes: [String] = [] + let childAcct = manager.borrowAccount(addr: child) ?? panic("child account not found") + // get all private paths + + for s in addr.storage.storagePaths { + let controllers = addr.capabilities.storage.getControllers(forPath: s) + for c in controllers { + // if !c.borrowType.isSubtype(of: providerType) { + // continue + // } + + if let nftCap = childAcct.getCapability(controllerID: c.capabilityID, type: nftProviderType) { + let providerCap = nftCap as! Capability + + if !providerCap.check(){ + continue + } + + foundTypes.append(nftCap.borrow<&AnyResource>()!.getType().identifier) + break + } + if let ftCap = childAcct.getCapability(controllerID: c.capabilityID, type: ftProviderType) { + let providerCap = ftCap as! Capability<&{FungibleToken.Provider}> + + if !providerCap.check(){ + continue + } + + foundTypes.append(ftCap.borrow<&AnyResource>()!.getType().identifier) + break + } + } + } + + return foundTypes +} diff --git a/Tests/AddressRegistorTests.swift b/Tests/AddressRegistorTests.swift index a3ac070..650dac7 100644 --- a/Tests/AddressRegistorTests.swift +++ b/Tests/AddressRegistorTests.swift @@ -12,6 +12,7 @@ final class AddressRegistorTests: XCTestCase { let addressA = Flow.Address(hex: "0x39416b4b085d94c7") let addressB = Flow.Address(hex: "0x84221fe0294044d7") + let addressBChild = Flow.Address(hex: "0x16c41a2b76dee69b") func testContract() { let result = flow.addressRegister.contractExists("0xFlowToken", on: .mainnet) @@ -47,6 +48,18 @@ final class AddressRegistorTests: XCTestCase { XCTAssertNotNil(result[result.keys.first!]?.name) } + func testChildAccessibleToken() async throws { + let result = try await flow.getChildAccessibleToken(address: addressBChild, parentAddress: addressB) + print(result) + XCTAssertTrue(result.count >= 0) + } + + func testChildAccessibleCollection() async throws { + let result = try await flow.getChildAccessibleCollection(address: addressBChild, parentAddress: addressB) + print(result) + XCTAssertTrue(result.count >= 0) + } + func testStake() async throws { let models = try await flow.getStakingInfo(address: addressB) XCTAssertTrue(!models.isEmpty) From 7868cc81bbbec0e21b9cf3a14c07f4b34c3b71a9 Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 26 May 2025 12:25:59 +0800 Subject: [PATCH 2/2] feat: child accessibleToken model --- Sources/Cadence/Cadence+Child.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/Cadence/Cadence+Child.swift b/Sources/Cadence/Cadence+Child.swift index d01802e..6e68ee2 100644 --- a/Sources/Cadence/Cadence+Child.swift +++ b/Sources/Cadence/Cadence+Child.swift @@ -44,7 +44,7 @@ public extension Flow { ).decode() } - func getChildAccessibleToken(address: Flow.Address, parentAddress: Flow.Address) async throws -> [String: UInt64] { + func getChildAccessibleToken(address: Flow.Address, parentAddress: Flow.Address) async throws -> [CadenceLoader.Category.Child.TokenInfo] { let script = try CadenceLoader.load(CadenceLoader.Category.Child.getAccessibleCoinInfo) return try await executeScriptAtLatestBlock( script: .init(text: script), @@ -71,4 +71,9 @@ extension CadenceLoader.Category.Child { public struct Thumbnail: Codable { public let url: URL? } + + public struct TokenInfo: Codable { + public let id: String? + public let balance: UInt64 + } }