From 231ee39850622d30b03c47c9f68a7afbdf30afeb Mon Sep 17 00:00:00 2001 From: wz-Tan Date: Thu, 7 Aug 2025 13:23:33 +0800 Subject: [PATCH 1/6] Marketplace - Core Functions --- marketplace_contract/sources/marketplace.move | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 marketplace_contract/sources/marketplace.move diff --git a/marketplace_contract/sources/marketplace.move b/marketplace_contract/sources/marketplace.move new file mode 100644 index 0000000..608c60e --- /dev/null +++ b/marketplace_contract/sources/marketplace.move @@ -0,0 +1,158 @@ +module marketplace_contract::marketplace; + +use marketplace_contract::nft::{NFT, getOwner, transferOwnershipXP}; +use std::vector::{push_back, index_of, remove}; +use sui::coin::{Coin, value, split}; +use sui::object::{uid_to_inner, delete}; +use sui::object_bag::{Self as obag, ObjectBag}; +use sui::sui::SUI; +use sui::tx_context::sender; +use sui::balance; +use sui::balance::Balance; +use sui::coin::into_balance; +use sui::balance::zero; +use sui::coin::from_balance; +use sui::balance::withdraw_all; + + +const E_NOT_OWNER: u64 = 1; +const E_LISTING_NOT_IN_BAG: u64 = 101; +const E_IS_OWNER: u64 = 102; +const E_INSUFFICIENT_COIN: u64 = 200; +const E_NO_EARNINGS: u64 =201; + +//Multiplier for 5% - Divide With 100 +const MARKETPLACE_TAX: u64 = 5; + +//For Coins, It is calculated in MIST - 1 million mist = 1 sui coin +public struct Marketplace has key { + id: UID, + //Contains all Listings After Transferring Ownership + listingBag: ObjectBag, + //Stores the Keys for Items in the listingBag (Used for Lookups) + listings: vector, + earnings: Balance, + owner: address +} + +public struct Listing has key, store { + id: UID, + price: u64, + owner: address, + time_of_creation: u64, + nft: NFT, +} + +//Create New Marketplace +fun init(ctx: &mut TxContext) { + let marketplace = Marketplace { + id: object::new(ctx), + listingBag: obag::new(ctx), + listings: vector[], + earnings: zero(), + owner: sender(ctx) + }; + + transfer::share_object(marketplace) +} + +//Create Listing (User Types In Sui Amount, we Multiply with 1 million to convert to Sui) +public fun createListing( + marketplace: &mut Marketplace, + nft: NFT, + price: u64, + ctx: &mut TxContext, +) { + //Only Owner Can List Their NFT + assert!(getOwner(&nft)==sender(ctx)); + let new_listing = Listing { + id: object::new(ctx), + price: price, + owner: sender(ctx), + time_of_creation: ctx.epoch(), + nft: nft, + }; + + //Add Listing into bag and keep the ID + push_back(&mut marketplace.listings, uid_to_inner(&new_listing.id)); + //Add Dynamic Object Field Into the Listing Bag + obag::add(&mut marketplace.listingBag, uid_to_inner(&new_listing.id), new_listing); +} + +#[allow(lint(self_transfer))] +public fun deleteListing(marketplace: &mut Marketplace, listing_id: ID, ctx: &mut TxContext) { + //Item Exists + assert!(obag::contains(&marketplace.listingBag, listing_id), E_LISTING_NOT_IN_BAG); + //Retrieve Listing, Then Retrieve NFT from Listing (listing id is the key for listing in the bag) + let listing_ref: &Listing = obag::borrow(&marketplace.listingBag, listing_id); + let nft_ref = &listing_ref.nft; + + //Is Owner + assert!(getOwner(nft_ref)==sender(ctx), E_NOT_OWNER); + + //Get Item + let listing: Listing = obag::remove(&mut marketplace.listingBag, listing_id); + + //Destructure + let Listing { id, price: _, owner: _, time_of_creation: _,nft } = listing; + + //Transfer Item Back to Owner, then destroy listing and its field in the object bag + //Clean Up Vector + let (_contains, i) = index_of(&marketplace.listings, &listing_id); + assert!(_contains, E_LISTING_NOT_IN_BAG); + remove(&mut marketplace.listings, i); + transfer::public_transfer(nft, sender(ctx)); + delete(id); +} + +#[allow(lint(self_transfer))] +public entry fun buyListing( + marketplace: &mut Marketplace, + mut payment: Coin, + listing_id: ID, + ctx: &mut TxContext, +) { + //Retrieve Listing + assert!(obag::contains(&marketplace.listingBag, listing_id), E_LISTING_NOT_IN_BAG); + + //Make Sure Not Owner + let listing_ref: &Listing = obag::borrow(&marketplace.listingBag, listing_id); + let nft_ref = &listing_ref.nft; + assert!(getOwner(nft_ref)!=sender(ctx), E_IS_OWNER); + + //Ensure Enough Money (Compare Mist to Mist) + assert!(value(&payment)>=listing_ref.price, E_INSUFFICIENT_COIN); + + //Pay First + let mut retrieved = split(&mut payment, listing_ref.price, ctx); + transfer::public_transfer(payment, sender(ctx)); + + //Transfer the Tax, Then to Buyer + let tax=(value(&retrieved)*MARKETPLACE_TAX)/100; + let taxCoin=split(&mut retrieved, tax, ctx); + balance::join(&mut marketplace.earnings, into_balance(taxCoin)); + + transfer::public_transfer(retrieved, listing_ref.owner); + + //Transfer NFT + let listing: Listing = obag::remove(&mut marketplace.listingBag, listing_id); + let Listing { id, price: _, owner: _, time_of_creation: _, mut nft } = listing; + transferOwnershipXP(&mut nft, ctx); + transfer::public_transfer(nft, sender(ctx)); + + + + //Clean Up Vector + let (_contains, i) = index_of(&marketplace.listings, &listing_id); + remove(&mut marketplace.listings, i); + delete(id); +} + +#[allow(lint(self_transfer))] +public entry fun withdrawRevenue(marketplace: &mut Marketplace, ctx: &mut TxContext){ + assert!(marketplace.owner==sender(ctx), E_NOT_OWNER); + assert!(balance::value(&marketplace.earnings)>0, E_NO_EARNINGS); + let revenue_balance=withdraw_all(&mut marketplace.earnings); + let revenue=from_balance(revenue_balance, ctx); + transfer::public_transfer(revenue, sender(ctx)); +} \ No newline at end of file From f6bb93f1e72ed6b798c82b4db537cae72651d4c6 Mon Sep 17 00:00:00 2001 From: wz-Tan Date: Thu, 7 Aug 2025 15:17:02 +0800 Subject: [PATCH 2/6] Bidding --- marketplace_contract/sources/bidding.move | 320 ++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 marketplace_contract/sources/bidding.move diff --git a/marketplace_contract/sources/bidding.move b/marketplace_contract/sources/bidding.move new file mode 100644 index 0000000..e8e717e --- /dev/null +++ b/marketplace_contract/sources/bidding.move @@ -0,0 +1,320 @@ +module marketplace_contract::bidding; + +use marketplace_contract::nft::{NFT, getOwner}; +use std::vector::push_back; +use sui::balance::{Self as balance, Balance}; +use sui::coin::{Self as coin, Coin, into_balance}; +use sui::object::{uid_to_inner}; +use sui::object_bag::{Self as obag, ObjectBag}; +use sui::sui::SUI; +use sui::balance::zero; +use sui::tx_context::sender; + +use sui::balance::destroy_zero; + +// TODO : rename to follow convention +// ownership +const E_NOT_OWNER: u64 = 1; + +// auction +const AUCTION_MINIMUM_DURATION: u64 = 86400; // minimum auction duration of one day +const AUCTION_TAX_PERCENTAGE: u64 = 5; // will be used as PERCENTAGE / 100 + +const E_DURATION_TOO_SHORT: u64 = 100; +const E_AUCTION_NO_EXIST: u64 = 101; + +// bid +const E_BID_TOO_LOW: u64 = 200; +const E_INSUFFICIENT_DEPOSIT:u64 = 201; +const DEPOSIT_AMOUNT :u64 = 100000000; + + +public struct AuctionHouse has key { + id: UID, + // All auction objects + auctionBag: ObjectBag, + // All auction addresses + auctionIDs: vector, + + // address to send revenue towards + owner: address, + earnings: Balance +} + +public struct Auction has key, store { + id: UID, + nft: NFT, + // All bid objects + bidBag: ObjectBag, + // All bid addresses + bidIDs: vector, + minPrice: u64, + // starting and ending, stored in epoch unix + starting: u64, + ending: u64, + // optional? + highestBidID: Option, + // user id of auction creator + owner: address, + deposit: Balance, + // is this necesary? + // hold addresses and bid ids + // payments: Table>, +} + +public struct Bid has key, store { + id: UID, + // both amount and balance are stored + // once a bid is outbid, balance is transferred, so will be 0 + // but amount will remain untouched + // + // this lets us see the bidding history + amount: u64, + balance: Balance, + time_placed: u64, + owner: address, +} + +// on nft +// user +// start bid +// nft listed for bidding for set amount of time +// owner puts in starting bid +// create bidding +// if current bid is highest, transfer the previous bid coins back to previous owner +// get all biddings +// task +// timer to conclude all biddings +// highest bid balance goes to nft owner +// nft transferred to bidder + +// on user +// get all biddings +// (all biddings theyve made) + +fun init (ctx: &mut TxContext) { + let auctionHouse = AuctionHouse{ + id: object::new(ctx), + auctionBag: obag::new(ctx), + auctionIDs: vector[], + earnings: zero(), + owner: sender(ctx) + }; + transfer::share_object(auctionHouse) +} + +public entry fun startAuction( + house: &mut AuctionHouse, + mut deposit_payment: Coin, + minPrice: u64, + duration: u64, + nft: NFT, + ctx: &mut TxContext, +) { + // check if nft has correct owner + assert!(getOwner(&nft) == sender(ctx), E_NOT_OWNER); + + // additional check for minimum duration and deposit amount + assert!(duration >= AUCTION_MINIMUM_DURATION, E_DURATION_TOO_SHORT); + assert!(coin::value(&deposit_payment)>=DEPOSIT_AMOUNT, E_INSUFFICIENT_DEPOSIT); + + // dont need to check if an auction has already been started for this nft + // once an auction starts, nft goes into obag + // thus, user cant call this function with the same nft + + let current_time = ctx.epoch(); + + //Split the deposit coin + let payment=coin::split(&mut deposit_payment, DEPOSIT_AMOUNT, ctx); + transfer::public_transfer(deposit_payment, sender(ctx)); + + let balance: Balance = payment.into_balance(); + + // create new bid obag + let bidIDs = vector[]; + let bidBag = obag::new(ctx); + + // create new auction + let new_auction = Auction { + id: object::new(ctx), + nft: nft, + starting: current_time, + ending: current_time + duration, + bidBag: bidBag, + bidIDs: bidIDs, + highestBidID: option::none(), + owner: sender(ctx), + deposit: balance, + minPrice: minPrice + }; + + // push into house obag + house.auctionIDs.push_back(uid_to_inner(&new_auction.id)); + house.auctionBag.add(uid_to_inner(&new_auction.id), new_auction); +} + +public entry fun createBidding( + house: &mut AuctionHouse, + // by auction id or nft? + auction_id: ID, + bid: Coin, + ctx: &mut TxContext, +) { + // verify id + assert!(house.auctionBag.contains(auction_id), E_AUCTION_NO_EXIST); + + // get current auction + let auction: &mut Auction = house.auctionBag.borrow_mut(auction_id); + + // candidate bid does not meet the minimum requirement + assert!(bid.value()>=auction.minPrice, E_BID_TOO_LOW); + + //Compare with previous + if (option::is_some(&auction.highestBidID)){ + //candidate not higher than previous bid (assuming exists) + let highest_bid: &Bid = auction.bidBag.borrow(auction.highestBidID); + assert!(bid.value() > highest_bid.balance.value(), E_BID_TOO_LOW); + }; + + + // create new bid + // convert first to enforce SUI type + let balance: Balance = bid.into_balance(); + let new_bid = Bid { + id: object::new(ctx), + amount: balance.value(), + balance: balance, + time_placed: ctx.epoch(), + owner: sender(ctx), + }; + + //Safe Check Against Reentrancy - Update State then Refund + + + // update auction.highest_bid + let previousHighestID:Option =option::swap_or_fill(&mut auction.highestBidID,uid_to_inner(&new_bid.id)); + + // add bid amounts + auction.bidIDs.push_back(uid_to_inner(&new_bid.id)); + auction.bidBag.add(uid_to_inner(&new_bid.id), new_bid); + + // transfer back to previous owner + if (option::is_some(&previousHighestID)){ + let highest_bid: &mut Bid = auction.bidBag.borrow_mut(previousHighestID); + let coin: Coin = highest_bid.balance.withdraw_all().into_coin(ctx); + transfer::public_transfer(coin, highest_bid.owner); + }; + +} + +public entry fun globalTimedTasks( + house: &mut AuctionHouse, + ctx: &mut TxContext +) { + let current_time = ctx.epoch(); + + let mut index = 0; + let length = house.auctionIDs.length(); + while (index < length) { + let auction: &Auction = house.auctionBag.borrow(house.auctionIDs[index]); + if (current_time < auction.ending) { + // not expired yet + index=index+1; + continue + }; + + let auction: Auction = house.auctionBag.remove(house.auctionIDs[index]); + // remove from vec + let (_, _index) = house.auctionIDs.index_of(&uid_to_inner(&auction.id)); // O(n) but idc + house.auctionIDs.remove(_index); + + //Destructure Auction and Bid + let Auction { + id, + mut nft, + mut bidBag, + bidIDs:_, + starting:_, + ending:_, + highestBidID, + owner:auction_owner, + minPrice: _, + mut deposit, + } = auction; + + //People Did Buy (Take The Highest Bidder) + if (option::is_some(&highestBidID)){ + transfer::public_transfer(deposit.withdraw_all().into_coin(ctx), auction_owner); + + let highest_bid: Bid = obag::remove(&mut bidBag,highestBidID); + + let Bid { + id, + amount, + mut balance, + time_placed, + owner, + } = highest_bid; + + //Create Bid Record + let bid_record=Bid{ + id: object::new(ctx), + amount: amount, + balance: balance::zero(), + time_placed: time_placed, + owner: owner + }; + + nft.addAuctionRecord(uid_to_inner(&bid_record.id)); + transfer::share_object(bid_record); + + let mut coin= balance.withdraw_all().into_coin(ctx); + let taxed_amount = (coin.value() * AUCTION_TAX_PERCENTAGE) / 100; + let taxed_coin: Coin = coin.split(taxed_amount, ctx); + + // transfer tax to house earnings + house.earnings.join(taxed_coin.into_balance()); + // transfer highest bid coin to auction starter + transfer::public_transfer(coin, auction_owner); + + //Transfer NFT + transfer::public_transfer(nft,owner); + + destroy_zero(balance); + object::delete(id); + } + + //Nobody Bought - Take The Deposit + else{ + let mut coin=deposit.withdraw_all().into_coin(ctx); + + let taxed_amount = (coin.value() * AUCTION_TAX_PERCENTAGE) / 100; + let taxed_coin: Coin = coin.split(taxed_amount, ctx); + + // transfer tax to house earnings + house.earnings.join(taxed_coin.into_balance()); + // transfer deposit and nft to auction starter + transfer::public_transfer(coin, auction_owner); + transfer::public_transfer(nft, auction_owner); + + }; + + //Delete Everything + balance::destroy_zero(deposit); + obag::destroy_empty(bidBag); + object::delete(id); + + index = index + 1; + } +} + +public entry fun withdrawEarnings( + house: &mut AuctionHouse, + ctx: &mut TxContext +) { + assert!(ctx.sender() == house.owner, E_NOT_OWNER); + + let balance: Balance = house.earnings.withdraw_all(); + let coin: Coin = balance.into_coin(ctx); + transfer::public_transfer(coin, house.owner); +} \ No newline at end of file From cafa429864c547a936da54ca214511b82864e544 Mon Sep 17 00:00:00 2001 From: wz-Tan Date: Thu, 7 Aug 2025 16:32:33 +0800 Subject: [PATCH 3/6] Routing --- devmatch2/src/constants.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/devmatch2/src/constants.ts b/devmatch2/src/constants.ts index 0ffffc2..ffd19c5 100644 --- a/devmatch2/src/constants.ts +++ b/devmatch2/src/constants.ts @@ -1,3 +1,6 @@ -export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO"; -export const TESTNET_COUNTER_PACKAGE_ID = "0xTODO"; -export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO"; + export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO"; + export const TESTNET_COUNTER_PACKAGE_ID = "0x868338ebc54baea8fa95225a0969e26980e5739932cae1058661b3c45f1c2cda"; + export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO"; + export const MARKETPLACE_ID="0x2c4a65d789f181ccae575ed5f79cb8e7d25ce777f924842a63f10721112ba187"; + export const AUCTIONHOUSE_ID="0x2cd0d9776bd9dcf99dfe9afbe26bb8e5c7fbc19d33c3ff6135e9ef78faec1b8c"; + export const NFT_TYPE= "0x868338ebc54baea8fa95225a0969e26980e5739932cae1058661b3c45f1c2cda::nft::NFT"; \ No newline at end of file From c19c7a7ce4545f08ffe180f18bfb21b107010b2b Mon Sep 17 00:00:00 2001 From: wz-Tan Date: Thu, 7 Aug 2025 20:25:27 +0800 Subject: [PATCH 4/6] Minting Page --- devmatch2/src/Counter.tsx | 106 -------------------------------- devmatch2/src/networkConfig.ts | 24 +++----- devmatch2/src/pages/Minting.tsx | 98 +++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 120 deletions(-) delete mode 100644 devmatch2/src/Counter.tsx create mode 100644 devmatch2/src/pages/Minting.tsx diff --git a/devmatch2/src/Counter.tsx b/devmatch2/src/Counter.tsx deleted file mode 100644 index 3ad530d..0000000 --- a/devmatch2/src/Counter.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { - useCurrentAccount, - useSignAndExecuteTransaction, - useSuiClient, - useSuiClientQuery, -} from "@mysten/dapp-kit"; -import type { SuiObjectData } from "@mysten/sui/client"; -import { Transaction } from "@mysten/sui/transactions"; -import { Button, Flex, Heading, Text } from "@radix-ui/themes"; -import { useNetworkVariable } from "./networkConfig"; -import { useState } from "react"; -import ClipLoader from "react-spinners/ClipLoader"; - -export function Counter({ id }: { id: string }) { - const counterPackageId = useNetworkVariable("counterPackageId"); - const suiClient = useSuiClient(); - const currentAccount = useCurrentAccount(); - const { mutate: signAndExecute } = useSignAndExecuteTransaction(); - const { data, isPending, error, refetch } = useSuiClientQuery("getObject", { - id, - options: { - showContent: true, - showOwner: true, - }, - }); - - const [waitingForTxn, setWaitingForTxn] = useState(""); - - const executeMoveCall = (method: "increment" | "reset") => { - setWaitingForTxn(method); - - const tx = new Transaction(); - - if (method === "reset") { - tx.moveCall({ - arguments: [tx.object(id), tx.pure.u64(0)], - target: `${counterPackageId}::counter::set_value`, - }); - } else { - tx.moveCall({ - arguments: [tx.object(id)], - target: `${counterPackageId}::counter::increment`, - }); - } - - signAndExecute( - { - transaction: tx, - }, - { - onSuccess: (tx) => { - suiClient.waitForTransaction({ digest: tx.digest }).then(async () => { - await refetch(); - setWaitingForTxn(""); - }); - }, - }, - ); - }; - - if (isPending) return Loading...; - - if (error) return Error: {error.message}; - - if (!data.data) return Not found; - - const ownedByCurrentAccount = - getCounterFields(data.data)?.owner === currentAccount?.address; - - return ( - <> - Counter {id} - - - Count: {getCounterFields(data.data)?.value} - - - {ownedByCurrentAccount ? ( - - ) : null} - - - - ); -} -function getCounterFields(data: SuiObjectData) { - if (data.content?.dataType !== "moveObject") { - return null; - } - - return data.content.fields as { value: number; owner: string }; -} diff --git a/devmatch2/src/networkConfig.ts b/devmatch2/src/networkConfig.ts index 7b6f8b7..5824c51 100644 --- a/devmatch2/src/networkConfig.ts +++ b/devmatch2/src/networkConfig.ts @@ -1,30 +1,26 @@ import { getFullnodeUrl } from "@mysten/sui/client"; -import { - DEVNET_COUNTER_PACKAGE_ID, - TESTNET_COUNTER_PACKAGE_ID, - MAINNET_COUNTER_PACKAGE_ID, -} from "./constants.ts"; import { createNetworkConfig } from "@mysten/dapp-kit"; +import { DEVNET_COUNTER_PACKAGE_ID, MAINNET_COUNTER_PACKAGE_ID, TESTNET_COUNTER_PACKAGE_ID } from "./constants"; const { networkConfig, useNetworkVariable, useNetworkVariables } = createNetworkConfig({ devnet: { url: getFullnodeUrl("devnet"), - variables: { - counterPackageId: DEVNET_COUNTER_PACKAGE_ID, - }, + variables:{ + PackageId: MAINNET_COUNTER_PACKAGE_ID, + } }, testnet: { url: getFullnodeUrl("testnet"), - variables: { - counterPackageId: TESTNET_COUNTER_PACKAGE_ID, - }, + variables:{ + PackageId: TESTNET_COUNTER_PACKAGE_ID, + } }, mainnet: { url: getFullnodeUrl("mainnet"), - variables: { - counterPackageId: MAINNET_COUNTER_PACKAGE_ID, - }, + variables:{ + PackageId: DEVNET_COUNTER_PACKAGE_ID, + } }, }); diff --git a/devmatch2/src/pages/Minting.tsx b/devmatch2/src/pages/Minting.tsx new file mode 100644 index 0000000..092c8f5 --- /dev/null +++ b/devmatch2/src/pages/Minting.tsx @@ -0,0 +1,98 @@ +import{ useState } from 'react' +import { useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit'; +import { Transaction } from '@mysten/sui/transactions'; +import { useNetworkVariable } from '../networkConfig'; + +const Minting = () => { + const packageID = useNetworkVariable("PackageId"); + const suiClient = useSuiClient(); + + const { + mutate: signAndExecute, + } = useSignAndExecuteTransaction(); + + async function mintNFT(seed: number, name: string, description: string, mediaURL: string) { + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.pure.u64(seed), tx.pure.string(name), tx.pure.string(description), tx.pure.string(mediaURL)], + target: `${packageID}::nft::mint_NFT` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + + } + } + ) + } + + function getSeed(): number { + return Math.floor(Math.random() * 100000); + } + + + const [imageURL, setImageURL] = useState("") + const [imageName, setImageName] = useState("") + const [imageDescription, setImageDescription] = useState("") + + return ( + <> +
+

