Smart Contract
CoatCheck
A.5c57f79c6694797f.CoatCheck
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3
4
5// CoatCheck
6//
7// A Smart contract Meant to hold items for another address should they not be able to receive them.
8// this contract is essentially an escrow that no one holds the keys to. It will support holding any Fungible and NonFungible Tokens
9// with a specified address that is allowed to claim them. In this way, dapps which need to ensure that accounts are able to "Receive" assets
10// have a way of putting them into a holding resource that the intended receiver can redeem.
11pub contract CoatCheck {
12
13 // | ---------------- Events ----------------- |
14 pub event Initialized()
15
16 pub event TicketCreated(ticketResourceID: UInt64, redeemer: Address, fungibleTokenTypes: [Type], nonFungibleTokenTypes: [Type])
17
18 pub event TicketRedeemed(ticketResourceID: UInt64, redeemer: Address)
19
20 pub event ValetDestroyed(resourceID: UInt64)
21
22 access(account) let valet: @CoatCheck.Valet
23
24 // A CoatCheck has been initialized. This resource can now be watched for deposited items that accounts can redeem
25 pub event CoatCheckInitialized(resourceID: UInt64)
26
27 pub resource interface TicketPublic {
28 pub fun redeem(fungibleTokenReceiver: &{FungibleToken.Receiver}?, nonFungibleTokenReceiver: &{NonFungibleToken.CollectionPublic}?)
29 pub fun getDetails(): CoatCheck.TicketDetails
30 }
31
32 pub struct TicketDetails {
33 pub let nftTypes: [Type]
34 pub let vaultTypes: [Type]
35 pub let redeemer: Address
36
37 init(
38 redeemer: Address,
39 nftTypes: [Type],
40 vaultTypes: [Type]
41 ) {
42 self.redeemer = redeemer
43 self.nftTypes = nftTypes
44 self.vaultTypes = vaultTypes
45 }
46 }
47
48 // Tickets are used to fungible tokens, non-fungible tokens, or both.
49 // A ticket can be created by the CoatCheck valet, and can be redeemed only by
50 // capabilities owned by the designated redeemer of a ticket
51 pub resource Ticket: TicketPublic {
52 // a ticket can have Fungible Tokens AND NonFungibleTokens
53 access(self) var fungibleTokenVaults: @[FungibleToken.Vault]?
54 access(self) var nonFungibleTokens: @[NonFungibleToken.NFT]?
55
56 pub let details: TicketDetails
57
58 // The following variables are maintained by the CoatCheck contract
59 access(self) var redeemed: Bool // a ticket can only be redeemed once. It also cannot be destroyed unless redeemed is set to true
60 access(self) var storageFee: UFix64 // the storage fee taken to hold this ticket in storage. It is returned when the ticket is redeemed.
61
62 init(
63 fungibleTokenVaults: @[FungibleToken.Vault]?,
64 nonFungibleTokens: @[NonFungibleToken.NFT]?,
65 redeemer: Address
66 ) {
67 assert(
68 fungibleTokenVaults != nil || nonFungibleTokens != nil,
69 message: "must provide either FungibleToken vaults or NonFungibleToken NFTs"
70 )
71
72 let ftTypes: [Type] = []
73 let nftTypes: [Type] = []
74
75 let ticketVaults: @[FungibleToken.Vault] <- []
76 let ticketTokens: @[NonFungibleToken.NFT] <- []
77
78 if fungibleTokenVaults != nil {
79 while fungibleTokenVaults?.length! > 0 {
80 let vault <- fungibleTokenVaults?.removeFirst()
81 ftTypes.append(vault?.getType()!)
82 ticketVaults.append(<-vault!)
83 }
84 }
85
86 if nonFungibleTokens != nil {
87 while nonFungibleTokens?.length! > 0 {
88 let token <- nonFungibleTokens?.removeFirst()
89 nftTypes.append(token?.getType()!)
90 ticketTokens.append(<-token!)
91 }
92 }
93
94 if ftTypes.length > 0 {
95 self.fungibleTokenVaults <- ticketVaults
96 } else {
97 self.fungibleTokenVaults <- nil
98 destroy ticketVaults
99 }
100
101 if nftTypes.length > 0 {
102 self.nonFungibleTokens <- ticketTokens
103 } else {
104 self.nonFungibleTokens <- nil
105 destroy ticketTokens
106 }
107
108 self.details = TicketDetails(redeemer: redeemer, nftTypes: nftTypes, vaultTypes: ftTypes)
109
110 self.redeemed = false
111 self.storageFee = 0.0
112
113 emit TicketCreated(ticketResourceID: self.uuid, redeemer: redeemer, fungibleTokenTypes: ftTypes, nonFungibleTokenTypes: nftTypes)
114
115 destroy fungibleTokenVaults
116 destroy nonFungibleTokens
117 }
118
119 // redeem the ticket using an optional receiver for fungible tokens and non-fungible tokens. The supplied receivers must be
120 // owned by the redeemer of this ticket.
121 pub fun redeem(fungibleTokenReceiver: &{FungibleToken.Receiver}?, nonFungibleTokenReceiver: &{NonFungibleToken.CollectionPublic}?) {
122 pre {
123 fungibleTokenReceiver == nil || (fungibleTokenReceiver!.owner!.address == self.details.redeemer) : "incorrect owner"
124 nonFungibleTokenReceiver == nil || (nonFungibleTokenReceiver!.owner!.address == self.details.redeemer) : "incorrect owner"
125 self.fungibleTokenVaults == nil || fungibleTokenReceiver != nil: "must provide fungibleTokenReceiver when there is a vault to claim"
126 self.nonFungibleTokens == nil || nonFungibleTokenReceiver != nil: "must provide nonFungibleTokenReceiver when there is a vault to claim"
127 }
128
129 self.redeemed = true
130
131 let vaults <- self.fungibleTokenVaults <- nil
132 let tokens <- self.nonFungibleTokens <- nil
133
134 // do we have vaults to distribute?
135 if vaults != nil && vaults?.length! > 0 {
136 while vaults?.length! > 0 {
137 // pop them off our list of vaults one by one and deposit them
138 let vault <- vaults?.remove(at: 0)!
139 fungibleTokenReceiver!.deposit(from: <-vault)
140 }
141 }
142
143 // do we have nfts to distribute?
144 if tokens != nil && tokens?.length! > 0 {
145 while tokens?.length! > 0 {
146 // pop them off our list of tokens one by one and deposit them
147 let token <- tokens?.remove(at: 0)!
148 nonFungibleTokenReceiver!.deposit(token: <-token)
149 }
150 }
151
152 emit TicketRedeemed(ticketResourceID: self.uuid, redeemer: self.details.redeemer)
153
154 destroy vaults
155 destroy tokens
156 }
157
158 pub fun getDetails(): CoatCheck.TicketDetails {
159 return self.details
160 }
161
162 destroy () {
163 pre {
164 self.redeemed : "not redeemed"
165 }
166
167 destroy self.fungibleTokenVaults
168 destroy self.nonFungibleTokens
169 }
170 }
171
172 // ValetPublic contains our main entry-point methods that
173 // anyone can use to make/redeem tickets.
174 pub resource interface ValetPublic {
175 // redeem a ticket, supplying an optional receiver to use for depositing
176 // any fts or nfts in the ticket
177 pub fun redeemTicket(
178 ticketID: UInt64,
179 fungibleTokenReceiver: &{FungibleToken.Receiver}?,
180 nonFungibleTokenReceiver: &{NonFungibleToken.CollectionPublic}?,
181 )
182 pub fun borrowTicket(ticketID: UInt64): &Ticket{TicketPublic}?
183
184 }
185
186 pub resource Valet: ValetPublic {
187 access(self) var tickets: @{UInt64: Ticket}
188
189 // we store the fees taken when a ticket is made so that the exact amount is withdrawn
190 // when a ticket is redeemed
191 access(self) var feesByTicketID: {UInt64: UFix64}
192
193 init() {
194 self.tickets <- {}
195 self.feesByTicketID = {}
196 }
197
198 // Create a new ticket containing a list of vaults, nfts, or both.
199 // The creator of a ticket must also include a vault to pay fees with,
200 // and a receiver to refund the fee taken once a ticket is redeemed.
201 // Any extra tokens sent for the storage fee are sent back when the ticket is made
202 access(account) fun createTicket(
203 redeemer: Address,
204 vaults: @[FungibleToken.Vault]?,
205 tokens: @[NonFungibleToken.NFT]?,
206 ) {
207 let ticket <- create Ticket(
208 fungibleTokenVaults: <-vaults,
209 nonFungibleTokens: <-tokens,
210 redeemer: redeemer,
211 )
212
213 let ticketID = ticket.uuid
214 let oldTicket <- self.tickets[ticketID] <- ticket
215 destroy oldTicket
216 }
217
218 pub fun borrowTicket(ticketID: UInt64): &Ticket{TicketPublic}? {
219 if self.tickets[ticketID] != nil {
220 return &self.tickets[ticketID] as! &Ticket{TicketPublic}?
221 } else {
222 return nil
223 }
224 }
225
226 // redeem the ticket using supplied receivers.
227 // if a ticket has fungible tokens, the fungibleTokenReceiver is required.
228 // if a ticket has nfts, the nonFungibleTokenReceiver is required.
229 pub fun redeemTicket(
230 ticketID: UInt64,
231 fungibleTokenReceiver: &{FungibleToken.Receiver}?,
232 nonFungibleTokenReceiver: &{NonFungibleToken.CollectionPublic}?
233 ) {
234 pre {
235 self.tickets[ticketID] != nil : "ticket does not exist"
236 }
237 // take the ticket out of storage and redeem it
238 let ticket <-! self.tickets[ticketID] <- nil
239 ticket?.redeem(fungibleTokenReceiver: fungibleTokenReceiver, nonFungibleTokenReceiver: nonFungibleTokenReceiver)
240 destroy ticket
241 }
242
243 destroy () {
244 emit ValetDestroyed(resourceID: self.uuid)
245 destroy self.tickets
246 }
247 }
248
249 pub fun getValetPublic(): &CoatCheck.Valet{CoatCheck.ValetPublic} {
250 return &self.valet as &CoatCheck.Valet{CoatCheck.ValetPublic}
251 }
252
253 access(account) fun getValet(): &CoatCheck.Valet {
254 return &self.valet as &CoatCheck.Valet
255 }
256
257 init() {
258 self.valet <- create Valet()
259 }
260}
261