MarketplaceSEALED
◇╲╱░○◇▓!▒^▓$●$&░?#▫?●░□○▒░$░▒▫$■▪~▒█▫▫%●■□*○╳~╱█╲▫◇^~*#%◆!%▫▪&○^
Transaction ID
Execution Fee
0.00314 FLOWTransaction Summary
MarketplaceCalled FungibleToken, DapperUtilityCoin, TokenForwarding +7 more
Script Arguments
0nftContractNameString
TuneGONFT
1saleItemIdUInt64
472018
2saleItemPriceUFix64
15.00000000
3salePaymentTypeString
DUC
4feesReceivers[Address]
5feesPercentages[UFix64]
[ "3.50000000" ]
6royaltiesReceivers[Address]
7royaltiesPercentages[UFix64]
[ "5.00000000" ]
8signatureExpirationUInt64
1770913541797
9signatureString
53216d59dec984f23943f7a347563c8ec01054896a31e9c21a19bd00c4081d511248c68e93d4736b91eb6f5ed7a58705facd3e75f620f24a0fc9f64c1207be00
10expiryUInt64
1928698001
Cadence Script
1import FungibleToken from 0xf233dcee88fe0abe
2import DapperUtilityCoin from 0xead892083b3e2c6c
3import TokenForwarding from 0xe544175ee0461c4b
4import NonFungibleToken from 0x1d7e57aa55817448
5import TuneGONFT from 0xc6945445cdbefec9
6import TuneGO from 0x0d9bc5af3fc0c2e3
7import TicalUniverse from 0xfef48806337aabf1
8import NFTStorefrontV2 from 0x4eb8a10cb9f87357
9import MetadataViews from 0x1d7e57aa55817448
10import PackNFT from 0xc6945445cdbefec9
11
12// Dapper App: TuneGO
13
14access(all) fun getOrCreateNFTProviderCapability(account: auth(IssueStorageCapabilityController) &Account, contractName: String): Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}> {
15 let pathMapping: {String: StoragePath} = {
16 "TuneGONFT": TuneGONFT.CollectionStoragePath,
17 "PackNFT": PackNFT.CollectionStoragePath,
18 "TuneGO": TuneGO.CollectionStoragePath,
19 "TicalUniverse": TicalUniverse.CollectionStoragePath
20 }
21 let storagePath = pathMapping[contractName] ?? panic("Contract not supported")
22 let provider = account.capabilities.storage.issue<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>(storagePath)
23 assert(provider.check(), message: "Missing or mis-typed ".concat(contractName).concat(" provider"))
24 return provider
25}
26access(all) fun setupStorefrontV2(acc: auth(IssueStorageCapabilityController, PublishCapability, UnpublishCapability, Storage) &Account): auth(NFTStorefrontV2.CreateListing) &NFTStorefrontV2.Storefront {
27 let type = acc.storage.type(at: NFTStorefrontV2.StorefrontStoragePath)
28 let targetType = Type<@NFTStorefrontV2.Storefront>()
29 // Backup invalid storefront
30 if type != nil && type != targetType {
31 let objectAddress = type!.identifier.slice(from: 2, upTo: 18)
32 let backupPath = StoragePath(identifier: "backupStorefrontV2_".concat(objectAddress))!
33 acc.storage.save<@AnyResource>(<-acc.storage.load<@AnyResource>(from: NFTStorefrontV2.StorefrontStoragePath)!, to: backupPath);
34 }
35 // Save and link correct storefront if needed
36 if type != targetType {
37 let storefront <- NFTStorefrontV2.createStorefront() as! @NFTStorefrontV2.Storefront
38 acc.storage.save(<-storefront, to: NFTStorefrontV2.StorefrontStoragePath)
39 }
40 let capability = acc.capabilities.get<&{NFTStorefrontV2.StorefrontPublic}>(NFTStorefrontV2.StorefrontPublicPath)
41 if capability.check() != true {
42 acc.capabilities.unpublish(NFTStorefrontV2.StorefrontPublicPath)
43 acc.capabilities.publish(
44 acc.capabilities.storage.issue<&{NFTStorefrontV2.StorefrontPublic}>(NFTStorefrontV2.StorefrontStoragePath),
45 at: NFTStorefrontV2.StorefrontPublicPath
46 )
47 }
48 return acc.storage.borrow<auth(NFTStorefrontV2.CreateListing) &NFTStorefrontV2.Storefront>(from: NFTStorefrontV2.StorefrontStoragePath)
49 ?? panic("Missing or mis-typed NFTStorefrontV2 Storefront")
50}
51
52access(all) fun getNftType(_ contractName: String): Type {
53 let pathMapping: {String: Type} = {
54 "TuneGONFT": Type<@TuneGONFT.NFT>(),
55 "PackNFT": Type<@PackNFT.NFT>(),
56 "TuneGO": Type<@TuneGO.NFT>(),
57 "TicalUniverse": Type<@TicalUniverse.NFT>()
58 }
59 return pathMapping[contractName] ?? panic("Contract not supported")
60}
61
62access(all) fun getPaymentVaultType(paymentType: String): Type {
63 switch paymentType {
64 case "DUC":
65 return Type<@DapperUtilityCoin.Vault>()
66 }
67 panic("Payment type not supported")
68}
69
70access(all) fun getPaymentReceiverPublicPath(paymentType: String): PublicPath {
71 switch paymentType {
72 case "DUC":
73 return /public/dapperUtilityCoinReceiver
74 }
75 panic("Payment type not supported")
76}
77
78
79access(all) fun roundAmount(amount: UFix64): UFix64 {
80 return UFix64(Int((amount + 0.005) * 100.0)) / 100.0
81}
82
83transaction(
84 nftContractName: String,
85 saleItemId: UInt64,
86 saleItemPrice: UFix64,
87 salePaymentType: String,
88 feesReceivers: [Address],
89 feesPercentages: [UFix64],
90 royaltiesReceivers: [Address],
91 royaltiesPercentages: [UFix64],
92 signatureExpiration: UInt64,
93 signature: String,
94 expiry: UInt64
95) {
96 let paymentReceiver: Capability<&{FungibleToken.Receiver}>
97 let nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>
98 let storefront: auth(NFTStorefrontV2.CreateListing) &NFTStorefrontV2.Storefront
99 let saleCuts: [NFTStorefrontV2.SaleCut]
100
101 prepare(seller: auth(
102 IssueStorageCapabilityController, BorrowValue, SaveValue, Storage,
103 PublishCapability, UnpublishCapability,
104 NFTStorefrontV2.CreateListing, NonFungibleToken.Withdraw
105 ) &Account) {
106
107 assert(["DUC"].contains(salePaymentType), message: "Payment type not supported")
108 let shouldRound = salePaymentType == "DUC"
109
110 assert(!shouldRound || saleItemPrice == roundAmount(amount: saleItemPrice), message: "Invalid sale item price")
111 let paymentReceiverPublicPath = getPaymentReceiverPublicPath(paymentType: salePaymentType)
112
113 self.paymentReceiver = seller.capabilities.get<&{FungibleToken.Receiver}>(paymentReceiverPublicPath)!
114 assert(self.paymentReceiver.borrow() != nil, message: "Missing or mis-typed payment receiver")
115
116 self.nftProviderCapability = getOrCreateNFTProviderCapability(account: seller, contractName: nftContractName)
117 assert(self.nftProviderCapability.borrow() != nil, message: "Missing nft provider")
118
119 self.saleCuts = []
120
121 var feesSignedParam = ""
122 var totalFeesCutsAmount: UFix64 = 0.0
123 for index, receiver in feesReceivers {
124 let account = getAccount(receiver)
125 let percentage = feesPercentages[index]
126 let exactAmount = saleItemPrice * percentage / 100.0;
127 let amount = shouldRound ? roundAmount(amount: exactAmount) : exactAmount
128 let feeReceiver = account.capabilities.get<&{FungibleToken.Receiver}>(paymentReceiverPublicPath)!
129 assert(feeReceiver.borrow() != nil, message: "Missing or mis-typed fee receiver")
130
131 self.saleCuts.append(NFTStorefrontV2.SaleCut(
132 receiver: feeReceiver,
133 amount: amount
134 ))
135 totalFeesCutsAmount = totalFeesCutsAmount + amount;
136 feesSignedParam = feesSignedParam.concat(":").concat(receiver.toString()).concat(":").concat(percentage.toString())
137 }
138 var royaltiesSignedParam = ""
139 var totalRoyaltiesCutsAmount: UFix64 = 0.0
140 for index, receiver in royaltiesReceivers {
141 let account = getAccount(receiver)
142 let percentage = royaltiesPercentages[index]
143 let exactAmount = saleItemPrice * percentage / 100.0;
144 let amount = shouldRound ? roundAmount(amount: exactAmount) : exactAmount
145 let royaltyReceiver = account.capabilities.get<&{FungibleToken.Receiver}>(paymentReceiverPublicPath)!
146 assert(royaltyReceiver.borrow() != nil, message: "Missing or mis-typed royalty receiver")
147
148 self.saleCuts.append(NFTStorefrontV2.SaleCut(
149 receiver: royaltyReceiver,
150 amount: amount
151 ))
152 totalRoyaltiesCutsAmount = totalRoyaltiesCutsAmount + amount;
153 royaltiesSignedParam = royaltiesSignedParam.concat(":").concat(receiver.toString()).concat(":").concat(percentage.toString())
154 }
155 let totalExtraCutsAmount = totalRoyaltiesCutsAmount + totalFeesCutsAmount
156 assert(totalExtraCutsAmount < saleItemPrice, message: "Total royalties and fees cuts amount must be less than sale price")
157
158 self.saleCuts.append(NFTStorefrontV2.SaleCut(
159 receiver: self.paymentReceiver,
160 amount: saleItemPrice - totalExtraCutsAmount
161 ))
162 var totalCutsAmount: UFix64 = 0.0
163 for cut in self.saleCuts {
164 totalCutsAmount = totalCutsAmount + cut.amount
165 }
166 assert(totalCutsAmount == saleItemPrice, message: "Total cuts amount must equal sale price")
167
168 self.storefront = setupStorefrontV2(acc: seller)
169
170 let publicKey = PublicKey(
171 publicKey: "abb32fcb741284ba71cd6b795ac6e59be678481227df882d25aab9ef6ece0041fbced2323381646c14e031bc9d6744e9f3364e4ee05d8b900b0851117d2b95a9".decodeHex(),
172 signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
173 )
174
175 let signedData = nftContractName
176 .concat(":")
177 .concat(saleItemId.toString())
178 .concat(":")
179 .concat(saleItemPrice.toString())
180 .concat(":")
181 .concat(salePaymentType)
182 .concat(feesSignedParam)
183 .concat(royaltiesSignedParam)
184 .concat(":")
185 .concat(signatureExpiration.toString())
186
187 let isValidSignature = publicKey.verify(
188 signature: signature.decodeHex(),
189 signedData: signedData.utf8,
190 domainSeparationTag: "",
191 hashAlgorithm: HashAlgorithm.SHA3_256
192 )
193
194 assert(isValidSignature, message: "Invalid signature for message: ".concat(signedData))
195 assert(UInt64(getCurrentBlock().timestamp) <= signatureExpiration, message: "Signature expired")
196 }
197
198 execute {
199 self.storefront.createListing(
200 nftProviderCapability: self.nftProviderCapability,
201 nftType: getNftType(nftContractName),
202 nftID: saleItemId,
203 salePaymentVaultType: getPaymentVaultType(paymentType: salePaymentType),
204 saleCuts: self.saleCuts,
205 marketplacesCapability: nil,
206 customID: nil,
207 commissionAmount: 0.0,
208 expiry: expiry
209 )
210 }
211}