Technical Specifications

Marketplace

Listings

Our listings offer a peer-to-peer method to buy & sell any kind of asset, without the risk of the asset being a scam, because of our built-in verification algorithm that only allows tokenized assets from specific issuers.

Process flow

  1. User connects his Metamask wallet.

  2. User selects the ListTokenForm in the menu.

  3. User selects the asset he wants to sell by putting in the assets contract address and the preferred token ID.

  4. The user selects a price he wants to list the asset for.

  5. The user either enables or disables financing / mortgages. (meaning buyers are either allowed or not allowed to finance that asset.

  6. The asset is listed.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
//import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";




contract MarketplaceNew is ReentrancyGuard {
    
    // using Counters for Counters.Counter;
    // Counters.Counter public _itemIds;
    // Counters.Counter public _itemsSold;

    address public _tokenAddress;
    address public _creator;
    IERC721  public _tokenContract;
    ERC721   public _tokenContractERC20;

    uint256 standardPrice = 1 wei;
    uint256 private counterItemsListing = 1;

    uint256 [] public itemsId;
    string []  itemsIdString;

    mapping(address => Listing) public itemsListingByAddress;
    mapping(string => Listing) public itemsListingByString;
    mapping(uint256 => Listing) public itemsListingByUint256;   
    mapping(bytes32 => Listing) public itemsListingByte32;     
    mapping(uint256 => mapping(uint256 => uint256)) public ownedItemsSTO;
    mapping(bytes32 => bool) itemsIdExist;
    mapping(address => mapping(bytes32 => uint256)) public ownedItemsSTOItemId;
    mapping(address => bool) public whitelist;

    bytes32[] public listingsIndex;

    struct Listing {
        uint256 transactionCounter;
        bytes32 transactionIdentify;
        IERC721 contractAddress721;
        uint256 tokenId;
        uint256 quantity;
        uint256 price;
        address creator;
        address payable seller;
        address owner;
        bool sold; 
        //    IERC1155 contractAddress1155;
        //    bool isERC721;
    }

    // Eventi
    event TokenListed(address indexed tokenContract, uint256 indexed tokenId, uint256 quantity, uint256 price, address indexed seller);
    event TokenSold(address indexed tokenContract, uint256 indexed tokenId, uint256 quantity, uint256 price, address indexed buyer);
    event TokenRemoved(address indexed tokenContract, uint256 indexed tokenId, uint256 quantity, address indexed seller);

    modifier onlySeller(uint256 listingId) {
        require(itemsListingByUint256[listingId].seller == msg.sender, "Not the seller");
        _;
    }
     constructor() 
    {}

    function removeToken(uint256 itemId) private returns (bool) {
        Listing storage ItemListed = itemsListingByUint256[itemId];

        delete itemsListingByUint256[itemId];
            
        emit TokenRemoved(address(ItemListed.contractAddress721), ItemListed.tokenId, ItemListed.quantity, msg.sender);
        counterItemsListing--;
        return true;
    }

    function removeTokenBySeller(uint256 itemId) onlySeller(itemId) external returns (bool) {
        Listing storage ItemListed = itemsListingByUint256[itemId];
        require(ItemListed.seller == msg.sender, "You are not allowed, only the seller can revoke the listing");
        IERC721(ItemListed.contractAddress721).safeTransferFrom(address(this), ItemListed.seller, ItemListed.tokenId);
        //emit TokenRemoved(ItemListed.contractAddress721, ItemListed.tokenId, ItemListed.quantity, msg.sender);
        delete itemsListingByUint256[itemId];
        return true;
    }

    function generateRandomCode() private returns (bytes32) {
            bytes32 randomBytes = keccak256(abi.encodePacked(block.timestamp, msg.sender));
            return bytes32(randomBytes);
    }

    function bytes32ToString(bytes32 _bytes32) private pure returns (bytes32) {
        uint8 i = 0;
        bytes memory bytesArray = new bytes(32);
        for (i = 0; i < 32; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return bytes32(bytesArray);
    }
    // Funzione per elencare un token
     function listToken(address tokenContract, uint256 tokenId, uint256 quantity, uint256 price) external {
        bytes32 newTransactionIdentify = generateRandomCode();
        require(bytes32(newTransactionIdentify).length > 0, "Invalid item ID");
        require(!itemsIdExist[newTransactionIdentify], "newTransactionIdentify already stored");
        require(price >= 0, "Price must be greater than zero");
        require(quantity > 0, "Quantity must be greater than zero");
        bytes32 listingId = keccak256(abi.encodePacked(tokenContract, tokenId, msg.sender));

        IERC721 erc721 = IERC721(tokenContract);
        //require(erc721.ownerOf(tokenId) == msg.sender, "Not the owner");

        require(erc721.balanceOf(msg.sender) >= quantity, "Insufficient balance");
        erc721.transferFrom(msg.sender, address(this), tokenId);

        // _itemIds.increment();
        // uint256 itemId = _itemIds.current();
        // itemsId.push(itemId);

        itemsListingByUint256[counterItemsListing] = Listing({
            transactionCounter: counterItemsListing,
            transactionIdentify : newTransactionIdentify,
            contractAddress721: IERC721(tokenContract),
            tokenId: tokenId,
            quantity: quantity,
            price: price,
            creator: erc721.ownerOf(tokenId),
            seller: payable(msg.sender),
            owner: msg.sender,
            sold: false
        });
        counterItemsListing++;
        ownedItemsSTOItemId[msg.sender][newTransactionIdentify] = quantity;
        itemsIdExist[newTransactionIdentify] = true;

        listingsIndex.push(listingId);

        emit TokenListed(tokenContract, tokenId, quantity, price, msg.sender);
    }

    function buyToken(uint256 itemId) external payable {
        Listing storage ItemListed = itemsListingByUint256[itemId];
        require(ItemListed.seller != address(0), "Listing not found");

        require(msg.value >= ItemListed.price, "Insufficient funds");

        ItemListed.contractAddress721.approve(msg.sender, ItemListed.tokenId);
        ItemListed.contractAddress721.transferFrom(address(this), msg.sender, ItemListed.tokenId);
        
        payable(ItemListed.seller).transfer(ItemListed.price);
    
        // Rimuovi il token dalla lista
        //emit TokenSold(ItemListed.tokenContract, ItemListed.tokenId, ItemListed.quantity, ItemListed.price, msg.sender);
        
        // itemsListingByUint256[itemId] = Listing(     
        //     ItemListed.transactionCounter,
        //     ItemListed.transactionIdentify,
        //     ItemListed.contractAddress721,
        //     ItemListed.tokenId,
        //     ItemListed.quantity,
        //     ItemListed.price,
        //     ItemListed.creator,
        //     ItemListed.seller,
        //     ItemListed.owner,
        // true );

        delete itemsListingByUint256[itemId];
        
    }


    function getAllListingsIndex() external view returns (bytes32[] memory) {
        return listingsIndex;
    }

    function listingsCount() public view returns (uint256) {
        return listingsIndex.length;
    }

    function removeFromListingsIndex(bytes32 listingId) private {
        for (uint256 i = 0; i < listingsIndex.length; i++) {
            if (listingsIndex[i] == listingId) {
                if (i < listingsIndex.length - 1) {
                    listingsIndex[i] = listingsIndex[listingsIndex.length - 1];
                }
                listingsIndex.pop();
                break;
            }
        }
    }

    function getAllListings() external view returns (Listing[] memory) {
        Listing[] memory allListings = new Listing[](counterItemsListing);

        for (uint256 i = 0; i < counterItemsListing; i++) {
            allListings[i] = itemsListingByUint256[i];
        }

        return allListings;
    }
}

Tokenized Asset Verification

We have developed a total of 2 methods for verifying if an asset is either a tokenized security or a normal NFT / token:

  • Whitelisting Method - Already integrated

  • Verification-Hash Method - Beta

Whitelisting Method

The Whitelisting method relies on a public whitelist of verified issuer addresses. When a user wants to list a token, we do a metadata check for the mintingAddress, comparing this address to our whitelist. If the address matches with some in our list, we can approve the listing.

Issues:

  • Having to contact all known tokenization companies -> getting their wallet addresses for tokenizing.

  • Relying on them not changing the wallet address -> if changed, they would have to update it.

  • Copyable -> Anyone can use this technique, making it not unique after a certain time has passed.

  • Security concerns -> Currently there are no universal / general metadata schemes or regulation how to mint tokens properly, thats why we would have to develop individual checks for individual solutions from different issuers.

While the whitelisting method is easy to develop and does not require an off-chain component, it does not offer the universal solution we want to have. Below you will see an overview of our metadata check solution.

Technical Overview

In general, we can summerize the required metadata in most countries into the following data fields:

  • issuerName string

  • issuerAddress string

  • issuerEmail string

  • issuerPhone string

  • issuerRegistrationNumber string

  • issuerWalletAddress string

  • assetType string

  • assetDescription string

  • assetLocation string

  • tokenName string

  • tokenSymbol string

  • totalSupply uint256

  • tokenStandard string

  • prospectusLink string

  • prospectusSummary string

  • prospectusRiskFactors string

  • regulationCompliance string

  • BaFinCompliance boolean

  • AML_KYC_Compliance boolean

  • transferRestrictions string

  • secondaryMarketAvailability boolean

The process flow can then be summerized like this:

In connection to this process, we can create our generalized solution on which we can integrate specific methods for special tokens, issuers, or metadata requirements with our off-chain issuer database:

While this method offers a great solution for small marketplaces, it would not bring the efficiency and the fitting strategy to our marketplace we want to deliver. That`s why we developed a more universal and advanced solution:

Verification Hash Method

Here, tokenization companies / digital asset issuers, can request the access to our APIs, which allows them to create a specific hash that let`s 3rd parties verify if an asset if either a verified tokenized security or just a normal asset. After users requested the verification hash at their issuers (either through mail or through a closed messaging service), the isser just has to provide his signature along with the the users contract and token address(es). Our API automatically creates a verification hash that can be stored within the contract`s or token`s metadata. The proccess uf updating the contracts metdata comes with the signature of the issuer, the completed updated metadata would contein the following in a completed version:

  • VerificationHash: A unique hash generated from asset-specific information.

  • LegalDocumentsLink: A link or hash pointing to legal documentation validating the asset. (Not necessary currently for beta/POC development)

  • IssuerIdentityHash: A hash representing the identity of the token issuer, normally represented through their website URL.

  • AssetDescription: A brief description of the asset, ensuring clarity and transparency. (Needs to be included in the user`s request)

  • Timestamp: The date and time when the asset was tokenized, adding a temporal dimension to the asset’s lifecycle.

Tokenization Teaser

The (Perfect) Workflow

Currently, when a company issues a tokenized asset, the asset ownership is perfectly fractionalized into multiple tokens, but does anyone know what happens with the metadata? No.

Since each fractionalied token hold mostly the ownership to the same asset, companies store the metadata of the asset, in each token. (For example, if we have a property which uses 100 tokens to represent to dividend rights on-chain, every token hold the exact same metadata, with only a few adjustments.) This creates extremely high costs and wasted storage + unnecessary waste of computing power.

What`s about to come with tokenizing assets:

Because of that, we have created a solution that is called the Centralized Data Repository Contract in connection with an off-chain de-duplication algorithm, allowing to check if metadata is already stored one time, and creating and managing references to the metadata, to ensure it is only stored one time.

Integrations

Financing / Mortgage Integration

Sellers of listed assets can enable "financing", if enables, buyers are allowed to finance the asset. First, a buyer has to make an initial payment of 20%, after the payment is confirmed, the listed asset, and the inital payment is transferred to an escrow contract. This escrow contrcat holds the assets and all repaid funds until the loan is repaid. Once repaid, the asset is released to the borrower / buyer, and the repayments + the paid interest rate is transferred to the seller.

Current terms:

  • Max. loan duration is currently 5 years (approval by BaFin awaited)

  • Interest rate is dependent on the loan duration, starting at 4%.

  • Initial payment is always 20%, after that, the asset is in pre-ownership which is converted into full ownership after repayment.

  • Only one mortgage per person at the time, KYC required.

  • Borrower and seller always remain private, no details or information is shared.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./MarketplaceNew.sol"; // Change path if needed

contract NFTMortgage is ReentrancyGuard {
    MarketplaceNew private marketplace;
    uint256 public constant ANNUAL_INTEREST_RATE = 4; // 4% interest rate
    uint256 public constant MAX_LOAN_DURATION = 1825 days; // 1 year max. duration

    struct Mortgage {
        uint256 listingId;
        address borrower;
        uint256 loanAmount;
        uint256 startTime;
        bool isActive;
    }

    mapping(uint256 => Mortgage) public mortgages;
		
    // Events 
    event FinancedPurchaseInitiated(uint256 indexed listingId, address indexed borrower, uint256 loanAmount);
    event MortgageFullyRepaid(uint256 indexed listingId, address indexed borrower);
    event MortgagePartiallyRepaid(uint256 indexed listingId, address indexed borrower, uint256 repaymentAmount);
    event MortgageLiquidated(uint256 indexed listingId, address indexed originalSeller);
    event ListingVerified(uint256 indexed listingId);
    event NFTApproved(uint256 indexed tokenId, address indexed tokenAddress);
    // event InterestCalculated(uint256 indexed listingId, uint256 interestAmount);
    event LoanDurationChecked(uint256 indexed listingId, bool isWithinDuration);
	
    /* constructor(address _marketplaceAddress) {
        marketplace = MarketplaceNew(_marketplaceAddress);
    }
		*/
		// Initiate mortage 
    function initiateFinancedPurchase(uint256 _listingId) external payable nonReentrant {
        require(marketplace.listingExists(_listingId), "Listing does not exist");
        MarketplaceNew.Listing memory listing = marketplace.itemsListingByUint256(_listingId);
		
        require(listing.eligibleForFinancing, "Listing not eligible for financing");
        require(!mortgages[_listingId].isActive, "NFT already under a mortgage");
				
				// Calculate downPayment
        uint256 downPayment = listing.price * 20 / 100;
        require(msg.value == downPayment, "Incorrect down payment amount");
					
				// Secure in Escrow 
        IERC721 nftContract = IERC721(listing.contractAddress721);
        require(nftContract.getApproved(listing.tokenId) == address(this) || nftContract.isApprovedForAll(listing.seller, address(this)), "NFT not approved for transfer");
        nftContract.safeTransferFrom(listing.seller, address(this), listing.tokenId);
				
        (bool sent, ) = listing.seller.call{value: msg.value}("");
        require(sent, "Failed to send Ether");

        mortgages[_listingId] = Mortgage({
            listingId: _listingId,
            borrower: msg.sender,
            loanAmount: listing.price - downPayment,
            startTime: block.timestamp,
            isActive: true
        });

        emit FinancedPurchaseInitiated(_listingId, msg.sender, listing.price - downPayment);
    }

    function repayMortgage(uint256 _listingId) external payable nonReentrant {
        Mortgage storage mortgage = mortgages[_listingId];
        require(mortgage.isActive, "No active mortgage for this NFT");
        require(mortgage.borrower == msg.sender, "Only the borrower can make repayments");

        uint256 timeElapsed = block.timestamp - mortgage.startTime;
        uint256 interest = (mortgage.loanAmount * ANNUAL_INTEREST_RATE / 100) * timeElapsed / 365 days;
        uint256 totalDue = mortgage.loanAmount + interest;

        require(msg.value >= totalDue, "Repayment amount is less than the total due");
        MarketplaceNew.Listing memory listing = marketplace.itemsListingByUint256(_listingId);

        (bool sent, ) = listing.seller.call{value: msg.value}("");
        require(sent, "Failed to send Ether");

        if (msg.value == totalDue) {
            IERC721(listing.contractAddress721).safeTransferFrom(address(this), mortgage.borrower, mortgage.tokenId);
            mortgage.isActive = false;
            emit MortgageFullyRepaid(_listingId, mortgage.borrower);
        } else {
            mortgage.loanAmount -= msg.value;
            emit MortgagePartiallyRepaid(_listingId, mortgage.borrower, msg.value);
        }

        if (msg.value > totalDue) {
            (bool refunded, ) = msg.sender.call{value: msg.value - totalDue}("");
            require(refunded, "Failed to refund overpayment");
        }
    }

    function handleNonRepayment(uint256 _listingId) external {
        Mortgage memory mortgage = mortgages[_listingId];
        require(mortgage.isActive, "Mortgage is not active");
        require(block.timestamp > mortgage.startTime + MAX_LOAN_DURATION, "Loan duration has not elapsed");

        MarketplaceNew.Listing memory listing = marketplace.itemsListingByUint256(_listingId);
        IERC721 nftContract = IERC721(listing.contractAddress721);
        address originalSeller = listing.seller;

        nftContract.safeTransferFrom(address(this), originalSeller, mortgage.tokenId);
        mortgages[_listingId].isActive = false;

        emit MortgageLiquidated(_listingId, originalSeller);
    }
}

Process Overview

Auctions

Structs and Data Mapping

  • Auction: Struct includes seller (address payable), starting and highest bids (uint256), highest bidder (address), end time (uint256), description (string), and active status (bool).

  • auctions: A mapping of uint256 token IDs to Auction structs, tracking each token's auction status.

Core Auction Functions

  • createAuction(uint256 tokenId, uint256 startingBid, uint256 duration, string calldata description): Initializes an auction. Verifies token ownership and auction non-existence for tokenId. Sets up Auction struct and transfers token to contract.

  • placeBid(uint256 tokenId): Facilitates bid placement. Validates ongoing auction status and bid amount exceeding current highest bid. Manages bid overtake logistics, including refund to previous highest bidder.

  • endAuction(uint256 tokenId): Concludes auction. Checks auction activity and time expiration. Handles asset transfer to highest bidder or returns to seller if no valid bids are present.

Enhanced Functionalities

  • Time Extension Logic: Integrated within placeBid. Extends endTime in Auction struct if a bid is placed within a critical time window before the current endTime.

  • Reserve Price: Incorporated into Auction struct and checked in endAuction. If highest bid doesn't meet reserve price, auction deemed unsuccessful; asset returned to seller.

Token Locking Logic

  • Enhancement: When a token is listed for auction, it is now transferred to the Auction contract (nftContract.transferFrom(msg.sender, address(this), tokenId) in createAuction).

  • Locking Mechanism: The token remains locked in the Auction contract until the auction ends or is cancelled.

  • Marketplace Coordination: Implement a status check in the Marketplace contract to verify if a token is currently in an auction before allowing any marketplace transactions.

  • Cancellation Handling: Add functionality to handle auction cancellation, ensuring safe return of the token to the seller if the auction is cancelled before completion.

Withdraw Bid Functionality

  • Rationale: Allow bidders to withdraw bids under specific conditions. Useful for extended auctions or if a bidder decides to opt out before auction closure.

  • Implementation: Function to withdraw bid, adjusting highestBid and highestBidder in Auction struct. Requires careful handling to maintain auction integrity and prevent misuse.

Example integration with ERC721 auctions:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ERC721Auction is ReentrancyGuard {
    IERC721 public nftContract;

    struct Auction {
        address payable seller;
        uint256 startingBid;
        uint256 highestBid;
        address highestBidder;
        uint256 endTime;
        uint256 reservePrice;
        string description;
        bool active;
    }

    mapping(uint256 => Auction) public auctions;
    mapping(address => uint256) public pendingReturns;

    // Events
    event AuctionCreated(uint256 indexed tokenId, uint256 startingBid, uint256 duration, string description, uint256 reservePrice);
    event BidPlaced(uint256 indexed tokenId, address bidder, uint256 bid);
    event AuctionEnded(uint256 indexed tokenId, address winner, uint256 bid);

    constructor(IERC721 _nftContract) {
        nftContract = _nftContract;
    }

    function createAuction(uint256 tokenId, uint256 startingBid, uint256 duration, string calldata description, uint256 reservePrice) external {
        require(nftContract.ownerOf(tokenId) == msg.sender, "Not the token owner");
        require(!auctions[tokenId].active, "Auction already active");
        // Transfer token to Auction contract for locking
        nftContract.transferFrom(msg.sender, address(this), tokenId);

        Auction memory newAuction = Auction({
            seller: payable(msg.sender),
            startingBid: startingBid,
            highestBid: 0,
            highestBidder: address(0),
            endTime: block.timestamp + duration,
            reservePrice: reservePrice,
            description: description,
            active: true
        });

        auctions[tokenId] = newAuction;
        emit AuctionCreated(tokenId, startingBid, duration, description, reservePrice);
    }

    function placeBid(uint256 tokenId) external payable nonReentrant {
        Auction storage auction = auctions[tokenId];
        require(auction.active, "Auction not active");
        require(block.timestamp < auction.endTime, "Auction over");
        require(msg.value > auction.highestBid, "Bid not high enough");

        if (auction.highestBidder != address(0)) {
            pendingReturns[auction.highestBidder] += auction.highestBid;
        }

        auction.highestBid = msg.value;
        auction.highestBidder = msg.sender;
        emit BidPlaced(tokenId, msg.sender, msg.value);

        if (block.timestamp > auction.endTime - 5 minutes) {
            auction.endTime += 5 minutes;
        }
    }

    function endAuction(uint256 tokenId) external nonReentrant {
        Auction storage auction = auctions[tokenId];
        require(auction.active, "Auction not active");
        require(block.timestamp >= auction.endTime, "Auction not yet ended");
        require(auction.highestBid >= auction.reservePrice, "Reserve price not met");

        auction.active = false;
        if (auction.highestBidder != address(0)) {
            auction.seller.transfer(auction.highestBid);
            nftContract.transferFrom(address(this), auction.highestBidder, tokenId);
            emit AuctionEnded(tokenId, auction.highestBidder, auction.highestBid);
        } else {
            nftContract.transferFrom(address(this), auction.seller, tokenId);
        }
    }

    function withdraw() external nonReentrant {
        uint256 amount = pendingReturns[msg.sender];
        require(amount > 0, "No funds to withdraw");

        pendingReturns[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
    }
}

User Verification

Our first idea was to create a hashed credential that can be integrated as a Snap in Metamask, but now, we have again created a more universal, interoperable solution to verify the identity and KYC data of a user on-chain. Besides our cooperation with Hokan.co and PaddleIdentity.org, we need an overall, simple solution that allows users to verify their data while remaining private.

Yes, zero knowlege proofs are the go-to address here, but they require a lot of development and knowledge, meaning we will implement them later in our verification structure, for now we just need to understand the simple cncepts. We have 2 solutions to offer:

Beta Solution:

KYC-Linked NFTs

KYC-linked NFTs offer an easy way to interact with KYC data with multiple wallets, independently, and at any time. The issue with KYC-Linked NFTs is the lack of technical advancements and security considerations, therefore, we have created a more secure, updatable, and regulated method, the BIPS.

Objectives

  1. Enhance Security: Ensure all marketplace participants are verified.

  2. Compliance with Regulations: Adhere to KYC norms.

  3. Seamless User Experience: Simplify the process of identity verification.

System Architecture

  1. KYC Verification System: Partnering with a third-party KYC Company like IDnow

  2. NFT Minting and Management: Smart contracts for minting and managing KYC NFTs.

  3. Marketplace Integration: Modify existing contracts to require KYC NFTs for transactions.

  4. User Wallet Integration: Ensure users can store and manage KYC NFTs in their wallets.

Technical Specifications

1. KYC Verification System

  • Function: Verify user identity.

  • Process:

    • User submits KYC documents.

    • Verification by KYC provider.

    • On successful verification, trigger NFT minting process.

2. NFT Smart Contract Development

  • Standard: ERC-721 (for unique, non-fungible tokens).

  • Key Functions:

    • mintKYCNFT : Called post-KYC verification, mints an NFT linked to the user's wallet.

      • Inputs: User's wallet address.

      • Output: Unique NFT token ID.

    • isKYCVerified : Checks if a user owns a KYC NFT.

      • Input: User's wallet address.

      • Output: Boolean indicating KYC status.

  • Non-Transferability: Override standard transfer functions to prevent NFT transfer.

3.Marketplace Integration

  • For Listing Items: Insert require(isKYCVerified(msg.sender), "Seller not KYC verified") in the listing function. This ensures only KYC verified users can list items.

  • For Transactions: Add require(isKYCVerified(receiverAddress), "Receiver not KYC verified") in transaction functions. This mandates KYC NFT possession for transaction receivers.

Transaction Flow:

  • Listing: Checks if the seller has a KYC NFT. If not, listing is rejected.

  • Transacting: Verifies if the receiver has a KYC NFT. Transactions are blocked if the receiver isn't KYC verified.

4. User Wallet Integration

  • Requirement: Users must use wallets compatible with ERC-721 tokens.

  • User Interface: Display KYC NFT status in the user's wallet interface.

Future: Blockchain-Based Identity Passport System (BIPS)

  • Concept: Developing a BIPS where each user gets a unique on-chain identity passport after completing a comprehensive KYC process. This passport is not just a hashed credential but a dynamic, updatable record that includes the user's KYC status, transaction history, and a trust score based on their marketplace activities.

  • Implementation: Using a smart contracts to create and update BIPS. The passport gets updated with every transaction, enhancing the user's trust score based on parameters like transaction volume, dispute resolutions, and feedback.

  • Advantage: Ensures KYC compliance and builds a transparent trust system beyond the marketplace, encouraging responsible and credible behavior.

Last updated

Logo