Mint NFT Here

+ + {setImageName(e.target.value)}} + /> + + + {setImageDescription(e.target.value)}} + /> + + + {setImageURL(e.target.value)}} + /> + +

Image View Here

+ + + + + +
+ + ) +} + +export default Minting \ No newline at end of file From 20bb52ccdb2d916ff682c0ad16dd81c31aa42d57 Mon Sep 17 00:00:00 2001 From: AngJianming Date: Sat, 9 Aug 2025 00:00:09 +0800 Subject: [PATCH 5/6] feat: added frontned, configure README.md, .gitignore .env --- devmatch2/README.md | 184 +++++------ devmatch2/index.html | 118 +++---- devmatch2/package.json | 80 ++--- devmatch2/prettier.config.cjs | 8 +- devmatch2/src/App.tsx | 91 ++---- devmatch2/src/Layout.tsx | 17 + devmatch2/src/OwnedObjects.tsx | 42 +++ devmatch2/src/WalletStatus.tsx | 22 ++ devmatch2/src/components/AuctionPopUp.tsx | 182 +++++++++++ devmatch2/src/components/ListingPopUp.tsx | 171 ++++++++++ devmatch2/src/components/Navbar.tsx | 23 ++ devmatch2/src/components/nftCard.tsx | 42 +++ devmatch2/src/constants.ts | 10 +- devmatch2/src/index.css | 2 + devmatch2/src/main.tsx | 50 ++- devmatch2/src/marketplace.tsx | 374 ++++++++++++++++++---- devmatch2/src/networkConfig.ts | 54 ++-- devmatch2/src/pages/Auction.tsx | 176 ++++++++++ devmatch2/src/pages/Landing.tsx | 193 +++++++++++ devmatch2/src/pages/Minting.tsx | 233 ++++++++------ devmatch2/src/vite-env.d.ts | 2 +- devmatch2/tsconfig.json | 50 +-- devmatch2/tsconfig.node.json | 20 +- devmatch2/vite.config.mts | 14 +- 24 files changed, 1648 insertions(+), 510 deletions(-) create mode 100644 devmatch2/src/Layout.tsx create mode 100644 devmatch2/src/OwnedObjects.tsx create mode 100644 devmatch2/src/WalletStatus.tsx create mode 100644 devmatch2/src/components/AuctionPopUp.tsx create mode 100644 devmatch2/src/components/ListingPopUp.tsx create mode 100644 devmatch2/src/components/Navbar.tsx create mode 100644 devmatch2/src/components/nftCard.tsx create mode 100644 devmatch2/src/index.css create mode 100644 devmatch2/src/pages/Auction.tsx create mode 100644 devmatch2/src/pages/Landing.tsx diff --git a/devmatch2/README.md b/devmatch2/README.md index 3dcde73..74f1cb6 100644 --- a/devmatch2/README.md +++ b/devmatch2/README.md @@ -1,92 +1,92 @@ -# Sui dApp Starter Template - -This dApp was created using `@mysten/create-dapp` that sets up a basic React -Client dApp using the following tools: - -- [React](https://react.dev/) as the UI framework -- [TypeScript](https://www.typescriptlang.org/) for type checking -- [Vite](https://vitejs.dev/) for build tooling -- [Radix UI](https://www.radix-ui.com/) for pre-built UI components -- [ESLint](https://eslint.org/) for linting -- [`@mysten/dapp-kit`](https://sdk.mystenlabs.com/dapp-kit) for connecting to - wallets and loading data -- [pnpm](https://pnpm.io/) for package management - -For a full guide on how to build this dApp from scratch, visit this -[guide](http://docs.sui.io/guides/developer/app-examples/e2e-counter#frontend). - -## Deploying your Move code - -### Install Sui cli - -Before deploying your move code, ensure that you have installed the Sui CLI. You -can follow the [Sui installation instruction](https://docs.sui.io/build/install) -to get everything set up. - -This template uses `testnet` by default, so we'll need to set up a testnet -environment in the CLI: - -```bash -sui client new-env --alias testnet --rpc https://fullnode.testnet.sui.io:443 -sui client switch --env testnet -``` - -If you haven't set up an address in the sui client yet, you can use the -following command to get a new address: - -```bash -sui client new-address secp256k1 -``` - -This well generate a new address and recover phrase for you. You can mark a -newly created address as you active address by running the following command -with your new address: - -```bash -sui client switch --address 0xYOUR_ADDRESS... -``` - -We can ensure we have some Sui in our new wallet by requesting Sui from the -faucet `https://faucet.sui.io`. - -### Publishing the move package - -The move code for this template is located in the `move` directory. To publish -it, you can enter the `move` directory, and publish it with the Sui CLI: - -```bash -cd move -sui client publish --gas-budget 100000000 counter -``` - -In the output there will be an object with a `"packageId"` property. You'll want -to save that package ID to the `src/constants.ts` file as `PACKAGE_ID`: - -```ts -export const TESTNET_COUNTER_PACKAGE_ID = ""; -``` - -Now that we have published the move code, and update the package ID, we can -start the app. - -## Starting your dApp - -To install dependencies you can run - -```bash -pnpm install -``` - -To start your dApp in development mode run - -```bash -pnpm dev -``` - -## Building - -To build your app for deployment you can run - -```bash -pnpm build -``` +# Sui dApp Starter Template + +This dApp was created using `@mysten/create-dapp` that sets up a basic React +Client dApp using the following tools: + +- [React](https://react.dev/) as the UI framework +- [TypeScript](https://www.typescriptlang.org/) for type checking +- [Vite](https://vitejs.dev/) for build tooling +- [Radix UI](https://www.radix-ui.com/) for pre-built UI components +- [ESLint](https://eslint.org/) for linting +- [`@mysten/dapp-kit`](https://sdk.mystenlabs.com/dapp-kit) for connecting to + wallets and loading data +- [pnpm](https://pnpm.io/) for package management + +For a full guide on how to build this dApp from scratch, visit this +[guide](http://docs.sui.io/guides/developer/app-examples/e2e-counter#frontend). + +## Deploying your Move code + +### Install Sui cli + +Before deploying your move code, ensure that you have installed the Sui CLI. You +can follow the [Sui installation instruction](https://docs.sui.io/build/install) +to get everything set up. + +This template uses `testnet` by default, so we'll need to set up a testnet +environment in the CLI: + +```bash +sui client new-env --alias testnet --rpc https://fullnode.testnet.sui.io:443 +sui client switch --env testnet +``` + +If you haven't set up an address in the sui client yet, you can use the +following command to get a new address: + +```bash +sui client new-address secp256k1 +``` + +This well generate a new address and recover phrase for you. You can mark a +newly created address as you active address by running the following command +with your new address: + +```bash +sui client switch --address 0xYOUR_ADDRESS... +``` + +We can ensure we have some Sui in our new wallet by requesting Sui from the +faucet `https://faucet.sui.io`. + +### Publishing the move package + +The move code for this template is located in the `move` directory. To publish +it, you can enter the `move` directory, and publish it with the Sui CLI: + +```bash +cd move +sui client publish --gas-budget 100000000 counter +``` + +In the output there will be an object with a `"packageId"` property. You'll want +to save that package ID to the `src/constants.ts` file as `PACKAGE_ID`: + +```ts +export const TESTNET_COUNTER_PACKAGE_ID = ""; +``` + +Now that we have published the move code, and update the package ID, we can +start the app. + +## Starting your dApp + +To install dependencies you can run + +```bash +pnpm install +``` + +To start your dApp in development mode run + +```bash +pnpm dev +``` + +## Building + +To build your app for deployment you can run + +```bash +pnpm build +``` diff --git a/devmatch2/index.html b/devmatch2/index.html index bb7f75b..8a7f3b5 100644 --- a/devmatch2/index.html +++ b/devmatch2/index.html @@ -1,59 +1,59 @@ - - - - - - - Sui dApp Starter - - - - -
- - - + + + + + + + Sui dApp Starter + + + + +
+ + + diff --git a/devmatch2/package.json b/devmatch2/package.json index d326169..92c9ec8 100644 --- a/devmatch2/package.json +++ b/devmatch2/package.json @@ -1,36 +1,44 @@ -{ - "name": "devmatch2", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" - }, - "dependencies": { - "@mysten/dapp-kit": "0.17.2", - "@mysten/sui": "1.37.1", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/themes": "^3.2.1", - "@tanstack/react-query": "^5.83.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-spinners": "^0.14.1" - }, - "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.38.0", - "@typescript-eslint/parser": "^8.38.0", - "@vitejs/plugin-react-swc": "^3.11.0", - "eslint": "^9.17.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "prettier": "^3.6.2", - "typescript": "^5.8.3", - "vite": "^7.0.5" - } -} \ No newline at end of file +{ + "name": "test_smartcontract", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@mysten/dapp-kit": "0.17.2", + "@mysten/sui": "1.37.1", + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-form": "^0.1.7", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/themes": "^3.2.1", + "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-query": "^5.83.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.7.1", + "supabase": "^2.33.9" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.7", + "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/parser": "^8.38.0", + "@vitejs/plugin-react-swc": "^3.11.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "tailwindcss": "^4.1.11", + "typescript": "^5.8.3", + "vite": "^7.0.5" + } +} diff --git a/devmatch2/prettier.config.cjs b/devmatch2/prettier.config.cjs index c075411..f7d9739 100644 --- a/devmatch2/prettier.config.cjs +++ b/devmatch2/prettier.config.cjs @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-undef -module.exports = { - proseWrap: "always", -}; +// eslint-disable-next-line no-undef +module.exports = { + proseWrap: "always", +}; diff --git a/devmatch2/src/App.tsx b/devmatch2/src/App.tsx index d5b7571..96c0c1b 100644 --- a/devmatch2/src/App.tsx +++ b/devmatch2/src/App.tsx @@ -1,61 +1,30 @@ -import { ConnectButton, useCurrentAccount } from "@mysten/dapp-kit"; -import { isValidSuiObjectId } from "@mysten/sui/utils"; -import { Box, Container, Flex, Heading } from "@radix-ui/themes"; -import { useState } from "react"; -import { Counter } from "./Counter"; -import { CreateCounter } from "./marketplace"; - -function App() { - const currentAccount = useCurrentAccount(); - const [counterId, setCounter] = useState(() => { - const hash = window.location.hash.slice(1); - return isValidSuiObjectId(hash) ? hash : null; - }); - - return ( - <> - - - dApp Starter Template - - - - - - - - - {currentAccount ? ( - counterId ? ( - - ) : ( - { - window.location.hash = id; - setCounter(id); - }} - /> - ) - ) : ( - Please connect your wallet - )} - - - - ); -} - -export default App; +// import { ConnectButton, useCurrentAccount } from "@mysten/dapp-kit"; +// import { Box, Container, Flex, Heading } from "@radix-ui/themes"; +// import { WalletStatus } from "./WalletStatus"; +import { Marketplace } from "./marketplace"; +import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from "react-router-dom"; +import "./index.css" +import Layout from "./Layout"; +import Minting from "./pages/Minting"; +import Landing from "./pages/Landing"; +import Auction from "./pages/Auction"; + +function App() { + + const router=createBrowserRouter( + createRoutesFromElements( + }> + }/> + }/> + }/> + }/> + + ) + ) + + return ( + + ); +} + +export default App; diff --git a/devmatch2/src/Layout.tsx b/devmatch2/src/Layout.tsx new file mode 100644 index 0000000..80244af --- /dev/null +++ b/devmatch2/src/Layout.tsx @@ -0,0 +1,17 @@ +// import React from 'react' +// import { ConnectButton } from '@mysten/dapp-kit' +// import { Marketplace } from './marketplace' +import Navbar from './components/Navbar' +import { Outlet } from 'react-router-dom' + +const Layout = () => { + return ( +
+ + +
+ + ) +} + +export default Layout \ No newline at end of file diff --git a/devmatch2/src/OwnedObjects.tsx b/devmatch2/src/OwnedObjects.tsx new file mode 100644 index 0000000..566a4da --- /dev/null +++ b/devmatch2/src/OwnedObjects.tsx @@ -0,0 +1,42 @@ +import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit"; +import { Flex, Heading, Text } from "@radix-ui/themes"; + +export function OwnedObjects() { + const account = useCurrentAccount(); + const { data, isPending, error } = useSuiClientQuery( + "getOwnedObjects", + { + owner: account?.address as string, + }, + { + enabled: !!account, + }, + ); + + if (!account) { + return; + } + + if (error) { + return Error: {error.message}; + } + + if (isPending || !data) { + return Loading...; + } + + return ( + + {data.data.length === 0 ? ( + No objects owned by the connected wallet + ) : ( + Objects owned by the connected wallet + )} + {data.data.map((object) => ( + + Object ID: {object.data?.objectId} + + ))} + + ); +} diff --git a/devmatch2/src/WalletStatus.tsx b/devmatch2/src/WalletStatus.tsx new file mode 100644 index 0000000..8176b4a --- /dev/null +++ b/devmatch2/src/WalletStatus.tsx @@ -0,0 +1,22 @@ +import { useCurrentAccount } from '@mysten/dapp-kit'; +import { Container, Flex, /* Heading */ Text } from '@radix-ui/themes'; + +export function WalletStatus() { + const account = useCurrentAccount(); + + return ( + + + {account ? ( + + Wallet connected + Address: {account.address} + + ) : ( + Wallet not connected + )} + + + ); +} + diff --git a/devmatch2/src/components/AuctionPopUp.tsx b/devmatch2/src/components/AuctionPopUp.tsx new file mode 100644 index 0000000..685fefd --- /dev/null +++ b/devmatch2/src/components/AuctionPopUp.tsx @@ -0,0 +1,182 @@ +import { useState } from 'react'; +import '../index.css'; + + +const ListingPopup = ({ isOpen, onClose, startAuction, userNFTs }: { isOpen: boolean, onClose: any, startAuction:any, userNFTs: any[] }) => { + + const [selectedNFT, setSelectedNFT] = useState(""); + const [price, setPrice] = useState(''); + const [duration, setDuration] = useState('7'); + + const handleNFTSelect = (nftId: string) => { + setSelectedNFT(nftId === selectedNFT ? '' : nftId); + }; + + const handleSubmitListing = () => { + if (selectedNFT === "") { + alert('Please select at least one NFT'); + return; + } + if (!price) { + alert('Please enter a price'); + return; + } + + const listingData = { + nftId: selectedNFT, + //Round Down to Smallest Unit of Billion + price: Math.floor(parseFloat(price)*1e9), + duration: parseInt(duration)*86400 + }; + + + startAuction( listingData.price, listingData.duration, listingData.nftId.id); + + // Reset form and close popup + setSelectedNFT(""); + setPrice(''); + setDuration('7'); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+

