2주차 과제는 ide테스트넷으로 nft를 발행해보고 임의의 마켓컨트랙트도 짜서 토큰을 보내보는 것이다.
내 컨트랙트는 이렇다.
pragma solidity >=0.4.24 <=0.5.6;
contract nft_kms {
address public kmsowner;
mapping (uint256 => address) private tokenIds; // private으로 전환
mapping (uint256 => string) private tokenURIs;
mapping (address => uint256[]) private _ownedTokens;
//소유한 토큰 리스트
bytes4 private constant _KIP17_RECEIVED = 0x6745782b;
constructor () public {
kmsowner = msg.sender;
tokenIds[0] = msg.sender; // 0번에 나만의 토큰 만듦*^^*
tokenURIs[0] = 'kms nft token';
_ownedTokens[msg.sender].push(0);
}
function tokenURI (uint256 tokenId) public view returns (address, string memory) {
return (tokenIds[tokenId], tokenURIs[tokenId]); // 여기서 id만 치면 (소유자, 정보)를 알수있다.
}
function mint_token (uint256 set_Id, address set_owner, string memory set_URI) public returns (bool) { // id, 오너, uri를 인자로 토큰을 발행
require (kmsowner == msg.sender, 'not owner'); // 나만이 토큰을 발행할수있다! ..사실 빼야하는 목록이다.
require (set_Id != 0, 'you cant mint nft "0"');
tokenIds[set_Id] = set_owner;
tokenURIs[set_Id] = set_URI;
_ownedTokens[set_owner].push(set_Id);
return true;
}
function safeTransferFrom (address from, address to, uint256 tokenId, bytes memory _data) public { // why from??
require (tokenIds[tokenId] == msg.sender, 'you are not token\'s owner');
//굳이 내 주소from을 넣어주었을까 이렇게 해도 되는데
//아마 토큰 오너가 잘못되었는지, msg.sender와 내 주소가 다른지의 오류메세지를 따로주기위함은 아닐지
tokenIds[tokenId] = to;
_ownedTokens[to].push(tokenId);
_removeToken(msg.sender, tokenId);
require(
_checkOnKIP17Received(from, to, tokenId, _data), "kIP17: transfer to non KIPreceiver implementer"
);
}
function _checkOnKIP17Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { //
bool success;
bytes memory returndata;
if (!isContract(to)) {
return true;
}
(success, returndata) = to.call( // error
abi.encodeWithSelector(
_KIP17_RECEIVED,
msg.sender,
from,
tokenId,
_data
)
);
if (
returndata.length != 0 &&
abi.decode(returndata, (bytes4)) == _KIP17_RECEIVED // error
) {
return true;
}
return false;
}
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account)}
return size > 0;
}
function _removeToken(address from, uint256 tokenId) private {
uint256 lastId_index = _ownedTokens[from].length - 1;
for (uint256 i = 0; i < _ownedTokens[from].length; i++){
if (tokenId == _ownedTokens[from][i]) {
_ownedTokens[from][i] = _ownedTokens[from][lastId_index];
break;
}
}
_ownedTokens[from].length--;
}
function ownedTokens(address owner) public view returns (uint256[] memory) {
return _ownedTokens[owner];
}
}
contract NFTMarket { // 마켓
mapping (uint256 => address) public seller;
function buyNFT(uint256 tokenId, address NFTaddress) public payable returns (bool) {
// 0.01klay
address payable receiver = address(uint256(seller[tokenId])); // payable??
//send 0.01klay at receiver(seller)
receiver.transfer(10**16);
nft_kms(NFTaddress).safeTransferFrom(address(this), msg.sender, tokenId, '0x00');
return true;
}
//마켓이 토큰을 받았을 때 판매자가 누구인지
function onKIP17Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4) {
seller[tokenId] = from;
return bytes4(keccak256("onKIP17Received(address, address, uint256, bytes)"));
}
}
뭐 대충 토큰을 발행하고 오너주소에 토큰id를 저장하고 토큰을 보내는 함수들이다.
아래 NFTMarket은 마켓에 토큰을 보내고 payable을 사용해 토큰을 사고 오너를 넘기는 함수들이 있다.
특이사항은 처음에 크롬으로 klaytn ide에서 컴파일 했을때 오류가 떴었다. 배포는 당연히 안된다. 조코딩님의 코드와 거의 같은데도 말이다.
코드를 복붙해도 마찬가지였기에 노트북 문제인줄 알았다. 하지만 우연히피씨방에서 컴파일을 했는데 자잘한 warning만 뜨고 되는것이다! 배포역시 잘 됬다!!
드디어 됬다는 감격과 이거 안된다고 슬랙에 징징짜고 몇일동안 고생했다는게 참 화나기도 했다.
똑같은 코드, 사이트인데 브라우저에 따라 환경이 달라진단 말인가...
집에서 Microsoft Edge라는 브라우저로 설마 컴파일 해봤더니 된다!!
이것참 klaytnIDE의 미스터리이다.(되도 크롬이 되야하는거 아닌가?)
이렇게 심증과 견증만 남겨둔채 ide 컴파일 오류사건은 일단락 되었다.
2주차 과제 제출 깃헙
강해져서 돌아온 3주차 과제!
이번 과제는 만든 컨트랙트를 js를 사용해 BAPP을 만드는 과제이다. 하지만 언제나 그랬듯 과제는 무슨 강의 따라가기에 바빴다.
결과물은 강의와 거의 비슷했다.
게다가 이번주는 아직 공부해보지못한 js를 난사하여 더더욱 그랬다.
일단 코드는 이렇다.
import logo from './logo.svg'; // 로고 띄우는 애
import QRCode from "qrcode.react"; // qr코드 api연결해주는 애
import Caver from "caver-js"; // caver쓰게 해주는 애
import './App.css';
import { privateKey } from 'caver-js/packages/caver-wallet/src/keyring/keyringFactory'; // ??
import React, { useState } from 'react'; // useState를 쓸수있게 해주는 애
import * as KlipAPI from "./UseKlip"; // 다른 파일에서 import하여 정리
// const MY_CONTRACT_ADDRESS = '0x84370bc1457Aed3D8685727D0C27cba58526AAd3';
const MY_CONTRACT_ADDRESS = '0x914Bdb3E825B573b51cE05DD3d19353e330d8F7F'; //MAIN
const ACCESS_KEY_ID = 'KASKQY...'; // KAS(klaytn Api Service)에서 회원가입 후 얻을 수 있음.
const SECRET_ACCESS_KEY = '9/Lw/c8uN...';
// const CHAIN_ID = '1001'; // baobab
const CHAIN_ID = '8217'; // main net
const CONTRACT_ABI = [ { "constant": false, "inputs": [ { "name": "set_Id", "type": "uint256" }, { "name": "set_owner", "type": "address" }, { "name": "set_URI", "type": "string" } ], "name": "mint_token", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "from", "type": "address" }, { "name": "to", "type": "address" }, { "name": "tokenId", "type": "uint256" }, { "name": "_data", "type": "bytes" } ], "name": "safeTransferFrom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "setting", "type": "uint256" } ], "name": "setkms", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "constant": true, "inputs": [], "name": "kms", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "kmsowner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "owner", "type": "address" } ], "name": "ownedTokens", "outputs": [ { "name": "", "type": "uint256[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "tokenId", "type": "uint256" } ], "name": "tokenURI", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" } ];
// ABI가 너무 길면 보기 그럴수 있으니 line break remover를 사용하자
const option = {
headers: [
// "x-chain-id: 1001", // 1번째 시도
// "Authorization: Basic S0FTS1FZWkZZMzRPMlpJUDFBOTQ5MUQ2OjkvTHcvYzh1Tk5KM0hUcDBvRXBKeTJaUnQ3NHFVVllQbUNVazU2OFE="
// {ACCESS_KEY_ID:SECRET_ACCESS_KEY // 2번째 시도
{ // 보고 따라한 것, 알고보니 아래 HttpProvider에서 괄호를 잘못써줌...
name: "Authorization",
value: "Basic " + Buffer.from(ACCESS_KEY_ID + ":" + SECRET_ACCESS_KEY).toString("base64")
},
{name: "x-chain-id", value: CHAIN_ID} //main net, test net
]
}
const caver = new Caver(new Caver.providers.HttpProvider("https://node-api.klaytnapi.com/v1/klaytn", option));
const nft_kms = new caver.contract(CONTRACT_ABI, MY_CONTRACT_ADDRESS); //
const readcontract = async () => { //
try {
const _nft = await nft_kms.methods.kmsowner().call(); //
console.log(`오너:${_nft}`); //
} catch(e) { // 예외처리
console.log(`[ERROR떴다요]${e}`);
}
}
const getBalance = (address) => {
return caver.rpc.klay.getBalance(address).then((response) => {
const _balance = caver.utils.convertFromPeb(caver.utils.hexToNumberString(response));
console.log(`BALANCE: ${_balance}`);
return _balance;
})
}
const mint_token = async (set_Id, set_owner, set_URI) => { //됨!!!! 감격스럽다, 대견스럽다만 개인키를 적어야 한다는게 흠.
// 사용할 계정 설정
try {
const privatekey1 = '0x5c7...'; // !!
const deployer = caver.wallet.keyring.createFromPrivateKey(privatekey1);
caver.wallet.add(deployer);
const receipt = await nft_kms.methods.mint_token(set_Id, set_owner, set_URI).send({
from: deployer.address,
gas: "0x5555555"
})
console.log(`보냈수${receipt}`);
} catch(e) {
console.log(`[ERROR_MINT]${e}`);
}
}
const show = (t_id) => { // 안됨..
try {
const result = nft_kms.methods.tokenURI(t_id).call(); //
console.log(`URI:${result}`); //
} catch(e) {
console.log(`[ERROR떴다요ㅜ]${e}`);
}
}
const DEFAULT_QR_CODE = 'DEFAULT';
function App() {
readcontract(); //
getBalance('0xb36229b6eba7980055898d077e53000ff3149463');
// show(100);
const [balance, setBalance] = useState("0");
const [qrValue, setQrvalue] = useState(DEFAULT_QR_CODE);
const onClickGetAddress = () => {
KlipAPI.getAddress(setQrvalue);
};
const onClickSetKms = () => {
KlipAPI.setkms(321, setQrvalue);
};
const onClickMint = () => {
KlipAPI.mint_token (321, 0xb36229b6eba7980055898d077e53000ff3149463, "you mint Token in API", setQrvalue);
};
return (
<div className="App">
<header className="App-header">
{/* <img src={logo} className="App-logo" alt="logo" /> */}
{/* <button title={'토큰 발행'} onClick={()=>{mint_token("100", "0xb36229b6eba7980055898d077e53000ff3149463", "mint from caver")}}>눌러</button> */}
<br />
<br />
<button title={'주소 가져오기'} onClick={()=>{
onClickGetAddress();
}}>
주소가져오기
</button> // 버튼은 요렇게 만든다
<button title={'setkms321'} onClick={()=>{
onClickSetKms();
}}>
kms321
</button>
<button title={'mint_tk'} onClick={()=>{
onClickMint();
}}>
토큰 발행하기
</button>
<br />
<br />
<QRCode value={qrValue} />
<p>{balance}</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
그리고 큐알코드 api js
import axios from "axios";
const MY_CONTRACT_ADDRESS = '0x914Bdb3E825B573b51cE05DD3d19353e330d8F7F';
const A2P_API_PREPARE_URL = "https://a2a-api.klipwallet.com/v2/a2a/prepare";
export const setkms = (count, setQrvalue) => { // 1함수
axios.post(
A2P_API_PREPARE_URL,{
bapp: {
name: 'KLAY_MARKET'
},
type: "execute_contract", //
transaction: {
to: MY_CONTRACT_ADDRESS, //이렇게 인자를 넣어준다
abi: '{ "constant": false, "inputs": [ { "name": "setting", "type": "uint256" } ], "name": "setkms", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }',
value: "0",
params: `[\"${count}\"]`
}
}).then((response) => {
const { request_key } = response.data;
const qrcode = `https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
setQrvalue(qrcode);
let timerId = setInterval(()=> {
axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`).then((res)=> {
if (res.data.result) { //왔따!!!
console.log(`[kms321 Result] ${JSON.stringify(res.data.result)}`);
if (res.data.result.status === 'success') {
clearInterval(timerId);
}
//
}
})
}, 1000)
})
}
export const getAddress = (setQrvalue) => { // 2함수
axios.post(
A2P_API_PREPARE_URL,{
bapp: {
name: 'KLAY_MARKET'
},
type: "auth"
}
).then((response) => {
const { request_key } = response.data;
const qrcode = `https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
setQrvalue(qrcode);
let timerId = setInterval(()=> {
axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`).then((res)=> {
if (res.data.result) { //왔따!!!
console.log(`[Address Result] ${JSON.stringify(res.data.result)}`);
clearInterval(timerId);
}
})
}, 1000)
})
}
export const mint_token = (count, address, string, setQrvalue) => { // 3함수
axios.post(
A2P_API_PREPARE_URL,{
bapp: {
name: 'KLAY_MARKET'
},
type: "excuse_contract",
transaction: {
to: MY_CONTRACT_ADDRESS,
abi: '{ "constant": false, "inputs": [ { "name": "set_Id", "type": "uint256" }, { "name": "set_owner", "type": "address" }, { "name": "set_URI", "type": "string" } ], "name": "mint_token", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }',
value: "0",
params: `[\"${count}\", ${address}, ${string}]`
}
}
).then((response) => {
const { request_key } = response.data;
const qrcode = `https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
setQrvalue(qrcode);
let timerId = setInterval(()=> {
axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`).then((res)=> {
if (res.data.result) { //왔따!!!
console.log(`[mint Result] ${JSON.stringify(res.data.result)}`);
clearInterval(timerId);
}
})
}, 1000)
})
}
...이렇게 만들어진 결과물 :
'토큰 발행하기'가 오류가 뜬 모습. 인자를 잘못넣어준듯 하다.
F12를 누르면 오른쪽과 같은 관리자 창?을 볼수 있다.
특이사항으로는 강의에서 인자가 하나인 컨트렉트 함수를 불러서 사용했지만 한번 인자를 여러개로 주려고 했지만 쉼표로 나누어야 할지 "`","`"로 나누어야 할지 []로 나누어야 할지 감이 잘 오지 않아 결국 에러만 냈다..ㅂㄷㅂㄷ
1주차에 비해 굉장히 강력해진 강의와 과제에 그저 끌려다니기만 했다. 이게 무슨뜻인지 생각해볼 겨를없이 똑같이 따라쓰기만 했던것 같다.
만약 강의없이 다시 해보라고 하면 바지에 똥을 쌀 자신이 있다.
뭐 그래도 나름의 이럴것이다~라는 감은 생겼다.
제일 화가 났던것은 설정할때와 알파벳 대문자소문자때문에 오류나고 고생했을 때였다.
슬랙에 도와주신 분들이 많아 고맙다.(헤엄님도!)
다만 다들 배우는 입장이라 추측으로 알려주셨는데 조코딩님이 짚고 넘겨주셨으면 했다.