반응형

사용할 프레임 워크 : chakra UI

강의에서는 리액트 17 버전을 사용중이기 때문에 차크라 UI 는 1버전으로 다운로드 해줘야 정상 실행된다.

 

 

App.tsx

import React, { FC, useState, useEffect  } from "react";

import { BrowserRouter, Routes, Route} from "react-router-dom"; 
import Layout from "./components/Layout";
import Main from "./routes/main";
import MyAnimal from "./routes/my-animal";
import SaleAnimal from "./routes/sale-animal";

const App: FC = () => {
  const [account, setAccount] = useState<string>("");

  const getAccount = async () => {
    try{
      if(window.ethereum){
        const accounts = await window.ethereum.request({
          method:'eth_requestAccounts',
        })
        setAccount(accounts[0])
      } else{
        alert("Install Metamask");
      }
    }catch(err){
      console.log(err);
    }
  }

  useEffect(() => {
    getAccount();
    console.log(account)
  }, [account])
  

  return (
    <BrowserRouter>
      <Layout>
        <Routes>
          <Route path="/" element={<Main account={account}/>} />
          <Route path="my-animal" element={<MyAnimal account={account}/>} />
          <Route path="sale-animal" element={<SaleAnimal account={account}/>} />
        </Routes>
      </Layout>
      
    </BrowserRouter>
  )
};

export default App;
  • account 가 바뀔때마다 렌더링을 해주는 방식
  • Metamask 설치 유무또한 확인해 주어야한다.

main.tsx

import React , {FC, useState} from 'react';
import {Button, Box, Text, Flex} from '@chakra-ui/react';
import { mintAnimalTokenContract } from '../contracts';
import AnimalCard from '../components/AnimalCard';

interface MainProps {
    account :string;
}