Auction Your NFTs

+ +
+ +
+ {/* NFT Selection Grid */} +
+

+ Select NFT To Auction +

+ + {userNFTs.length === 0 ? ( +
+
๐Ÿ“ฆ
+

No NFTs found in your wallet

+

Connect your wallet or mint some NFTs first

+
+ ) : ( +
+ {userNFTs.map((nft, key) => ( +
handleNFTSelect(nft.id)} + > +
+ {nft.name} +
+
+

{nft.name}

+

{nft.collection}

+ {selectedNFT === nft.id && ( +
+ + Selected โœ“ + +
+ )} +
+
+ ))} +
+ )} +
+ + {/* Listing Configuration */} + {selectedNFT!="" ? +
+

Listing Details - Users Need To Pay 0.1 SUI as Deposit (Taxed 5% If No One Bids)

+ {/* Price */} +
+ + { + if (parseFloat(e.target.value)<0){ + setPrice("0") + } + else{ + setPrice(e.target.value) + } + + } + } + placeholder="0.00" + className="w-full bg-gray-800 text-white px-4 py-2 rounded-lg border border-gray-600 focus:border-orange-500 focus:outline-none" + /> +
+ + + {/* Auction Duration */} +
+ + +
+
+ : null + } + + {/* Footer */} +
+ +
+ + +
+
+
+
+
+ ); +}; + +export default ListingPopup; \ No newline at end of file diff --git a/devmatch2/src/components/ListingPopUp.tsx b/devmatch2/src/components/ListingPopUp.tsx new file mode 100644 index 0000000..f389baa --- /dev/null +++ b/devmatch2/src/components/ListingPopUp.tsx @@ -0,0 +1,171 @@ +import { useState } from 'react'; +import '../index.css'; + + + +const ListingPopup = ({ isOpen, onClose, createListing, userNFTs }: { isOpen: boolean, onClose: any, createListing:any, userNFTs: any[] }) => { + + const [selectedNFT, setSelectedNFT] = useState(""); + const [listingType, setListingType] = useState('fixed'); + const [price, setPrice] = useState(''); + const [duration, setDuration] = useState('7'); + + const handleNFTSelect = (nftId: string) => { + setSelectedNFT(nftId === selectedNFT ? '' : nftId); + console.log("Currently Selected" , selectedNFT) + }; + + const handleSubmitListing = () => { + if (selectedNFT === "") { + alert('Please select at least one NFT'); + return; + } + if (!price) { + alert('Please enter a price'); + return; + } + + const listingData = { + nftId: selectedNFT, + type: listingType, + //Round Down to Smallest Unit of Billion + price: Math.floor(parseFloat(price)*1e9), + duration: listingType === 'auction' ? duration : null + }; + + + createListing(listingData.nftId.id, listingData.price); + + // Reset form and close popup + setSelectedNFT(""); + setPrice(''); + setDuration('7'); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+

List Your NFTs

+ +
+ +
+ {/* NFT Selection Grid */} +
+

+ Select NFT To List +

+ + {userNFTs.length === 0 ? ( +
+
๐Ÿ“ฆ
+

No NFTs found in your wallet

+

Connect your wallet or mint some NFTs first

+
+ ) : ( +
+ {userNFTs.map((nft, key) => ( +
handleNFTSelect(nft.id)} + > +
+ {nft.name} +
+
+

{nft.name}

+

{nft.collection}

+ {selectedNFT === nft.id && ( +
+ + Selected โœ“ + +
+ )} +
+
+ ))} +
+ )} +
+ + {/* Listing Configuration */} + {selectedNFT != "" && ( +
+

Listing Details

+ + {/* Listing Type */} + + + {/* Price */} +
+ + { + if (parseFloat(e.target.value)<0){ + setPrice("0") + } + else{ + setPrice(e.target.value) + } + } + + } + placeholder="0.00" + className="w-full bg-gray-800 text-white px-4 py-2 rounded-lg border border-gray-600 focus:border-orange-500 focus:outline-none" + /> +
+ + +
+ )} +
+ + {/* Footer */} +
+ +
+ + +
+
+
+
+ ); +}; + +export default ListingPopup; \ No newline at end of file diff --git a/devmatch2/src/components/Navbar.tsx b/devmatch2/src/components/Navbar.tsx new file mode 100644 index 0000000..73d389f --- /dev/null +++ b/devmatch2/src/components/Navbar.tsx @@ -0,0 +1,23 @@ +import { ConnectButton } from "@mysten/dapp-kit"; +import { Link } from "react-router-dom"; +// import React from "react" +import '../index.css'; + + +function Navbar(){ + return( +
+
Move Market
+ + +
+ ) + +} + +export default Navbar; \ No newline at end of file diff --git a/devmatch2/src/components/nftCard.tsx b/devmatch2/src/components/nftCard.tsx new file mode 100644 index 0000000..31a73b1 --- /dev/null +++ b/devmatch2/src/components/nftCard.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import '../index.css'; + + + +const NFTCard=({listing, buyListing} : {listing: any, buyListing: any})=>{ + let nft=listing.nft.fields; + + return( +
+
+
+
+

Price-

+

{listing.price}

+
+
+
+ +
+
+
+
+

NFT name

+ {nft.name} +
+
countdown
+
+
+ +
+
+
+ ) +} + + +export default NFTCard; \ No newline at end of file diff --git a/devmatch2/src/constants.ts b/devmatch2/src/constants.ts index ffd19c5..3a824f0 100644 --- a/devmatch2/src/constants.ts +++ b/devmatch2/src/constants.ts @@ -1,6 +1,6 @@ - export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO"; - export const TESTNET_COUNTER_PACKAGE_ID = "0x868338ebc54baea8fa95225a0969e26980e5739932cae1058661b3c45f1c2cda"; - export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO"; - export const MARKETPLACE_ID="0x2c4a65d789f181ccae575ed5f79cb8e7d25ce777f924842a63f10721112ba187"; - export const AUCTIONHOUSE_ID="0x2cd0d9776bd9dcf99dfe9afbe26bb8e5c7fbc19d33c3ff6135e9ef78faec1b8c"; + export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO"; + export const TESTNET_COUNTER_PACKAGE_ID = "0x868338ebc54baea8fa95225a0969e26980e5739932cae1058661b3c45f1c2cda"; + export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO"; + export const MARKETPLACE_ID="0x2c4a65d789f181ccae575ed5f79cb8e7d25ce777f924842a63f10721112ba187"; + export const AUCTIONHOUSE_ID="0x2cd0d9776bd9dcf99dfe9afbe26bb8e5c7fbc19d33c3ff6135e9ef78faec1b8c"; export const NFT_TYPE= "0x868338ebc54baea8fa95225a0969e26980e5739932cae1058661b3c45f1c2cda::nft::NFT"; \ No newline at end of file diff --git a/devmatch2/src/index.css b/devmatch2/src/index.css new file mode 100644 index 0000000..541bd11 --- /dev/null +++ b/devmatch2/src/index.css @@ -0,0 +1,2 @@ +@import "tailwindcss"; +@tailwind utilities; \ No newline at end of file diff --git a/devmatch2/src/main.tsx b/devmatch2/src/main.tsx index 55f323a..5a36ba3 100644 --- a/devmatch2/src/main.tsx +++ b/devmatch2/src/main.tsx @@ -1,26 +1,24 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import "@mysten/dapp-kit/dist/index.css"; -import "@radix-ui/themes/styles.css"; - -import { SuiClientProvider, WalletProvider } from "@mysten/dapp-kit"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { Theme } from "@radix-ui/themes"; -import App from "./App.tsx"; -import { networkConfig } from "./networkConfig.ts"; - -const queryClient = new QueryClient(); - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - - - - - - - - - , -); +// import React from "react"; +import ReactDOM from "react-dom/client"; +import "@mysten/dapp-kit/dist/index.css"; +import "@radix-ui/themes/styles.css"; + +import { SuiClientProvider, WalletProvider } from "@mysten/dapp-kit"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Theme } from "@radix-ui/themes"; +import App from "./App.tsx"; +import { networkConfig } from "./networkConfig.ts"; + +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + + + +); diff --git a/devmatch2/src/marketplace.tsx b/devmatch2/src/marketplace.tsx index 32d8aaf..b267126 100644 --- a/devmatch2/src/marketplace.tsx +++ b/devmatch2/src/marketplace.tsx @@ -1,60 +1,314 @@ -import { Transaction } from "@mysten/sui/transactions"; -import { Button, Container } from "@radix-ui/themes"; -import { useSignAndExecuteTransaction, useSuiClient } from "@mysten/dapp-kit"; -import { useNetworkVariable } from "./networkConfig"; -import ClipLoader from "react-spinners/ClipLoader"; - -export function CreateCounter({ - onCreated, -}: { - onCreated: (id: string) => void; -}) { - const counterPackageId = useNetworkVariable("counterPackageId"); - const suiClient = useSuiClient(); - const { - mutate: signAndExecute, - isSuccess, - isPending, - } = useSignAndExecuteTransaction(); - - function create() { - const tx = new Transaction(); - - tx.moveCall({ - arguments: [], - target: `${counterPackageId}::counter::create`, - }); - - signAndExecute( - { - transaction: tx, - }, - { - onSuccess: async ({ digest }) => { - const { effects } = await suiClient.waitForTransaction({ - digest: digest, - options: { - showEffects: true, - }, - }); - - onCreated(effects?.created?.[0]?.reference?.objectId!); - }, - }, - ); - } - - return ( - - - - ); -} +import { useState, useEffect } from "react"; +import { useNetworkVariable } from "./networkConfig"; +import { useCurrentAccount, useSignAndExecuteTransaction, useSuiClient } from "@mysten/dapp-kit"; +import { Button, Flex } from "@radix-ui/themes"; +import { Transaction } from "@mysten/sui/transactions"; +import { MARKETPLACE_ID, NFT_TYPE } from "./constants"; +// import { CoinStruct } from "@mysten/sui/client"; +import NFTCard from "./components/nftCard"; + +export function Marketplace() { + let userAccount = useCurrentAccount(); + let marketplace; + let marketplaceInfo; + let listingBagFields; + let listingBag: any; + let actualListings: object[] = []; + let listingIdList: string[] = []; + let [displayList, setDisplayList] = useState([]) + let [chooseNFT, setChooseNFT] = useState(false) + + let payments; + const packageID = useNetworkVariable("PackageId"); + const suiClient = useSuiClient(); + const [loading, setLoading] = useState(true); + const { + mutate: signAndExecute, + isSuccess, + isPending + } = useSignAndExecuteTransaction(); + + //Retrieve Marketplace First + useEffect(() => { + const fetchMarketplace = async () => { + //Get Marketplace + marketplace = await suiClient.getObject({ + id: MARKETPLACE_ID, + options: { + showContent: true, + showType: true + } + }); + + //Get Required Fields + marketplaceInfo = marketplace?.data?.content; + if (marketplaceInfo?.dataType === "moveObject") { + payments = (marketplaceInfo as any).fields.payments + listingBag = (marketplaceInfo as any).fields.listingBag + } + + //Get Listing IDs + listingBagFields = await suiClient.getDynamicFields({ parentId: listingBag.fields.id.id }) + listingBagFields = listingBagFields.data + + listingBagFields.map((listing) => { + listingIdList.push(listing.objectId) + }) + + //Add the Listing Info Into actualListings + await Promise.all(listingIdList.map(async (listingId) => { + let currListing = await suiClient.getObject({ + id: listingId, + options: { + showContent: true, + showType: true + } + }); + + if (currListing.data?.content?.dataType === "moveObject") actualListings.push(currListing.data?.content?.fields) + })) + + console.log(actualListings) + setDisplayList(actualListings) + setLoading(false) + } + fetchMarketplace() + }, + []); + + let [userNFTs, setUserNFTs]= useState([]); + + async function retrieveOwnedNFT() { + setUserNFTs([]) + if (!userAccount?.address) return; + let res = await suiClient.getOwnedObjects({ + owner: userAccount?.address, + options: { + showType: true + } + }); + + let ownedAsset = res.data; + + //Filter Out Valid NFTs + for (var asset of ownedAsset) { + if (asset.data?.type === NFT_TYPE) { + let objectId=asset.data.objectId + setUserNFTs(userNFTs => [...userNFTs, objectId]) + console.log(userNFTs) + } + } + } + + + function createListing(nft: string, price: number) { + retrieveOwnedNFT(); + + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID), tx.object(nft), tx.pure.u64(price*1e9)], + target: `${packageID}::marketplace::createListing` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + setLoading(false); + } + } + ) + + + } + + //Pass in the object ID -> The Key for the Listing + function deleteListing(listing_id: string) { + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID), tx.object(listing_id)], + target: `${packageID}::marketplace::deleteListing` + }); + + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + setLoading(false); + } + } + ) + } + + function withdrawRevenue(){ + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID)], + target: `${packageID}::marketplace::withdrawRevenue` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + setLoading(false); + } + } + ) + } + + async function buyListing(listing_id: string) { + const tx = new Transaction(); + let coins: any; + let validCoins: string[] = []; + let validCoin; + + let listing=await suiClient.getObject({ + id: listing_id, + options:{showContent: true} + }) + + + let listing_price; + + if (listing.data?.content?.dataType==="moveObject"){ + listing_price= Number((listing as any).data?.content?.fields.price)} + + + const fees=tx.splitCoins(tx.gas,[tx.pure.u64(listing_price!)]) + + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID), tx.object(fees), tx.object(listing_id)], + target: `${packageID}::marketplace::buyListing` + }); + + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + setLoading(false); + } + } + ) + } + + + + if (loading) { + return ( + <> + {chooseNFT ? + <> +
+

