-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add HybridCustody setup & cross-account listing txns
- Loading branch information
1 parent
7f4b936
commit 12760b4
Showing
4 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
118 changes: 118 additions & 0 deletions
118
transactions/hybrid-custody/sell_item_in_child_from_parent.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import "NonFungibleToken" | ||
import "MetadataViews" | ||
import "FungibleToken" | ||
import "FlowToken" | ||
|
||
import "HybridCustody" | ||
|
||
import "NFTStorefrontV2" | ||
|
||
/// Cross-account NFT listing transaction | ||
/// | ||
/// Lists an NFT located in the signer's child account for sale in the storefront of the signing parent account with | ||
/// the parent account as beneficiary of the sale. | ||
/// | ||
transaction( | ||
childAddress: Address, | ||
collectionProviderPath: PrivatePath, | ||
collectionPublicPath: PublicPath, | ||
nftTypeIdentifier: String, | ||
saleItemID: UInt64, | ||
saleItemPrice: UFix64, | ||
customID: String?, | ||
commissionAmount: UFix64, | ||
expiry: UInt64, | ||
marketplacesAddress: [Address] | ||
) { | ||
let flowReceiverCap: Capability<&{FungibleToken.Receiver}> | ||
let providerCap: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}> | ||
let storefront: &NFTStorefrontV2.Storefront | ||
var saleCuts: [NFTStorefrontV2.SaleCut] | ||
var marketplaceCaps: [Capability<&{FungibleToken.Receiver}>] | ||
let nftType: Type | ||
|
||
prepare(acct: AuthAccount) { | ||
self.saleCuts = [] | ||
self.marketplaceCaps = [] | ||
self.nftType = CompositeType(nftTypeIdentifier) ?? panic("Invalid NFT Type Identifier provided") | ||
|
||
// Configure Storefront if one doesn't yet exist | ||
if acct.borrow<&NFTStorefrontV2.Storefront>(from: NFTStorefrontV2.StorefrontStoragePath) == nil { | ||
acct.save(<-NFTStorefrontV2.createStorefront(), to: NFTStorefrontV2.StorefrontStoragePath) | ||
acct.link<&NFTStorefrontV2.Storefront{NFTStorefrontV2.StorefrontPublic}>( | ||
NFTStorefrontV2.StorefrontPublicPath, | ||
target: NFTStorefrontV2.StorefrontStoragePath | ||
) | ||
} | ||
// Borrow a reference to the signer's Storefront | ||
self.storefront = acct.borrow<&NFTStorefrontV2.Storefront>(from: NFTStorefrontV2.StorefrontStoragePath) | ||
?? panic("Missing or mis-typed NFTStorefront Storefront") | ||
|
||
// Get a FlowToken Receiver as beneficiary of listing & validate | ||
self.flowReceiverCap = acct.getCapability<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) | ||
assert(self.flowReceiverCap.check(), message: "Missing or mis-typed FlowToken receiver") | ||
|
||
// Get reference to the child account | ||
let manager = acct.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) | ||
?? panic("Could not borrow reference to HybridCustody Manager") | ||
let childAccount = manager.borrowAccount(addr: childAddress) | ||
?? panic("No child account exists for the given address") | ||
|
||
// Get the NFT provider capability from the child account & validate | ||
self.providerCap = childAccount.getCapability( | ||
path: collectionProviderPath, | ||
type: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>() | ||
) as! Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>? | ||
?? panic("NFT Provider Capability is not accessible from child account for specified path") | ||
assert(self.providerCap.check(), message: "Missing or mis-typed Provider Capability") | ||
|
||
// Borrow the NFT as ViewResolver to get Royalties information | ||
let collection = getAccount(childAddress).getCapability<&{MetadataViews.ResolverCollection}>( | ||
collectionPublicPath | ||
).borrow() | ||
?? panic("Could not borrow a reference to the child account's collection") | ||
var totalRoyaltyCut = 0.0 | ||
let effectiveSaleItemPrice = saleItemPrice - commissionAmount | ||
let resolver = collection.borrowViewResolver(id: saleItemID) | ||
assert(resolver.getType() == self.nftType, message: "NFT Type mismatch") | ||
|
||
// Check whether the NFT implements the MetadataResolver or not. | ||
if resolver.getViews().contains(Type<MetadataViews.Royalties>()) { | ||
let royaltiesRef = resolver.resolveView(Type<MetadataViews.Royalties>())?? panic("Unable to retrieve the royalties") | ||
let royalties = (royaltiesRef as! MetadataViews.Royalties).getRoyalties() | ||
for royalty in royalties { | ||
self.saleCuts.append( | ||
NFTStorefrontV2.SaleCut(receiver: royalty.receiver, amount: royalty.cut * effectiveSaleItemPrice) | ||
) | ||
totalRoyaltyCut = totalRoyaltyCut + royalty.cut * effectiveSaleItemPrice | ||
} | ||
} | ||
self.saleCuts.append(NFTStorefrontV2.SaleCut( | ||
receiver: self.flowReceiverCap, | ||
amount: effectiveSaleItemPrice - totalRoyaltyCut | ||
)) | ||
|
||
for marketplace in marketplacesAddress { | ||
// Here we are making a fair assumption that all given addresses would have | ||
// the capability to receive the `FlowToken` | ||
self.marketplaceCaps.append( | ||
getAccount(marketplace).getCapability<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) | ||
) | ||
} | ||
} | ||
|
||
execute { | ||
// Create listing | ||
self.storefront.createListing( | ||
nftProviderCapability: self.providerCap, | ||
nftType: self.nftType, | ||
nftID: saleItemID, | ||
salePaymentVaultType: Type<@FlowToken.Vault>(), | ||
saleCuts: self.saleCuts, | ||
marketplacesCapability: self.marketplaceCaps.length == 0 ? nil : self.marketplaceCaps, | ||
customID: customID, | ||
commissionAmount: commissionAmount, | ||
expiry: expiry | ||
) | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
transactions/hybrid-custody/setup/dev-setup/setup_nft_filter_and_factory_manager.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import "CapabilityFilter" | ||
import "CapabilityFactory" | ||
import "NFTCollectionPublicFactory" | ||
import "NFTProviderAndCollectionFactory" | ||
import "NFTProviderFactory" | ||
import "FTProviderFactory" | ||
|
||
import "NonFungibleToken" | ||
import "FungibleToken" | ||
|
||
/* --- Helper Methods --- */ | ||
// | ||
/// Returns a type identifier for an NFT Collection | ||
/// | ||
access(all) fun deriveCollectionTypeIdentifier(_ contractAddress: Address, _ contractName: String): String { | ||
return "A.".concat(withoutPrefix(contractAddress.toString())).concat(".").concat(contractName).concat(".Collection") | ||
} | ||
|
||
/// Taken from AddressUtils private method | ||
/// | ||
access(all) fun withoutPrefix(_ input: String): String{ | ||
var address=input | ||
|
||
//get rid of 0x | ||
if address.length>1 && address.utf8[1] == 120 { | ||
address = address.slice(from: 2, upTo: address.length) | ||
} | ||
|
||
//ensure even length | ||
if address.length%2==1{ | ||
address="0".concat(address) | ||
} | ||
return address | ||
} | ||
|
||
/* --- Transaction Block --- */ | ||
// | ||
/// This transaction can be used by most developers implementing HybridCustody as the single pre-requisite transaction | ||
/// to setup filter functionality between linked parent and child accounts. | ||
/// | ||
/// Creates a CapabilityFactory Manager and CapabilityFilter.AllowlistFilter in the signing account (if needed), adding | ||
/// NFTCollectionPublicFactory, NFTProviderAndCollectionFactory, & NFTProviderFactory to the CapabilityFactory Manager | ||
/// and the Collection Type to the CapabilityFilter.AllowlistFilter | ||
/// | ||
/// For more info, see docs at https://developers.onflow.org/docs/hybrid-custody/ | ||
//// | ||
transaction(nftContractAddress: Address, nftContractName: String) { | ||
prepare(acct: AuthAccount) { | ||
|
||
/* --- CapabilityFactory Manager configuration --- */ | ||
// | ||
if acct.borrow<&AnyResource>(from: CapabilityFactory.StoragePath) == nil { | ||
let f <- CapabilityFactory.createFactoryManager() | ||
acct.save(<-f, to: CapabilityFactory.StoragePath) | ||
} | ||
|
||
if !acct.getCapability<&CapabilityFactory.Manager{CapabilityFactory.Getter}>(CapabilityFactory.PublicPath).check() { | ||
acct.unlink(CapabilityFactory.PublicPath) | ||
acct.link<&CapabilityFactory.Manager{CapabilityFactory.Getter}>(CapabilityFactory.PublicPath, target: CapabilityFactory.StoragePath) | ||
} | ||
|
||
assert( | ||
acct.getCapability<&CapabilityFactory.Manager{CapabilityFactory.Getter}>(CapabilityFactory.PublicPath).check(), | ||
message: "CapabilityFactory is not setup properly" | ||
) | ||
|
||
let factoryManager = acct.borrow<&CapabilityFactory.Manager>(from: CapabilityFactory.StoragePath) | ||
?? panic("CapabilityFactory Manager not found") | ||
|
||
// Add NFT-related Factories to the Manager | ||
factoryManager.updateFactory(Type<&{NonFungibleToken.CollectionPublic}>(), NFTCollectionPublicFactory.Factory()) | ||
factoryManager.updateFactory(Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(), NFTProviderAndCollectionFactory.Factory()) | ||
factoryManager.updateFactory(Type<&{NonFungibleToken.Provider}>(), NFTProviderFactory.Factory()) | ||
|
||
/* --- AllowlistFilter configuration --- */ | ||
// | ||
if acct.borrow<&CapabilityFilter.AllowlistFilter>(from: CapabilityFilter.StoragePath) == nil { | ||
acct.save(<-CapabilityFilter.create(Type<@CapabilityFilter.AllowlistFilter>()), to: CapabilityFilter.StoragePath) | ||
} | ||
|
||
if !acct.getCapability<&CapabilityFilter.AllowlistFilter{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath).check() { | ||
acct.unlink(CapabilityFilter.PublicPath) | ||
acct.link<&CapabilityFilter.AllowlistFilter{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath, target: CapabilityFilter.StoragePath) | ||
} | ||
|
||
assert( | ||
acct.getCapability<&CapabilityFilter.AllowlistFilter{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath).check(), | ||
message: "AllowlistFilter is not setup properly" | ||
) | ||
|
||
let filter = acct.borrow<&CapabilityFilter.AllowlistFilter>(from: CapabilityFilter.StoragePath) | ||
?? panic("AllowlistFilter does not exist") | ||
|
||
// Construct an NFT Collection Type from the provided args & add to the AllowlistFilter | ||
let c = CompositeType(deriveCollectionTypeIdentifier(nftContractAddress, nftContractName)) | ||
?? panic("Problem constructing CompositeType from given NFT contract address and name") | ||
filter.addType(c) | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
transactions/hybrid-custody/setup/linking/redeem_account.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import "MetadataViews" | ||
|
||
import "HybridCustody" | ||
import "CapabilityFilter" | ||
|
||
transaction(childAddress: Address, filterAddress: Address?, filterPath: PublicPath?) { | ||
prepare(acct: AuthAccount) { | ||
var filter: Capability<&{CapabilityFilter.Filter}>? = nil | ||
if filterAddress != nil && filterPath != nil { | ||
filter = getAccount(filterAddress!).getCapability<&{CapabilityFilter.Filter}>(filterPath!) | ||
} | ||
|
||
if acct.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) == nil { | ||
let m <- HybridCustody.createManager(filter: filter) | ||
acct.save(<- m, to: HybridCustody.ManagerStoragePath) | ||
|
||
acct.unlink(HybridCustody.ManagerPublicPath) | ||
acct.unlink(HybridCustody.ManagerPrivatePath) | ||
|
||
acct.link<&HybridCustody.Manager{HybridCustody.ManagerPrivate, HybridCustody.ManagerPublic}>(HybridCustody.ManagerPrivatePath, target: HybridCustody.ManagerStoragePath) | ||
acct.link<&HybridCustody.Manager{HybridCustody.ManagerPublic}>(HybridCustody.ManagerPublicPath, target: HybridCustody.ManagerStoragePath) | ||
} | ||
|
||
let inboxName = HybridCustody.getChildAccountIdentifier(acct.address) | ||
let cap = acct.inbox.claim<&HybridCustody.ChildAccount{HybridCustody.AccountPrivate, HybridCustody.AccountPublic, MetadataViews.Resolver}>(inboxName, provider: childAddress) | ||
?? panic("child account cap not found") | ||
|
||
let manager = acct.borrow<&HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath) | ||
?? panic("manager no found") | ||
|
||
manager.addAccount(cap: cap) | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
transactions/hybrid-custody/setup/linking/setup_owned_account_and_publish_to_parent.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#allowAccountLinking | ||
|
||
import "MetadataViews" | ||
|
||
import "HybridCustody" | ||
import "CapabilityFactory" | ||
import "CapabilityFilter" | ||
import "CapabilityDelegator" | ||
|
||
/// This transaction configures an OwnedAccount in the signer if needed, and proceeds to create a ChildAccount | ||
/// using CapabilityFactory.Manager and CapabilityFilter.Filter Capabilities from the given addresses. A | ||
/// Capability on the ChildAccount is then published to the specified parent account. | ||
/// | ||
transaction( | ||
parent: Address, | ||
factoryAddress: Address, | ||
filterAddress: Address, | ||
name: String?, | ||
desc: String?, | ||
thumbnailURL: String? | ||
) { | ||
|
||
prepare(acct: AuthAccount) { | ||
// Configure OwnedAccount if it doesn't exist | ||
if acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) == nil { | ||
var acctCap = acct.getCapability<&AuthAccount>(HybridCustody.LinkedAccountPrivatePath) | ||
if !acctCap.check() { | ||
acctCap = acct.linkAccount(HybridCustody.LinkedAccountPrivatePath)! | ||
} | ||
let ownedAccount <- HybridCustody.createOwnedAccount(acct: acctCap) | ||
acct.save(<-ownedAccount, to: HybridCustody.OwnedAccountStoragePath) | ||
} | ||
|
||
// check that paths are all configured properly | ||
acct.unlink(HybridCustody.OwnedAccountPrivatePath) | ||
acct.link<&HybridCustody.OwnedAccount{HybridCustody.BorrowableAccount, HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPrivatePath, target: HybridCustody.OwnedAccountStoragePath) | ||
|
||
acct.unlink(HybridCustody.OwnedAccountPublicPath) | ||
acct.link<&HybridCustody.OwnedAccount{HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPublicPath, target: HybridCustody.OwnedAccountStoragePath) | ||
|
||
let owned = acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) | ||
?? panic("owned account not found") | ||
|
||
// Set the display metadata for the OwnedAccount | ||
if name != nil && desc != nil && thumbnailURL != nil { | ||
let thumbnail = MetadataViews.HTTPFile(url: thumbnailURL!) | ||
let display = MetadataViews.Display(name: name!, description: desc!, thumbnail: thumbnail!) | ||
owned.setDisplay(display) | ||
} | ||
|
||
// Get CapabilityFactory & CapabilityFilter Capabilities | ||
let factory = getAccount(factoryAddress).getCapability<&CapabilityFactory.Manager{CapabilityFactory.Getter}>(CapabilityFactory.PublicPath) | ||
assert(factory.check(), message: "factory address is not configured properly") | ||
|
||
let filter = getAccount(filterAddress).getCapability<&{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath) | ||
assert(filter.check(), message: "capability filter is not configured properly") | ||
|
||
// Finally publish a ChildAccount capability on the signing account to the specified parent | ||
owned.publishToParent(parentAddress: parent, factory: factory, filter: filter) | ||
} | ||
} |