├── .gitignore ├── Move.toml ├── README.md └── sources └── Marketplace.move /.gitignore: -------------------------------------------------------------------------------- 1 | # move 2 | *build 3 | build.log 4 | 5 | # aptos 6 | *.aptos/ 7 | -------------------------------------------------------------------------------- /Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Lotus" 3 | version = "0.1.0" 4 | license = "MIT" 5 | 6 | [addresses] 7 | lotus="_" 8 | std="0x1" 9 | aptos_framework="0x1" 10 | aptos_std="0x1" 11 | aptos_token="0x3" 12 | 13 | [dependencies] 14 | AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir="aptos-move/framework/aptos-framework/", rev="main" } 15 | AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir="aptos-move/framework/aptos-stdlib/", rev="main" } 16 | AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir="aptos-move/framework/aptos-token/", rev="main" } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aptos NFT Marketplace Contract 2 | 3 | Vexpy is Explore, collect and sell Arts & NFTs. Base on Aptos blockchain, inspired by Opensea. hatched by Tonotice. 4 | 5 | ## **Getting started** 6 | 7 | 1. Initialize the aptos configuration, if you don't already have it 8 | ```shell 9 | # create marketplace addr 10 | aptos init --profile marketplace 11 | ``` 12 | 13 | 2. Fund with faucet 14 | ```shell 15 | # faucet 16 | aptos account fund-with-faucet --account marketplace 17 | ``` 18 | 19 | 3. Compile contract 20 | ```shell 21 | aptos move compile --named-addresses lotus=marketplace 22 | ``` 23 | 24 | 4. Test Contract 25 | 26 | ```shell 27 | # test 28 | aptos move test --named-addresses lotus=marketplace 29 | ``` 30 | 31 | 5. Publish Contract to DevNet/TestNet 32 | ```shell 33 | 34 | # publish 35 | aptos move publish --included-artifacts none --named-addresses lotus=marketplace --profile=marketplace 36 | ``` 37 | 38 | 6. marketplace addr call contract `initial_market_script` 39 | 40 | ```shell 41 | aptos move run --function-id 'marketplace::Marketplace::initial_market_script' --profile=marketplace 42 | ``` 43 | 44 | ## **Features** 45 | 46 | - [x] initial market 47 | - [x] batch list token 48 | - [x] batch delist token 49 | - [x] batch buy token 50 | - [x] change token price 51 | - [x] make offer 52 | - [x] cancel offer 53 | - [x] accept offer 54 | - [x] claim accept token offer 55 | - [x] inital auction 56 | - [x] bid 57 | - [x] claim auction token(buyer) 58 | - [x] claim action coin(seller) 59 | 60 | ## **Contributing** 61 | 62 | Bug report or pull request are welcome. 63 | 64 | ## **Make a pull request** 65 | 66 | 1. Fork it 67 | 2. Create your feature branch (`git checkout -b my-new-feature`) 68 | 3. Commit your changes (`git commit -am 'Add some feature'`) 69 | 4. Push to the branch (`git push origin my-new-feature`) 70 | 5. Create new Pull Request 71 | 72 | Please write unit test with your code if necessary. 73 | 74 | ## **License** 75 | 76 | web3 is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 77 | -------------------------------------------------------------------------------- /sources/Marketplace.move: -------------------------------------------------------------------------------- 1 | module lotus::Marketplace { 2 | 3 | use aptos_framework::aptos_coin::AptosCoin; 4 | use aptos_framework::coin::{Self, Coin}; 5 | use aptos_framework::timestamp; 6 | use aptos_framework::account; 7 | use aptos_framework::account::SignerCapability; 8 | use aptos_framework::guid; 9 | 10 | use std::signer; 11 | use std::option::{Self, Option, some}; 12 | use std::string::String; 13 | use std::vector; 14 | use aptos_std::event::{Self, EventHandle}; 15 | use aptos_std::table::{Self, Table}; 16 | // use aptos_std::debug::{print}; 17 | 18 | use aptos_token::token::{Self, Token, TokenId}; 19 | 20 | // ERROR's 21 | const ERROR_INVALID_BUYER: u64 = 0; 22 | const ERROR_INSUFFICIENT_BID: u64 = 1; 23 | const ERROR_AUCTION_INACTIVE: u64 = 2; 24 | const ERROR_AUCTION_NOT_COMPLETE: u64 = 3; 25 | const ERROR_NOT_CLAIMABLE: u64 = 4; 26 | const ERROR_CLAIM_COINS_FIRST: u64 = 5; 27 | const ERROR_ALREADY_CLAIMED: u64 = 6; 28 | const ERROR_INVALID_SELLER: u64 = 7; 29 | const ERROR_ALREADY_LISTING: u64 = 8; 30 | const ERROR: u64 = 9; 31 | const ERROR_INVALID_OWNER:u64 = 10; 32 | const ERROR_NOT_ENOUGH_LENGTH:u64 = 11; 33 | const ERROR_ALREADY_OFFER:u64 = 12; 34 | const ERROR_EXPIRED_TIME_ACCEPT:u64 = 13; 35 | 36 | struct TokenCap has key { 37 | cap: SignerCapability, 38 | } 39 | 40 | struct MarketData has key { 41 | fee: u64, 42 | fund_address: address 43 | } 44 | 45 | // Set of data sent to the event stream during a listing of a token (for fixed price) 46 | struct ListEvent has drop, store { 47 | id: TokenId, 48 | amount: u64, 49 | timestamp: u64, 50 | listing_id: u64, 51 | seller_address: address, 52 | royalty_payee: address, 53 | royalty_numerator: u64, 54 | royalty_denominator: u64 55 | } 56 | 57 | struct DelistEvent has drop, store { 58 | id: TokenId, 59 | timestamp: u64, 60 | listing_id: u64, 61 | amount: u64, 62 | seller_address: address, 63 | } 64 | 65 | // Set of data sent to the event stream during a buying of a token (for fixed price) 66 | struct BuyEvent has drop, store { 67 | id: TokenId, 68 | timestamp: u64, 69 | listing_id: u64, 70 | seller_address: address, 71 | buyer_address: address 72 | } 73 | 74 | struct ListedItem has store { 75 | amount: u64, 76 | timestamp: u64, 77 | listing_id: u64, 78 | locked_token: Option, 79 | seller_address: address 80 | } 81 | 82 | struct ChangePriceEvent has drop, store { 83 | id: TokenId, 84 | amount: u64, 85 | listing_id: u64, 86 | timestamp: u64, 87 | seller_address: address, 88 | } 89 | 90 | struct ListedItemsData has key { 91 | listed_items: Table, 92 | listing_events: EventHandle, 93 | buying_events: EventHandle, 94 | delisting_events: EventHandle, 95 | changing_price_events: EventHandle 96 | } 97 | 98 | // auction mode 99 | struct AuctionItem has key, store { 100 | min_selling_price: u64, 101 | auction_id: u64, 102 | timestamp: u64, 103 | duration: u64, 104 | started_at: u64, 105 | current_bid: u64, 106 | current_bidder: address, 107 | locked_token: Option, 108 | owner_token: address 109 | } 110 | 111 | struct CoinEscrow has key { 112 | locked_coins: Table>, 113 | } 114 | 115 | // Set of data sent to the event stream during a auctioning a token 116 | struct AuctionEvent has store, drop { 117 | id: TokenId, 118 | timestamp: u64, 119 | auction_id: u64, 120 | min_selling_price: u64, 121 | duration: u64, 122 | started_at:u64, 123 | owner_address: address, 124 | royalty_payee: address, 125 | royalty_numerator: u64, 126 | royalty_denominator: u64 127 | } 128 | 129 | // Set of data sent to the event stream during a bidding for a token 130 | struct BidEvent has store, drop { 131 | id: TokenId, 132 | auction_id: u64, 133 | timestamp: u64, 134 | bid: u64, 135 | bidder_address: address 136 | } 137 | 138 | struct AuctionData has key { 139 | auction_items: Table, 140 | auction_events: EventHandle, 141 | bid_events: EventHandle, 142 | claim_coins_events: EventHandle, 143 | claim_token_events: EventHandle, 144 | } 145 | 146 | struct Offerer has store, drop { 147 | offer_address: address, 148 | timestamp: u64, 149 | amount: u64, 150 | offer_id: u64, 151 | started_at: u64, // seconds enough 152 | duration: u64 // seconds enough 153 | } 154 | 155 | struct ClaimCoinsEvent has store, drop { 156 | id: TokenId, 157 | auction_id: u64, 158 | timestamp: u64, 159 | owner_token: address 160 | } 161 | 162 | struct ClaimTokenEvent has store, drop { 163 | id: TokenId, 164 | auction_id: u64, 165 | timestamp: u64, 166 | bidder_address: address 167 | } 168 | 169 | // offer mode 170 | struct OfferItem has key, store { 171 | offerers: vector, 172 | token: Option, 173 | claimable_token_address: address, 174 | claimable_offer_id: u64, 175 | accept_address: address 176 | } 177 | 178 | struct CoinEscrowOffer has key { 179 | locked_coins: Table> 180 | } 181 | 182 | struct TokenEscrowOffer has key { 183 | locked_tokens: Table 184 | } 185 | 186 | struct OfferEvent has store, drop { 187 | id: TokenId, 188 | offer_id: u64, 189 | timestamp: u64, 190 | offerer: address, 191 | amount: u64, 192 | started_at: u64, 193 | duration: u64, 194 | royalty_payee: address, 195 | royalty_numerator: u64, 196 | royalty_denominator: u64 197 | } 198 | 199 | struct AcceptOfferEvent has store, drop { 200 | id: TokenId, 201 | offer_id: u64, 202 | timestamp: u64, 203 | offerer: address, 204 | amount: u64, 205 | owner_token: address 206 | } 207 | 208 | struct ClaimTokenOffer has store, drop { 209 | id: TokenId, 210 | timestamp: u64, 211 | claimer: address, 212 | } 213 | 214 | struct CancelOfferEvent has store, drop { 215 | id: TokenId, 216 | offer_id: u64, 217 | timestamp: u64, 218 | offerer: address 219 | } 220 | 221 | struct OfferData has key, store { 222 | offer_items: Table, 223 | offer_events: EventHandle, 224 | accept_offer_events: EventHandle, 225 | claim_token_offer_events: EventHandle, 226 | cancel_offer_events: EventHandle 227 | } 228 | 229 | // publish contract && initial resource 230 | public entry fun initial_market_script(sender: &signer) { 231 | let sender_addr = signer::address_of(sender); 232 | let (market_signer, market_cap) = account::create_resource_account(sender, x"01"); 233 | let market_signer_address = signer::address_of(&market_signer); 234 | 235 | assert!(sender_addr == @lotus, ERROR_INVALID_OWNER); 236 | 237 | if(!exists(@lotus)){ 238 | move_to(sender, TokenCap { 239 | cap: market_cap 240 | }) 241 | }; 242 | 243 | if (!exists(market_signer_address)){ 244 | move_to(&market_signer, MarketData { 245 | fee: 200, 246 | fund_address: sender_addr 247 | }) 248 | }; 249 | 250 | if (!exists(market_signer_address)) { 251 | move_to(&market_signer, ListedItemsData { 252 | listed_items:table::new(), 253 | listing_events: account::new_event_handle(&market_signer), 254 | buying_events: account::new_event_handle(&market_signer), 255 | delisting_events: account::new_event_handle(&market_signer), 256 | changing_price_events: account::new_event_handle(&market_signer) 257 | }); 258 | }; 259 | 260 | if (!exists(market_signer_address)) { 261 | move_to(&market_signer, AuctionData { 262 | auction_items: table::new(), 263 | auction_events: account::new_event_handle(&market_signer), 264 | bid_events: account::new_event_handle(&market_signer), 265 | claim_coins_events: account::new_event_handle(&market_signer), 266 | claim_token_events: account::new_event_handle(&market_signer), 267 | }); 268 | }; 269 | 270 | if (!exists(market_signer_address)){ 271 | move_to(&market_signer, OfferData { 272 | offer_items: table::new(), 273 | offer_events: account::new_event_handle(&market_signer), 274 | accept_offer_events: account::new_event_handle(&market_signer), 275 | claim_token_offer_events: account::new_event_handle(&market_signer), 276 | cancel_offer_events: account::new_event_handle(&market_signer) 277 | }) 278 | } 279 | } 280 | 281 | fun list_token( 282 | sender: &signer, 283 | token_id: TokenId, 284 | price: u64, 285 | ) acquires ListedItemsData, TokenCap { 286 | let sender_addr = signer::address_of(sender); 287 | let market_cap = &borrow_global(@lotus).cap; 288 | let market_signer = &account::create_signer_with_capability(market_cap); 289 | let market_signer_address = signer::address_of(market_signer); 290 | 291 | let token = token::withdraw_token(sender, token_id, 1); 292 | let listed_items_data = borrow_global_mut(market_signer_address); 293 | let listed_items = &mut listed_items_data.listed_items; 294 | 295 | let royalty = token::get_royalty(token_id); 296 | let royalty_payee = token::get_royalty_payee(&royalty); 297 | let royalty_numerator = token::get_royalty_numerator(&royalty); 298 | let royalty_denominator = token::get_royalty_denominator(&royalty); 299 | 300 | // get unique id 301 | let guid = account::create_guid(market_signer); 302 | let listing_id = guid::creation_num(&guid); 303 | 304 | event::emit_event( 305 | &mut listed_items_data.listing_events, 306 | ListEvent { 307 | id: token_id, 308 | amount: price, 309 | seller_address: sender_addr, 310 | timestamp: timestamp::now_seconds(), 311 | listing_id, 312 | royalty_payee, 313 | royalty_numerator, 314 | royalty_denominator 315 | }, 316 | ); 317 | 318 | table::add(listed_items, token_id, ListedItem { 319 | amount: price, 320 | listing_id, 321 | timestamp: timestamp::now_seconds(), 322 | locked_token: option::some(token), 323 | seller_address: sender_addr 324 | }) 325 | 326 | } 327 | 328 | // entry batch list script by token owners 329 | public entry fun batch_list_script( 330 | sender: &signer, 331 | creators: vector
, 332 | collection_names: vector, 333 | token_names: vector, 334 | property_versions: vector, 335 | prices: vector 336 | ) acquires ListedItemsData, TokenCap { 337 | 338 | let length_creators = vector::length(&creators); 339 | let length_collections = vector::length(&collection_names); 340 | let length_token_names = vector::length(&token_names); 341 | let length_prices = vector::length(&prices); 342 | let length_properties = vector::length(&property_versions); 343 | 344 | assert!(length_collections == length_creators 345 | && length_creators == length_token_names 346 | && length_token_names == length_prices 347 | && length_prices == length_properties, ERROR_NOT_ENOUGH_LENGTH); 348 | 349 | let i = length_properties; 350 | 351 | while (i > 0) { 352 | let creator = vector::pop_back(&mut creators); 353 | let token_name = vector::pop_back(&mut token_names); 354 | let collection_name = vector::pop_back(&mut collection_names); 355 | let price = vector::pop_back(&mut prices); 356 | let property_version = vector::pop_back(&mut property_versions); 357 | 358 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 359 | 360 | list_token(sender, token_id, price); 361 | 362 | i = i - 1; 363 | } 364 | } 365 | 366 | // delist token 367 | fun delist_token( 368 | sender: &signer, 369 | token_id: TokenId 370 | ) acquires ListedItemsData, TokenCap { 371 | let sender_addr = signer::address_of(sender); 372 | let market_cap = &borrow_global(@lotus).cap; 373 | let market_signer = &account::create_signer_with_capability(market_cap); 374 | let market_signer_address = signer::address_of(market_signer); 375 | 376 | let listed_items_data = borrow_global_mut(market_signer_address); 377 | let listed_items = &mut listed_items_data.listed_items; 378 | let listed_item = table::borrow_mut(listed_items, token_id); 379 | 380 | event::emit_event( 381 | &mut listed_items_data.delisting_events, 382 | DelistEvent { 383 | id: token_id, 384 | amount: listed_item.amount, 385 | listing_id: listed_item.listing_id, 386 | timestamp: timestamp::now_seconds(), 387 | seller_address: sender_addr 388 | }, 389 | ); 390 | 391 | let token = option::extract(&mut listed_item.locked_token); 392 | token::deposit_token(sender, token); 393 | 394 | let ListedItem {amount: _, timestamp: _, locked_token, seller_address: _, listing_id: _} = table::remove(listed_items, token_id); 395 | option::destroy_none(locked_token); 396 | } 397 | 398 | public entry fun batch_delist_script( 399 | sender: &signer, 400 | creators: vector
, 401 | collection_names: vector, 402 | token_names: vector, 403 | property_versions: vector 404 | ) acquires ListedItemsData, TokenCap { 405 | 406 | let length_creators = vector::length(&creators); 407 | let length_collections = vector::length(&collection_names); 408 | let length_token_names = vector::length(&token_names); 409 | let length_properties = vector::length(&property_versions); 410 | 411 | assert!(length_collections == length_creators 412 | && length_creators == length_token_names 413 | && length_token_names == length_properties, ERROR_NOT_ENOUGH_LENGTH); 414 | 415 | let i = length_token_names; 416 | 417 | while (i > 0) { 418 | let creator = vector::pop_back(&mut creators); 419 | let collection_name = vector::pop_back(&mut collection_names); 420 | let token_name = vector::pop_back(&mut token_names); 421 | let property_version = vector::pop_back(&mut property_versions); 422 | 423 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 424 | delist_token(sender, token_id); 425 | 426 | i = i - 1; 427 | } 428 | } 429 | 430 | // part of the fixed price sale flow 431 | fun buy_token( 432 | sender: &signer, 433 | token_id: TokenId, 434 | ) acquires ListedItemsData, TokenCap, MarketData { 435 | let sender_addr = signer::address_of(sender); 436 | 437 | let market_cap = &borrow_global(@lotus).cap; 438 | let market_signer = &account::create_signer_with_capability(market_cap); 439 | let market_signer_address = signer::address_of(market_signer); 440 | let market_data = borrow_global_mut(market_signer_address); 441 | 442 | let listed_items_data = borrow_global_mut(market_signer_address); 443 | let listed_items = &mut listed_items_data.listed_items; 444 | let listed_item = table::borrow_mut(listed_items, token_id); 445 | let seller = listed_item.seller_address; 446 | 447 | assert!(sender_addr != seller, ERROR_INVALID_BUYER); 448 | 449 | let royalty = token::get_royalty(token_id); 450 | let royalty_payee = token::get_royalty_payee(&royalty); 451 | let royalty_numerator = token::get_royalty_numerator(&royalty); 452 | let royalty_denominator = token::get_royalty_denominator(&royalty); 453 | 454 | let _fee_royalty: u64 = 0; 455 | 456 | if (royalty_denominator == 0){ 457 | _fee_royalty = 0; 458 | } else { 459 | _fee_royalty = royalty_numerator * listed_item.amount / royalty_denominator; 460 | }; 461 | 462 | let fee_listing = listed_item.amount * market_data.fee / 10000; 463 | let sub_amount = listed_item.amount - fee_listing - _fee_royalty; 464 | 465 | // print 466 | // print(&listed_item.amount); 467 | // print(&market_data.fee); 468 | // print(&fee_listing); 469 | // print(&sub_amount); 470 | 471 | if (_fee_royalty > 0) { 472 | coin::transfer(sender, royalty_payee, _fee_royalty); 473 | }; 474 | 475 | if (fee_listing > 0) { 476 | coin::transfer(sender, market_data.fund_address, fee_listing); 477 | }; 478 | 479 | coin::transfer(sender, seller, sub_amount); 480 | 481 | let token = option::extract(&mut listed_item.locked_token); 482 | token::deposit_token(sender, token); 483 | 484 | event::emit_event( 485 | &mut listed_items_data.buying_events, 486 | BuyEvent { 487 | id: token_id, 488 | listing_id: listed_item.listing_id, 489 | seller_address: listed_item.seller_address, 490 | timestamp: timestamp::now_seconds(), 491 | buyer_address: sender_addr 492 | }, 493 | ); 494 | 495 | let ListedItem {amount: _, timestamp: _, locked_token, seller_address: _, listing_id: _} = table::remove(listed_items, token_id); 496 | option::destroy_none(locked_token); 497 | } 498 | 499 | // batch buy script 500 | public entry fun batch_buy_script( 501 | sender: &signer, 502 | creators: vector
, 503 | collection_names: vector, 504 | token_names: vector, 505 | property_versions: vector 506 | ) acquires ListedItemsData, TokenCap, MarketData { 507 | let length_creators = vector::length(&creators); 508 | let length_collections = vector::length(&collection_names); 509 | let length_token_names = vector::length(&token_names); 510 | let length_properties = vector::length(&property_versions); 511 | 512 | assert!(length_collections == length_creators 513 | && length_creators == length_token_names 514 | && length_token_names == length_properties, ERROR_NOT_ENOUGH_LENGTH); 515 | 516 | let i = length_token_names; 517 | 518 | while (i > 0){ 519 | let creator = vector::pop_back(&mut creators); 520 | let collection_name = vector::pop_back(&mut collection_names); 521 | let token_name = vector::pop_back(&mut token_names); 522 | let property_version = vector::pop_back(&mut property_versions); 523 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 524 | 525 | buy_token(sender, token_id); 526 | 527 | i = i - 1; 528 | } 529 | } 530 | 531 | // offer mode 532 | // make offer by listing token (by buyer) 533 | public entry fun make_offer_script( 534 | sender: &signer, 535 | creator: address, 536 | collection_name: String, 537 | token_name: String, 538 | property_version: u64, 539 | offer_amount: u64, 540 | duration: u64 541 | ) acquires TokenCap, OfferData, CoinEscrowOffer { 542 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 543 | make_offer(sender, token_id, offer_amount, duration) 544 | } 545 | 546 | // make offer for listing token 547 | fun make_offer( 548 | sender: &signer, 549 | token_id: TokenId, 550 | offer_amount: u64, 551 | duration: u64 552 | ) acquires TokenCap, OfferData, CoinEscrowOffer { 553 | let sender_addr = signer::address_of(sender); 554 | let market_cap = &borrow_global(@lotus).cap; 555 | let market_signer = &account::create_signer_with_capability(market_cap); 556 | let market_signer_address = signer::address_of(market_signer); 557 | 558 | let offer_data = borrow_global_mut(market_signer_address); 559 | let offer_items = &mut offer_data.offer_items; 560 | let is_contain = table::contains(offer_items, token_id); 561 | 562 | let royalty = token::get_royalty(token_id); 563 | let royalty_payee = token::get_royalty_payee(&royalty); 564 | let royalty_numerator = token::get_royalty_numerator(&royalty); 565 | let royalty_denominator = token::get_royalty_denominator(&royalty); 566 | 567 | if(!exists>(sender_addr)){ 568 | move_to(sender, CoinEscrowOffer { 569 | locked_coins: table::new>() 570 | }) 571 | }; 572 | 573 | // unique offer id 574 | let guid = account::create_guid(market_signer); 575 | let offer_id = guid::creation_num(&guid); 576 | 577 | let started_at = timestamp::now_seconds(); 578 | 579 | if (!is_contain){ 580 | let offerer = Offerer { 581 | offer_address: sender_addr, 582 | timestamp: timestamp::now_seconds(), 583 | amount: offer_amount, 584 | offer_id, 585 | started_at, 586 | duration 587 | }; 588 | 589 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 590 | let coins = coin::withdraw(sender, offer_amount); 591 | table::add(locked_coins, token_id, coins); 592 | 593 | let offerers = vector::empty(); 594 | 595 | vector::push_back(&mut offerers, offerer); 596 | table::add(offer_items, token_id, OfferItem { 597 | offerers, 598 | token:option::none(), 599 | claimable_token_address: @0x0, 600 | claimable_offer_id: 0, 601 | accept_address: @0x0 602 | }); 603 | 604 | } else { 605 | //1. check sender has been offered 606 | let already_offered = false; 607 | let index = 0; 608 | let offer_item = table::borrow_mut(offer_items, token_id); 609 | let offerers = &mut offer_item.offerers; 610 | let i = vector::length(offerers); 611 | 612 | while (i > 0){ 613 | let offerer = vector::borrow(offerers, i - 1); 614 | if (offerer.offer_address == sender_addr) { 615 | already_offered = true; 616 | index = i - 1; 617 | break 618 | }; 619 | 620 | i = i - 1; 621 | }; 622 | if (already_offered) { 623 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 624 | let coins_refund = table::remove(locked_coins, token_id); 625 | coin::deposit(sender_addr, coins_refund); 626 | // locked new amount to offer 627 | let coins = coin::withdraw(sender, offer_amount); 628 | table::add(locked_coins, token_id, coins); 629 | 630 | // update offer of sender 631 | let _offerer = vector::borrow_mut(offerers, index); 632 | 633 | // update offer_id 634 | offer_id = _offerer.offer_id; 635 | 636 | _offerer.amount = offer_amount; 637 | _offerer.started_at = started_at; 638 | _offerer.duration = duration; 639 | 640 | } else { 641 | //2. locked_coins offered 642 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 643 | let coins = coin::withdraw(sender, offer_amount); 644 | table::add(locked_coins, token_id, coins); 645 | 646 | //3. add to offerers 647 | let offerer = Offerer { 648 | offer_address: sender_addr, 649 | timestamp: timestamp::now_seconds(), 650 | amount: offer_amount, 651 | offer_id, 652 | started_at, 653 | duration 654 | }; 655 | 656 | vector::push_back(offerers, offerer); 657 | } 658 | }; 659 | 660 | event::emit_event(&mut offer_data.offer_events, OfferEvent { 661 | id: token_id, 662 | offerer: sender_addr, 663 | amount: offer_amount, 664 | timestamp: timestamp::now_seconds(), 665 | offer_id, 666 | started_at, 667 | duration, 668 | royalty_payee, 669 | royalty_numerator, 670 | royalty_denominator 671 | }) 672 | } 673 | 674 | // accept offer by token owner. 675 | public entry fun accept_offer_script( 676 | sender: &signer, 677 | creator: address, 678 | collection_name: String, 679 | token_name: String, 680 | property_version: u64, 681 | offerer: address, 682 | ) acquires TokenCap, MarketData, OfferData, CoinEscrowOffer, TokenEscrowOffer { 683 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 684 | accept_offer(sender, token_id, offerer) 685 | } 686 | 687 | fun accept_offer( 688 | sender: &signer, 689 | token_id: TokenId, 690 | offerer: address, 691 | ) acquires TokenCap, MarketData, OfferData, CoinEscrowOffer, TokenEscrowOffer { 692 | let sender_addr = signer::address_of(sender); 693 | 694 | if (!exists(sender_addr)){ 695 | move_to(sender, TokenEscrowOffer{ 696 | locked_tokens: table::new() 697 | }) 698 | }; 699 | 700 | let market_cap = &borrow_global(@lotus).cap; 701 | let market_signer = &account::create_signer_with_capability(market_cap); 702 | let market_signer_address = signer::address_of(market_signer); 703 | 704 | let market_data = borrow_global_mut(market_signer_address); 705 | let offer_data = borrow_global_mut(market_signer_address); 706 | // 707 | let offer_items = &mut offer_data.offer_items; 708 | 709 | assert!(table::contains(offer_items, token_id), ERROR); 710 | 711 | let offer_item = table::borrow_mut(offer_items, token_id); 712 | let offerers = &mut offer_item.offerers; 713 | // 714 | let royalty = token::get_royalty(token_id); 715 | let royalty_payee = token::get_royalty_payee(&royalty); 716 | let royalty_numerator = token::get_royalty_numerator(&royalty); 717 | let royalty_denominator = token::get_royalty_denominator(&royalty); 718 | 719 | // check time can accept 720 | let can_accept = false; 721 | // check offerer 722 | let offer_id: u64 = 0; 723 | 724 | let offerers_length = vector::length(offerers); 725 | while (offerers_length > 0){ 726 | let _offerer = vector::borrow(offerers, offerers_length - 1); 727 | let time_can_accept = _offerer.duration + _offerer.started_at; 728 | 729 | if(time_can_accept >= timestamp::now_seconds() && _offerer.offer_address == offerer){ 730 | can_accept = true; 731 | offer_id = _offerer.offer_id; 732 | break 733 | }; 734 | 735 | offerers_length = offerers_length - 1; 736 | }; 737 | 738 | assert!(can_accept, ERROR_EXPIRED_TIME_ACCEPT); 739 | // 740 | let locked_coins = &mut borrow_global_mut>(offerer).locked_coins; 741 | assert!(table::contains(locked_coins, token_id), ERROR_ALREADY_CLAIMED); 742 | // 743 | let coins = table::remove(locked_coins, token_id); 744 | let amount = coin::value(&coins); 745 | 746 | let fee = market_data.fee * amount / 10000; 747 | let royalty_fee = amount * royalty_numerator / royalty_denominator; 748 | 749 | if (fee > 0) { 750 | coin::deposit(market_data.fund_address, coin::extract(&mut coins, fee)); 751 | }; 752 | 753 | if (royalty_fee > 0) { 754 | coin::deposit(royalty_payee, coin::extract(&mut coins, royalty_fee)); 755 | }; 756 | 757 | coin::deposit(sender_addr, coins); 758 | 759 | // update offer_item 760 | offer_item.claimable_token_address = offerer; 761 | offer_item.claimable_offer_id = offer_id; 762 | option::destroy_none(offer_item.token); 763 | offer_item.token = some(token_id); 764 | offer_item.accept_address = sender_addr; 765 | 766 | // lock token offer 767 | let token = token::withdraw_token(sender, token_id, 1); 768 | let locked_token_offers = &mut borrow_global_mut(sender_addr).locked_tokens; 769 | 770 | table::add(locked_token_offers, token_id, token); 771 | 772 | event::emit_event(&mut offer_data.accept_offer_events, AcceptOfferEvent { 773 | id: token_id, 774 | timestamp: timestamp::now_seconds(), 775 | offerer, 776 | offer_id, 777 | amount, 778 | owner_token: sender_addr 779 | }) 780 | 781 | } 782 | 783 | // cancel offer by buyer 784 | public entry fun cancel_offer_script( 785 | sender: &signer, 786 | creator: address, 787 | collection_name: String, 788 | token_name: String, 789 | property_version: u64 790 | ) acquires TokenCap, OfferData, CoinEscrowOffer { 791 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 792 | cancel_offer(sender, token_id) 793 | } 794 | 795 | fun cancel_offer( 796 | sender: &signer, 797 | token_id: TokenId, 798 | ) acquires TokenCap, OfferData, CoinEscrowOffer { 799 | let sender_addr = signer::address_of(sender); 800 | 801 | let market_cap = &borrow_global(@lotus).cap; 802 | let market_signer = &account::create_signer_with_capability(market_cap); 803 | let market_signer_address = signer::address_of(market_signer); 804 | let offer_data = borrow_global_mut(market_signer_address); 805 | 806 | let offer_items = &mut offer_data.offer_items; 807 | assert!(table::contains(offer_items, token_id), ERROR); 808 | 809 | let offer_item = table::borrow_mut(offer_items, token_id); 810 | let offerers = &mut offer_item.offerers; 811 | 812 | // remove offer from offerers 813 | let current_offer_id: u64 = 0; 814 | 815 | let i = vector::length(offerers); 816 | while (i > 0) { 817 | let _offerer = vector::borrow_mut(offerers,i - 1); 818 | if (_offerer.offer_address == sender_addr){ 819 | current_offer_id = _offerer.offer_id; 820 | vector::remove(offerers, i - 1); 821 | break 822 | }; 823 | 824 | i = i -1; 825 | }; 826 | 827 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 828 | let coins = table::remove(locked_coins, token_id); 829 | coin::deposit(sender_addr, coins); 830 | 831 | event::emit_event(&mut offer_data.cancel_offer_events, CancelOfferEvent { 832 | id: token_id, 833 | offer_id: current_offer_id, 834 | timestamp: timestamp::now_seconds(), 835 | offerer: sender_addr 836 | }) 837 | } 838 | 839 | // claim offer token by buyer(when seller accept offer) 840 | public entry fun claim_offer_token_script( 841 | sender: &signer, 842 | creator: address, 843 | collection_name: String, 844 | token_name: String, 845 | property_version: u64 846 | ) acquires TokenCap, OfferData, TokenEscrowOffer { 847 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 848 | claim_offer_token(sender, token_id); 849 | } 850 | 851 | fun claim_offer_token( 852 | sender: &signer, 853 | token_id: TokenId, 854 | ) acquires TokenCap, OfferData, TokenEscrowOffer { 855 | let sender_addr = signer::address_of(sender); 856 | 857 | let market_cap = &borrow_global(@lotus).cap; 858 | let market_signer = &account::create_signer_with_capability(market_cap); 859 | let market_signer_address = signer::address_of(market_signer); 860 | let offer_data = borrow_global_mut(market_signer_address); 861 | 862 | let offer_items = &mut offer_data.offer_items; 863 | assert!(table::contains(offer_items, token_id), ERROR); 864 | 865 | let offer_item = table::borrow_mut(offer_items, token_id); 866 | let token_id_claim = option::extract(&mut offer_item.token); 867 | 868 | assert!(offer_item.claimable_token_address == sender_addr, ERROR_NOT_CLAIMABLE); 869 | assert!(token_id == token_id_claim, ERROR_NOT_CLAIMABLE); 870 | 871 | // claim token 872 | let locked_token_offers = &mut borrow_global_mut(offer_item.accept_address).locked_tokens; 873 | 874 | assert!(table::contains(locked_token_offers, token_id), ERROR_ALREADY_CLAIMED); 875 | 876 | let token = table::remove(locked_token_offers, token_id); 877 | token::deposit_token(sender, token); 878 | 879 | event::emit_event(&mut offer_data.claim_token_offer_events, ClaimTokenOffer { 880 | id: token_id, 881 | timestamp: timestamp::now_seconds(), 882 | claimer: sender_addr 883 | }) 884 | } 885 | 886 | fun initial_auction( 887 | sender: &signer, 888 | token_id: TokenId, 889 | min_selling_price: u64, 890 | duration: u64, 891 | ) acquires AuctionData, TokenCap { 892 | let sender_addr = signer::address_of(sender); 893 | 894 | let started_at = timestamp::now_seconds(); 895 | let token = token::withdraw_token(sender, token_id, 1); 896 | 897 | let market_cap = &borrow_global(@lotus).cap; 898 | let market_signer = &account::create_signer_with_capability(market_cap); 899 | let market_signer_address = signer::address_of(market_signer); 900 | 901 | let auction_data = borrow_global_mut(market_signer_address); 902 | let auction_items = &mut auction_data.auction_items; 903 | 904 | // if auction_items still contain token_id, this means that when sender_addr last auctioned this token, 905 | // they did not claim the coins from the highest bidder 906 | // sender_addr has received the same token somehow but has not claimed the coins from the initial auction 907 | assert!(!table::contains(auction_items, token_id), ERROR_CLAIM_COINS_FIRST); 908 | 909 | // create new auction item 910 | let guid = account::create_guid(market_signer); 911 | let auction_id = guid::creation_num(&guid); 912 | 913 | let royalty = token::get_royalty(token_id); 914 | let royalty_payee = token::get_royalty_payee(&royalty); 915 | let royalty_numerator = token::get_royalty_numerator(&royalty); 916 | let royalty_denominator = token::get_royalty_denominator(&royalty); 917 | 918 | event::emit_event( 919 | &mut auction_data.auction_events, 920 | AuctionEvent { 921 | id: token_id, 922 | min_selling_price, 923 | timestamp: timestamp::now_seconds(), 924 | auction_id, 925 | started_at, 926 | duration, 927 | owner_address: sender_addr, 928 | royalty_payee, 929 | royalty_numerator, 930 | royalty_denominator 931 | }, 932 | ); 933 | 934 | table::add(auction_items, token_id, AuctionItem { 935 | min_selling_price, 936 | duration, 937 | started_at, 938 | auction_id, 939 | current_bid: min_selling_price - 1, 940 | current_bidder: sender_addr, 941 | locked_token: some(token), 942 | owner_token: sender_addr, 943 | timestamp: timestamp::now_seconds() 944 | }) 945 | 946 | } 947 | 948 | // auction mode 949 | // inital by token owner 950 | public entry fun initial_auction_script( 951 | sender: &signer, 952 | creator: address, 953 | collection_name: String, 954 | token_name: String, 955 | property_version: u64, 956 | min_selling_price: u64, 957 | duration: u64, 958 | ) acquires AuctionData, TokenCap { 959 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 960 | initial_auction(sender, token_id, min_selling_price, duration) 961 | } 962 | 963 | fun is_auction_active(started_at: u64, duration: u64): bool { 964 | let current_time = timestamp::now_seconds(); 965 | current_time <= started_at + duration && current_time >= started_at 966 | } 967 | 968 | fun is_auction_complete(started_at: u64, duration: u64): bool { 969 | let current_time = timestamp::now_seconds(); 970 | current_time > started_at + duration 971 | } 972 | 973 | fun get_market_signer_address(market_addr: address) : address acquires TokenCap { 974 | let market_cap = &borrow_global(market_addr).cap; 975 | let market_signer = &account::create_signer_with_capability(market_cap); 976 | let market_signer_address = signer::address_of(market_signer); 977 | 978 | market_signer_address 979 | } 980 | 981 | // bid by bidder 982 | public entry fun bid_script( 983 | sender: &signer, 984 | creator: address, 985 | collection_name: String, 986 | token_name: String, 987 | property_version: u64, 988 | bid: u64, 989 | ) acquires CoinEscrow, AuctionData, TokenCap { 990 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 991 | bid(sender, token_id, bid) 992 | } 993 | 994 | fun bid( 995 | sender: &signer, 996 | token_id: TokenId, 997 | bid: u64, 998 | ) acquires CoinEscrow, AuctionData, TokenCap { 999 | let sender_addr = signer::address_of(sender); 1000 | let market_cap = &borrow_global(@lotus).cap; 1001 | let market_signer = &account::create_signer_with_capability(market_cap); 1002 | let market_signer_address = signer::address_of(market_signer); 1003 | 1004 | let auction_data = borrow_global_mut(market_signer_address); 1005 | let auction_items = &mut auction_data.auction_items; 1006 | let auction_item = table::borrow_mut(auction_items, token_id); 1007 | let seller = auction_item.owner_token; 1008 | 1009 | assert!(sender_addr != seller, ERROR_INVALID_BUYER); 1010 | assert!(is_auction_active(auction_item.started_at, auction_item.duration), ERROR_AUCTION_INACTIVE); 1011 | assert!(bid > auction_item.current_bid, ERROR_INSUFFICIENT_BID); 1012 | 1013 | if (!exists>(sender_addr)) { 1014 | move_to(sender, CoinEscrow { 1015 | locked_coins: table::new>() 1016 | }); 1017 | }; 1018 | 1019 | // refund coin to bidder prev 1020 | if (auction_item.current_bidder != seller) { 1021 | let current_bidder_locked_coins = &mut borrow_global_mut>(auction_item.current_bidder).locked_coins; 1022 | let coins = table::remove(current_bidder_locked_coins, token_id); 1023 | coin::deposit(auction_item.current_bidder, coins); 1024 | }; 1025 | 1026 | event::emit_event( 1027 | &mut auction_data.bid_events, 1028 | BidEvent { 1029 | id: token_id, 1030 | auction_id: auction_item.auction_id, 1031 | bid, 1032 | timestamp: timestamp::now_seconds(), 1033 | bidder_address: sender_addr 1034 | }, 1035 | ); 1036 | 1037 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 1038 | let coins = coin::withdraw(sender, bid); 1039 | table::add(locked_coins, token_id, coins); 1040 | 1041 | auction_item.current_bidder = sender_addr; 1042 | auction_item.current_bid = bid; 1043 | } 1044 | 1045 | public entry fun claim_auction_token_script( 1046 | sender: &signer, 1047 | creator: address, 1048 | collection_name: String, 1049 | token_name: String, 1050 | property_version: u64 1051 | ) acquires AuctionData, TokenCap, MarketData, CoinEscrow { 1052 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 1053 | claim_auction_token(sender, token_id) 1054 | } 1055 | 1056 | // claim token by buyer 1057 | fun claim_auction_token( 1058 | sender: &signer, 1059 | token_id: TokenId 1060 | ) acquires AuctionData, TokenCap, MarketData, CoinEscrow { 1061 | let sender_addr = signer::address_of(sender); 1062 | 1063 | let market_cap = &borrow_global(@lotus).cap; 1064 | let market_signer = &account::create_signer_with_capability(market_cap); 1065 | let market_signer_address = signer::address_of(market_signer); 1066 | 1067 | let market_data = borrow_global(market_signer_address); 1068 | let auction_data = borrow_global_mut(market_signer_address); 1069 | let auction_items = &mut auction_data.auction_items; 1070 | let auction_item = table::borrow_mut(auction_items, token_id); 1071 | 1072 | if (!exists>(sender_addr)) { 1073 | move_to(sender, CoinEscrow { 1074 | locked_coins: table::new>() 1075 | }); 1076 | }; 1077 | 1078 | assert!(is_auction_complete(auction_item.started_at, auction_item.duration), ERROR_AUCTION_NOT_COMPLETE); 1079 | assert!(sender_addr == auction_item.current_bidder, ERROR_NOT_CLAIMABLE); 1080 | 1081 | event::emit_event( 1082 | &mut auction_data.claim_token_events, 1083 | ClaimTokenEvent { 1084 | id: token_id, 1085 | auction_id: auction_item.auction_id, 1086 | timestamp: timestamp::now_seconds(), 1087 | bidder_address: sender_addr 1088 | }, 1089 | ); 1090 | 1091 | let token = option::extract(&mut auction_item.locked_token); 1092 | token::deposit_token(sender, token); 1093 | 1094 | let royalty = token::get_royalty(token_id); 1095 | let royalty_payee = token::get_royalty_payee(&royalty); 1096 | let royalty_numerator = token::get_royalty_numerator(&royalty); 1097 | let royalty_denominator = token::get_royalty_denominator(&royalty); 1098 | 1099 | // the auction item can be removed from the auctiondata of the seller once the token and coins are claimed 1100 | let locked_coins = &mut borrow_global_mut>(sender_addr).locked_coins; 1101 | // deposit the locked coins to the seller's sender if they have not claimed yet 1102 | if (table::contains(locked_coins, token_id)){ 1103 | event::emit_event( 1104 | &mut auction_data.claim_coins_events, 1105 | ClaimCoinsEvent { 1106 | id: token_id, 1107 | auction_id: auction_item.auction_id, 1108 | timestamp: timestamp::now_seconds(), 1109 | owner_token: auction_item.owner_token 1110 | }, 1111 | ); 1112 | 1113 | let coins = table::remove(locked_coins, token_id); 1114 | let amount = coin::value(&coins); 1115 | let fee = market_data.fee * amount / 10000; 1116 | let royalty_fee = amount * royalty_numerator / royalty_denominator; 1117 | 1118 | if (fee > 0) { 1119 | coin::deposit(market_data.fund_address, coin::extract(&mut coins, fee)); 1120 | }; 1121 | 1122 | if (royalty_fee > 0) { 1123 | coin::deposit(royalty_payee, coin::extract(&mut coins, royalty_fee)); 1124 | }; 1125 | 1126 | let seller = auction_item.owner_token; 1127 | coin::deposit(seller, coins); 1128 | }; 1129 | 1130 | // remove aution data when token has been claimed 1131 | let AuctionItem { min_selling_price: _, timestamp: _, duration: _, started_at: _, current_bid: _, current_bidder: _, auction_id: _, locked_token, owner_token:_ } = table::remove(auction_items, token_id); 1132 | option::destroy_none(locked_token); 1133 | } 1134 | 1135 | public entry fun claim_auction_coin_script( 1136 | sender: &signer, 1137 | creator: address, 1138 | collection_name: String, 1139 | token_name: String, 1140 | property_version: u64 1141 | ) acquires CoinEscrow, AuctionData, TokenCap, MarketData { 1142 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_version); 1143 | claim_auction_coin(sender, token_id) 1144 | } 1145 | 1146 | // claim coin by token owner 1147 | fun claim_auction_coin( 1148 | sender: &signer, 1149 | token_id: TokenId, 1150 | ) acquires CoinEscrow, AuctionData, TokenCap, MarketData { 1151 | let sender_addr = signer::address_of(sender); 1152 | let market_cap = &borrow_global(@lotus).cap; 1153 | let market_signer = &account::create_signer_with_capability(market_cap); 1154 | let market_signer_address = signer::address_of(market_signer); 1155 | 1156 | let market_data = borrow_global(market_signer_address); 1157 | 1158 | let auction_data = borrow_global_mut(market_signer_address); 1159 | let auction_items = &mut auction_data.auction_items; 1160 | 1161 | assert!(table::contains(auction_items, token_id), ERROR_ALREADY_CLAIMED); 1162 | 1163 | let auction_item = table::borrow(auction_items, token_id); 1164 | assert!(is_auction_complete(auction_item.started_at, auction_item.duration), ERROR_AUCTION_NOT_COMPLETE); 1165 | assert!(sender_addr != auction_item.current_bidder, ERROR_NOT_CLAIMABLE); 1166 | 1167 | let locked_coins = &mut borrow_global_mut>(auction_item.current_bidder).locked_coins; 1168 | assert!(table::contains(locked_coins, token_id), ERROR_ALREADY_CLAIMED); 1169 | 1170 | 1171 | let royalty = token::get_royalty(token_id); 1172 | let royalty_payee = token::get_royalty_payee(&royalty); 1173 | let royalty_numerator = token::get_royalty_numerator(&royalty); 1174 | let royalty_denominator = token::get_royalty_denominator(&royalty); 1175 | 1176 | event::emit_event( 1177 | &mut auction_data.claim_coins_events, 1178 | ClaimCoinsEvent { 1179 | auction_id: auction_item.auction_id, 1180 | id: token_id, 1181 | timestamp: timestamp::now_seconds(), 1182 | owner_token: sender_addr 1183 | }, 1184 | ); 1185 | 1186 | let coins = table::remove(locked_coins, token_id); 1187 | let amount = coin::value(&coins); 1188 | let fee = market_data.fee * amount / 10000; 1189 | let royalty_fee = amount * royalty_numerator / royalty_denominator; 1190 | 1191 | if (fee > 0 ) { 1192 | coin::deposit(market_data.fund_address, coin::extract(&mut coins,fee)); 1193 | }; 1194 | 1195 | if (royalty_fee > 0){ 1196 | coin::deposit(royalty_payee,coin::extract(&mut coins,royalty_fee)); 1197 | }; 1198 | 1199 | coin::deposit(sender_addr, coins); 1200 | } 1201 | 1202 | public entry fun change_token_price_script( 1203 | sender: &signer, 1204 | creator: address, 1205 | collection_name: String, 1206 | token_name: String, 1207 | property_vertion: u64, 1208 | new_price: u64, 1209 | ) acquires ListedItemsData, TokenCap { 1210 | let token_id = token::create_token_id_raw(creator, collection_name, token_name, property_vertion); 1211 | change_token_price(sender, token_id, new_price); 1212 | } 1213 | 1214 | // change token price 1215 | fun change_token_price( 1216 | sender: &signer, 1217 | token_id: TokenId, 1218 | new_price: u64, 1219 | ) acquires ListedItemsData, TokenCap { 1220 | let sender_addr = signer::address_of(sender); 1221 | let market_cap = &borrow_global(@lotus).cap; 1222 | let market_signer = &account::create_signer_with_capability(market_cap); 1223 | let market_signer_address = signer::address_of(market_signer); 1224 | 1225 | let listed_items_data = borrow_global_mut(market_signer_address); 1226 | let listed_items = &mut listed_items_data.listed_items; 1227 | 1228 | assert!(table::contains(listed_items, token_id), ERROR_ALREADY_CLAIMED); 1229 | 1230 | let listed_item = table::borrow_mut(listed_items, token_id); 1231 | 1232 | listed_item.amount = new_price; 1233 | 1234 | event::emit_event(&mut listed_items_data.changing_price_events, ChangePriceEvent { 1235 | id: token_id, 1236 | listing_id: listed_item.listing_id, 1237 | amount: new_price, 1238 | timestamp: timestamp::now_seconds(), 1239 | seller_address: sender_addr 1240 | }) 1241 | } 1242 | 1243 | // TODO's 1244 | // - [x] initial_market 1245 | // - [x] list_token 1246 | // - [x] delist_token 1247 | // - [x] buy_token 1248 | // - [x] make_offer 1249 | // - [x] cancel_offer 1250 | // - [x] accept_offer 1251 | // - [x] claim_offer 1252 | // - [x] initial_auction 1253 | // - [x] bid 1254 | // - [x] claim_auction_token 1255 | // - [x] claim_auction_coin 1256 | 1257 | #[test(market = @lotus)] 1258 | public fun test_initial_market_script(market: &signer) acquires TokenCap, MarketData { 1259 | // create amount 1260 | account::create_account_for_test(signer::address_of(market)); 1261 | // call initial_market_script 1262 | initial_market_script(market); 1263 | 1264 | // test market data 1265 | let market_addr = signer::address_of(market); 1266 | let market_signer_address = get_market_signer_address(market_addr); 1267 | let market_data = borrow_global_mut(market_signer_address); 1268 | 1269 | assert!(market_data.fund_address == market_addr, 0); 1270 | } 1271 | 1272 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAE)] 1273 | public fun test_list_token(market: &signer, aptos_framework: &signer, seller: &signer) acquires ListedItemsData, TokenCap { 1274 | // set timestamp 1275 | timestamp::set_time_has_started_for_testing(aptos_framework); 1276 | // create amount 1277 | account::create_account_for_test(signer::address_of(aptos_framework)); 1278 | account::create_account_for_test(signer::address_of(market)); 1279 | account::create_account_for_test(signer::address_of(seller)); 1280 | 1281 | // initial market 1282 | initial_market_script(market); 1283 | 1284 | let market_addr = signer::address_of(market); 1285 | 1286 | let token_id = token::create_collection_and_token( 1287 | seller, 1288 | 2, 1289 | 2, 1290 | 2, 1291 | vector[], 1292 | vector>[], 1293 | vector[], 1294 | vector[false, false, false], 1295 | vector[false, false, false, false, false], 1296 | ); 1297 | 1298 | list_token(seller, token_id, 100); 1299 | 1300 | let market_signer_address = get_market_signer_address(market_addr); 1301 | let listed_items_data = borrow_global_mut(market_signer_address); 1302 | let listed_items = &mut listed_items_data.listed_items; 1303 | 1304 | assert!(table::contains(listed_items, token_id), 0); 1305 | } 1306 | 1307 | #[test(aptos_framework = @0x1, market = @lotus, seller=@0xAF)] 1308 | public fun test_delist_token(market: &signer, aptos_framework: &signer, seller: &signer) acquires ListedItemsData, TokenCap { 1309 | // set timestamp 1310 | timestamp::set_time_has_started_for_testing(aptos_framework); 1311 | // create amount 1312 | account::create_account_for_test(signer::address_of(aptos_framework)); 1313 | account::create_account_for_test(signer::address_of(market)); 1314 | account::create_account_for_test(signer::address_of(seller)); 1315 | 1316 | // initial market 1317 | initial_market_script(market); 1318 | 1319 | let market_addr = signer::address_of(market); 1320 | 1321 | let token_id = token::create_collection_and_token( 1322 | seller, 1323 | 2, 1324 | 2, 1325 | 2, 1326 | vector[], 1327 | vector>[], 1328 | vector[], 1329 | vector[false, false, false], 1330 | vector[false, false, false, false, false], 1331 | ); 1332 | 1333 | list_token(seller, token_id, 100); 1334 | 1335 | delist_token(seller, token_id); 1336 | 1337 | let market_signer_address = get_market_signer_address(market_addr); 1338 | let listed_items_data = borrow_global_mut(market_signer_address); 1339 | let listed_items = &mut listed_items_data.listed_items; 1340 | 1341 | assert!(!table::contains(listed_items, token_id), 0); 1342 | } 1343 | 1344 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, buyer = @0xAE)] 1345 | public fun test_buy_token(market: &signer, aptos_framework: &signer, seller: &signer, buyer: &signer) acquires ListedItemsData, TokenCap, MarketData { 1346 | // set timestamp 1347 | timestamp::set_time_has_started_for_testing(aptos_framework); 1348 | // create amount 1349 | account::create_account_for_test(signer::address_of(aptos_framework)); 1350 | account::create_account_for_test(signer::address_of(market)); 1351 | account::create_account_for_test(signer::address_of(seller)); 1352 | account::create_account_for_test(signer::address_of(buyer)); 1353 | 1354 | // initial market 1355 | initial_market_script(market); 1356 | 1357 | let token_id = token::create_collection_and_token( 1358 | seller, 1359 | 2, 1360 | 2, 1361 | 2, 1362 | vector[], 1363 | vector>[], 1364 | vector[], 1365 | vector[false, false, false], 1366 | vector[false, false, false, false, false], 1367 | ); 1368 | 1369 | list_token(seller, token_id, 300); 1370 | 1371 | let market_addr = signer::address_of(market); 1372 | let market_signer_address = get_market_signer_address(market_addr); 1373 | let listed_items_data = borrow_global_mut(market_signer_address); 1374 | let listed_items = &mut listed_items_data.listed_items; 1375 | 1376 | assert!(table::contains(listed_items, token_id), 0); 1377 | 1378 | coin::create_fake_money(aptos_framework, buyer, 300); 1379 | coin::transfer(aptos_framework, signer::address_of(buyer), 300); 1380 | 1381 | assert!(coin::balance(signer::address_of(buyer)) == 300, 1); 1382 | 1383 | coin::register(seller); 1384 | coin::register(market); 1385 | 1386 | buy_token(buyer, token_id); 1387 | 1388 | assert!(coin::balance(signer::address_of(market)) == 6, 1); 1389 | assert!(coin::balance(signer::address_of(seller)) == 294, 1); 1390 | assert!(coin::balance(signer::address_of(buyer)) == 0, 1); 1391 | } 1392 | 1393 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAE)] 1394 | public fun test_change_token_price(market: &signer, aptos_framework: &signer, seller: &signer) acquires ListedItemsData, TokenCap { 1395 | 1396 | // set timestamp 1397 | timestamp::set_time_has_started_for_testing(aptos_framework); 1398 | // create amount 1399 | account::create_account_for_test(signer::address_of(aptos_framework)); 1400 | account::create_account_for_test(signer::address_of(market)); 1401 | account::create_account_for_test(signer::address_of(seller)); 1402 | 1403 | // initial market 1404 | initial_market_script(market); 1405 | 1406 | let token_id = token::create_collection_and_token( 1407 | seller, 1408 | 2, 1409 | 2, 1410 | 2, 1411 | vector[], 1412 | vector>[], 1413 | vector[], 1414 | vector[false, false, false], 1415 | vector[false, false, false, false, false], 1416 | ); 1417 | 1418 | list_token(seller, token_id, 100); 1419 | change_token_price(seller, token_id, 200); 1420 | 1421 | let market_addr = signer::address_of(market); 1422 | let market_signer_address = get_market_signer_address(market_addr); 1423 | let listed_items_data = borrow_global_mut(market_signer_address); 1424 | let listed_items = &mut listed_items_data.listed_items; 1425 | let listed_item = table::borrow_mut(listed_items, token_id); 1426 | 1427 | assert!(listed_item.amount == 200, 0); 1428 | } 1429 | 1430 | // listing with offer mode 1431 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, buyer = @0xAE)] 1432 | public fun test_make_offer_script(market: &signer, aptos_framework: &signer, seller: &signer, buyer: &signer) acquires ListedItemsData, OfferData, CoinEscrowOffer, TokenCap { 1433 | // set timestamp 1434 | timestamp::set_time_has_started_for_testing(aptos_framework); 1435 | // create amount 1436 | account::create_account_for_test(signer::address_of(aptos_framework)); 1437 | account::create_account_for_test(signer::address_of(market)); 1438 | account::create_account_for_test(signer::address_of(seller)); 1439 | account::create_account_for_test(signer::address_of(buyer)); 1440 | 1441 | // initial market 1442 | initial_market_script(market); 1443 | 1444 | let token_id = token::create_collection_and_token( 1445 | seller, 1446 | 2, 1447 | 2, 1448 | 2, 1449 | vector[], 1450 | vector>[], 1451 | vector[], 1452 | vector[false, false, false], 1453 | vector[false, false, false, false, false], 1454 | ); 1455 | 1456 | list_token(seller, token_id, 100); 1457 | 1458 | coin::register(seller); 1459 | coin::register(market); 1460 | 1461 | coin::create_fake_money(aptos_framework, buyer, 300); 1462 | coin::transfer(aptos_framework, signer::address_of(buyer), 300); 1463 | 1464 | make_offer(buyer, token_id, 50, 120000); 1465 | 1466 | let market_addr = signer::address_of(market); 1467 | let market_signer_address = get_market_signer_address(market_addr); 1468 | let offer_data = borrow_global_mut(market_signer_address); 1469 | let offer_items = &mut offer_data.offer_items; 1470 | 1471 | assert!(table::contains(offer_items, token_id), 0); 1472 | } 1473 | 1474 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, buyer = @0xAE)] 1475 | public fun test_cancel_offer(market: &signer, aptos_framework: &signer, seller: &signer, buyer: &signer) acquires ListedItemsData, OfferData, CoinEscrowOffer, TokenCap { 1476 | // set timestamp 1477 | timestamp::set_time_has_started_for_testing(aptos_framework); 1478 | // create amount 1479 | account::create_account_for_test(signer::address_of(aptos_framework)); 1480 | account::create_account_for_test(signer::address_of(market)); 1481 | account::create_account_for_test(signer::address_of(seller)); 1482 | account::create_account_for_test(signer::address_of(buyer)); 1483 | 1484 | // initial market 1485 | initial_market_script(market); 1486 | 1487 | let token_id = token::create_collection_and_token( 1488 | seller, 1489 | 2, 1490 | 2, 1491 | 2, 1492 | vector[], 1493 | vector>[], 1494 | vector[], 1495 | vector[false, false, false], 1496 | vector[false, false, false, false, false], 1497 | ); 1498 | 1499 | list_token(seller, token_id, 100); 1500 | 1501 | coin::register(seller); 1502 | coin::register(market); 1503 | 1504 | coin::create_fake_money(aptos_framework, buyer, 300); 1505 | coin::transfer(aptos_framework, signer::address_of(buyer), 300); 1506 | 1507 | make_offer(buyer, token_id, 300, 120000); 1508 | 1509 | assert!(coin::balance(signer::address_of(buyer)) == 0, 1); 1510 | 1511 | cancel_offer(buyer, token_id); 1512 | assert!(coin::balance(signer::address_of(buyer)) == 300, 1); 1513 | } 1514 | 1515 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, buyer1 = @0xAE, buyer2 = @0xAD)] 1516 | public fun test_accept_offer(market: &signer, aptos_framework: &signer, seller: &signer, buyer1: &signer, buyer2: &signer) acquires ListedItemsData, OfferData, CoinEscrowOffer, MarketData, TokenCap, TokenEscrowOffer { 1517 | // set timestamp 1518 | timestamp::set_time_has_started_for_testing(aptos_framework); 1519 | // create amount 1520 | account::create_account_for_test(signer::address_of(aptos_framework)); 1521 | account::create_account_for_test(signer::address_of(market)); 1522 | account::create_account_for_test(signer::address_of(seller)); 1523 | account::create_account_for_test(signer::address_of(buyer1)); 1524 | account::create_account_for_test(signer::address_of(buyer2)); 1525 | 1526 | // initial market 1527 | initial_market_script(market); 1528 | 1529 | let token_id = token::create_collection_and_token( 1530 | seller, 1531 | 2, 1532 | 2, 1533 | 2, 1534 | vector[], 1535 | vector>[], 1536 | vector[], 1537 | vector[false, false, false], 1538 | vector[false, false, false, false, false], 1539 | ); 1540 | 1541 | list_token(seller, token_id, 100); 1542 | 1543 | coin::register(seller); 1544 | coin::register(market); 1545 | coin::register(buyer2); 1546 | 1547 | coin::create_fake_money(aptos_framework, buyer1, 500); 1548 | coin::transfer(aptos_framework, signer::address_of(buyer1), 300); 1549 | coin::transfer(aptos_framework, signer::address_of(buyer2), 200); 1550 | 1551 | make_offer(buyer1, token_id, 300, 120000); 1552 | make_offer(buyer2, token_id, 200, 120000); 1553 | 1554 | assert!(coin::balance(signer::address_of(buyer1)) == 0, 1); 1555 | assert!(coin::balance(signer::address_of(buyer2)) == 0, 1); 1556 | 1557 | accept_offer(seller, token_id, signer::address_of(buyer1)); 1558 | 1559 | assert!(coin::balance(signer::address_of(market)) == 6, 0); 1560 | assert!(coin::balance(signer::address_of(seller)) == 294, 0); 1561 | } 1562 | 1563 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, buyer = @0xAE)] 1564 | public fun test_claim_offer_token(market: &signer, aptos_framework: &signer, seller: &signer, buyer: &signer) acquires ListedItemsData, OfferData, CoinEscrowOffer, MarketData, TokenCap, TokenEscrowOffer { 1565 | // set timestamp 1566 | timestamp::set_time_has_started_for_testing(aptos_framework); 1567 | // create amount 1568 | account::create_account_for_test(signer::address_of(aptos_framework)); 1569 | account::create_account_for_test(signer::address_of(market)); 1570 | account::create_account_for_test(signer::address_of(seller)); 1571 | account::create_account_for_test(signer::address_of(buyer)); 1572 | 1573 | // initial market 1574 | initial_market_script(market); 1575 | 1576 | let token_id = token::create_collection_and_token( 1577 | seller, 1578 | 2, 1579 | 2, 1580 | 2, 1581 | vector[], 1582 | vector>[], 1583 | vector[], 1584 | vector[false, false, false], 1585 | vector[false, false, false, false, false], 1586 | ); 1587 | 1588 | list_token(seller, token_id, 100); 1589 | 1590 | coin::register(seller); 1591 | coin::register(market); 1592 | 1593 | coin::create_fake_money(aptos_framework, buyer, 300); 1594 | coin::transfer(aptos_framework, signer::address_of(buyer), 300); 1595 | 1596 | make_offer(buyer, token_id, 300, 120000); 1597 | 1598 | assert!(coin::balance(signer::address_of(buyer)) == 0, 1); 1599 | 1600 | accept_offer(seller, token_id, signer::address_of(buyer)); 1601 | 1602 | assert!(coin::balance(signer::address_of(market)) == 6, 0); 1603 | assert!(coin::balance(signer::address_of(seller)) == 294, 0); 1604 | 1605 | claim_offer_token(buyer, token_id); 1606 | 1607 | assert!(token::balance_of(signer::address_of(buyer), token_id) == 1, 0); 1608 | } 1609 | 1610 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF)] 1611 | public fun test_initial_auction(market: &signer, aptos_framework: &signer, seller: &signer) acquires AuctionData, TokenCap { 1612 | // set timestamp 1613 | timestamp::set_time_has_started_for_testing(aptos_framework); 1614 | // create amount 1615 | account::create_account_for_test(signer::address_of(aptos_framework)); 1616 | account::create_account_for_test(signer::address_of(market)); 1617 | account::create_account_for_test(signer::address_of(seller)); 1618 | 1619 | // initial market 1620 | initial_market_script(market); 1621 | 1622 | let token_id = token::create_collection_and_token( 1623 | seller, 1624 | 2, 1625 | 2, 1626 | 2, 1627 | vector[], 1628 | vector>[], 1629 | vector[], 1630 | vector[false, false, false], 1631 | vector[false, false, false, false, false], 1632 | ); 1633 | 1634 | initial_auction(seller, token_id, 100, 120000); 1635 | 1636 | let market_addr = signer::address_of(market); 1637 | let market_signer_address = get_market_signer_address(market_addr); 1638 | let auction_data = borrow_global_mut(market_signer_address); 1639 | let auction_items = &mut auction_data.auction_items; 1640 | 1641 | assert!(table::contains(auction_items, token_id), 0); 1642 | } 1643 | 1644 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, bidder1 = @0xAE)] 1645 | public fun test_bid(market: &signer, aptos_framework: &signer, seller: &signer, bidder1: &signer) acquires CoinEscrow, AuctionData, TokenCap { 1646 | // set timestamp 1647 | timestamp::set_time_has_started_for_testing(aptos_framework); 1648 | // create amount 1649 | account::create_account_for_test(signer::address_of(aptos_framework)); 1650 | account::create_account_for_test(signer::address_of(market)); 1651 | account::create_account_for_test(signer::address_of(seller)); 1652 | account::create_account_for_test(signer::address_of(bidder1)); 1653 | // account::create_account_for_test(signer::address_of(bidder2)); 1654 | 1655 | // initial market 1656 | initial_market_script(market); 1657 | 1658 | let token_id = token::create_collection_and_token( 1659 | seller, 1660 | 2, 1661 | 2, 1662 | 2, 1663 | vector[], 1664 | vector>[], 1665 | vector[], 1666 | vector[false, false, false], 1667 | vector[false, false, false, false, false], 1668 | ); 1669 | 1670 | initial_auction(seller, token_id, 100, 120000); 1671 | 1672 | coin::create_fake_money(aptos_framework, bidder1, 600); 1673 | coin::transfer(aptos_framework, signer::address_of(bidder1), 300); 1674 | 1675 | bid(bidder1, token_id, 200); 1676 | 1677 | let market_addr = signer::address_of(market); 1678 | let market_signer_address = get_market_signer_address(market_addr); 1679 | let auction_data = borrow_global_mut(market_signer_address); 1680 | let auction_items = &mut auction_data.auction_items; 1681 | let auction_item = table::borrow_mut(auction_items, token_id); 1682 | 1683 | assert!(auction_item.current_bid == 200, 0); 1684 | } 1685 | 1686 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, bidder1 = @0xAE, bidder2 = @0xAD)] 1687 | public fun test_claim_auction_token(market: &signer, aptos_framework: &signer, seller: &signer, bidder1: &signer, bidder2: &signer) acquires CoinEscrow, AuctionData, MarketData, TokenCap { 1688 | // set timestamp 1689 | timestamp::set_time_has_started_for_testing(aptos_framework); 1690 | // create amount 1691 | account::create_account_for_test(signer::address_of(aptos_framework)); 1692 | account::create_account_for_test(signer::address_of(market)); 1693 | account::create_account_for_test(signer::address_of(seller)); 1694 | account::create_account_for_test(signer::address_of(bidder1)); 1695 | account::create_account_for_test(signer::address_of(bidder2)); 1696 | 1697 | // initial market 1698 | initial_market_script(market); 1699 | 1700 | let token_id = token::create_collection_and_token( 1701 | seller, 1702 | 2, 1703 | 2, 1704 | 2, 1705 | vector[], 1706 | vector>[], 1707 | vector[], 1708 | vector[false, false, false], 1709 | vector[false, false, false, false, false], 1710 | ); 1711 | 1712 | // initial auction 1713 | initial_auction(seller, token_id, 100, 120); 1714 | 1715 | coin::register(seller); 1716 | 1717 | coin::create_fake_money(aptos_framework, bidder1, 600); 1718 | coin::transfer(aptos_framework, signer::address_of(bidder1), 300); 1719 | 1720 | coin::register(bidder2); 1721 | coin::register(market); 1722 | 1723 | coin::transfer(aptos_framework, signer::address_of(bidder2), 300); 1724 | 1725 | bid(bidder1, token_id, 200); 1726 | bid(bidder2, token_id, 250); 1727 | 1728 | timestamp::update_global_time_for_test(130000*1000); 1729 | 1730 | claim_auction_token(bidder2, token_id); 1731 | 1732 | assert!(coin::balance(signer::address_of(market)) == 5, 1); 1733 | assert!(coin::balance(signer::address_of(seller)) == 245, 1); 1734 | } 1735 | 1736 | #[test(aptos_framework = @0x1, market = @lotus, seller = @0xAF, bidder1 = @0xAE, bidder2 = @0xAD)] 1737 | public fun test_claim_auction_coin(market: &signer, aptos_framework: &signer, seller: &signer, bidder1: &signer, bidder2: &signer) acquires CoinEscrow, AuctionData, MarketData, TokenCap { 1738 | // set timestamp 1739 | timestamp::set_time_has_started_for_testing(aptos_framework); 1740 | // create amount 1741 | account::create_account_for_test(signer::address_of(aptos_framework)); 1742 | account::create_account_for_test(signer::address_of(market)); 1743 | account::create_account_for_test(signer::address_of(seller)); 1744 | account::create_account_for_test(signer::address_of(bidder1)); 1745 | account::create_account_for_test(signer::address_of(bidder2)); 1746 | 1747 | // initial market 1748 | initial_market_script(market); 1749 | 1750 | let token_id = token::create_collection_and_token( 1751 | seller, 1752 | 2, 1753 | 2, 1754 | 2, 1755 | vector[], 1756 | vector>[], 1757 | vector[], 1758 | vector[false, false, false], 1759 | vector[false, false, false, false, false], 1760 | ); 1761 | 1762 | // initial auction 1763 | initial_auction(seller, token_id, 100, 120); 1764 | 1765 | coin::register(seller); 1766 | 1767 | coin::create_fake_money(aptos_framework, bidder1, 600); 1768 | coin::transfer(aptos_framework, signer::address_of(bidder1), 300); 1769 | 1770 | coin::register(bidder2); 1771 | coin::register(market); 1772 | 1773 | coin::transfer(aptos_framework, signer::address_of(bidder2), 300); 1774 | 1775 | bid(bidder1, token_id, 200); 1776 | bid(bidder2, token_id, 250); 1777 | 1778 | timestamp::update_global_time_for_test(130000*1000); 1779 | 1780 | claim_auction_coin(seller, token_id); 1781 | 1782 | assert!(coin::balance(signer::address_of(market)) == 5, 1); 1783 | assert!(coin::balance(signer::address_of(seller)) == 245, 1); 1784 | } 1785 | } --------------------------------------------------------------------------------