diff --git a/contracts/ContractManager.cdc b/contracts/ContractManager.cdc index eafd1c8..eb7c052 100644 --- a/contracts/ContractManager.cdc +++ b/contracts/ContractManager.cdc @@ -1,6 +1,6 @@ import "FlowToken" import "FungibleToken" -import "FungibleTokenSwitchboard" +import "FungibleTokenRouter" access(all) contract ContractManager { access(all) let StoragePath: StoragePath @@ -10,7 +10,7 @@ access(all) contract ContractManager { access(all) resource Manager { access(self) let acct: Capability - access(self) let switchboardCap: Capability + access(self) let routerCap: Capability access(all) let data: {String: AnyStruct} access(all) let resources: @{String: AnyResource} @@ -19,17 +19,13 @@ access(all) contract ContractManager { return self.acct.borrow()! } - access(Manage) fun addFungibleTokenReceiver(_ cap: Capability<&{FungibleToken.Receiver}>) { - pre { - cap.check(): "capability is not valid" - } - - let switchboard = self.switchboardCap.borrow() ?? panic("fungible token switchboard is not valid") - switchboard.addNewVault(capability: cap) + access(Manage) fun addOverride(type: Type, addr: Address) { + let router = self.routerCap.borrow() ?? panic("fungible token router is not valid") + router.addOverride(type: type, addr: addr) } - access(Manage) fun getSwitchboard(): auth(FungibleTokenSwitchboard.Owner) &FungibleTokenSwitchboard.Switchboard { - return self.switchboardCap.borrow()! + access(Manage) fun getSwitchboard(): auth(FungibleTokenRouter.Owner) &FungibleTokenRouter.Router { + return self.routerCap.borrow()! } access(all) fun addFlowTokensToAccount(_ tokens: @FlowToken.Vault) { @@ -40,7 +36,7 @@ access(all) contract ContractManager { return getAccount(self.acct.address) } - init(tokens: @FlowToken.Vault) { + init(tokens: @FlowToken.Vault, defaultRouterAddress: Address) { pre { tokens.balance >= 0.001: "minimum balance of 0.001 required for initialization" } @@ -51,27 +47,27 @@ access(all) contract ContractManager { acct.storage.borrow<&{FungibleToken.Receiver}>(from: /storage/flowTokenVault)!.deposit(from: <-tokens) - let switchboard <- FungibleTokenSwitchboard.createSwitchboard() - acct.storage.save(<-switchboard, to: FungibleTokenSwitchboard.StoragePath) + let router <- FungibleTokenRouter.createRouter(defaultAddress: defaultRouterAddress) + acct.storage.save(<-router, to: FungibleTokenRouter.StoragePath) - let receiver = acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.StoragePath) + let receiver = acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenRouter.StoragePath) assert(receiver.check(), message: "invalid switchboard receiver capability") - acct.capabilities.publish(receiver, at: FungibleTokenSwitchboard.ReceiverPublicPath) + acct.capabilities.publish(receiver, at: FungibleTokenRouter.PublicPath) acct.capabilities.publish( - acct.capabilities.storage.issue<&FungibleTokenSwitchboard.Switchboard>(FungibleTokenSwitchboard.StoragePath), - at: FungibleTokenSwitchboard.PublicPath + acct.capabilities.storage.issue<&FungibleTokenRouter.Router>(FungibleTokenRouter.StoragePath), + at: FungibleTokenRouter.PublicPath ) - self.switchboardCap = acct.capabilities.storage.issue(FungibleTokenSwitchboard.StoragePath) + self.routerCap = acct.capabilities.storage.issue(FungibleTokenRouter.StoragePath) self.data = {} self.resources <- {} } } - access(all) fun createManager(tokens: @FlowToken.Vault): @Manager { - return <- create Manager(tokens: <- tokens) + access(all) fun createManager(tokens: @FlowToken.Vault, defaultRouterAddress: Address): @Manager { + return <- create Manager(tokens: <- tokens, defaultRouterAddress: defaultRouterAddress) } init() { diff --git a/contracts/FlowtyDrops.cdc b/contracts/FlowtyDrops.cdc index 34f882f..e5da834 100644 --- a/contracts/FlowtyDrops.cdc +++ b/contracts/FlowtyDrops.cdc @@ -2,7 +2,8 @@ import "NonFungibleToken" import "FungibleToken" import "MetadataViews" import "AddressUtils" -import "FungibleTokenSwitchboard" +import "FungibleTokenMetadataViews" +import "FungibleTokenRouter" access(all) contract FlowtyDrops { access(all) let ContainerStoragePath: StoragePath @@ -336,8 +337,8 @@ access(all) contract FlowtyDrops { access(all) resource interface Minter { access(contract) fun mint(payment: @{FungibleToken.Vault}, amount: Int, phase: &FlowtyDrops.Phase, data: {String: AnyStruct}): @[{NonFungibleToken.NFT}] { let resourceAddress = AddressUtils.parseAddress(self.getType())! - let receiver = getAccount(resourceAddress).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.ReceiverPublicPath).borrow() - ?? panic("invalid token receiver") + let receiver = getAccount(resourceAddress).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenRouter.PublicPath).borrow() + ?? panic("missing receiver at fungible token router path") receiver.deposit(from: <-payment) let nfts: @[{NonFungibleToken.NFT}] <- [] diff --git a/flow.json b/flow.json index 5ebfa10..a9540dd 100644 --- a/flow.json +++ b/flow.json @@ -32,7 +32,7 @@ } }, "flowty-drops-testnet": { - "address": "0xd84b33493cc8c87e", + "address": "0x772a10c786851a1b", "key": { "type": "google-kms", "index": 0, @@ -42,7 +42,7 @@ } }, "droptypes-testnet": { - "address": "0xce1f567aa5ccc21f", + "address": "0x934da91a977f1ac4", "key": { "type": "google-kms", "index": 0, @@ -254,12 +254,13 @@ "mainnet": "0xa340dc0a4ec828ab" } }, - "FungibleTokenSwitchboard": { - "source": "./node_modules/@flowtyio/flow-contracts/contracts/FungibleTokenSwitchboard.cdc", + "FungibleTokenRouter": { + "source": "./node_modules/@flowtyio/flow-contracts/contracts/fungible-token-router/FungibleTokenRouter.cdc", "aliases": { - "emulator": "0xee82856bf20e2aa6", - "testnet": "0x9a0766d93b6608b7", - "mainnet": "0xf233dcee88fe0abe" + "testing": "0x0000000000000007", + "emulator": "0xf8d6e0586b0a20c7", + "testnet": "0x83231f90a288bc35", + "mainnet": "0x707c0b39a8d689cb" } } }, @@ -290,7 +291,7 @@ "ArrayUtils", "StringUtils", "AddressUtils", - "FungibleTokenSwitchboard" + "FungibleTokenRouter" ], "emulator-ft": [ "FungibleToken", diff --git a/package-lock.json b/package-lock.json index 0911561..becc283 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@flowtyio/flow-contracts": "^0.1.0-beta.27" + "@flowtyio/flow-contracts": "0.1.0-beta.31" } }, "node_modules/@flowtyio/flow-contracts": { - "version": "0.1.0-beta.27", - "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.27.tgz", - "integrity": "sha512-QvIM4onQGfOCM79+1SyiAq6ySvr9kB3YiO5TEB3t3MCS7C5j1B5YnyKNaWvkfVf+VoUUxB6QknjHG4djFruUdw==", + "version": "0.1.0-beta.31", + "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.31.tgz", + "integrity": "sha512-aqh2DqzagZSHbfsVdTT2gpsFMm6/RAWEiaKNk8eOBJI3ItXZzIQ/9o1sL3qZ532duYafT/lJSBO40xhwslAuhA==", "dependencies": { "commander": "^11.0.0" }, diff --git a/package.json b/package.json index f5f7069..ad008fc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "author": "", "license": "ISC", "dependencies": { - "@flowtyio/flow-contracts": "^0.1.0-beta.27" + "@flowtyio/flow-contracts": "0.1.0-beta.31" } } diff --git a/tests/test_helpers.cdc b/tests/test_helpers.cdc index 738d4c7..98feadd 100644 --- a/tests/test_helpers.cdc +++ b/tests/test_helpers.cdc @@ -93,6 +93,7 @@ access(all) fun deployAll() { deploy("ArrayUtils", "../node_modules/@flowtyio/flow-contracts/contracts/flow-utils/ArrayUtils.cdc", []) deploy("StringUtils", "../node_modules/@flowtyio/flow-contracts/contracts/flow-utils/StringUtils.cdc", []) deploy("AddressUtils", "../node_modules/@flowtyio/flow-contracts/contracts/flow-utils/AddressUtils.cdc", []) + deploy("FungibleTokenRouter", "../node_modules/@flowtyio/flow-contracts/contracts/fungible-token-router/FungibleTokenRouter.cdc", []) deploy("FlowtyDrops", "../contracts/FlowtyDrops.cdc", []) deploy("NFTMetadata", "../contracts/nft/NFTMetadata.cdc", []) diff --git a/transactions/drops/add_endless_open_edition.cdc b/transactions/drops/add_endless_open_edition.cdc index bca7c49..ba13dd1 100644 --- a/transactions/drops/add_endless_open_edition.cdc +++ b/transactions/drops/add_endless_open_edition.cdc @@ -2,7 +2,7 @@ import "FlowtyDrops" import "DropFactory" import "MetadataViews" -import "FungibleTokenSwitchboard" +import "FungibleTokenRouter" import "FungibleToken" transaction( @@ -26,20 +26,13 @@ transaction( ) } - if acct.storage.borrow<&AnyResource>(from: FungibleTokenSwitchboard.StoragePath) == nil { - let switchboard <- FungibleTokenSwitchboard.createSwitchboard() - switchboard.addNewVault(capability: acct.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)) - - acct.storage.save(<-switchboard, to: FungibleTokenSwitchboard.StoragePath) - - acct.capabilities.publish( - acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.StoragePath), - at: FungibleTokenSwitchboard.ReceiverPublicPath - ) + if acct.storage.borrow<&AnyResource>(from: FungibleTokenRouter.StoragePath) == nil { + let switchboard <- FungibleTokenRouter.createRouter(defaultAddress: acct.address) + acct.storage.save(<-switchboard, to: FungibleTokenRouter.StoragePath) acct.capabilities.publish( - acct.capabilities.storage.issue<&FungibleTokenSwitchboard.Switchboard>(FungibleTokenSwitchboard.StoragePath), - at: FungibleTokenSwitchboard.PublicPath + acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenRouter.StoragePath), + at: FungibleTokenRouter.PublicPath ) } diff --git a/transactions/drops/add_time_based_open_edition.cdc b/transactions/drops/add_time_based_open_edition.cdc index aeb1ab4..10dbbcb 100644 --- a/transactions/drops/add_time_based_open_edition.cdc +++ b/transactions/drops/add_time_based_open_edition.cdc @@ -2,7 +2,7 @@ import "FlowtyDrops" import "DropFactory" import "MetadataViews" -import "FungibleTokenSwitchboard" +import "FungibleTokenRouter" import "FungibleToken" transaction( @@ -28,20 +28,14 @@ transaction( ) } - if acct.storage.borrow<&AnyResource>(from: FungibleTokenSwitchboard.StoragePath) == nil { - let switchboard <- FungibleTokenSwitchboard.createSwitchboard() - switchboard.addNewVault(capability: acct.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)) + if acct.storage.borrow<&AnyResource>(from: FungibleTokenRouter.StoragePath) == nil { + let switchboard <- FungibleTokenRouter.createRouter(defaultAddress: acct.address) - acct.storage.save(<-switchboard, to: FungibleTokenSwitchboard.StoragePath) + acct.storage.save(<-switchboard, to: FungibleTokenRouter.StoragePath) acct.capabilities.publish( - acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.StoragePath), - at: FungibleTokenSwitchboard.ReceiverPublicPath - ) - - acct.capabilities.publish( - acct.capabilities.storage.issue<&FungibleTokenSwitchboard.Switchboard>(FungibleTokenSwitchboard.StoragePath), - at: FungibleTokenSwitchboard.PublicPath + acct.capabilities.storage.issue<&FungibleTokenRouter.Router>(FungibleTokenRouter.StoragePath), + at: FungibleTokenRouter.PublicPath ) } diff --git a/transactions/factory/create_open_edition_drop.cdc b/transactions/factory/create_open_edition_drop.cdc new file mode 100644 index 0000000..e33c38c --- /dev/null +++ b/transactions/factory/create_open_edition_drop.cdc @@ -0,0 +1,110 @@ +import "ContractFactoryTemplate" +import "ContractFactory" +import "OpenEditionTemplate" +import "MetadataViews" +import "OpenEditionInitializer" +import "ContractManager" +import "FungibleToken" +import "FlowToken" +import "FlowtyDrops" +import "NFTMetadata" +import "FlowtyActiveCheckers" +import "FlowtyPricers" +import "FlowtyAddressVerifiers" + +transaction(contractName: String, managerInitialTokenBalance: UFix64, start: UInt64?, end: UInt64?, price: UFix64, paymentTokenType: String, phaseArgs: {String: String}, metadataArgs: {String: String}, collectionInfoArgs: {String: String}, dropDetailArgs: {String: String}) { + prepare(acct: auth(Storage, Capabilities) &Account) { + if acct.storage.borrow<&AnyResource>(from: ContractManager.StoragePath) == nil { + let v = acct.storage.borrow(from: /storage/flowTokenVault)! + let tokens <- v.withdraw(amount: managerInitialTokenBalance) as! @FlowToken.Vault + + acct.storage.save(<- ContractManager.createManager(tokens: <-tokens), to: ContractManager.StoragePath) + + acct.capabilities.publish( + acct.capabilities.storage.issue<&ContractManager.Manager>(ContractManager.StoragePath), + at: ContractManager.PublicPath + ) + } + + let manager: auth(ContractManager.Manage) &ContractManager.Manager = acct.storage.borrow(from: ContractManager.StoragePath) + ?? panic("manager was not borrowed successfully") + + let data: {String: AnyStruct} = {} + let nftMetadata: NFTMetadata.Metadata = NFTMetadata.Metadata( + name: metadataArgs["name"]!, + description: metadataArgs["description"]!, + thumbnail: MetadataViews.IPFSFile(cid: metadataArgs["cid"]!, path: metadataArgs["path"]), + traits: nil, + editions: nil, + externalURL: metadataArgs["externalURL"] != nil ? MetadataViews.ExternalURL(metadataArgs["externalURL"]!) : nil, + royalties: nil, + data: data + ) + + let socials: {String: MetadataViews.ExternalURL} = {} + let keys = ["twitter", "x", "discord", "instagram"] + for k in keys { + if let v = collectionInfoArgs[k] { + socials[k] = MetadataViews.ExternalURL(v) + } + } + + let collectionDisplay = MetadataViews.NFTCollectionDisplay( + name: collectionInfoArgs["name"]!, + description: collectionInfoArgs["description"]!, + externalURL: MetadataViews.ExternalURL(collectionInfoArgs["externalURL"]!), + squareImage: MetadataViews.Media( + file: MetadataViews.IPFSFile(cid: collectionInfoArgs["squareImageCid"]!, path: collectionInfoArgs["squareImagePath"]), + mediaType: collectionInfoArgs["squareImageMediaType"]! + ), + bannerImage: MetadataViews.Media( + file: MetadataViews.IPFSFile(cid: collectionInfoArgs["bannerImageCid"]!, path: collectionInfoArgs["bannerImagePath"]), + mediaType: collectionInfoArgs["bannerImageMediaType"]! + ), + socials: socials + ) + + let addrStr = manager.getAccount().address.toString() + let nftType = "A.".concat(addrStr.slice(from: 2, upTo: addrStr.length)).concat(".").concat(contractName).concat(".NFT") + + let dropDetails = FlowtyDrops.DropDetails( + display: MetadataViews.Display( + name: dropDetailArgs["name"]!, + description: dropDetailArgs["description"]!, + thumbnail: MetadataViews.IPFSFile(cid: dropDetailArgs["thumbnailCid"]!, path: dropDetailArgs["thumbnailPath"]) + ), + medias: nil, + commissionRate: 0.05, + nftType: nftType + ) + + let phaseDetails = FlowtyDrops.PhaseDetails( + activeChecker: FlowtyActiveCheckers.TimestampChecker(start: start, end: end), + display: phaseArgs["displayName"] != nil ? MetadataViews.Display( + name: phaseArgs["displayName"]!, + description: phaseArgs["displayDescription"]!, + thumbnail: MetadataViews.IPFSFile(cid: phaseArgs["displayCid"]!, path: phaseArgs["displayPath"]) + ) : nil, + pricer: FlowtyPricers.FlatPrice(price: price, paymentTokenType: CompositeType(paymentTokenType)!), + addressVerifier: FlowtyAddressVerifiers.AllowAll(maxPerMint: 10) + ) + + // The Open edition initializer requires at least two keys: + // - data: NFTMetadata.Metadata + // - collectionInfo: NFTMetadata.CollectionInfo + // + // You can also specify some optional paramters: + // - dropDetails: FlowtyDrops.DropDetails + // - phaseDetails: [FlowtyDrops.PhaseDetails] + // - minterController: This is supplied in the initialization of the contract itself + let arr: [FlowtyDrops.PhaseDetails] = [phaseDetails] + let params: {String: AnyStruct} = { + "data": nftMetadata, + "collectionInfo": NFTMetadata.CollectionInfo(collectionDisplay: collectionDisplay), + "dropDetails": dropDetails, + "phaseDetails": arr + } + + ContractFactory.createContract(templateType: Type(), acct: manager.borrowContractAccount(), name: contractName, params: params, initializeIdentifier: Type().identifier) + } +} \ No newline at end of file