Smart Contract

MikoSeaMarketHistoryV2

A.0b80e42aaab305f0.MikoSeaMarketHistoryV2

Deployed

14h ago
Feb 28, 2026, 02:29:56 AM UTC

Dependents

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