Available NFTs

+ {userNFTs.map((nft)=>( +

{nft}

+ ))} +
+ + : + null + } +
LOADING
+ + ) + } + + return ( + <> + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ {displayList.map((displayListItem) => ( + + ))} + + ) +} diff --git a/devmatch2/src/networkConfig.ts b/devmatch2/src/networkConfig.ts index 5824c51..391ca65 100644 --- a/devmatch2/src/networkConfig.ts +++ b/devmatch2/src/networkConfig.ts @@ -1,27 +1,27 @@ -import { getFullnodeUrl } from "@mysten/sui/client"; -import { createNetworkConfig } from "@mysten/dapp-kit"; -import { DEVNET_COUNTER_PACKAGE_ID, MAINNET_COUNTER_PACKAGE_ID, TESTNET_COUNTER_PACKAGE_ID } from "./constants"; - -const { networkConfig, useNetworkVariable, useNetworkVariables } = - createNetworkConfig({ - devnet: { - url: getFullnodeUrl("devnet"), - variables:{ - PackageId: MAINNET_COUNTER_PACKAGE_ID, - } - }, - testnet: { - url: getFullnodeUrl("testnet"), - variables:{ - PackageId: TESTNET_COUNTER_PACKAGE_ID, - } - }, - mainnet: { - url: getFullnodeUrl("mainnet"), - variables:{ - PackageId: DEVNET_COUNTER_PACKAGE_ID, - } - }, - }); - -export { useNetworkVariable, useNetworkVariables, networkConfig }; +import { getFullnodeUrl } from "@mysten/sui/client"; +import { createNetworkConfig } from "@mysten/dapp-kit"; +import { DEVNET_COUNTER_PACKAGE_ID, MAINNET_COUNTER_PACKAGE_ID, TESTNET_COUNTER_PACKAGE_ID } from "./constants"; + +const { networkConfig, useNetworkVariable, useNetworkVariables } = + createNetworkConfig({ + devnet: { + url: getFullnodeUrl("devnet"), + variables:{ + PackageId: MAINNET_COUNTER_PACKAGE_ID, + } + }, + testnet: { + url: getFullnodeUrl("testnet"), + variables:{ + PackageId: TESTNET_COUNTER_PACKAGE_ID, + } + }, + mainnet: { + url: getFullnodeUrl("mainnet"), + variables:{ + PackageId: DEVNET_COUNTER_PACKAGE_ID, + } + }, + }); + +export { useNetworkVariable, useNetworkVariables, networkConfig }; diff --git a/devmatch2/src/pages/Auction.tsx b/devmatch2/src/pages/Auction.tsx new file mode 100644 index 0000000..10aff7e --- /dev/null +++ b/devmatch2/src/pages/Auction.tsx @@ -0,0 +1,176 @@ +import { useCurrentAccount, useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit'; +import React, { useEffect, useState } from 'react' +import { useNetworkVariable } from '../networkConfig'; +import { AUCTIONHOUSE_ID, NFT_TYPE } from '../constants'; +import { Transaction } from '@mysten/sui/transactions'; +import { Button } from '@radix-ui/themes'; +import ListingPopup from '../components/AuctionPopUp'; +// import NFTCard from '../components/nftCard'; +import '../index.css'; + + +const Auction = () => { + let userAccount = useCurrentAccount(); + const packageID = useNetworkVariable("PackageId"); + const suiClient = useSuiClient(); + const [popUp, setPopUp] = useState(false); + const [ownedNFTs, setOwnedNFTs] = useState([]); + + const [displayList, setDisplayList] = useState([]); + const { + mutate: signAndExecute, + } = useSignAndExecuteTransaction(); + + useEffect(() => { fetchAuction() }, []); + + async function fetchAuction() { + //Get Marketplace + let auctionHouse = await suiClient.getObject({ + id: AUCTIONHOUSE_ID, + options: { + showContent: true, + showType: true + } + }); + + + + //Get Required Fields + let auctionHouseInfo = auctionHouse?.data?.content; + let auctionBag; + if (auctionHouseInfo?.dataType === "moveObject") { + auctionBag = (auctionHouseInfo as any).fields.auctionBag + } + + //Get Auction IDs + let auctionBagFields; + let auctionIdList: string[] = []; + + auctionBagFields = await suiClient.getDynamicFields({ parentId: auctionBag.fields.id.id }) + auctionBagFields = auctionBagFields.data + + console.log("Auction Bag Items", auctionBagFields) + + auctionBagFields.map((listing) => { + auctionIdList.push(listing.objectId) + }) + + let auctionItems: object[] = []; + + //Add the Listing Info Into actualListings + await Promise.all(auctionIdList.map(async (auctionId) => { + let currListing = await suiClient.getObject({ + id: auctionId, + options: { + showContent: true, + showType: true + } + }); + + if (currListing.data?.content?.dataType === "moveObject") auctionItems.push(currListing.data?.content?.fields) + })) + + setDisplayList(auctionItems) + } + + function startAuction(minPrice: number, duration: number, nft: string) { + const tx = new Transaction(); + + let deposit = tx.splitCoins(tx.gas, [tx.pure.u64(100000000)]); + + tx.moveCall({ + arguments: [tx.object(AUCTIONHOUSE_ID), tx.object(deposit), tx.pure.u64(minPrice), tx.pure.u64(duration), tx.object(nft)], + target: `${packageID}::bidding::startAuction` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + fetchAuction() + } + } + ) + } + + function createBidding(auctionId: string, bid_amount: number) { + const tx = new Transaction(); + + let bid = tx.splitCoins(tx.gas, [tx.pure.u64(bid_amount)]); + + tx.moveCall({ + arguments: [tx.object(AUCTIONHOUSE_ID), tx.pure.id(auctionId), tx.object(bid)], + target: `${packageID}::bidding::createBidding` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + fetchAuction() + } + } + ) + } + + async function retrieveOwnedNFT(): Promise { + let userNFTs: object[] = []; + let res = await suiClient.getOwnedObjects({ + owner: userAccount!.address, + options: { + showType: true + } + }); + + let ownedAsset = res.data; + + //Filter Out Valid NFTs + await Promise.all(ownedAsset.map(async (asset) => { + if (asset.data?.type === NFT_TYPE) { + let nftItem = await suiClient.getObject({ + id: asset.data.objectId, + options: { + showContent: true, + } + }); + if (nftItem.data?.content?.dataType === "moveObject") userNFTs.push(nftItem.data?.content?.fields) + } + })) + return userNFTs + } + + return ( + <> +

Auction Page

+ + {setPopUp(false) }} startAuction={( price: number, duration: number, nft: string) => { startAuction(price, duration, nft) }} userNFTs={ownedNFTs} /> + {displayList.map((nft,key)=>{ +

nft

+ })} + + ) +} + +export default Auction \ No newline at end of file diff --git a/devmatch2/src/pages/Landing.tsx b/devmatch2/src/pages/Landing.tsx new file mode 100644 index 0000000..09a6127 --- /dev/null +++ b/devmatch2/src/pages/Landing.tsx @@ -0,0 +1,193 @@ +import React, { useEffect, useState } from 'react' +import ListingPopup from '../components/ListingPopUp' +import { useCurrentAccount, useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit'; +import { useNetworkVariable } from '../networkConfig'; +import { MARKETPLACE_ID, NFT_TYPE } from '../constants'; +import { Button } from '@radix-ui/themes'; +import NFTCard from '../components/nftCard'; +import { Transaction } from '@mysten/sui/transactions'; +import '../index.css'; + + +const Landing = () => { + let userAccount = useCurrentAccount(); + const packageID = useNetworkVariable("PackageId"); + const suiClient = useSuiClient(); + const [displayList, setDisplayList] = useState([]); + const [popUp, setPopUp] = useState(false) + const [ownedNFTs, setOwnedNFTs] = useState([]); + + + const { + mutate: signAndExecute, + } = useSignAndExecuteTransaction(); + + useEffect(() => { fetchMarketplace() }, []) + + async function fetchMarketplace() { + //Get Marketplace + let marketplace = await suiClient.getObject({ + id: MARKETPLACE_ID, + options: { + showContent: true, + showType: true + } + }); + + //Get Required Fields + let marketplaceInfo = marketplace?.data?.content; + let listingBag; + if (marketplaceInfo?.dataType === "moveObject") { + listingBag = (marketplaceInfo as any).fields.listingBag + + } + + //Get Listing IDs + let listingBagFields; + let listingIdList: string[] = []; + listingBagFields = await suiClient.getDynamicFields({ parentId: listingBag.fields.id.id }) + listingBagFields = listingBagFields.data + + listingBagFields.map((listing) => { + listingIdList.push(listing.objectId) + }) + + let actualListings: object[] = []; + + //Add the Listing Info Into actualListings + await Promise.all(listingIdList.map(async (listingId) => { + let currListing = await suiClient.getObject({ + id: listingId, + options: { + showContent: true, + showType: true + } + }); + + if (currListing.data?.content?.dataType === "moveObject") actualListings.push(currListing.data?.content?.fields) + })) + + setDisplayList(actualListings) + } + + async function retrieveOwnedNFT(): Promise { + let userNFTs: object[] = []; + let res = await suiClient.getOwnedObjects({ + owner: userAccount!.address, + options: { + showType: true + } + }); + + let ownedAsset = res.data; + + //Filter Out Valid NFTs + await Promise.all(ownedAsset.map(async (asset) => { + if (asset.data?.type === NFT_TYPE) { + let nftItem = await suiClient.getObject({ + id: asset.data.objectId, + options: { + showContent: true, + } + }); + if (nftItem.data?.content?.dataType === "moveObject") userNFTs.push(nftItem.data?.content?.fields) + } + })) + return userNFTs + } + + async function createListing(nft: string, price: number) { + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID), tx.object(nft), tx.pure.u64(price)], + target: `${packageID}::marketplace::createListing` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + fetchMarketplace() + } + } + ) + } + + async function buyListing(listing_id: string) { + console.log("Buying Listing") + const tx = new Transaction(); + + let listing=await suiClient.getObject({ + id: listing_id, + options:{showContent: true} + }) + + + let listing_price; + + if (listing.data?.content?.dataType==="moveObject"){ + listing_price= Number((listing as any).data?.content?.fields.price)} + + + const fees=tx.splitCoins(tx.gas,[tx.pure.u64(listing_price!)]) + + + tx.moveCall({ + arguments: [tx.object(MARKETPLACE_ID), tx.object(fees), tx.object(listing_id)], + target: `${packageID}::marketplace::buyListing` + }); + + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + fetchMarketplace() + } + } + ) + } + + + + + + + return ( + <> + + + { setPopUp(false) }} createListing={(nft: string, price: number) => { createListing(nft, price) }} userNFTs={ownedNFTs} /> + +
+ {displayList.map((displayListItem, key) => ( + {buyListing(id)}}/> + ))} +
+ + + ) +} + +export default Landing \ No newline at end of file diff --git a/devmatch2/src/pages/Minting.tsx b/devmatch2/src/pages/Minting.tsx index 092c8f5..342933a 100644 --- a/devmatch2/src/pages/Minting.tsx +++ b/devmatch2/src/pages/Minting.tsx @@ -1,98 +1,137 @@ -import{ useState } from 'react' -import { useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit'; -import { Transaction } from '@mysten/sui/transactions'; -import { useNetworkVariable } from '../networkConfig'; - -const Minting = () => { - const packageID = useNetworkVariable("PackageId"); - const suiClient = useSuiClient(); - - const { - mutate: signAndExecute, - } = useSignAndExecuteTransaction(); - - async function mintNFT(seed: number, name: string, description: string, mediaURL: string) { - const tx = new Transaction(); - - tx.moveCall({ - arguments: [tx.pure.u64(seed), tx.pure.string(name), tx.pure.string(description), tx.pure.string(mediaURL)], - target: `${packageID}::nft::mint_NFT` - }); - - //Continue Here - signAndExecute({ transaction: tx }, - { - onSuccess: async ({ digest }) => { - await suiClient.waitForTransaction({ - digest: digest, - options: { - showEffects: true, - }, - }); - - } - } - ) - } - - function getSeed(): number { - return Math.floor(Math.random() * 100000); - } - - - const [imageURL, setImageURL] = useState("") - const [imageName, setImageName] = useState("") - const [imageDescription, setImageDescription] = useState("") - - return ( - <> -
-

Mint NFT Here

- - {setImageName(e.target.value)}} - /> - - - {setImageDescription(e.target.value)}} - /> - - - {setImageURL(e.target.value)}} - /> - -

