Smart Contract
BasketballsMarket
A.eee6bdee2b2bdfc8.BasketballsMarket
1import Basketballs from 0xeee6bdee2b2bdfc8
2import FungibleToken from 0xf233dcee88fe0abe
3import NonFungibleToken from 0x1d7e57aa55817448
4
5/*
6 This is a simple Basketballs initial sale contract for the DApp to use
7 in order to list and sell Basketballs.
8
9 Its structure is neither what it would be if it was the simplest possible
10 marjet contract or if it was a complete general purpose market contract.
11 Rather it's the simplest possible version of a more general purpose
12 market contract that indicates how that contract might function in
13 broad strokes. This has been done so that integrating with this contract
14 is a useful preparatory exercise for code that will integrate with the
15 later more general purpose market contract.
16
17 It allows:
18 - Anyone to create Sale Offers and place them in a collection, making it
19 publicly accessible.
20 - Anyone to accept the offer and buy the item.
21
22 It notably does not handle:
23 - Multiple different sale NFT contracts.
24 - Multiple different payment FT contracts.
25 - Splitting sale payments to multiple recipients.
26
27 */
28
29access(all) contract BasketballsMarket {
30 // SaleOffer events.
31 //
32 // A sale offer has been created.
33 access(all) event SaleOfferCreated(itemID: UInt64, price: UFix64)
34 // Someone has purchased an item that was offered for sale.
35 access(all) event SaleOfferAccepted(itemID: UInt64)
36 // A sale offer has been destroyed, with or without being accepted.
37 access(all) event SaleOfferFinished(itemID: UInt64)
38
39 // Collection events.
40 //
41 // A sale offer has been inserted into the collection of Address.
42 access(all) event CollectionInsertedSaleOffer(saleItemID: UInt64, saleItemCollection: Address)
43 // A sale offer has been removed from the collection of Address.
44 access(all) event CollectionRemovedSaleOffer(saleItemID: UInt64, saleItemCollection: Address)
45
46 // Named paths
47 //
48 access(all) let CollectionStoragePath: StoragePath
49 access(all) let CollectionPublicPath: PublicPath
50
51 // SaleOfferPublicView
52 // An interface providing a read-only view of a SaleOffer
53 //
54 access(all) resource interface SaleOfferPublicView {
55 access(all) var saleCompleted: Bool
56 access(all) let saleItemID: UInt64
57 access(all) let salePrice: UFix64
58 }
59
60 access(all) struct SaleOfferView {
61 access(all) let saleItemID: UInt64
62 access(all) let salePrice: UFix64
63 access(all) let saleCompleted: Bool
64 access(all) let editionID: UInt32
65 access(all) let name: String
66 access(all) let description: String
67 access(all) let imageURL: String
68 access(all) let serialNumber: UInt64
69 access(all) let circulatingCount: UInt64
70
71 init(
72 saleItemID: UInt64,
73 salePrice: UFix64,
74 saleCompleted: Bool,
75 editionID: UInt32,
76 serialNumber: UInt64,
77 name: String,
78 description: String,
79 imageURL: String,
80 circulatingCount: UInt64
81 ) {
82 self.saleItemID = saleItemID
83 self.salePrice = salePrice
84 self.saleCompleted = saleCompleted
85 self.editionID = editionID
86 self.name = name
87 self.description = description
88 self.imageURL = imageURL
89 self.serialNumber = serialNumber
90 self.circulatingCount = circulatingCount
91 }
92 }
93
94 // SaleOffer
95 // A Basketballs NFT being offered to sale for a set fee
96 //
97 access(all) resource SaleOffer: SaleOfferPublicView {
98 // Whether the sale has completed with someone purchasing the item.
99 access(all) var saleCompleted: Bool
100
101 // The Basketballs NFT ID for sale.
102 access(all) let saleItemID: UInt64
103 // The collection containing that ID.
104 access(self) let sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &Basketballs.Collection>
105
106 // The sale payment price.
107 access(all) let salePrice: UFix64
108 // The vault that will receive that payment if teh sale completes successfully.
109 access(self) let sellerPaymentReceiver: Capability<&{FungibleToken.Receiver}>
110
111 // Called by a purchaser to accept the sale offer.
112 // If they send the correct payment and if the item is still available,
113 // the Basketballs NFT will be placed in their Basketballs.Collection .
114 //
115 access(all) fun accept(
116 buyerCollection: &Basketballs.Collection,
117 buyerPayment: @{FungibleToken.Vault}
118 ) {
119 panic("Not implemented")
120 }
121
122 // initializer
123 // Take the information required to create a sale offer, notably the capability
124 // to transfer the Basketballs NFT and the capability to receive a fungible token in payment.
125 //
126 init(
127 sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &Basketballs.Collection>,
128 saleItemID: UInt64,
129 sellerPaymentReceiver: Capability<&{FungibleToken.Receiver}>,
130 salePrice: UFix64
131 ) {
132 pre {
133 sellerItemProvider.borrow() != nil: "Cannot borrow seller"
134 sellerPaymentReceiver.borrow() != nil: "Cannot borrow sellerPaymentReceiver"
135 }
136
137 self.saleCompleted = false
138
139 self.sellerItemProvider = sellerItemProvider
140 self.saleItemID = saleItemID
141
142 self.sellerPaymentReceiver = sellerPaymentReceiver
143 self.salePrice = salePrice
144
145 emit SaleOfferCreated(itemID: self.saleItemID, price: self.salePrice)
146 }
147 }
148
149 // createSaleOffer
150 // Make creating a SaleOffer publicly accessible.
151 //
152 access(all) fun createSaleOffer (
153 sellerItemProvider: Capability<auth(NonFungibleToken.Withdraw) &Basketballs.Collection>,
154 saleItemID: UInt64,
155 sellerPaymentReceiver: Capability<&{FungibleToken.Receiver}>,
156 salePrice: UFix64
157 ): @SaleOffer {
158 return <-create SaleOffer(
159 sellerItemProvider: sellerItemProvider,
160 saleItemID: saleItemID,
161 sellerPaymentReceiver: sellerPaymentReceiver,
162 salePrice: salePrice
163 )
164 }
165
166 // CollectionManager
167 // An interface for adding and removing SaleOffers to a collection, intended for
168 // use by the collection's owner.
169 //
170 access(all) resource interface CollectionManager {
171 access(all) fun insert(offer: @BasketballsMarket.SaleOffer)
172 access(all) fun remove(saleItemID: UInt64): @SaleOffer
173 }
174
175 // CollectionPurchaser
176 // An interface to allow purchasing items via SaleOffers in a collection.
177 // This function is also provided by CollectionPublic, it is here to support
178 // more fine-grained access to the collection for as yet unspecified future use cases.
179 //
180 access(all) resource interface CollectionPurchaser {
181 access(all) fun purchase(
182 saleItemID: UInt64,
183 buyerCollection: &Basketballs.Collection,
184 buyerPayment: @{FungibleToken.Vault}
185 )
186 }
187
188 // CollectionPublic
189 // An interface to allow listing and borrowing SaleOffers, and purchasing items via SaleOffers in a collection.
190 //
191 access(all) resource interface CollectionPublic {
192 access(all) fun getSaleOfferIDs(): [UInt64]
193 access(all) fun borrowSaleItem(saleItemID: UInt64): &{SaleOfferPublicView}?
194 access(all) fun purchase(
195 saleItemID: UInt64,
196 buyerCollection: &Basketballs.Collection,
197 buyerPayment: @{FungibleToken.Vault}
198 )
199 }
200
201 // Collection
202 // A resource that allows its owner to manage a list of SaleOffers, and purchasers to interact with them.
203 //
204 access(all) resource Collection : CollectionManager, CollectionPurchaser, CollectionPublic {
205 access(all) var saleOffers: @{UInt64: SaleOffer}
206
207 // insert
208 // Insert a SaleOffer into the collection, replacing one with the same saleItemID if present.
209 //
210 access(all) fun insert(offer: @BasketballsMarket.SaleOffer) {
211 let id: UInt64 = offer.saleItemID
212
213 // add the new offer to the dictionary which removes the old one
214 let oldOffer <- self.saleOffers[id] <- offer
215 destroy oldOffer
216
217 emit CollectionInsertedSaleOffer(saleItemID: id, saleItemCollection: self.owner?.address!)
218 }
219
220 // remove
221 // Remove and return a SaleOffer from the collection.
222 access(all) fun remove(saleItemID: UInt64): @SaleOffer {
223 emit CollectionRemovedSaleOffer(saleItemID: saleItemID, saleItemCollection: self.owner?.address!)
224 return <-(self.saleOffers.remove(key: saleItemID) ?? panic("missing SaleOffer"))
225 }
226
227 // purchase
228 // If the caller passes a valid saleItemID and the item is still for sale, and passes a vault
229 // typed as a FungibleToken.Vault (SomeToken.deposit() handles should cast to concrete type)
230 // containing the correct payment amount, this will transfer the Basketball to the caller's
231 // Basketballs collection.
232 // It will then remove and destroy the offer.
233 // Note that is means that events will be emitted in this order:
234 // 1. Collection.CollectionRemovedSaleOffer
235 // 2. Basketballs.Withdraw
236 // 3. Basketballs.Deposit
237 // 4. SaleOffer.SaleOfferFinished
238 //
239 access(all) fun purchase(
240 saleItemID: UInt64,
241 buyerCollection: &Basketballs.Collection,
242 buyerPayment: @{FungibleToken.Vault}
243 ) {
244 pre {
245 self.saleOffers[saleItemID] != nil: "SaleOffer does not exist in the collection!"
246 }
247 let offer <- self.remove(saleItemID: saleItemID)
248 offer.accept(buyerCollection: buyerCollection, buyerPayment: <-buyerPayment)
249 //FIXME: Is this correct? Or should we return it to the caller to dispose of?
250 destroy offer
251 }
252
253 // getSaleOfferIDs
254 // Returns an array of the IDs that are in the collection
255 //
256 access(all) fun getSaleOfferIDs(): [UInt64] {
257 return self.saleOffers.keys
258 }
259
260 // borrowSaleItem
261 // Returns an Optional read-only view of the SaleItem for the given saleItemID if it is contained by this collection.
262 // The optional will be nil if the provided saleItemID is not present in the collection.
263 //
264 access(all) fun borrowSaleItem(saleItemID: UInt64): &{SaleOfferPublicView}? {
265 if self.saleOffers[saleItemID] == nil {
266 return nil
267 } else {
268 return &self.saleOffers[saleItemID]
269 }
270 }
271
272 // constructor
273 //
274 init () {
275 self.saleOffers <- {}
276 }
277 }
278
279 // createEmptyCollection
280 // Make creating a Collection publicly accessible.
281 //
282 access(all) fun createEmptyCollection(): @Collection {
283 return <-create Collection()
284 }
285
286 init () {
287 self.CollectionStoragePath = /storage/BasketballsMarketCollection
288 self.CollectionPublicPath = /public/BasketballsMarketCollection
289 }
290}