const Main : FC<MainProps> = ({account}) => {
    const [NewAnimalType, setNewAnimalType] = useState<string>("")

    const onClickMint = async () =>{
        try{
            if(!account) return;

            const response = await mintAnimalTokenContract.methods
                                    .mintAnimalToken()
                                    .send({from: account});

            console.log(response);
            if(response.status){
                const balanceLength = await mintAnimalTokenContract.methods
                                    .balanceOf(account)
                                    .call();

                const animalTokenId = await mintAnimalTokenContract.methods
                .tokenOfOwnerByIndex(account, parseInt(balanceLength.length, 10) -1)
                .call();

                const animalType = await mintAnimalTokenContract.methods
                .animalTypes(animalTokenId)
                .call();

                setNewAnimalType(animalType);
                console.log(NewAnimalType);
            }
        }catch(err){
            console.log(err);
        }
    }

    return <Flex w="full" h= "100vh" justifyContent="center" alignItems="center" direction="column">
        <Box>
            {NewAnimalType ? <AnimalCard animalType={NewAnimalType} /> : <Text>Let's mint Animal Card!</Text>}
        </Box>
        <Box>
            <Button mt={4} size="sm" colorScheme="blue" onClick={onClickMint}>Mint</Button>
        </Box>
    </Flex>
}
export default Main;

함수설명

  1. onClickMint
    • 컨트랙트 함수에서 mint 를 호출하여 토큰을 생성하고 토큰의 id, type 을 저장한다.

my-animal.tsx

import React , {FC, useState, useEffect} from "react";
import AnimalCard from "../components/AnimalCard";
import { mintAnimalTokenContract, saleAnimalTokenAddress, saleAnimalTokenContract } from "../contracts";
import {Grid, Box , Button, Text, Flex} from '@chakra-ui/react'
import MyAnimalCard, { IMyanimalCard } from "../components/MyAnimalCard";
interface MyAnimalProps {
    account : string;
}

const MyAnimal : FC<MyAnimalProps> = ({account}) =>{
    const [AnimalCardArray, setAnimalCardArray] = useState<IMyanimalCard[]>();
    const [SaleStatus, setSaleStatus] = useState<boolean>(false);

    const getAnimalTokens = async() => {
        try{
            
            const balanceLength = await mintAnimalTokenContract.methods
            .balanceOf(account)
            .call();

            if(balanceLength =="0") return ;

            const tempAnimalCardArray: IMyanimalCard[] = [];

            const response = await mintAnimalTokenContract.methods.getAnimalTokens(account).call();
            console.log(response)

            response.map((v: IMyanimalCard) => {
                tempAnimalCardArray.push({animalTokenId : v.animalTokenId, animalType : v.animalType, animalPrice : v.animalPrice})
            })
            
            setAnimalCardArray(tempAnimalCardArray);
        }catch(err){
            console.log(err);
        }
    };

    const getIsApprovedForAll = async () =>{
        try{
            const response = await mintAnimalTokenContract.methods
            .isApprovedForAll(account, saleAnimalTokenAddress)
            .call();
            if(response){
                setSaleStatus(response);
            }
            console.log(response);
        }catch(err){
            console.log(err)
        }
    }

    const onClickApproveToggle = async () =>{
        try{
            if(!account) return;

            const response = await mintAnimalTokenContract.methods
            .setApprovalForAll(saleAnimalTokenAddress, !SaleStatus)
            .send({from:account});

            if(response.status){
                setSaleStatus(!SaleStatus);
            }
        }catch(err){
            console.log(err);
        }
    }

    useEffect(() => {
        if(!account) return;
        getAnimalTokens();
        getIsApprovedForAll();
    }, [account])
    
    useEffect(() => {
        console.log(AnimalCardArray)
    }, [AnimalCardArray])

    return(
        <>
        <Flex alignItems="center">
            <Text display="inline-block">
                Sale Status : {SaleStatus ? "True" : "False"}
            </Text>
            <Button size="xs" ml={2} colorScheme={SaleStatus ? "red" : "blue"} onClick={onClickApproveToggle}>
                {SaleStatus ? "Cancel" : "Approve"}
            </Button>
        </Flex>
        <Grid templateColumns ="repeat(4,1fr)" gap={8} mt={2}>
            {
                AnimalCardArray && AnimalCardArray.map((v,i) =>{
                    return <MyAnimalCard key={i} animalTokenId={v.animalTokenId} animalType={v.animalType} animalPrice={v.animalPrice} saleStatus={SaleStatus} account={account}/>;
                }
            )}
        </Grid>
        </>
    );
}

export default MyAnimal

함수 설명

  1. getAnimalTokens
    • account 가 소유한 카드들을 불러와서 배열형태로 리턴한다.
  2. getIsApprovedAll
    • account 와 카드의 주인이 지닌 주소를 비교해 같지 않은지 확인 한다.
    • true 값이 반환되어야 판매등록이 가능하다.
  3. onClickApproveToggle
    • getIsApprovedAll 을 위한 버튼 함수

sale-animal.tsx

import React , {FC, useState, useEffect} from "react";
import {Grid} from "@chakra-ui/react"
import { IMyanimalCard } from "../components/MyAnimalCard";
import { mintAnimalTokenContract, saleAnimalTokenContract } from "../contracts";
import SaleAnimalCard from "../components/SaleAnimalCard";


interface SaleAnimalProps {
    account:string;
}

const SaleAnimal:FC<SaleAnimalProps> = ({account}) =>{
    const [saleAnimalCard, setSaleAnimalCard] = useState<IMyanimalCard[]>()

    const getOnSaleAnimalTokens = async () =>{
        try{
            const getOnSaleAnimalTokenArrayLength = await saleAnimalTokenContract.methods.getOnSaleAnimalTokenArrayLength().call();

            const tempOnSaleArray : IMyanimalCard[] = [];

            for(let i = 0;i<parseInt(getOnSaleAnimalTokenArrayLength, 10); i++){
                const animalTokenId = await saleAnimalTokenContract.methods.onSaleAnimalTokenArray(i).call();

                const animalType = await mintAnimalTokenContract.methods.animalTypes(animalTokenId).call();

                const animalPrice = await saleAnimalTokenContract.methods.animalTokenPrices(animalTokenId).call();

                tempOnSaleArray.push({animalTokenId, animalType, animalPrice})
            }

            setSaleAnimalCard(tempOnSaleArray);
        } catch(err){
            console.log(err);
        }
    }

    useEffect(() => {
      getOnSaleAnimalTokens();
    }, [])
    
  
    

    return(
        <Grid mt={4} templateColumns="repeat(4, 1fr)" gap={8}>
                {saleAnimalCard && saleAnimalCard.map((v,i) => {
                    return <SaleAnimalCard key={i} animalType={v.animalType} animalPrice={v.animalPrice} animalTokenId={v.animalTokenId} account={account} getOnSaleAnimalTokens={getOnSaleAnimalTokens}/>
                })}
        </Grid>
    );
}

export default SaleAnimal

함수 설명

  1. getOnSaleAnimalTokens
    • 판매중인 토큰들을 불러오는 함수
    • my-animal 과 같이 컨트랙트에서 배열을 받아오면 되지만 강의에서는 귀찮아서 for문으로 했다.
반응형

'BlockChain > Solidity' 카테고리의 다른 글

[DP/NFT] 3. SaleAnimalToken 작성  (0) 2022.09.05
[DP/NFT] 2. Minting contract 작성  (0) 2022.09.05
[DP/NFT] 1. Install Solidity & Metamask  (0) 2022.08.25
반응형
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "MintAnimalToken.sol";

contract SaleAnimalToken {
    MintAnimalToken public mintAnimalTokenAddress;
    //디플로이 주소값을 담는다.

    constructor (address _mintAnimalTokenAddress) {
        mintAnimalTokenAddress = MintAnimalToken(_mintAnimalTokenAddress);
    }

    mapping(uint256 => uint256) public animalTokenPrices;
    //가격관리 맵핑

    uint256[] public onSaleAnimalTokenArray;
    //FE 에서 사용할 배열. 판매중이 토큰 확인용

    function setForSaleAnimalToken(uint256 _animalTokenId, uint256 _price) public {
        //판매 등록 함수

        address animalTokenOwner = mintAnimalTokenAddress.ownerOf(_animalTokenId);

        require(animalTokenOwner == msg.sender, "Caller is not animal token owner");
        //함수 실행 사람이 토큰의 주인이 맞는지
        require(_price > 0 , "Price is zero or lower");

        require(animalTokenPrices[_animalTokenId] == 0, "This animal token is already on sale.");

        require(mintAnimalTokenAddress.isApprovedForAll(animalTokenOwner, address(this)), "Animal token owner did not approve token.");
        //스마트컨트랙트 판매권한 여부를 확인
        
        animalTokenPrices[_animalTokenId] = _price;

        onSaleAnimalTokenArray.push(_animalTokenId);
    }
}

 

알고리즘

  • MintAnimalToken 장부를 먼저 Deploy 해야한다.
  • Deploy 된 장부의 주소값을 인자로 등록한다.
  • setForSaleAnimalToken 함수
    • 등록할 토큰의 ID 를 가진 주인의 주소를 불러온다.
    • 함수를 실행하는 사람이 msg.sender 가 주인이 맞는지 확인한다.
    • 가격이 0 초과인지 확인한다.
    • 판매등록이 되지 않은 상품인지 확인한다.
    • 해당 장부가 판매등록이 허가가 된 장부인지 확인한다.
    • 배열에 판매등록을 한다.

 

function purchaseAnimalToken(uint256 _animalTokenId) public payable {
        //payble 을 붙여야 matic 이 왔다갔다하는 함수를 실행할 수 있다.

        uint256 price = animalTokenPrices[_animalTokenId];
        address animalTokenOwner = mintAnimalTokenAddress.ownerOf(_animalTokenId);

        require(price > 0, "Animal Token not sale.");
        require(price <= msg.value, "Caller sent lower than price.");
        //함수를 실행할때 보내는 MATIC 의 양이 같거나 큰지 확인
        require(animalTokenOwner != msg.sender, "Caller is animal Token owner");

        payable(animalTokenOwner).transfer(msg.value);
        //가격만큼의 양이 돈의 주인으로 보내진다.

        mintAnimalTokenAddress.safeTransferFrom(animalTokenOwner, msg.sender, _animalTokenId);

        animalTokenPrices[_animalTokenId] = 0;

        for(uint256 i = 0; i<onSaleAnimalTokenArray.length;i++){
            if(animalTokenPrices[onSaleAnimalTokenArray[i]] == 0){
                onSaleAnimalTokenArray[i] = onSaleAnimalTokenArray[onSaleAnimalTokenArray.length -1];
                onSaleAnimalTokenArray.pop();
                //맨뒤랑 바꿔서 맨뒤 삭제
            }
        }
        
        
    }
    //읽기 전용 FE 함수
    //판매중인 리스트 확인용
    function getOnSaleAnimalTokenArrayLength() view public returns (uint256){
            return onSaleAnimalTokenArray.length;
    }

 

알고리즘

  • payable 을 사용해야 구입 판매 함수를 사용할 수 있다.
  • 구매할 토큰의 ID 와 토큰 주인의 주소를 불러온다.
  • 토큰의 판매여부를 확인
  • 구매자가 보낸 금액이 토큰 가격보다 크거나 같은지 확인
  • 구매자가 판매자와 동일한지 확인
  • payable 함수를 이용해 msg.value 에 저장된 금액을 판매자로 송금
  • 판매된 토큰을 배열에서 제거

후기

  • Solidity 언어를 많이 다룰줄 알았는데 아닌것같다. 그래도 대충 하는 법을 익힌듯 해서 얼른 만들어버리고 다른 강의 들어야겠다.

 

전체 알고리즘

MintAnimal Token Deploy
mint 주소를 이용해 Sale 장부 실행
Animal 민팅
Sale 장부 허가
판매 등록후 확인
다른 계정으로 구매후 구매확인

반응형

'BlockChain > Solidity' 카테고리의 다른 글

[DP/NFT] 4. 리액트 세팅  (0) 2022.09.05
[DP/NFT] 2. Minting contract 작성  (0) 2022.09.05
[DP/NFT] 1. Install Solidity & Metamask  (0) 2022.08.25
반응형
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract MintAnimalToken is ERC721Enumerable {
    constructor() ERC721("h662Animals", "HAS"){}

    mapping(uint256 => uint256) public animalTypes;
    // animalTokenID 입력하면 animalType 이 나오게 맵핑

    function mintAnimalToken() public {
        uint256 animalTokenId = totalSupply() + 1;
        //nft 에 유일한 값을 부여

        //랜덤한 값을 생성
        //Keccak 알고리즘을 사용하기 위해 byte 값이 필요
        //abi.encodePacked 에 세가지 수를 사용하여 겹치지 않는 수 생성
        //5로 나눈 나머지에 1을 더하여 animal type 이 1~5 사이의 값이 생성되도록 함
        uint256 animalType = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, animalTokenId))) % 5 + 1;

        animalTypes[animalTokenId] = animalType;
        //mapping

        _mint(msg.sender, animalTokenId); // 명령어를 실행하는 사람, 토큰 ID 를 민팅
    }
}

 