Image View Here

- - - - - -
- - ) -} - +import React, { useState } from 'react' +// import { WalletAccount } from '@wallet-standard/base'; +// import { SuiClient } from '@mysten/sui/client'; +import { useCurrentAccount, useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit'; +import { Transaction } from '@mysten/sui/transactions'; +import { useNetworkVariable } from '../networkConfig'; +import { Button, Container, Heading, TextField, Text } from '@radix-ui/themes' +import '../index.css'; + +const Minting = () => { + let userAccount = useCurrentAccount(); + const packageID = useNetworkVariable("PackageId"); + const suiClient = useSuiClient(); + + const { + mutate: signAndExecute, + isSuccess, + isPending + } = useSignAndExecuteTransaction(); + + async function mintNFT(seed: number, name: string, description: string, mediaURL: string) { + const tx = new Transaction(); + + tx.moveCall({ + arguments: [tx.pure.u64(seed), tx.pure.string(name), tx.pure.string(description), tx.pure.string(mediaURL)], + target: `${packageID}::nft::mint_NFT` + }); + + //Continue Here + signAndExecute({ transaction: tx }, + { + onSuccess: async ({ digest }) => { + await suiClient.waitForTransaction({ + digest: digest, + options: { + showEffects: true, + }, + }); + + let digestInfo = await suiClient.getTransactionBlock({ + digest: digest, + options: { + showBalanceChanges: true, + showEffects: true, + showEvents: true, + showInput: true, + showObjectChanges: true + } + }); + } + } + ) + } + + function getSeed(): number { + return Math.floor(Math.random() * 100000); + } + + const [imageURL, setImageURL] = useState("") + const [imageName, setImageName] = useState("") + const [imageDescription, setImageDescription] = useState("") + + return ( +
+ +
+ + Mint Your NFT + + +
+ setImageName(e.target.value)} + className="w-full px-4 py-3 bg-gray-700/60 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500 placeholder-gray-400" + /> + + + + + setImageDescription(e.target.value)} + className="w-full px-4 py-3 bg-gray-700/60 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500 placeholder-gray-400" + /> + + setImageURL(e.target.value)} + className="w-full px-4 py-3 bg-gray-700/60 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500 placeholder-gray-400" + /> + + Preview +
+ NFT Preview +
+
+ + +
+
+
+ ) +} + export default Minting \ No newline at end of file diff --git a/devmatch2/src/vite-env.d.ts b/devmatch2/src/vite-env.d.ts index 11f02fe..7d0ff9e 100644 --- a/devmatch2/src/vite-env.d.ts +++ b/devmatch2/src/vite-env.d.ts @@ -1 +1 @@ -/// +/// diff --git a/devmatch2/tsconfig.json b/devmatch2/tsconfig.json index a7fc6fb..9bdaa77 100644 --- a/devmatch2/tsconfig.json +++ b/devmatch2/tsconfig.json @@ -1,25 +1,25 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/devmatch2/tsconfig.node.json b/devmatch2/tsconfig.node.json index a858353..ff08923 100644 --- a/devmatch2/tsconfig.node.json +++ b/devmatch2/tsconfig.node.json @@ -1,10 +1,10 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.mts"] -} +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.mts"] +} diff --git a/devmatch2/vite.config.mts b/devmatch2/vite.config.mts index d366e8c..ada5387 100644 --- a/devmatch2/vite.config.mts +++ b/devmatch2/vite.config.mts @@ -1,7 +1,7 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}); +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); From 195ff75df91604bdacf793f9c5763e41d36b4392 Mon Sep 17 00:00:00 2001 From: AngJianming Date: Sat, 9 Aug 2025 00:09:53 +0800 Subject: [PATCH 6/6] Resolve merge conflict by keeping README.md file --- .gitignore | 24 ++++++- README.md | 146 ++++++++++++++++++++++++++++++++++++++++++ devmatch2/src/App.tsx | 2 +- 3 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/.gitignore b/.gitignore index a007fea..2b8efbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,23 @@ -build/* +marketplace_contract/build/* + +test_smartcontract/node_modules/ +test_smartcontract/package-lock.json/ +test_smartcontract/.env/ + +frontend/.env +*.env +*.env.local +*.env.development +*.env.production + +#ignore all those node_modules and npm, pnpm , yarn lock files +frontend/node_modules/ +*node_modules/ +*.next +*pnpm-lock.yaml +*package-lock.json +*yarn.lock +*.log + +front-end/node_modules/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..63b6569 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# QuestismOS โ€“ Ranked Gamified NFTs Marketplace with zkLogin to better bridge Web2 to Web3. + +## ๐Ÿ† Overview + +**QuestismOS** is a **gamified NFT marketplace** built on **Sui** that allows NFTs to **level up or rank up** when completing certain quests or tasks. +Our mission is to create a **better bridge from Web2 to Web3**, making blockchain accessible, rewarding, and fun. + +Players and collectors can **buy, sell, auction, and interact** with NFTs that evolve dynamically over time โ€” creating a sense of progression like in gaming. +We also implement **zkLogin** for seamless onboarding and **dynamic NFT fields** to support special items, seasonal promotions, and rewards for active buyers. + +--- + +## ๐ŸŽฏ Key Features + +### ๐Ÿ–ผ NFT Marketplace Core +- **Mint NFTs** with security checks. +- **Buy & Sell NFTs** with configurable transaction fees (2โ€“5%). +- **Auction System** with bidding logic. +- **Dynamic NFTs** that level up or rank up based on trades, purchases, or quest completions. +- **Rarity Display** on all listing cards. +- **Special/Seasonal Categories** (e.g., Summer Sale). + +### ๐ŸŽฎ Gamification & Rewards +- Incentivized early buyers and reviewers via **Reward Pool**. +- Rank-up system for frequent traders. +- Coupons and perks for loyal buyers. + +### ๐Ÿ›ก Web3 & Security +- zkLogin integration for **passwordless authentication**. +- Gas optimization with verification and parallel processing. +- Optional Web2 backend integration (Supabase) for history and user profiles. + +### ๐ŸŒ Blockchain for Good +Aligned with **United Nationsโ€™ Sustainable Development Goals (SDGs)**, QuestismOS promotes: +- **Sustainable commerce** through transparent NFT provenance. +- **Global accessibility** with zkLogin for non-crypto-native users. + +--- + +## ๐Ÿ“‚ Project Structure + + +### Frontend (React + TypeScript) +devmatch2/src/ + โ”œโ”€โ”€ components/ + โ”‚ โ”œโ”€โ”€ AuctionPopUp.tsx # Auction interaction UI + โ”‚ โ”œโ”€โ”€ ListingPopUp.tsx # NFT listing form + โ”‚ โ”œโ”€โ”€ Navbar.tsx # Navigation bar + โ”‚ โ”œโ”€โ”€ nftCard.tsx # NFT card with rarity display + โ”œโ”€โ”€ pages/ + โ”‚ โ”œโ”€โ”€ Auction.tsx # Auction page + โ”‚ โ”œโ”€โ”€ Landing.tsx # Home & marketplace overview + โ”‚ โ”œโ”€โ”€ Minting.tsx # NFT minting page + โ”œโ”€โ”€ App.tsx # App routing & layout + โ”œโ”€โ”€ constants.ts # Contract addresses & config + โ”œโ”€โ”€ index.css # Styling + โ”œโ”€โ”€ Layout.tsx # Page layout wrapper + โ”œโ”€โ”€ main.tsx # App entry point + โ”œโ”€โ”€ marketplace.tsx # Marketplace UI & contract integration + โ”œโ”€โ”€ networkConfig.ts # Sui network settings + โ”œโ”€โ”€ OwnedObjects.tsx # Display user-owned NFTs + โ”œโ”€โ”€ WalletStatus.tsx # Wallet connection & status + โ””โ”€โ”€ vite-env.d.ts + + +### Smart Contracts (Move) +marketplace_contract/ +โ”œโ”€โ”€ sources/ +โ”‚ โ”œโ”€โ”€ bidding.move # Auction and bidding logic +โ”‚ โ”œโ”€โ”€ marketplace.move # Core marketplace functions (list, buy, sell) +โ”‚ โ”œโ”€โ”€ nft.move # NFT object definitions and minting logic +โ”œโ”€โ”€ tests/ +โ”‚ โ””โ”€โ”€ marketplace_contract_tests.move +โ””โ”€โ”€ Move.toml # Sui Move package configuration + + +--- + +## ๐Ÿ›  Tech Stack + +### Blockchain & Smart Contracts +- **Sui Move** โ€“ Marketplace, NFT, and auction logic. +- **zkLogin** โ€“ Secure passwordless authentication. +- **Dynamic Fields** โ€“ For NFT rank-up and seasonal categorization. + +### Frontend +- **React + TypeScript** +- **Vite** โ€“ Fast build and hot-reload. +- **Supabase (optional)** โ€“ Web2 backend for user history and profiles. + +### Development Tools +- **Figma** โ€“ UI/UX prototyping. +- **Lucidchart** โ€“ Architecture diagramming. +- **Canva** โ€“ Pitch deck creation. + +--- + +## ๐Ÿ— Architecture + +*import the pic later.. + + +--- + +## ๐Ÿš€ Getting Started on your own local machine.. + +### Prerequisites +- Node.js 18+ +- pnpm / npm +- Sui CLI installed and configured + +### Install Frontend +```bash +cd devmatch2 +pnpm install +pnpm run dev +``` + +--- + +## ๐Ÿ“œ Hackathon Tracks +### SUI Track +- zkLogin Application โ€“ Seamless onboarding. + +- On-chain Marketplace โ€“ Dynamic NFTs & auctions. + +### Blockchain for Good Track +Aligns with SDG 17 goals for sustainability and accessibility. (be more specific later..) + +### ChatAndBuild Track +Leverages Web3 tech for real-world relevance and originality. + +## ๐Ÿค Contributions +Smart Contracts: Wen Zhe, Ian Hon + +Frontend & Wallet Integration: Ang Jianming, Ian Hon, Jun Kai + +Backend (Supabase): Ian Hon + +UI/UX: Jun Kai, Yao Ren + +Slides & Pitch Deck: Yao Ren , Ang Jianming + + +## ๐Ÿ“„ License +Apache License โ€“ See LICENSE for details. \ No newline at end of file diff --git a/devmatch2/src/App.tsx b/devmatch2/src/App.tsx index 96c0c1b..82c6ad8 100644 --- a/devmatch2/src/App.tsx +++ b/devmatch2/src/App.tsx @@ -2,7 +2,7 @@ // import { Box, Container, Flex, Heading } from "@radix-ui/themes"; // import { WalletStatus } from "./WalletStatus"; import { Marketplace } from "./marketplace"; -import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from "react-router-dom"; +import { createBrowserRouter, createRoutesFromElemenAngts, Route, RouterProvider } from "react-router-dom"; import "./index.css" import Layout from "./Layout"; import Minting from "./pages/Minting";