├── .gitignore ├── Move.toml ├── README.md └── sources ├── market_event.move └── market.move /.gitignore: -------------------------------------------------------------------------------- 1 | */build/ 2 | .idea 3 | build 4 | Move.lock -------------------------------------------------------------------------------- /Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Hero-Marketplace-Demo" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | hero = "0x0" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SUI NFT Marketplace 2 | 3 | **The First NFT AMM Smart Contract on the Sui Blockchain** 4 | 5 | Welcome to **NFT Marketplace**, the first-of-its-kind **Automated Market Maker (AMM)** for NFTs on the **Sui blockchain**. This protocol allows seamless peer-to-peer trading of NFTs in a fully decentralized and transparent manner. 6 | 7 | As one of the early movers on Sui, this project is pioneering new ground in the NFT space, providing fundamental trading infrastructure for the next wave of digital asset innovation. 8 | 9 | --- 10 | 11 | ## 🚀 Key Features 12 | 13 | ### ✅ Live 14 | 15 | * **List NFTs** – Allow users to securely list NFTs for sale on-chain. 16 | * **Buy/Sell NFTs** – Seamless trading of NFTs using AMM-style pricing logic. 17 | * **Transfer NFTs** – Enable safe and gas-efficient NFT transfers between users. 18 | 19 | ### 🔜 Coming Soon 20 | 21 | * **Auctions** – A decentralized bidding system for NFTs. 22 | * **Raffles** – Randomized NFT distribution for community engagement and fair access. 23 | 24 | --- 25 | 26 | ## ⚙️ Current Status 27 | 28 | * 🧪 **Early-stage Demo** – Basic functionality is implemented. 29 | * 🔧 **Actively in Development** – Features are continuously being improved and expanded. 30 | * 🧩 **Open for Feedback & Contributions** – Community feedback is highly appreciated. 31 | 32 | --- 33 | 34 | ## 🧑‍💻 How to Use 35 | 36 | 1. **Clone the repository** 37 | 38 | ```bash 39 | git clone https://github.com/toptrendev0829/SUI-NFT-Marketplace.git 40 | ``` 41 | 42 | 2. **Navigate to the directory** 43 | 44 | ```bash 45 | cd SUI-NFT-Marketplace 46 | ``` 47 | 48 | 3. **Build and test** 49 | Follow Sui Move standard practices to build and test smart contracts: 50 | 51 | ```bash 52 | sui move build 53 | sui move test 54 | ``` 55 | 56 | > **Note:** You will need a [Sui CLI](https://docs.sui.io/build/install) and a funded devnet/testnet wallet to interact with the contract on-chain. 57 | 58 | --- 59 | 60 | ## 🧠 Project Structure 61 | 62 | * `sources/` – Move smart contracts for NFT listing, trading, and transfer. 63 | * `tests/` – Unit tests for contract modules. 64 | * `Move.toml` – Project configuration for Move packages. 65 | 66 | --- 67 | 68 | ## 🤝 Contributing 69 | 70 | We welcome all contributions that help improve the protocol or expand its capabilities: 71 | 72 | * Bug reports 🐛 73 | * Feature requests 💡 74 | * Code enhancements 🛠️ 75 | * Documentation updates 📝 76 | 77 | To contribute: 78 | 79 | 1. Fork the repository 80 | 2. Create your feature branch (`git checkout -b feature-name`) 81 | 3. Commit your changes (`git commit -m 'Add feature'`) 82 | 4. Push to the branch (`git push origin feature-name`) 83 | 5. Open a Pull Request 84 | 85 | --- 86 | 87 | ## 📩 Get in Touch 88 | 89 | Have suggestions or want to collaborate? Feel free to reach out: 90 | 91 | * 📬 Twitter: [@abcede_12345](https://x.com/abcede_12345) 92 | * ☎ Telegram: [@TopTrendingDev](https://t.me/TopTrendingDev) 93 | -------------------------------------------------------------------------------- /sources/market_event.move: -------------------------------------------------------------------------------- 1 | module hero::market_event { 2 | use sui::object::ID; 3 | use sui::event; 4 | 5 | friend hero::market; 6 | 7 | struct MarketCreatedEvent has copy, drop { 8 | market_id: ID, 9 | owner: address, 10 | } 11 | 12 | struct CollectionCreatedEvent has copy, drop { 13 | collection_id: ID, 14 | creator_address: address 15 | } 16 | 17 | struct ItemListedEvent has copy, drop { 18 | collection_id: ID, 19 | item_id: ID, 20 | seller: address, 21 | price: u64, 22 | } 23 | 24 | struct ItemPurchasedEvent has copy, drop { 25 | collection_id: ID, 26 | item_id: ID, 27 | seller: address, 28 | price: u64, 29 | 30 | } 31 | 32 | struct ItemDeListedEvent has copy, drop { 33 | collection_id: ID, 34 | item_id: ID, 35 | listing_id: ID, 36 | operator: address, 37 | price: u64, 38 | 39 | } 40 | 41 | struct ItemAdjustPriceEvent has copy, drop { 42 | collection_id: ID, 43 | listing_id: ID, 44 | operator: address, 45 | price: u64, 46 | } 47 | 48 | struct PoolCreatedEvent has copy, drop { 49 | pool_type: bool, 50 | author: address, 51 | } 52 | 53 | public(friend) fun market_created_event( 54 | market_id: ID, 55 | owner: address 56 | ) { 57 | event::emit(MarketCreatedEvent { 58 | market_id, 59 | owner 60 | }) 61 | } 62 | 63 | public(friend) fun collection_created_event( 64 | collection_id: ID, 65 | creator_address: address 66 | ) { 67 | event::emit(CollectionCreatedEvent { 68 | collection_id, 69 | creator_address 70 | }) 71 | } 72 | 73 | public(friend) fun item_list_event( 74 | collection_id: ID, 75 | item_id: ID, 76 | seller: address, 77 | price: u64 78 | ) { 79 | event::emit(ItemListedEvent { 80 | collection_id, 81 | item_id, 82 | seller, 83 | price 84 | }) 85 | } 86 | 87 | public(friend) fun item_puchased_event( 88 | collection_id: ID, 89 | item_id: ID, 90 | seller: address, 91 | price: u64 92 | ) { 93 | event::emit(ItemPurchasedEvent { 94 | collection_id, 95 | item_id, 96 | seller, 97 | price 98 | }) 99 | } 100 | 101 | public(friend) fun item_delisted_event( 102 | collection_id: ID, 103 | item_id: ID, 104 | listing_id: ID, 105 | operator: address, 106 | price: u64 107 | ) { 108 | event::emit(ItemDeListedEvent { 109 | collection_id, 110 | item_id, 111 | listing_id, 112 | operator, 113 | price, 114 | }) 115 | } 116 | 117 | public(friend) fun item_adjust_price_event( 118 | collection_id: ID, 119 | listing_id: ID, 120 | operator: address, 121 | price: u64 122 | ) { 123 | event::emit(ItemAdjustPriceEvent { 124 | collection_id, 125 | listing_id, 126 | operator, 127 | price 128 | }) 129 | } 130 | 131 | public(friend) fun pool_created_event( 132 | pool_type: bool, 133 | author: address, 134 | ) { 135 | event::emit(PoolCreatedEvent { 136 | pool_type, 137 | author, 138 | }) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /sources/market.move: -------------------------------------------------------------------------------- 1 | module hero::market { 2 | use std::ascii::String; 3 | use std::type_name::{into_string, get}; 4 | use std::vector; 5 | 6 | use sui::balance::{Self, Balance, zero}; 7 | use sui::coin::{Self, Coin}; 8 | use sui::event::emit; 9 | use sui::object::{Self, UID, ID}; 10 | use sui::object_table::{Self, ObjectTable}; 11 | use sui::dynamic_object_field as ofield; 12 | use std::option::{Self, Option}; 13 | use sui::sui::SUI; 14 | use sui::pay; 15 | use sui::transfer; 16 | use sui::tx_context::{Self, TxContext}; 17 | 18 | // ======= Event ======= 19 | use hero::market_event; 20 | 21 | // ======= Error ======= 22 | 23 | // For when someone tries to delist without ownership. 24 | const ERR_NOT_OWNER: u64 = 0; 25 | // For when amount paid does not match the expected. 26 | const ERR_AMOUNT_INCORRECT: u64 = 1; 27 | const ERR_EXCEED_NFT: u64 = 2; 28 | const EAmountZero: u64 = 3; 29 | const EOwnerAuth: u64 = 4; 30 | const EObjectNotExist: u64 = 5; 31 | const EAlreadyExistCollectionType: u64 = 6; 32 | 33 | // ======= Types ======= 34 | 35 | struct Listing has store, key { 36 | id: UID, 37 | price: u64, 38 | owner: address 39 | } 40 | 41 | struct WithdrawMarket has key { 42 | id: UID, 43 | } 44 | 45 | /// A Capability for market manager. 46 | struct Marketplace has key { 47 | id: UID, 48 | buy_pools: vector, 49 | sell_pools: vector, 50 | } 51 | 52 | /// A Buy pool 53 | struct BuyPool has key, store { 54 | id: UID, 55 | owner: address, 56 | spot_price: u64, 57 | curve_type: bool, 58 | delta: u64, 59 | number_of_nfts: u64, 60 | funds: Coin, 61 | bought: u64 62 | } 63 | 64 | /// A Sell pool 65 | struct SellPool has key, store { 66 | id: UID, 67 | owner: address, 68 | spot_price: u64, 69 | curve_type: bool, 70 | delta: u64, 71 | sold: u64 72 | } 73 | 74 | /// A Liquidity pool 75 | struct LiquidityPool has key, store { 76 | id: UID, 77 | owner: address, 78 | fee: u64, 79 | spot_price: u64, 80 | curve_type: bool, 81 | delta: u64, 82 | number_of_nfts: u64, 83 | // nfts_for_sale: vector<> 84 | } 85 | 86 | // ======= Publishing ======= 87 | 88 | fun init(ctx: &mut TxContext) { 89 | let market = Marketplace { 90 | id: object::new(ctx), 91 | buy_pools: vector::empty(), 92 | sell_pools: vector::empty(), 93 | }; 94 | market_event::market_created_event(object::id(&market), tx_context::sender(ctx)); 95 | transfer::share_object(market) 96 | } 97 | 98 | // ======= Actions ======= 99 | 100 | public entry fun create_buy_pool( 101 | market: &mut Marketplace, 102 | spot_price: u64, 103 | curve_type: bool, 104 | delta: u64, 105 | number_of_nfts: u64, 106 | paid: Coin, 107 | ctx: &mut TxContext 108 | ) { 109 | let id = object::new(ctx); 110 | let owner = tx_context::sender(ctx); 111 | 112 | let sum = 0; 113 | let price = spot_price; 114 | let i = 0; 115 | while(i < number_of_nfts) { 116 | if(curve_type){ 117 | price = price * (100 - delta) / 100; 118 | 119 | } else { 120 | price = price - delta; 121 | }; 122 | sum = sum + price; 123 | i = i + 1; 124 | }; 125 | 126 | assert!(coin::value(&paid) >= sum, ERR_AMOUNT_INCORRECT); 127 | 128 | let pool = BuyPool{ 129 | id, 130 | owner, 131 | spot_price, 132 | curve_type, 133 | delta, 134 | number_of_nfts, 135 | funds: paid, 136 | bought: 0 137 | }; 138 | let pool_id = object::id(&pool); 139 | 140 | vector::push_back(&mut market.buy_pools, object::id(&pool)); 141 | ofield::add(&mut market.id, pool_id, pool); 142 | } 143 | 144 | public entry fun create_sell_pool( 145 | market: &mut Marketplace, 146 | spot_price: u64, 147 | curve_type: bool, 148 | delta: u64, 149 | nfts_for_sale: vector, 150 | ctx: &mut TxContext 151 | ) { 152 | let id = object::new(ctx); 153 | let owner = tx_context::sender(ctx); 154 | let length = vector::length(&nfts_for_sale); 155 | 156 | let i = 0; 157 | while(i < length) { 158 | let item = vector::pop_back(&mut nfts_for_sale); 159 | let item_id = object::id(&item); 160 | ofield::add(&mut id, item_id, item); 161 | i = i + 1; 162 | }; 163 | 164 | let pool = SellPool{ 165 | id, 166 | owner, 167 | spot_price, 168 | curve_type, 169 | delta, 170 | sold: 0 171 | }; 172 | let pool_id = object::id(&pool); 173 | 174 | vector::destroy_empty(nfts_for_sale); 175 | vector::push_back(&mut market.sell_pools, object::id(&pool)); 176 | ofield::add(&mut market.id, pool_id, pool); 177 | } 178 | 179 | public entry fun buy_and_take( 180 | market: &mut Marketplace, 181 | pool_id: ID, 182 | item_id: ID, 183 | paid: Coin, 184 | ctx: &mut TxContext 185 | ) { 186 | transfer::transfer(buy(market, pool_id, item_id, paid, ctx), tx_context::sender(ctx)) 187 | } 188 | 189 | public entry fun sell_and_take(){} 190 | 191 | public entry fun list( 192 | market: &mut Marketplace, 193 | pool_id: ID, 194 | item: Item, 195 | ctx: &mut TxContext 196 | ) { 197 | let pool = ofield::borrow_mut(&mut market.id, pool_id); 198 | assert!(pool.number_of_nfts >= (pool.bought + 1), ERR_EXCEED_NFT); 199 | 200 | let price = pool.spot_price; 201 | let i = 0; 202 | while(i < pool.bought) { 203 | if(pool.curve_type){ 204 | price = price * (100 - pool.delta) / 100; 205 | } else { 206 | price = price - pool.delta; 207 | }; 208 | i = i + 1; 209 | }; 210 | let item_id = object::id(&item); 211 | pay::split_and_transfer(&mut pool.funds, price, tx_context::sender(ctx), ctx); 212 | ofield::add(&mut pool.id, item_id, item); 213 | 214 | market_event::item_list_event(object::id(pool), item_id, tx_context::sender(ctx), price); 215 | } 216 | 217 | public fun buy( 218 | market: &mut Marketplace, 219 | pool_id: ID, 220 | item_id: ID, 221 | paid: Coin, 222 | ctx: &mut TxContext 223 | ): Item { 224 | let pool = ofield::borrow_mut(&mut market.id, pool_id); 225 | let price = pool.spot_price; 226 | let i = 0; 227 | while(i < pool.sold) { 228 | if(pool.curve_type){ 229 | price = price * (100 + pool.delta) / 100; 230 | } else { 231 | price = price + pool.delta; 232 | }; 233 | i = i + 1; 234 | }; 235 | 236 | assert!(coin::value(&paid) >= price, ERR_AMOUNT_INCORRECT); 237 | 238 | let item = ofield::remove(&mut pool.id, item_id); 239 | 240 | pay::split_and_transfer(&mut paid, price, pool.owner, ctx); 241 | 242 | market_event::item_puchased_event(object::id(pool), item_id, pool.owner, price); 243 | 244 | transfer::transfer(paid, tx_context::sender(ctx)); 245 | item 246 | } 247 | 248 | } 249 | --------------------------------------------------------------------------------