Skip to content

Commit affe645

Browse files
committed
feat: child accessible tokens and collections
1 parent 9fd1bbc commit affe645

File tree

4 files changed

+176
-1
lines changed

4 files changed

+176
-1
lines changed

Sources/Cadence/Cadence+Child.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ extension CadenceLoader.Category {
1212
public enum Child: String, CaseIterable, CadenceLoaderProtocol {
1313
case getChildAddress = "get_child_addresses"
1414
case getChildAccountMeta = "get_child_account_meta"
15-
15+
case getAccessibleCoinInfo = "get_accessible_coin_info"
16+
case getChildAccountAllowTypes = "get_child_account_allow_types"
1617
var filename: String {
1718
rawValue
1819
}
@@ -43,6 +44,21 @@ public extension Flow {
4344
).decode()
4445
}
4546

47+
func getChildAccessibleToken(address: Flow.Address, parentAddress: Flow.Address) async throws -> [String: UInt64] {
48+
let script = try CadenceLoader.load(CadenceLoader.Category.Child.getAccessibleCoinInfo)
49+
return try await executeScriptAtLatestBlock(
50+
script: .init(text: script),
51+
arguments: [.address(parentAddress), .address(address)])
52+
.decode()
53+
}
54+
55+
func getChildAccessibleCollection(address: Flow.Address, parentAddress: Flow.Address) async throws -> [String] {
56+
let script = try CadenceLoader.load(CadenceLoader.Category.Child.getChildAccountAllowTypes)
57+
return try await executeScriptAtLatestBlock(
58+
script: .init(text: script),
59+
arguments: [.address(parentAddress), .address(address)])
60+
.decode()
61+
}
4662
}
4763

