Smart Contract

MatrixMarketOpenOffer

A.2162bbe13ade251e.MatrixMarketOpenOffer

Deployed

2h ago
Feb 28, 2026, 09:42:42 PM UTC

Dependents

0 imports
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