알고리즘 요약

  • Keccak 알고리즘을 통해 1~5 랜덤값 생성
  • 맵핑을 통한 ID 별로 types 결정

디버그

  • 컨트랙트이름과 함수이름을 동일하게 짓는 실수는 하지말자
반응형

'BlockChain > Solidity' 카테고리의 다른 글

[DP/NFT] 4. 리액트 세팅  (0) 2022.09.05
[DP/NFT] 3. SaleAnimalToken 작성  (0) 2022.09.05
[DP/NFT] 1. Install Solidity & Metamask  (0) 2022.08.25
반응형

Solidity remix IDE 를 local 연결

VScode 를 이용해 Solidity IDE 와 연결을 하려고한다.

그러기 위해서는 remixd 라는 패키지를 설치해야한다.

 

npm install -g @remix-project/remixd

위의 명령어로 remixd 를 설치해야한다. 설치 도중 권한 오류가 나오면 Windows 기준 powershell을 열어 다음과 같이 설정을 바꾼다.

 

 

remixd -s . --remix-ide [https://remix.ethereum.org](https://remix.ethereum.org/)

명령어를 이용해 IDE와 연결할 준비를 한다.

다음 remix IDE 에 들어와 local 과 연결을 누르면 연결이 된다.

 

 

Metamask & Polygon testnet

https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/related?hl=ko

Metamask를 다운받는다.

 

 

 

네트워크 추가창에서 다음과 같이 입력한다.

네트워크이름 : 자유

새RPC URL : https://matic-mumbai.chainstacklabs.com

체인 ID : 50001

통화기호 : MATIC

블록 탐색기 URL : https://mumbai.polygonscan.com

이 후 네트워크가 생성이 되면

https://faucet.polygon.technology/

에서 자신의 지갑 주소를 입력후 테스트용 코인을 발급받는다.

 

 

성공

반응형

'BlockChain > Solidity' 카테고리의 다른 글

[DP/NFT] 4. 리액트 세팅  (0) 2022.09.05
[DP/NFT] 3. SaleAnimalToken 작성  (0) 2022.09.05
[DP/NFT] 2. Minting contract 작성  (0) 2022.09.05

+ Recent posts