Smart Contract
StarlyCardBidV3
A.5b82f21c0edf76e3.StarlyCardBidV3
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import StarlyIDParser from 0x5b82f21c0edf76e3
4import StakedStarlyCard from 0x29fcd0b5e444242a
5import StarlyCard from 0x5b82f21c0edf76e3
6import StarlyCardMarket from 0x5b82f21c0edf76e3
7import StarlyCardStaking from 0x29fcd0b5e444242a
8import StarlyRoyalties from 0x5b82f21c0edf76e3
9
10pub contract StarlyCardBidV3 {
11
12 pub var totalCount: UInt64
13
14 pub event StarlyCardBidCreated(
15 bidID: UInt64,
16 nftID: UInt64,
17 starlyID: String,
18 bidPrice: UFix64,
19 bidVaultType: String,
20 bidderAddress: Address,
21 beneficiarySaleCut: StarlyCardMarket.SaleCut,
22 creatorSaleCut: StarlyCardMarket.SaleCut,
23 additionalSaleCuts: [StarlyCardMarket.SaleCut])
24
25 pub event StarlyCardBidAccepted(
26 bidID: UInt64,
27 nftID: UInt64,
28 starlyID: String)
29
30 // Declined by the card owner
31 pub event StarlyCardBidDeclined(
32 bidID: UInt64,
33 nftID: UInt64,
34 starlyID: String)
35
36 // Bid cancelled by the bidder
37 pub event StarlyCardBidCancelled(
38 bidID: UInt64,
39 nftID: UInt64,
40 starlyID: String)
41
42 // Bid invalidated due to changed conditions, i.e. remaining resource
43 pub event StarlyCardBidInvalidated(
44 bidID: UInt64,
45 nftID: UInt64,
46 starlyID: String)
47
48 // Paths
49 pub let CollectionStoragePath: StoragePath
50 pub let CollectionPublicPath: PublicPath
51
52 pub resource interface BidPublicView {
53 pub let id: UInt64
54 pub let nftID: UInt64
55 pub let starlyID: String
56 pub let remainingResource: UFix64
57 pub let bidPrice: UFix64
58 pub let bidVaultType: Type
59 }
60
61 pub resource Bid: BidPublicView {
62 pub let id: UInt64
63
64 pub let nftID: UInt64
65 pub let starlyID: String
66
67 // card's remainig resource
68 pub let remainingResource: UFix64
69
70 // The price offered by the bidder
71 pub let bidPrice: UFix64
72 // The Type of the FungibleToken that payments must be made in
73 pub let bidVaultType: Type
74 access(self) let bidVault: @FungibleToken.Vault
75
76 pub let bidderAddress: Address
77 access(self) let bidderFungibleReceiver: Capability<&{FungibleToken.Receiver}>
78
79 access(self) let beneficiarySaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2
80 access(self) let creatorSaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2
81 access(self) let additionalSaleCutReceivers: [StarlyCardMarket.SaleCutReceiverV2]
82
83 pub fun accept(
84 bidderCardCollection: &StarlyCard.Collection{StarlyCard.StarlyCardCollectionPublic},
85 sellerFungibleReceiver: Capability<&{FungibleToken.Receiver}>,
86 sellerCardCollection: Capability<&StarlyCard.Collection{NonFungibleToken.Provider}>,
87 sellerStakedCardCollection: &StakedStarlyCard.Collection,
88 sellerMarketCollection: &StarlyCardMarket.Collection,
89 cardStakeId: UInt64?
90 ) {
91 pre {
92 self.bidVault.balance == self.bidPrice: "The amount of locked funds is incorrect"
93 }
94
95 let currentRemainingResource = StarlyCardStaking.getRemainingResourceWithDefault(starlyID: self.starlyID)
96 if currentRemainingResource != self.remainingResource {
97 emit StarlyCardBidInvalidated(bidID: self.id, nftID: self.nftID, starlyID: self.starlyID)
98 return
99 }
100
101 if let stakeId = cardStakeId {
102 self.unstakeCardIfStaked(sellerStakedCardCollection: sellerStakedCardCollection, stakeId: stakeId)
103 self.removeMarketSaleOfferIfExists(sellerMarketCollection: sellerMarketCollection)
104 }
105
106 // sale cuts
107 let beneficiaryCutAmount = self.bidPrice * self.beneficiarySaleCutReceiver.percent
108 let beneficiaryCut <- self.bidVault.withdraw(amount: beneficiaryCutAmount)
109 self.beneficiarySaleCutReceiver.receiver.borrow()!.deposit(from: <- beneficiaryCut)
110
111 let creatorCutAmount = self.bidPrice * self.creatorSaleCutReceiver.percent
112 let creatorCut <- self.bidVault.withdraw(amount: creatorCutAmount)
113 self.creatorSaleCutReceiver.receiver.borrow()!.deposit(from: <- creatorCut)
114
115 for additionalSaleCutReceiver in self.additionalSaleCutReceivers {
116 let additionalCutAmount = self.bidPrice * additionalSaleCutReceiver.percent
117 let additionalCut <- self.bidVault.withdraw(amount: additionalCutAmount)
118 additionalSaleCutReceiver.receiver.borrow()!.deposit(from: <- additionalCut)
119 }
120
121 // The rest goes to the seller
122 let sellerCutAmount = self.bidVault.balance
123 sellerFungibleReceiver.borrow()!.deposit(from: <- self.bidVault.withdraw(amount: sellerCutAmount))
124
125 // transfer card
126 let nft <- sellerCardCollection.borrow()!.withdraw(withdrawID: self.nftID)
127 bidderCardCollection.deposit(token: <- nft)
128
129 emit StarlyCardBidAccepted(
130 bidID: self.id,
131 nftID: self.nftID,
132 starlyID: self.starlyID)
133 }
134
135 access(self) fun removeMarketSaleOfferIfExists(sellerMarketCollection: &StarlyCardMarket.Collection) {
136 let marketOfferIds = sellerMarketCollection.getSaleOfferIDs()
137 if marketOfferIds.contains(self.nftID) {
138 let offer <- sellerMarketCollection.remove(itemID: self.nftID)
139 destroy offer
140 }
141 }
142
143 access(self) fun unstakeCardIfStaked(sellerStakedCardCollection: &StakedStarlyCard.Collection, stakeId: UInt64) {
144 let cardStake = sellerStakedCardCollection.borrowStakePrivate(id: stakeId)
145 if cardStake.getStarlyID() == self.starlyID {
146 sellerStakedCardCollection.unstake(id: stakeId)
147 }
148 }
149
150 init(
151 nftID: UInt64,
152 starlyID: String,
153 bidPrice: UFix64,
154 bidVaultType: Type,
155 bidderAddress: Address,
156 bidderFungibleReceiver: Capability<&{FungibleToken.Receiver}>,
157 bidderFungibleProvider: &AnyResource{FungibleToken.Provider},
158 beneficiarySaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2,
159 creatorSaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2,
160 additionalSaleCutReceivers: [StarlyCardMarket.SaleCutReceiverV2],
161 ) {
162 pre {
163 bidPrice > 0.0: "The bid price must be non zero"
164 bidderFungibleProvider.isInstance(bidVaultType): "Wrong Bid fungible provider type"
165 StarlyCardMarket.checkSaleCutReceiverV2(saleCutReceiver: beneficiarySaleCutReceiver): "Cannot borrow receiver in beneficiarySaleCutReceiver"
166 StarlyCardMarket.checkSaleCutReceiverV2(saleCutReceiver: creatorSaleCutReceiver): "Cannot borrow receiver in creatorSaleCutReceiver"
167 StarlyCardMarket.checkSaleCutReceiversV2(saleCutReceivers: additionalSaleCutReceivers): "Cannot borrow receiver in additionalSaleCutReceivers"
168 }
169
170 self.id = StarlyCardBidV3.totalCount
171 self.nftID = nftID
172 self.starlyID = starlyID
173 self.bidPrice = bidPrice
174 self.bidVaultType = bidVaultType
175 self.bidderAddress = bidderAddress
176 self.bidderFungibleReceiver = bidderFungibleReceiver
177 self.beneficiarySaleCutReceiver = beneficiarySaleCutReceiver
178 self.creatorSaleCutReceiver = creatorSaleCutReceiver
179 self.additionalSaleCutReceivers = additionalSaleCutReceivers
180
181 self.remainingResource = StarlyCardStaking.getRemainingResourceWithDefault(starlyID: starlyID)
182
183 let beneficiaryCutAmount = self.bidPrice * self.beneficiarySaleCutReceiver.percent
184 let creatorCutAmount = self.bidPrice * self.creatorSaleCutReceiver.percent
185 var additionalSaleCutAmountSum = 0.0
186 var additionalSaleCuts: [StarlyCardMarket.SaleCut] = []
187 for additionalSaleCutReceiver in self.additionalSaleCutReceivers {
188 let additionalCutAmount = self.bidPrice * additionalSaleCutReceiver.percent
189 additionalSaleCutAmountSum = additionalSaleCutAmountSum + additionalCutAmount
190 additionalSaleCuts.append(StarlyCardMarket.SaleCut(
191 address: additionalSaleCutReceiver.receiver.address,
192 amount: additionalCutAmount,
193 percent: additionalSaleCutReceiver.percent));
194 }
195
196 self.bidVault <- bidderFungibleProvider.withdraw(amount: bidPrice)
197
198 StarlyCardBidV3.totalCount = StarlyCardBidV3.totalCount + (1 as UInt64)
199
200 emit StarlyCardBidCreated(
201 bidID: self.id,
202 nftID: self.nftID,
203 starlyID: self.starlyID,
204 bidPrice: self.bidPrice,
205 bidVaultType: self.bidVaultType.identifier,
206 bidderAddress: self.bidderAddress,
207 beneficiarySaleCut: StarlyCardMarket.SaleCut(
208 address: self.beneficiarySaleCutReceiver.receiver.address,
209 amount: beneficiaryCutAmount,
210 percent: self.beneficiarySaleCutReceiver.percent),
211 creatorSaleCut: StarlyCardMarket.SaleCut(
212 address: self.creatorSaleCutReceiver.receiver.address,
213 amount: creatorCutAmount,
214 percent: self.creatorSaleCutReceiver.percent),
215 additionalSaleCuts: additionalSaleCuts
216 )
217 }
218
219 destroy () {
220 if self.bidVault.balance > 0.0 {
221 // return back to the bidder
222 self.bidderFungibleReceiver.borrow()!.deposit(from: <- self.bidVault)
223 } else {
224 destroy self.bidVault
225 }
226 }
227 }
228
229 pub resource interface CollectionManager {
230 pub fun insert(bid: @StarlyCardBidV3.Bid)
231 pub fun remove(bidID: UInt64): @Bid
232 pub fun cancel(bidID: UInt64)
233 }
234
235 pub resource interface CollectionPublic {
236 pub fun getBidIDs(): [UInt64]
237
238 pub fun borrowBid(bidID: UInt64): &Bid{BidPublicView}?
239
240 pub fun accept(
241 bidID: UInt64,
242 bidderCardCollection: &StarlyCard.Collection{StarlyCard.StarlyCardCollectionPublic},
243 sellerFungibleReceiver: Capability<&{FungibleToken.Receiver}>,
244 sellerCardCollection: Capability<&StarlyCard.Collection{NonFungibleToken.Provider}>,
245 sellerStakedCardCollection: &StakedStarlyCard.Collection,
246 sellerMarketCollection: &StarlyCardMarket.Collection,
247 cardStakeId: UInt64?)
248
249 pub fun decline(bidID: UInt64)
250 }
251
252 pub resource Collection : CollectionManager, CollectionPublic {
253 pub var bids: @{UInt64: Bid}
254
255 pub fun getBidIDs(): [UInt64] {
256 return self.bids.keys
257 }
258
259 pub fun borrowBid(bidID: UInt64): &Bid{BidPublicView}? {
260 if self.bids[bidID] == nil {
261 return nil
262 }
263
264 return &self.bids[bidID] as &Bid{BidPublicView}?
265 }
266
267 pub fun insert(bid: @StarlyCardBidV3.Bid) {
268 let oldBid <- self.bids[bid.id] <- bid
269 destroy oldBid
270 }
271
272 pub fun remove(bidID: UInt64): @Bid {
273 return <- (self.bids.remove(key: bidID) ?? panic("missing bid"))
274 }
275
276 pub fun accept(
277 bidID: UInt64,
278 bidderCardCollection: &StarlyCard.Collection{StarlyCard.StarlyCardCollectionPublic},
279 sellerFungibleReceiver: Capability<&{FungibleToken.Receiver}>,
280 sellerCardCollection: Capability<&StarlyCard.Collection{NonFungibleToken.Provider}>,
281 sellerStakedCardCollection: &StakedStarlyCard.Collection,
282 sellerMarketCollection: &StarlyCardMarket.Collection,
283 cardStakeId: UInt64?
284 ) {
285 let bid <- self.remove(bidID: bidID)
286 bid.accept(
287 bidderCardCollection: bidderCardCollection,
288 sellerFungibleReceiver: sellerFungibleReceiver,
289 sellerCardCollection: sellerCardCollection,
290 sellerStakedCardCollection: sellerStakedCardCollection,
291 sellerMarketCollection: sellerMarketCollection,
292 cardStakeId: cardStakeId
293 )
294 destroy bid
295 }
296
297 pub fun decline(bidID: UInt64) {
298 let bid <- self.remove(bidID: bidID)
299 emit StarlyCardBidDeclined(bidID: bidID, nftID: bid.nftID, starlyID: bid.starlyID)
300 destroy bid
301 }
302
303 pub fun cancel(bidID: UInt64) {
304 let bid <- self.remove(bidID: bidID)
305 emit StarlyCardBidCancelled(bidID: bidID, nftID: bid.nftID, starlyID: bid.starlyID)
306 destroy bid
307 }
308
309 destroy () {
310 destroy self.bids
311 }
312
313 init () {
314 self.bids <- {}
315 }
316 }
317
318 pub fun createBid(
319 nftID: UInt64,
320 starlyID: String,
321 bidPrice: UFix64,
322 bidVaultType: Type,
323 bidderAddress: Address,
324 bidderFungibleReceiver: Capability<&{FungibleToken.Receiver}>,
325 bidderFungibleProvider: &AnyResource{FungibleToken.Provider},
326 beneficiarySaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2,
327 creatorSaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2,
328 minterSaleCutReceiver: StarlyCardMarket.SaleCutReceiverV2,
329 ): @Bid {
330 let parsedStarlyID = StarlyIDParser.parse(starlyID: starlyID)
331 let collectionID = parsedStarlyID.collectionID
332 let starlyRoyalty = StarlyRoyalties.getStarlyRoyalty()
333 let collectionRoyalty = StarlyRoyalties.getCollectionRoyalty(collectionID: collectionID)
334 ?? panic("Could not get creator royalty")
335 let minterRoyalty = StarlyRoyalties.getMinterRoyalty(collectionID: collectionID, starlyID: starlyID)
336 ?? panic("Could not get minter royalty")
337
338 assert(beneficiarySaleCutReceiver.receiver.address == starlyRoyalty.address, message: "Incorrect Starly royalty address")
339 assert(creatorSaleCutReceiver.receiver.address == collectionRoyalty.address, message: "Incorrect creator royalty address")
340 assert(minterSaleCutReceiver.receiver.address == minterRoyalty.address, message: "Incorrect minter royalty address")
341
342 assert(beneficiarySaleCutReceiver.percent == starlyRoyalty.cut, message: "Incorrect Starly royalty percent")
343 assert(creatorSaleCutReceiver.percent == collectionRoyalty.cut, message: "Incorrect creator royalty percent")
344 assert(minterSaleCutReceiver.percent == minterRoyalty.cut, message: "Incorrect minter royalty percent")
345
346 return <- create Bid(
347 nftID: nftID,
348 starlyID: starlyID,
349 bidPrice: bidPrice,
350 bidVaultType: bidVaultType,
351 bidderAddress: bidderAddress,
352 bidderFungibleReceiver: bidderFungibleReceiver,
353 bidderFungibleProvider: bidderFungibleProvider,
354 beneficiarySaleCutReceiver: beneficiarySaleCutReceiver,
355 creatorSaleCutReceiver: creatorSaleCutReceiver,
356 additionalSaleCutReceivers: [minterSaleCutReceiver],
357 )
358 }
359
360 pub fun createEmptyCollection(): @Collection {
361 return <- create Collection()
362 }
363
364 init() {
365 self.totalCount = 0
366 self.CollectionStoragePath = /storage/starlyCardBidV3Collection
367 self.CollectionPublicPath = /public/starlyCardBidV3Collection
368 }
369}
370