Smart Contract
MikoSeaMarketHistoryV2
A.0b80e42aaab305f0.MikoSeaMarketHistoryV2
1import MetadataViews from 0x1d7e57aa55817448
2import Burner from 0xf233dcee88fe0abe
3
4access(all)contract MikoSeaMarketHistoryV2 {
5 //------------------------------------------------------------
6 // Events
7 //------------------------------------------------------------
8 access(all)event TransactionCreated(transactionId: UInt64, ownerAddress: Address, amount: UFix64, message: String, type: String, metadata: {String:String}, createdAt: UFix64)
9 access(all)event RevenueUpdatedStatus(revenueId: UInt64, status: String, updatedAt: UFix64)
10 access(all)event RevenueCreated(revenueId: UInt64, userAddress: Address, amount: UFix64, transactionIds: [UInt64], metadata: {String:String}, createdAt: UFix64)
11
12 //------------------------------------------------------------
13 // Path
14 //------------------------------------------------------------
15 access(all)let AdminStoragePath: StoragePath
16 access(all)let AdminPublicPath: PublicPath
17
18 //------------------------------------------------------------
19 // Data
20 //------------------------------------------------------------
21 access(all)let adminCap: Capability<&{AdminPublicCollection}>
22 access(all)var nextTransactionId: UInt64
23 access(all)var nextRevenueId: UInt64
24
25 //------------------------------------------------------------
26 // Transaction resource
27 //------------------------------------------------------------
28 access(all)resource Transaction {
29 access(all)let transactionId: UInt64
30 access(all)let userAddress: Address
31 // ¥, without fee
32 access(all)let amount: UFix64
33 // ¥
34 access(all)let royalties: MetadataViews.Royalties?
35
36 // secondMarket
37 // creatorFee
38 // withdraw
39 // rejectRefund
40 access(all)let type: String
41 access(all)var metadata: {String:String}
42 access(all)let createdAt: UFix64
43 access(all)var updatedAt: UFix64
44 access(all)var message: String
45 access(all)var revenueId: UInt64?
46
47 init(
48 userAddress: Address,
49 amount: UFix64,
50 royalties: MetadataViews.Royalties?,
51 type: String,
52 metadata: {String:String},
53 message: String
54 ){
55 pre {
56 ["secondMarket", "creatorFee", "rejectRefund", "withdraw", "platformFee"].contains(type) : "type is invalid"
57 }
58
59 self.amount=amount
60 self.royalties=royalties
61 self.userAddress=userAddress
62 self.type=type
63 self.metadata=metadata
64 self.message=message
65 self.createdAt=getCurrentBlock().timestamp
66 self.updatedAt=getCurrentBlock().timestamp
67 self.revenueId = nil
68 self.transactionId = MikoSeaMarketHistoryV2.nextTransactionId
69 MikoSeaMarketHistoryV2.nextTransactionId = MikoSeaMarketHistoryV2.nextTransactionId + 1
70 }
71
72 access(contract) fun updateMetadata(_ metadata: {String:String}) {
73 self.metadata=metadata
74 self.updatedAt=getCurrentBlock().timestamp
75 }
76
77 access(contract) fun updateMessage(_ message: String) {
78 self.message=message
79 self.updatedAt=getCurrentBlock().timestamp
80 }
81
82 access(contract) fun updateRevenueId(_ revenueId: UInt64) {
83 self.revenueId=revenueId
84 self.updatedAt=getCurrentBlock().timestamp
85 }
86 }
87
88 access(all)resource Revenue {
89 access(all)let revenueId: UInt64
90 access(all)let transactionIds: [UInt64]
91 access(all)let amount: UFix64
92 access(all)let userAddress: Address
93
94 // request amount status, only admin can do that
95 // created: transaction is created
96 // done: admin sent amount to user
97 // rejected: admin rejected this transaction
98 access(all)var status: String
99
100 access(all)let createdAt: UFix64
101 access(all)var updatedAt: UFix64
102
103 access(all)var metadata: {String:String}
104
105 init (
106 transactionIds: [UInt64],
107 metadata: {String:String},
108 amount: UFix64,
109 userAddress: Address
110 ) {
111 self.revenueId = MikoSeaMarketHistoryV2.nextRevenueId
112 self.transactionIds = transactionIds
113 self.status = "created"
114 self.createdAt = getCurrentBlock().timestamp
115 self.updatedAt = getCurrentBlock().timestamp
116 self.metadata = metadata
117 self.amount = amount
118 self.userAddress = userAddress
119 MikoSeaMarketHistoryV2.nextRevenueId = MikoSeaMarketHistoryV2.nextRevenueId + 1
120
121 emit RevenueCreated(revenueId: self.revenueId, userAddress: userAddress, amount: amount, transactionIds: transactionIds, metadata: metadata, createdAt: self.createdAt)
122 }
123
124 access(contract) fun updateStatus(_ status: String) {
125 pre {
126 ["rejected", "done"].firstIndex(of: status) != nil : "status is invalid"
127 }
128 self.status=status
129 self.updatedAt=getCurrentBlock().timestamp
130
131 emit RevenueUpdatedStatus(revenueId: self.revenueId, status: self.status, updatedAt: self.updatedAt)
132 }
133 }
134
135 //------------------------------------------------------------
136 // Admin public
137 //------------------------------------------------------------
138 access(all)resource interface AdminPublicCollection {
139 access(all)fun getTransactionById(_ id: UInt64): &Transaction?
140 access(all)fun getRevenueById(_ id: UInt64): &Revenue?
141 }
142
143 access(all)resource Admin: AdminPublicCollection {
144 // map: {userAddress: [transactionId]}
145 access(all)let userTransactions: {Address: [UInt64]}
146
147 // map: {userAddress: [revenueId]}
148 access(all)let userRevenues: {Address: [UInt64]}
149
150 // map: {userAddress : yen balance}
151 access(all)let userBalances: {Address: Fix64}
152
153 // map: {userAddress : pointBalance}
154 access(all)let userPointBalances: {Address: Fix64}
155
156 // all transaction data
157 // map: {transactionId : Tranaction}
158 access(all)let transactionData: @{UInt64: Transaction}
159
160 // all Revenue data
161 // map: {revenueId : Revenue}
162 access(all)let revenueData: @{UInt64: Revenue}
163
164 init() {
165 self.userTransactions = {}
166 self.transactionData <- {}
167 self.userBalances = {}
168 self.userPointBalances = {}
169 self.userRevenues = {}
170 self.revenueData <- {}
171 }
172 access(all)fun getTransactionById(_ id: UInt64): &Transaction? {
173 return &self.transactionData[id] as &Transaction?
174 }
175
176 access(all)fun getRevenueById(_ id: UInt64): &Revenue? {
177 return &self.revenueData[id] as &Revenue?
178 }
179
180 access(all)fun addTransaction(
181 owner: Address,
182 amount: UFix64,
183 royalties: MetadataViews.Royalties?,
184 type: String,
185 metadata: {String:String},
186 message: String
187 ): &Transaction {
188 let tx <- create Transaction(userAddress: owner, amount: amount, royalties: royalties, type: type, metadata: metadata, message: message)
189 let transactionId = tx.transactionId
190 if !self.userTransactions.containsKey(owner) {
191 self.userTransactions[owner] = []
192 self.userBalances[owner] = 0.0
193 }
194 self.userTransactions[owner]!.insert(at: 0, tx.transactionId)
195 if type == "withdraw" {
196 self.userBalances[owner] = self.userBalances[owner]! - Fix64(amount)
197 } else {
198 self.userBalances[owner] = self.userBalances[owner]! + Fix64(amount)
199 }
200
201 emit TransactionCreated(transactionId: tx.transactionId, ownerAddress: tx.userAddress, amount: tx.amount, message: tx.message, type: tx.type, metadata: tx.metadata, createdAt: tx.createdAt)
202
203 // add transaction
204 let old <- self.transactionData[tx.transactionId] <- tx
205 destroy old
206
207 return self.getTransactionById(transactionId)!;
208 }
209
210 access(all)fun addTransactionWithoutFee(
211 owner: Address,
212 amount: UFix64,
213 type: String,
214 metadata: {String:String},
215 message: String
216 ): &Transaction {
217 return self.addTransaction(owner: owner, amount: amount, royalties: nil, type: type, metadata: metadata, message: message)
218 }
219
220 access(all)fun updateMetadata(_ transactionId: UInt64, metadata: {String:String}): &Transaction? {
221 self.transactionData[transactionId]?.updateMetadata(metadata)
222 return self.getTransactionById(transactionId);
223 }
224
225 access(all)fun updateMessage(_ transactionId: UInt64, message: String): &Transaction? {
226 self.transactionData[transactionId]?.updateMessage(message)
227 return self.getTransactionById(transactionId);
228 }
229
230 // not in use
231 access(self) fun createRequestRevenueByTransactionIds(_ address: Address, metadata: {String:String}) {
232 let ids = self.userTransactions[address] ?? []
233 if ids.length == 0 {
234 return
235 }
236
237 // get transactionIds
238 let transactionIds:[UInt64] = []
239 let transactions: [&Transaction] = []
240 for id in ids {
241 if let tx = self.getTransactionById(id) {
242 if tx.revenueId == nil {
243 transactionIds.append(tx.transactionId)
244 transactions.append(tx)
245 }
246 }
247 }
248
249 // add event withdraw && update balance
250 let totalAmount = MikoSeaMarketHistoryV2.getTotalAmount(transactionIds)
251 if totalAmount <= 0.0 {
252 return
253 }
254 let txWithdraw = self.addTransaction(owner: address, amount: totalAmount, royalties: nil, type: "withdraw", metadata: metadata, message: "振込申請")
255 transactionIds.append(txWithdraw.transactionId)
256 transactions.append(txWithdraw)
257
258 // create revenue
259 let revenue <- create Revenue(transactionIds: transactionIds, metadata: metadata, amount: totalAmount, userAddress: address)
260 let revenueId = revenue.revenueId
261 let old <- self.revenueData[revenueId] <- revenue
262 destroy old
263
264 // add user revenue
265 if !self.userRevenues.containsKey(address) {
266 self.userRevenues[address] = []
267 }
268 self.userRevenues[address]!.insert(at: 0, revenueId)
269
270 // update revenueId for transactions
271 for tx in transactions {
272 tx.updateRevenueId(revenueId)
273 }
274 }
275
276 access(all)fun createRequestRevenue(_ address: Address, amount: UFix64, metadata: {String:String}) {
277 // add event withdraw && update balance
278 let userBalance = self.userBalances[address] ?? 0.0
279 if Fix64(amount) > userBalance {
280 return
281 }
282 let txWithdraw = self.addTransaction(owner: address, amount: amount, royalties: nil, type: "withdraw", metadata: metadata, message: "振込申請")
283
284 // create revenue
285 let revenue <- create Revenue(transactionIds: [], metadata: metadata, amount: amount, userAddress: address)
286 let revenueId = revenue.revenueId
287 txWithdraw.updateRevenueId(revenueId)
288 let old <- self.revenueData[revenueId] <- revenue
289 destroy old
290
291 // add user revenue
292 if !self.userRevenues.containsKey(address) {
293 self.userRevenues[address] = []
294 }
295 self.userRevenues[address]!.insert(at: 0, revenueId)
296 }
297
298 // when admin transfer monney done
299 access(all)fun updateStatusRevenue(_ revenueId: UInt64, status: String) {
300 self.revenueData[revenueId]?.updateStatus(status)
301 }
302 }
303
304 access(self) fun getAdminRef(): &MikoSeaMarketHistoryV2.Admin {
305 return self.account.capabilities.borrow<&MikoSeaMarketHistoryV2.Admin>(MikoSeaMarketHistoryV2.AdminPublicPath)!
306 }
307
308 access(all)fun getById(_ id: UInt64): &Transaction? {
309 return self.getAdminRef().getTransactionById(id)
310 }
311
312 access(all)fun getUserTransactionIds(_ address: Address): &[UInt64] {
313 let res = self.getAdminRef().userTransactions[address]
314 return self.getAdminRef().userTransactions[address] ?? &[]
315 }
316
317 access(all)fun getUserBalance(_ address: Address): Fix64 {
318 return self.getAdminRef().userBalances[address] ?? 0.0
319 }
320
321 access(all)fun getRevenuesByAddress(_ address: Address): [&Revenue] {
322 let revenueIds = self.getAdminRef().userRevenues[address] ?? &[]
323 let res: [&Revenue] = []
324 for id in revenueIds {
325 if let revenue = self.getAdminRef().getRevenueById(id) {
326 res.append(revenue)
327 }
328 }
329 return res
330 }
331
332 access(all)fun getTotalAmount(_ transactionIds: [UInt64]): UFix64 {
333 var totalAmount = 0.0
334 for id in transactionIds {
335 if let tx = MikoSeaMarketHistoryV2.getById(id){
336 if tx.type != "withdraw" {
337 totalAmount = totalAmount + tx.amount
338 }
339 }
340 }
341 return totalAmount
342 }
343
344 //------------------------------------------------------------
345 // Initializer
346 //------------------------------------------------------------
347 init () {
348 let isMainnetAccount = self.account.address == Address(0x0b80e42aaab305f0)
349 if isMainnetAccount {
350 self.AdminStoragePath = /storage/MikoSeaMarketHistoryV2Admin
351 self.AdminPublicPath = /public/MikoSeaMarketHistoryV2Admin
352 } else {
353 self.AdminStoragePath = /storage/TestMikoSeaMarketHistoryV2Admin
354 self.AdminPublicPath = /public/TestMikoSeaMarketHistoryV2Admin
355 }
356 self.nextTransactionId = 1
357 self.nextRevenueId = 1
358
359 // Create a Collection resource and save it to storage
360 let collection <- create Admin()
361 self.account.storage.save(<-collection, to: self.AdminStoragePath)
362 // create a public capability for the collection
363 self.adminCap = self.account.capabilities.storage.issue<&{AdminPublicCollection}>(self.AdminStoragePath)
364 self.account.capabilities.publish(self.adminCap, at: self.AdminPublicPath)
365 }
366}
367