├── .gitignore ├── Move.toml ├── README.md └── sources ├── market.move └── market_event.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 | # 🎭 Heros NFT AMM on Sui 2 | 3 | ## 🚀 The First NFT AMM Smart Contract on SUI Blockchain 4 | 5 | Heros NFT AMM is **the first Automated Market Maker (AMM) for NFTs** on the **Sui blockchain**, enabling users to **trade non-fungible tokens (NFTs) seamlessly**. As Sui is still in its early stages, we are pioneering **decentralized NFT trading** with this innovative contract. 6 | 7 | --- 8 | 9 | ## ⚡ Current Status 10 | 11 | 🔹 **Early-stage demo version** – Several features are still under development. 12 | 🔹 **Core Trading Features Implemented** – Users can **list, transfer, buy, and sell NFTs**. 13 | 🔹 **Upcoming Features** – **Auctions & Raffles**, popular in traditional NFT marketplaces, are planned for future updates. 14 | 15 | We welcome **feedback, feature requests, and contributions** to improve the protocol! 🎨📈 16 | 17 | --- 18 | 19 | ## 🛠️ Features (Current & Planned) 20 | 21 | ✅ **List & Transfer NFTs** – Securely list NFTs for trade and transfer between users. 22 | ✅ **Buy & Sell** – Simple NFT purchase and sales mechanisms with automated pricing. 23 | 🔜 **Auctions** – Users will be able to bid on NFTs in a decentralized auction system. 24 | 🔜 **Raffles** – Randomized NFT distribution for fair allocation and engagement. 25 | 26 | --- 27 | 28 | ## 📩 Get Involved 29 | 30 | I appreciate all **feedback, suggestions, and contributions** from the my project! If you have ideas or proposals, feel free to share me. 🌟 31 | 32 | Join the conversation and help shape the future of **NFT trading on Sui**! 🚀 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------