Smart Contract
MatrixMarketOpenOffer
A.2162bbe13ade251e.MatrixMarketOpenOffer
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3
4pub contract MatrixMarketOpenOffer {
5
6 // initialize StoragePath and OpenOfferPublicPath
7 pub event MatrixMarketOpenOfferInitialized()
8
9 // MatrixMarketOpenOffer initialized
10 pub event OpenOfferInitialized(OpenOfferResourceId: UInt64)
11
12 pub event OpenOfferDestroyed(OpenOfferResourceId: UInt64)
13
14 // event: create a bid
15 pub event OfferAvailable(
16 bidAddress: Address,
17 bidId: UInt64,
18 vaultType: Type,
19 bidPrice: UFix64,
20 nftType: Type,
21 nftId: UInt64,
22 brutto: UFix64,
23 cuts: {Address:UFix64},
24 expirationTime: UFix64,
25 )
26
27 // event: close a bid (purchased or removed)
28 pub event OfferCompleted(
29 bidId: UInt64,
30 purchased: Bool,
31 )
32
33 // payment splitter
34 pub struct Cut {
35 pub let receiver: Capability<&{FungibleToken.Receiver}>
36 pub let amount: UFix64
37
38 init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
39 self.receiver = receiver
40 self.amount = amount
41 }
42 }
43
44 pub struct OfferDetails {
45 pub let bidId: UInt64
46 pub let vaultType: Type
47 pub let bidPrice: UFix64
48 pub let nftType: Type
49 pub let nftId: UInt64
50 pub let brutto: UFix64
51 pub let cuts: [Cut]
52 pub let expirationTime: UFix64
53
54 pub var purchased: Bool
55
56 access(contract) fun setToPurchased() {
57 self.purchased = true
58 }
59
60 init(
61 bidId: UInt64,
62 vaultType: Type,
63 bidPrice: UFix64,
64 nftType: Type,
65 nftId: UInt64,
66 brutto: UFix64,
67 cuts: [Cut],
68 expirationTime: UFix64,
69 ) {
70 self.bidId = bidId
71 self.vaultType = vaultType
72 self.bidPrice = bidPrice
73 self.nftType = nftType
74 self.nftId = nftId
75 self.brutto = brutto
76 self.cuts = cuts
77 self.expirationTime = expirationTime
78 self.purchased = false
79 }
80 }
81
82 pub resource interface OfferPublic {
83 pub fun purchase(item: @NonFungibleToken.NFT): @FungibleToken.Vault?
84 pub fun getDetails(): OfferDetails
85 }
86
87 pub resource Offer: OfferPublic {
88 access(self) let details: OfferDetails
89 access(contract) let vaultRefCapability: Capability<&{FungibleToken.Receiver, FungibleToken.Balance, FungibleToken.Provider}>
90 access(contract) let rewardCapability: Capability<&{NonFungibleToken.CollectionPublic}>
91
92 init(
93 vaultRefCapability: Capability<&{FungibleToken.Receiver, FungibleToken.Balance, FungibleToken.Provider}>,
94 offerPrice: UFix64,
95 rewardCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
96 nftType: Type,
97 nftId: UInt64,
98 cuts: [Cut],
99 expirationTime: UFix64,
100 ) {
101 pre {
102 rewardCapability.check(): "reward capability not valid"
103 cuts.length <= 10: "length of cuts too long"
104 }
105 self.vaultRefCapability = vaultRefCapability
106 self.rewardCapability = rewardCapability
107
108 var price: UFix64 = offerPrice
109 let cutsInfo: {Address:UFix64} = {}
110
111 for cut in cuts {
112 assert(cut.receiver.check(), message: "invalid cut receiver")
113 assert(price > cut.amount, message: "price must be > 0")
114
115 price = price - cut.amount
116 cutsInfo[cut.receiver.address] = cut.amount
117 }
118
119 let vaultRef = self.vaultRefCapability.borrow() ?? panic("cannot borrow vaultRefCapability")
120 self.details = OfferDetails(
121 bidId: self.uuid,
122 vaultType: vaultRef.getType(),
123 bidPrice: price,
124 nftType: nftType,
125 nftId: nftId,
126 brutto: offerPrice,
127 cuts: cuts,
128 expirationTime: expirationTime,
129 )
130
131 emit OfferAvailable(
132 bidAddress: rewardCapability.address,
133 bidId: self.details.bidId,
134 vaultType: self.details.vaultType,
135 bidPrice: self.details.bidPrice,
136 nftType: self.details.nftType,
137 nftId: self.details.nftId,
138 brutto: self.details.brutto,
139 cuts: cutsInfo,
140 expirationTime: self.details.expirationTime,
141 )
142 }
143
144 pub fun purchase(item: @NonFungibleToken.NFT): @FungibleToken.Vault {
145 pre {
146 self.details.expirationTime > getCurrentBlock().timestamp: "Offer has expired"
147 !self.details.purchased: "Offer has already been purchased"
148 item.isInstance(self.details.nftType): "item NFT is not of specified type"
149 item.id == self.details.nftId: "item NFT does not have specified ID"
150 }
151 self.details.setToPurchased()
152
153 self.rewardCapability.borrow()!.deposit(token: <- item)
154
155 let payment <- self.vaultRefCapability.borrow()!.withdraw(amount: self.details.brutto)
156
157 for cut in self.details.cuts {
158 if let receiver = cut.receiver.borrow() {
159 let part <- payment.withdraw(amount: cut.amount)
160 receiver.deposit(from: <- part)
161 }
162 }
163
164 emit OfferCompleted(
165 bidId: self.details.bidId,
166 purchased: self.details.purchased,
167 )
168
169 return <- payment
170 }
171
172 pub fun getDetails(): OfferDetails {
173 return self.details
174 }
175
176 destroy() {
177 if !self.details.purchased {
178 emit OfferCompleted(
179 bidId: self.details.bidId,
180 purchased: self.details.purchased,
181 )
182 }
183 }
184 }
185
186 pub resource interface OpenOfferManager {
187 pub fun createOffer(
188 vaultRefCapability: Capability<&{FungibleToken.Receiver, FungibleToken.Balance, FungibleToken.Provider}>,
189 offerPrice: UFix64,
190 rewardCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
191 nftType: Type,
192 nftId: UInt64,
193 cuts: [Cut],
194 expirationTime: UFix64,
195 ): UInt64
196 pub fun removeOffer(bidId: UInt64)
197 }
198
199 pub resource interface OpenOfferPublic {
200 pub fun getOfferIds(): [UInt64]
201 pub fun borrowOffer(bidId: UInt64): &Offer{OfferPublic}?
202 pub fun cleanup(bidId: UInt64)
203 }
204
205 pub resource OpenOffer : OpenOfferManager, OpenOfferPublic {
206 access(self) var bids: @{UInt64:Offer}
207
208 pub fun createOffer(
209 vaultRefCapability: Capability<&{FungibleToken.Receiver,FungibleToken.Balance,FungibleToken.Provider}>,
210 offerPrice: UFix64,
211 rewardCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
212 nftType: Type,
213 nftId: UInt64,
214 cuts: [Cut],
215 expirationTime: UFix64,
216 ): UInt64 {
217 let bid <- create Offer(
218 vaultRefCapability: vaultRefCapability,
219 offerPrice: offerPrice,
220 rewardCapability: rewardCapability,
221 nftType: nftType,
222 nftId: nftId,
223 cuts: cuts,
224 expirationTime: expirationTime,
225 )
226
227 let bidId = bid.uuid
228 let dummy <- self.bids[bidId] <- bid
229 destroy dummy
230
231 return bidId
232 }
233
234 pub fun removeOffer(bidId: UInt64) {
235 destroy self.bids.remove(key: bidId) ?? panic("missing bid")
236 }
237
238 pub fun getOfferIds(): [UInt64] {
239 return self.bids.keys
240 }
241
242 pub fun borrowOffer(bidId: UInt64): &Offer{OfferPublic}? {
243 if self.bids[bidId] != nil {
244 return &self.bids[bidId] as! &Offer{OfferPublic}?
245 } else {
246 return nil
247 }
248 }
249
250 pub fun cleanup(bidId: UInt64) {
251 pre {
252 self.bids[bidId] != nil: "could not find Offer with given id"
253 }
254 let bid <- self.bids.remove(key: bidId)!
255 assert(bid.getDetails().purchased == true, message: "Offer is not purchased, only admin can remove")
256 destroy bid
257 }
258
259 init() {
260 self.bids <- {}
261 emit OpenOfferInitialized(OpenOfferResourceId: self.uuid)
262 }
263
264 destroy() {
265 destroy self.bids
266 emit OpenOfferDestroyed(OpenOfferResourceId: self.uuid)
267 }
268 }
269
270 // create openbid resource
271 pub fun createOpenOffer(): @OpenOffer {
272 return <-create OpenOffer()
273 }
274
275 pub let OpenOfferStoragePath: StoragePath
276 pub let OpenOfferPublicPath: PublicPath
277
278 init () {
279 self.OpenOfferStoragePath = /storage/MatrixMarketOpenOffer
280 self.OpenOfferPublicPath = /public/MatrixMarketOpenOffer
281
282 emit MatrixMarketOpenOfferInitialized()
283 }
284}
285