4864
extension CadenceLoader.Category.Child {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import HybridCustody from 0xHybridCustody
2+
import MetadataViews from 0xMetadataViews
3+
import FungibleToken from 0xFungibleToken
4+
import NonFungibleToken from 0xNonFungibleToken
5+
6+
7+
access(all) struct TokenInfo {
8+
access(all) let id: String
9+
access(all) let balance: UFix64
10+
11+
init(id: String, balance: UFix64) {
12+
self.id = id
13+
self.balance = balance
14+
}
15+
}
16+
17+
access(all) fun main(parent: Address, childAddress: Address): [TokenInfo] {
18+
let manager = getAuthAccount<auth(Storage) &Account>(parent).storage.borrow<auth(HybridCustody.Manage) &HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) ?? panic ("manager does not exist")
19+
20+
var typeIdsWithProvider: {Address: [String]} = {}
21+
22+
var coinInfoList: [TokenInfo] = []
23+
let providerType = Type<Capability<&{FungibleToken.Provider}>>()
24+
let vaultType: Type = Type<@{FungibleToken.Vault}>()
25+
26+
// Iterate through child accounts
27+
28+
let acct = getAuthAccount<auth(Storage, Capabilities) &Account> (childAddress)
29+
let foundTypes: [String] = []
30+
let vaultBalances: {String: UFix64} = {}
31+
let childAcct = manager.borrowAccount(addr: childAddress) ?? panic("child account not found")
32+
// get all private paths
33+
acct.storage.forEachStored(fun (path: StoragePath, type: Type): Bool {
34+
// Check which private paths have NFT Provider AND can be borrowed
35+
if !type.isSubtype(of: providerType){
36+
return true
37+
}
38+
39+
let controllers = acct.capabilities.storage.getControllers(forPath: path)
40+
41+
// let providerCap = cap as! Capability<&{FungibleToken.Provider}>
42+
43+
for c in controllers {
44+
if !c.borrowType.isSubtype(of: providerType) {
45+
continue
46+
}
47+
48+
if let cap = childAcct.getCapability(controllerID: c.capabilityID, type: providerType) {
49+
let providerCap = cap as! Capability<&{NonFungibleToken.Provider}>
50+
51+
if !providerCap.check(){
52+
continue
53+
}
54+
foundTypes.append(cap.borrow<&AnyResource>()!.getType().identifier)
55+
}
56+
}
57+
return true
58+
})
59+
typeIdsWithProvider[childAddress] = foundTypes
60+
61+
62+
acct.storage.forEachStored(fun (path: StoragePath, type: Type): Bool {
63+
64+
if typeIdsWithProvider[childAddress] == nil {
65+
return true
66+
}
67+
68+
for key in typeIdsWithProvider.keys {
69+
for idx, value in typeIdsWithProvider[key]! {
70+
let value = typeIdsWithProvider[key]!
71+
72+
if value[idx] != type.identifier {
73+
continue
74+
} else {
75+
if type.isInstance(vaultType) {
76+
continue
77+
}
78+
if let vault = acct.storage.borrow<&{FungibleToken.Balance}>(from: path) {
79+
// Iterate over IDs & resolve the view
80+
coinInfoList.append(
81+
TokenInfo(id: type.identifier, balance: vault.balance))
82+
}
83+
continue
84+
}
85+
}
86+
}
87+
return true
88+
})
89+
90+
91+
return coinInfoList
92+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import HybridCustody from 0xHybridCustody
2+
import NonFungibleToken from 0xNonFungibleToken
3+
import FungibleToken from 0xFungibleToken
4+
5+
6+
// This script iterates through a parent's child accounts,
7+
// identifies private paths with an accessible NonFungibleToken.Provider, and returns the corresponding typeIds
8+
access(all) fun main(addr: Address, child: Address): [String]? {
9+
let account = getAuthAccount<auth(Storage) &Account>(addr)
10+
let manager = getAuthAccount<auth(Storage) &Account>(addr).storage.borrow<auth(HybridCustody.Manage) &HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) ?? panic ("manager does not exist")
11+
12+
13+
14+
let nftProviderType = Type<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider}>()
15+
let ftProviderType = Type<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>()
16+
17+
// Iterate through child accounts
18+
let addr = getAuthAccount<auth(Storage, Capabilities) &Account>(child)
19+
let foundTypes: [String] = []
20+
let childAcct = manager.borrowAccount(addr: child) ?? panic("child account not found")
21+
// get all private paths
22+
23+
for s in addr.storage.storagePaths {
24+
let controllers = addr.capabilities.storage.getControllers(forPath: s)
25+
for c in controllers {
26+
// if !c.borrowType.isSubtype(of: providerType) {
27+
// continue
28+
// }
29+
30+
if let nftCap = childAcct.getCapability(controllerID: c.capabilityID, type: nftProviderType) {
31+
let providerCap = nftCap as! Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider}>
32+
33+
if !providerCap.check(){
34+
continue
35+
}
36+
37+
foundTypes.append(nftCap.borrow<&AnyResource>()!.getType().identifier)
38+
break
39+
}
40+
if let ftCap = childAcct.getCapability(controllerID: c.capabilityID, type: ftProviderType) {
41+
let providerCap = ftCap as! Capability<&{FungibleToken.Provider}>
42+
43+
if !providerCap.check(){
44+
continue
45+
}
46+
47+
foundTypes.append(ftCap.borrow<&AnyResource>()!.getType().identifier)
48+
break
49+
}
50+
}
51+
}
52+
53+
return foundTypes
54+
}

Tests/AddressRegistorTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ final class AddressRegistorTests: XCTestCase {
1212

1313
let addressA = Flow.Address(hex: "0x39416b4b085d94c7")
1414
let addressB = Flow.Address(hex: "0x84221fe0294044d7")
15+
let addressBChild = Flow.Address(hex: "0x16c41a2b76dee69b")
1516

1617
func testContract() {
1718
let result = flow.addressRegister.contractExists("0xFlowToken", on: .mainnet)
@@ -47,6 +48,18 @@ final class AddressRegistorTests: XCTestCase {
4748
XCTAssertNotNil(result[result.keys.first!]?.name)
4849
}
4950

51+
func testChildAccessibleToken() async throws {
52+
let result = try await flow.getChildAccessibleToken(address: addressBChild, parentAddress: addressB)
53+
print(result)
54+
XCTAssertTrue(result.count >= 0)
55+
}
56+
57+
func testChildAccessibleCollection() async throws {
58+
let result = try await flow.getChildAccessibleCollection(address: addressBChild, parentAddress: addressB)
59+
print(result)
60+
XCTAssertTrue(result.count >= 0)
61+
}
62+
5063
func testStake() async throws {
5164
let models = try await flow.getStakingInfo(address: addressB)
5265
XCTAssertTrue(!models.isEmpty)

0 commit comments

Comments
 (0)