Smart Contract

StarlyCardMarket

A.5b82f21c0edf76e3.StarlyCardMarket

Deployed

16h ago
Feb 28, 2026, 02:30:51 AM UTC

Dependents

0 imports
1import FUSD from 0x3c5959b568896393
2import StarlyCard from 0x5b82f21c0edf76e3
3import FungibleToken from 0xf233dcee88fe0abe
4import NonFungibleToken from 0x1d7e57aa55817448
5
6access(all) contract StarlyCardMarket {
7
8    access(all) struct SaleCutReceiver {
9        access(all) let receiver: Capability<&FUSD.Vault>
10        access(all) let percent: UFix64
11
12        init(receiver: Capability<&FUSD.Vault>, percent: UFix64) {
13            self.receiver = receiver
14            self.percent = percent
15        }
16    }
17
18    access(all) view fun checkSaleCutReceiver(saleCutReceiver: StarlyCardMarket.SaleCutReceiver): Bool {
19        return saleCutReceiver.receiver.borrow() != nil
20    }
21
22    access(all) view fun checkSaleCutReceivers(saleCutReceivers: [StarlyCardMarket.SaleCutReceiver]): Bool {
23        for saleCutReceiver in saleCutReceivers {
24            if (saleCutReceiver.receiver.borrow() == nil) {
25                return false
26            }
27        }
28        return true
29    }
30
31    access(all) struct SaleCutReceiverV2 {
32        access(all) let receiver: Capability<&{FungibleToken.Receiver}>
33        access(all) let percent: UFix64
34
35        init(receiver: Capability<&{FungibleToken.Receiver}>, percent: UFix64) {
36            self.receiver = receiver
37            self.percent = percent
38        }
39    }
40
41    access(all) view fun checkSaleCutReceiverV2(saleCutReceiver: StarlyCardMarket.SaleCutReceiverV2): Bool {
42        return saleCutReceiver.receiver.borrow() != nil
43    }
44
45    access(all) view fun checkSaleCutReceiversV2(saleCutReceivers: [StarlyCardMarket.SaleCutReceiverV2]): Bool {
46        for saleCutReceiver in saleCutReceivers {
47            if (saleCutReceiver.receiver.borrow() == nil) {
48                return false
49            }
50        }
51        return true
52    }
53
54    access(all) struct SaleCut {
55        access(all) let address: Address
56        access(all) let amount: UFix64
57        access(all) let percent: UFix64
58
59        init(address: Address, amount: UFix64, percent: UFix64) {
60            self.address = address
61            self.amount = amount
62            self.percent = percent
63        }
64    }
65
66    access(all) event SaleOfferCreated(
67        itemID: UInt64,
68        starlyID: String,
69        price: UFix64,
70        sellerSaleCut: SaleCut,
71        beneficiarySaleCut: SaleCut,
72        creatorSaleCut: SaleCut,
73        additionalSaleCuts: [SaleCut])
74    access(all) event SaleOfferAccepted(
75        itemID: UInt64,
76        starlyID: String,
77        price: UFix64,
78        buyerAddress: Address,
79        sellerSaleCut: SaleCut,
80        beneficiarySaleCut: SaleCut,
81        creatorSaleCut: SaleCut,
82        additionalSaleCuts: [SaleCut])
83    access(all) event SaleOfferFinished(itemID: UInt64, sellerAddress: Address)
84    access(all) event CollectionRemovedSaleOffer(itemID: UInt64, sellerAddress: Address)
85    access(all) event CollectionInsertedSaleOffer(
86      itemID: UInt64,
87      starlyID: String,
88      price: UFix64,
89      sellerAddress: Address)
90
91    access(all) let CollectionStoragePath: StoragePath
92    access(all) let CollectionPublicPath: PublicPath
93
94    access(all) resource interface SaleOfferPublicView {
95        access(all) let itemID: UInt64
96        access(all) let starlyID: String
97        access(all) let price: UFix64
98    }
99
100    access(all) resource SaleOffer: SaleOfferPublicView {
101        access(self) var saleCompleted: Bool
102        access(all) let itemID: UInt64
103        access(all) let starlyID: String
104        access(all) let price: UFix64
105        access(self) let sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &StarlyCard.Collection>
106        access(self) let sellerSaleCutReceiver: SaleCutReceiver
107        access(self) let beneficiarySaleCutReceiver: SaleCutReceiver
108        access(self) let creatorSaleCutReceiver: SaleCutReceiver
109        access(self) let additionalSaleCutReceivers: [SaleCutReceiver]
110
111        access(all) fun accept(
112            buyerCollection: &StarlyCard.Collection,
113            buyerPayment: @{FungibleToken.Vault},
114            buyerAddress: Address
115        ) {
116            pre {
117                buyerPayment.balance == self.price: "payment does not equal offer price"
118                self.saleCompleted == false: "the sale offer has already been accepted"
119            }
120
121            self.saleCompleted = true
122
123            let beneficiaryCutAmount = self.price * self.beneficiarySaleCutReceiver.percent
124            let beneficiaryCut <- buyerPayment.withdraw(amount: beneficiaryCutAmount)
125            self.beneficiarySaleCutReceiver.receiver.borrow()!.deposit(from: <- beneficiaryCut)
126
127            let creatorCutAmount = self.price * self.creatorSaleCutReceiver.percent
128            let creatorCut <- buyerPayment.withdraw(amount: creatorCutAmount)
129            self.creatorSaleCutReceiver.receiver.borrow()!.deposit(from: <- creatorCut)
130
131            var additionalSaleCuts: [SaleCut] = []
132            for additionalSaleCutReceiver in self.additionalSaleCutReceivers {
133                let additionalCutAmount = self.price * additionalSaleCutReceiver.percent
134                let additionalCut <- buyerPayment.withdraw(amount: additionalCutAmount)
135                additionalSaleCutReceiver.receiver.borrow()!.deposit(from: <- additionalCut)
136                additionalSaleCuts.append(StarlyCardMarket.SaleCut(
137                    address: additionalSaleCutReceiver.receiver.address,
138                    amount: additionalCutAmount,
139                    percent: additionalSaleCutReceiver.percent));
140            }
141
142            let sellerCutAmount = buyerPayment.balance
143            self.sellerSaleCutReceiver.receiver.borrow()!.deposit(from: <- buyerPayment)
144
145            let nft <- self.sellerItemProvider.borrow()!.withdraw(withdrawID: self.itemID)
146            buyerCollection.deposit(token: <- nft)
147
148            emit SaleOfferAccepted(
149                itemID: self.itemID,
150                starlyID: self.starlyID,
151                price: self.price,
152                buyerAddress: buyerAddress,
153                sellerSaleCut: SaleCut(
154                    address: self.sellerSaleCutReceiver.receiver.address,
155                    amount: sellerCutAmount,
156                    percent: self.sellerSaleCutReceiver.percent),
157                beneficiarySaleCut: SaleCut(
158                    address: self.beneficiarySaleCutReceiver.receiver.address,
159                    amount: beneficiaryCutAmount,
160                    percent: self.beneficiarySaleCutReceiver.percent),
161                creatorSaleCut: SaleCut(
162                    address: self.creatorSaleCutReceiver.receiver.address,
163                    amount: creatorCutAmount,
164                    percent: self.creatorSaleCutReceiver.percent),
165                additionalSaleCuts: additionalSaleCuts)
166        }
167
168        init(
169            itemID: UInt64,
170            starlyID: String,
171            price: UFix64,
172            sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &StarlyCard.Collection>,
173            sellerSaleCutReceiver: SaleCutReceiver,
174            beneficiarySaleCutReceiver: SaleCutReceiver,
175            creatorSaleCutReceiver: SaleCutReceiver,
176            additionalSaleCutReceivers: [SaleCutReceiver],
177        ) {
178            pre {
179                sellerItemProvider.borrow() != nil: "Cannot borrow seller"
180                StarlyCardMarket.checkSaleCutReceiver(saleCutReceiver: sellerSaleCutReceiver): "Cannot borrow receiver in sellerSaleCutReceiver"
181                StarlyCardMarket.checkSaleCutReceiver(saleCutReceiver: beneficiarySaleCutReceiver): "Cannot borrow receiver in beneficiarySaleCutReceiver"
182                StarlyCardMarket.checkSaleCutReceiver(saleCutReceiver: creatorSaleCutReceiver): "Cannot borrow receiver in creatorSaleCutReceiver"
183                StarlyCardMarket.checkSaleCutReceivers(saleCutReceivers: additionalSaleCutReceivers): "Cannot borrow receiver in additionalSaleCutReceivers"
184            }
185
186            self.saleCompleted = false
187            self.itemID = itemID
188            self.starlyID = starlyID
189            self.price = price
190            self.sellerItemProvider = sellerItemProvider
191            self.sellerSaleCutReceiver = sellerSaleCutReceiver
192            self.beneficiarySaleCutReceiver = beneficiarySaleCutReceiver
193            self.creatorSaleCutReceiver = creatorSaleCutReceiver
194            self.additionalSaleCutReceivers = additionalSaleCutReceivers
195
196            let sellerCutAmount = price * sellerSaleCutReceiver.percent
197            let beneficiaryCutAmount = price * beneficiarySaleCutReceiver.percent
198            let creatorCutAmount = price * creatorSaleCutReceiver.percent
199
200            var additionalSaleCuts: [SaleCut] = []
201            for additionalSaleCutReceiver in additionalSaleCutReceivers {
202                let additionalCutAmount = price * additionalSaleCutReceiver.percent
203                additionalSaleCuts.append(StarlyCardMarket.SaleCut(
204                    address: additionalSaleCutReceiver.receiver.address,
205                    amount: additionalCutAmount,
206                    percent: additionalSaleCutReceiver.percent));
207            }
208
209            emit SaleOfferCreated(
210                itemID: self.itemID,
211                starlyID: self.starlyID,
212                price: self.price,
213                sellerSaleCut: SaleCut(
214                    address: self.sellerSaleCutReceiver.receiver.address,
215                    amount: sellerCutAmount,
216                    percent: self.sellerSaleCutReceiver.percent),
217                beneficiarySaleCut: SaleCut(
218                    address: self.beneficiarySaleCutReceiver.receiver.address,
219                    amount: beneficiaryCutAmount,
220                    percent: self.beneficiarySaleCutReceiver.percent),
221                creatorSaleCut: SaleCut(
222                    address: self.creatorSaleCutReceiver.receiver.address,
223                    amount: creatorCutAmount,
224                    percent: self.creatorSaleCutReceiver.percent),
225                additionalSaleCuts: additionalSaleCuts)
226        }
227    }
228
229    access(all) fun createSaleOffer (
230        itemID: UInt64,
231        starlyID: String,
232        price: UFix64,
233        sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &StarlyCard.Collection>,
234        sellerSaleCutReceiver: SaleCutReceiver,
235        beneficiarySaleCutReceiver: SaleCutReceiver,
236        creatorSaleCutReceiver: SaleCutReceiver,
237        additionalSaleCutReceivers: [SaleCutReceiver]
238    ): @SaleOffer {
239        return <- create SaleOffer(
240            itemID: itemID,
241            starlyID: starlyID,
242            price: price,
243            sellerItemProvider: sellerItemProvider,
244            sellerSaleCutReceiver: sellerSaleCutReceiver,
245            beneficiarySaleCutReceiver: beneficiarySaleCutReceiver,
246            creatorSaleCutReceiver: creatorSaleCutReceiver,
247            additionalSaleCutReceivers: additionalSaleCutReceivers)
248    }
249
250    access(all) resource interface CollectionManager {
251        access(all) fun insert(offer: @StarlyCardMarket.SaleOffer)
252        access(all) fun remove(itemID: UInt64): @SaleOffer
253    }
254
255    access(all) resource interface CollectionPurchaser {
256        access(all) fun purchase(
257            itemID: UInt64,
258            buyerCollection: &StarlyCard.Collection,
259            buyerPayment: @{FungibleToken.Vault},
260            buyerAddress: Address
261        )
262    }
263
264    access(all) resource interface CollectionPublic {
265        access(all) view fun getSaleOfferIDs(): [UInt64]
266        access(all) view fun borrowSaleItem(itemID: UInt64): &SaleOffer?
267        access(all) fun purchase(
268            itemID: UInt64,
269            buyerCollection: &StarlyCard.Collection,
270            buyerPayment: @{FungibleToken.Vault},
271            buyerAddress: Address
272        )
273   }
274
275    access(all) resource Collection : CollectionManager, CollectionPurchaser, CollectionPublic {
276        access(all) var saleOffers: @{UInt64: SaleOffer}
277
278         access(all) fun insert(offer: @StarlyCardMarket.SaleOffer) {
279            let itemID: UInt64 = offer.itemID
280            let starlyID: String = offer.starlyID
281            let price: UFix64 = offer.price
282
283            let oldOffer <- self.saleOffers[itemID] <- offer
284            destroy oldOffer
285
286            emit CollectionInsertedSaleOffer(
287              itemID: itemID,
288              starlyID: starlyID,
289              price: price,
290              sellerAddress: self.owner?.address!
291            )
292        }
293
294        access(all) fun remove(itemID: UInt64): @SaleOffer {
295            emit CollectionRemovedSaleOffer(itemID: itemID, sellerAddress: self.owner?.address!)
296            return <- (self.saleOffers.remove(key: itemID) ?? panic("missing SaleOffer"))
297        }
298
299        access(all) fun purchase(
300            itemID: UInt64,
301            buyerCollection: &StarlyCard.Collection,
302            buyerPayment: @{FungibleToken.Vault},
303            buyerAddress: Address
304        ) {
305            pre {
306                self.saleOffers[itemID] != nil: "SaleOffer does not exist in the collection!"
307            }
308            let offer <- self.remove(itemID: itemID)
309            offer.accept(buyerCollection: buyerCollection, buyerPayment: <- buyerPayment, buyerAddress: buyerAddress)
310            destroy offer
311        }
312
313        access(all) view fun getSaleOfferIDs(): [UInt64] {
314            return self.saleOffers.keys
315        }
316
317        access(all) view fun borrowSaleItem(itemID: UInt64): &SaleOffer? {
318            if self.saleOffers[itemID] == nil {
319                return nil
320            } else {
321                return (&self.saleOffers[itemID] as &SaleOffer?)!
322            }
323        }
324
325        init () {
326            self.saleOffers <- {}
327        }
328    }
329
330    access(all) fun createEmptyCollection(): @Collection {
331        return <- create Collection()
332    }
333
334    init () {
335        self.CollectionStoragePath = /storage/starlyCardMarketCollection
336        self.CollectionPublicPath = /public/starlyCardMarketCollection
337    }
338}
339