Smart Contract
Offers
A.b8ea91944fd51c43.Offers
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import DapperUtilityCoin from 0xead892083b3e2c6c
4
5// Offers
6//
7// Contract holds the Offer resource and a public method to create them.
8//
9// Each Offer can have one or more royalties of the sale price that
10// goes to one or more addresses.
11//
12// Owners of NFT can watch for OfferAvailable events and check
13// the Offer amount to see if they wish to accept the offer.
14//
15// Marketplaces and other aggregators can watch for OfferAvailable events
16// and list offers of interest to logged in users.
17//
18pub contract Offers {
19 // OfferAvailable
20 // An Offer has been created and added to the users DapperOffer resource.
21 //
22 pub event OfferAvailable(
23 offerAddress: Address,
24 offerId: UInt64,
25 nftType: Type,
26 nftId: UInt64,
27 offerAmount: UFix64,
28 royalties: {Address:UFix64},
29 )
30
31 // OfferCompleted
32 // The Offer has been resolved. The offer has either been accepted
33 // by the NFT owner, or the offer has been removed and destroyed.
34 //
35 pub event OfferCompleted(
36 offerId: UInt64,
37 nftType: Type,
38 nftId: UInt64,
39 purchased: Bool,
40 acceptingAddress: Address?,
41 )
42
43 // Royalty
44 // A struct representing a recipient that must be sent a certain amount
45 // of the payment when a NFT is sold.
46 //
47 pub struct Royalty {
48 pub let receiver: Capability<&{FungibleToken.Receiver}>
49 pub let amount: UFix64
50
51 init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
52 self.receiver = receiver
53 self.amount = amount
54 }
55 }
56
57 // OfferDetails
58 // A struct containing Offers' data.
59 //
60 pub struct OfferDetails {
61 // The ID of the offer
62 pub let offerId: UInt64
63 // The Type of the NFT
64 pub let nftType: Type
65 // The ID of the NFT
66 pub let nftId: UInt64
67 // The Offer amount for the NFT
68 pub let offerAmount: UFix64
69 // Flag to tracked the purchase state
70 pub var purchased: Bool
71 // This specifies the division of payment between recipients.
72 pub let royalties: [Royalty]
73
74 // setToPurchased
75 // Irreversibly set this offer as purchased.
76 //
77 access(contract) fun setToPurchased() {
78 self.purchased = true
79 }
80
81 // initializer
82 //
83 init(
84 offerId: UInt64,
85 nftType: Type,
86 nftId: UInt64,
87 offerAmount: UFix64,
88 royalties: [Royalty]
89 ) {
90 self.offerId = offerId
91 self.nftType = nftType
92 self.nftId = nftId
93 self.offerAmount = offerAmount
94 self.purchased = false
95 self.royalties = royalties
96 }
97 }
98
99 // OfferPublic
100 // An interface providing a useful public interface to an Offer resource.
101 //
102 pub resource interface OfferPublic {
103 // accept
104 // This will accept the offer if provided with the NFT id that matches the Offer
105 //
106 pub fun accept(
107 item: @NonFungibleToken.NFT,
108 receiverCapability: Capability<&{FungibleToken.Receiver}>
109 ): Void
110 // getDetails
111 // Return Offer details
112 //
113 pub fun getDetails(): OfferDetails
114 }
115
116 pub resource Offer: OfferPublic {
117 // The OfferDetails struct of the Offer
118 access(self) let details: OfferDetails
119 // The vault which will handle the payment if the Offer is accepted.
120 access(contract) let vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>
121 // Receiver address for the NFT when/if the Offer is accepted.
122 access(contract) let nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>
123
124 init(
125 vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
126 nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
127 nftType: Type,
128 nftId: UInt64,
129 amount: UFix64,
130 royalties: [Royalty],
131 ) {
132 pre {
133 nftReceiverCapability.check(): "reward capability not valid"
134 }
135 self.vaultRefCapability = vaultRefCapability
136 self.nftReceiverCapability = nftReceiverCapability
137
138 var price: UFix64 = amount
139 let royaltyInfo: {Address:UFix64} = {}
140
141 for royalty in royalties {
142 assert(royalty.receiver.check(), message: "invalid royalty receiver")
143 price = price - royalty.amount
144 royaltyInfo[royalty.receiver.address] = royalty.amount
145 }
146
147 assert(price > 0.0, message: "price must be > 0")
148
149 self.details = OfferDetails(
150 offerId: self.uuid,
151 nftType: nftType,
152 nftId: nftId,
153 offerAmount: amount,
154 royalties: royalties
155 )
156
157 emit OfferAvailable(
158 offerAddress: nftReceiverCapability.address,
159 offerId: self.details.offerId,
160 nftType: self.details.nftType,
161 nftId: self.details.nftId,
162 offerAmount: self.details.offerAmount,
163 royalties: royaltyInfo,
164 )
165 }
166
167 // accept
168 // Accept the offer if...
169 // - Calling from an Offer that hasn't been purchased/desetoryed.
170 // - Provided with a NFT matching the NFT id within the Offer details.
171 // - Provided with a NFT matching the NFT Type within the Offer details.
172 //
173 pub fun accept(
174 item: @NonFungibleToken.NFT,
175 receiverCapability: Capability<&{FungibleToken.Receiver}>
176 ): Void {
177
178 pre {
179 !self.details.purchased: "Offer has already been purchased"
180 item.id == self.details.nftId: "item NFT does not have specified ID"
181 item.isInstance(self.details.nftType): "item NFT is not of specified type"
182 }
183
184 self.details.setToPurchased()
185 self.nftReceiverCapability.borrow()!.deposit(token: <- item)
186
187 let initalDucSupply = self.vaultRefCapability.borrow()!.balance
188 let payment <- self.vaultRefCapability.borrow()!.withdraw(amount: self.details.offerAmount)
189
190 // Payout royalties
191 for royalty in self.details.royalties {
192 if let receiver = royalty.receiver.borrow() {
193 let amount = royalty.amount
194 let part <- payment.withdraw(amount: amount)
195 receiver.deposit(from: <- part)
196 }
197 }
198
199 receiverCapability.borrow()!.deposit(from: <- payment)
200
201 // If a DUC vault is being used for payment we must assert that no DUC is leaking from the transactions.
202 let isDucVault = self.vaultRefCapability.isInstance(
203 Type<Capability<&DapperUtilityCoin.Vault{FungibleToken.Provider, FungibleToken.Balance}>>()
204 )
205
206 if isDucVault {
207 assert(self.vaultRefCapability.borrow()!.balance == initalDucSupply, message: "DUC is leaking")
208 }
209
210 emit OfferCompleted(
211 offerId: self.details.offerId,
212 nftType: self.details.nftType,
213 nftId: self.details.nftId,
214 purchased: self.details.purchased,
215 acceptingAddress: receiverCapability.address,
216 )
217 }
218
219 // getDetails
220 // Return Offer details
221 //
222 pub fun getDetails(): OfferDetails {
223 return self.details
224 }
225
226 destroy() {
227 if !self.details.purchased {
228 emit OfferCompleted(
229 offerId: self.details.offerId,
230 nftType: self.details.nftType,
231 nftId: self.details.nftId,
232 purchased: self.details.purchased,
233 acceptingAddress: nil,
234 )
235 }
236 }
237 }
238
239 // makeOffer
240 pub fun makeOffer(
241 vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
242 nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
243 nftType: Type,
244 nftId: UInt64,
245 amount: UFix64,
246 royalties: [Royalty],
247 ): @Offer {
248 let newOfferResource <- create Offer(
249 vaultRefCapability: vaultRefCapability,
250 nftReceiverCapability: nftReceiverCapability,
251 nftType: nftType,
252 nftId: nftId,
253 amount: amount,
254 royalties: royalties,
255 )
256 return <-newOfferResource
257 }
258}
259