# Caver.js 에서 Kip17 토큰 소각

**URL:** <https://devforum.kaia.io/t/caver-js-kip17/4272>\
**Category:** Caver\
**Tags:** caver-js\
**Created:** [3월 9, 2022, 9:43오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272 "2022-03-09T09:43:08Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 9, 2022, 9:43오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/1 "2022-03-09T09:43:08Z")

</div>

안녕하세요, 클레이튼 IDE를 이용해 KIP17 컨트랙트를 발행했고

컨트랙트로 만들어진 NFT들을 소각하려고 합니다.

아래와 같이 caver.js로 소각을 시도했습니다만,

```
ret = caver.klay.accounts.createWithAccountKey(minterAddress, minterPrivateKey);
ret = await caver.klay.accounts.wallet.add(ret);
const kip17Instance = await new caver.klay.KIP17({컨트랙트주소});
  ret = await kip17Instance.burn({토큰ID},{
    from : minterAddress,
    gas: '850000'
  }).then(console.log());

```

계속 revert가 발생중입니다. 토큰의 전송이나 다른 api들은 정상적으로 처리가 되는데, 유독 burn만 작동이 안되고 있습니다. 특별한 이유가 있을까요?  
혹시 몰라서 setApprovalForAll을 다른 주소에 넘겨주고 해당 주소로도 burn을 해봤는데 작동이 안됩니다.

---

<div class="post-metadata">

**Author:** ![Denver](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/denver/32/557_2.png) [@Denver](https://devforum.kaia.io/u/Denver)\
**Post date:** [3월 10, 2022, 2:47오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/2 "2022-03-10T02:47:32Z")

</div>

@eklee808

안녕하세요.  
burn은 내부적으로 [\_isApprovedOwner](https://github.com/klaytn/klaytn-contracts/blob/5abb4a5ceb3c45dc840e7670c0b7cca7dddca666/contracts/token/KIP17/KIP17.sol#L196-L207) 를 사용해서 Burn할 자격이 있는지를 검증하고 있습니다.

링크로 드린 코드를 참고해보셔서 정말 자격이 있는지를 검증해보는 게 우선일 거 같습니다.

감사합니다.

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 10, 2022, 8:25오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/3 "2022-03-10T08:25:30Z")

</div>

답변 감사합니다!

링크해주신 컨트랙트를 보면 \_isApprovedOwner는 burn 함수에는 없고 transferFrom에 들어가 있는데, burn에서도 사용하는 부분이 또 있는 건가요?

말씀해주신대로 isApprovedOrOwner를 참고해보면 아래처럼 자격을 체크하는 부분이 있는데,

return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));

일단 owner 주소를 넣어도 소각이 안되서 isApprovedForAll()에 (owner주소,owner주소)로 파라미터를 넘겨보니 false가 떨어지더군요. 해서 setApprovalForAll() 함수로 다른 주소에 권한을 주고, 권한을 받은 주소를 넣고 burn을 시도해봐도 똑같이 revert가 뜨더군요;;

일단 \_burn에 보면

require(ownerOf(tokenId) == owner, “KIP17: burn of token that is not own”);

이렇게 토큰의 소유자만 최종적으로 권한을 주는 것으로 보여서 다른 계정으로 토큰을 전송하고 burn을 해보려고 합니다.

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 20, 2022, 4:07오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/5 "2022-03-20T16:07:16Z")

</div>

현재 caver를 사용한 소각기능이 전체적으로 적용이 되지 않습니다. 위에 서술한 것처럼 다른 계정으로 KIP17 토큰을 전송하고 burn을 해도 여전히 되지 않네요.

---

<div class="post-metadata">

**Author:** ![Denver](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/denver/32/557_2.png) [@Denver](https://devforum.kaia.io/u/Denver)\
**Post date:** [3월 21, 2022, 4:35오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/6 "2022-03-21T04:35:28Z")

</div>

@eklee808

혹시 배포하신 컨트랙트 코드가 어떻게 되시나요?  
배포하신 컨트랙트 코드와 컨트랙트 주소 공유 부탁드립니다.

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 21, 2022, 5:01오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/7 "2022-03-21T05:01:40Z")

</div>

컨트랙트 주소는 다음과 같습니다. 바오밥 네트워크입니다.  
0x0F94F275AC8E7f108794bCE4490cd4F5e320FE05 - 팩토리 컨트랙트  
0xb1b488632179e46ff0431b2af0dcd67dd5ddfc2e - 팩토리에서 생성된 KIP17 컨트랙트

팩토리 컨트랙트에서 문제가 발생할 것으로 추정되는 위치는 다음과 같습니다.

```auto
pragma solidity ^0.5.6;
import './NFTMinter.sol';

contract NFTManager{

    address payable owner;  
    mapping (address => NFTMinter) public nftManager;

    constructor() public {
        owner = msg.sender;
    }
    function mintNewToken(string memory _name, string memory _symbol) public returns (address){
        NFTMinter minter = new NFTMinter(owner, _name, _symbol); 
//여기서 일단 팩토리가 컨트랙트를 만드는데, 이 부분에서 Creator가 일단 팩토리 컨트랙트로 정해집니다.
        nftManager[msg.sender] = minter;        
        minter.transferOwnership(msg.sender);
//이 부분에서 오너십은 메소드를 호출한 주소로 전송됩니다. Creator와 Owner가 분리됩니다.
        return address(nftManager[msg.sender]);
    }

    function burnToken(uint _tokenNum, address minterAddress) public {
        nftManager[minterAddress].burnToken(_tokenNum);
//Kip17 컨트랙트의 소각을 연결합니다. 
    }

```

위 코드의 burnToken은 caver.klay.kip17.burn()이 revert가 나기 때문에 직접 kip17 컨트랙트와 연결해서 sendTransaction으로 시도해보기 위해서 만들었습니다.

KIP17의 \_burn 메소드를 보면 ownerOf(tokenId) == owner를 require하는데, '토큰의 소유자’만 필터링해서 토큰을 삭제하는 것으로 보여집니다. 헌데 직접 배포하고 발행까지 한 주소에서는 소각이 안되고, 이를 다른 주소로 보내더라도 역시 소각이 되지 않습니다.

달리 소각이 안될만한 이유가 있을까요?

---

<div class="post-metadata">

**Author:** ![Denver](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/denver/32/557_2.png) [@Denver](https://devforum.kaia.io/u/Denver)\
**Post date:** [3월 21, 2022, 5:06오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/8 "2022-03-21T05:06:50Z")

</div>

@eklee808

팩토리 컨트랙트 뿐만 아니라 문제가 되는 컨트랙트 자체도 공유해주시면 감사하겠습니다.

그리고 NFTManager.burnToken 메서드의 경우 내부적으로 NFTMinter.burnToken 을 호출하고 있는 것으로 보이는데요, Caver에서 제공하는 KIP17.burn 의 경우 아래와 같이 `burn(uint256)` 이라는 메서드 시그니쳐와 매핑되는 기능입니다.

이 부분을 헷갈리신 건 아닌지도 확인 부탁드려요.

```auto
pragma solidity 0.4.24;

/// @title KIP-17 Non-Fungible Token Standard, optional burning extension
/// Note: KIP-13 identifier for this interface is 0x42966c68.
interface IKIP17Burnable {
    /// @notice Destroy the specified token
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    /// operator, or the approved address for this NFT. Throws `_tokenId`
    /// is not a valid NFT. 
    /// @param _tokenId The token ID to be burned
    function burn(uint256 _tokenId) public;
}

```

---

<div class="post-metadata">

**Author:** ![Kale](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/kale/32/148_2.png) [@Kale](https://devforum.kaia.io/u/Kale)\
**Post date:** [3월 21, 2022, 5:16오전 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/9 "2022-03-21T05:16:14Z")

</div>

안녕하세요. @eklee808

코드를 확인해보니 NftMinter의 burnToToken을 호출하게 되어있는데, 이 부분은 함수가 어떻게 되어있는지요?

연결 및 사용하는 KIP17 contract의 code를 명확히 파악하신 후 burn에서 사용하는 권한 체크가 어떻게 되어있는지 확인하시어 코드를 작성 및 디버깅 해보시는게 빠를 것 같습니다.

제가 확인해본바로는 클레이튼에서 제공하는 KIP-17 contract의 burn함수는 권한체크하는 함수가 있습니다.  
이 부분 확인해보셨는지요?

> <https://github.com/klaytn/klaytn-contracts/blob/master/contracts/token/KIP17/KIP17Burnable.sol#L33>

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 21, 2022, 1:43오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/10 "2022-03-21T13:43:41Z")

</div>

답변 늦어진 점 죄송합니다.  
아래는 팩토리에서 생성된 컨트랙트의 생성자와 소각부분입니다.

```auto
pragma solidity ^0.5.6;

import "@klaytn/contracts/token/KIP17/KIP17Full.sol";
import "./Ownable.sol";

contract NFTMinter is KIP17Full, Ownable{  
  address payable ownerAddr;  

  constructor(address payable _owner, string memory _name, string memory _symbol) KIP17Full(_name, _symbol) public { 
    ownerAddr = _owner;

  }

  function burnToken(uint _tokenNum) public{
    _burn(msg.sender, _tokenNum);

    }

```

말씀해주신 부분을 보니 caver에서 제공하는 burn의 경우 KIP17Burnable의 burn 메소드와 매칭되는 것 같은데, 그러면 KIP17Full을 받아서 생성되는 컨트랙트의 경우 KIP17Metadata와 KIP17Enumerable만 포함하고 있기 때문에 적용이 안될 수 있을까요?  
KIP17.sol의 경우에도 역시 burn 메소드를 포함하고 있고, KIP17.sol의 burn 메소드의 경우는 다음과 같이 토큰ID의 소유권만 체크하고 있습니다.

```auto

    function _burn(address owner, uint256 tokenId) internal {
        require(ownerOf(tokenId) == owner, "KIP17: burn of token that is not own");

        _clearApproval(tokenId);

        _ownedTokensCount[owner].decrement();
        _tokenOwner[tokenId] = address(0);

        emit Transfer(owner, address(0), tokenId);
    }

```

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 21, 2022, 1:53오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/11 "2022-03-21T13:53:26Z")

</div>

안녕하세요, 답변 감사합니다!

말씀해주신 컨트랙트의 함수는 다음과 같습니다.

```auto
pragma solidity ^0.5.6;

import "@klaytn/contracts/token/KIP17/KIP17Full.sol";
import "./Ownable.sol";

contract NFTMinter is KIP17Full, Ownable{  
  address payable ownerAddr;  

  constructor(address payable _owner, string memory _name, string memory _symbol) KIP17Full(_name, _symbol) public { 
    ownerAddr = _owner;

  }

  function burnToken(uint _tokenNum) public{
    _burn(msg.sender, _tokenNum);

    }

```

링크해주신 KIP-17 contract의 burn 함수는 Burnable 컨트랙트를 is로 받아서 생성해야 적용이 될까요?  
일단, 제 컨트랙트의 경우엔 KIP17Full만 받았기 때문에 Burnable의 메소드는 호출되지 않을 것이라고 추측됩니다만, Burnable의 메소드로 매칭될 경우에, isApprovedOrOwner의 경우에는 다음과 같이 호출주소가 Token의 Owner이면 Approved의 유무와 상관 없이 소각되어야 할 것 같은데, 역시 Burnable이 포함되지 않아서 매칭이 되지 않는 걸까요?  
(참고로 컨트랙트는 Klaytn IDE로 배포했습니다.)

```auto

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        require(_exists(tokenId), "KIP17: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

```

또, Burnable의 burn을 KIP17 instance로 호출하지 않고, 제가 작성한 burnToken으로 KIP17의 \_burn 메소드를 호출하지는 않을지 궁금합니다.

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 21, 2022, 2:08오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/12 "2022-03-21T14:08:06Z")

</div>

추가적으로 방금 컨트랙트에 KIP17Burnable을 추가하고 테스트해보니 토큰의 소유주라면 소각이 가능했습니다. 정말 감사합니다!

앞으로의 문제는 해결되었습니다만, KIP17Burnable이 아닌 KIP17Full의 \_burn() 메소드로는 소각이 안되는 걸까요? 아니면 다른 부분에서 제가 확인해볼 수 있는 부분이 있을까요?

---

<div class="post-metadata">

**Author:** ![Denver](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/denver/32/557_2.png) [@Denver](https://devforum.kaia.io/u/Denver)\
**Post date:** [3월 21, 2022, 3:26오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/13 "2022-03-21T15:26:56Z")

</div>

@eklee808  
`_burn()` 메서드는 internal로 선언되어 있어 외부에서 호출이 불가합니다. 해당 내용은 솔리디티 문서를 참고하시면 더 자세히 알아보실 수 있을 거예요.

추가적으로 internal 이 아니었다 해도 메서드 시그니쳐 중 이름과 데이터 타입이 서로 다르기 때문에(앞에 언더바가 있냐 없냐의 유무 + uint256이 아닌 uint로 선언되어 있음) Caver의 KIP17의 burn으로는 호출이 안됩니다.  
스마트 컨트랙트의 메서드를 호출하는 행위는 로우레벨에서 보면  
메서드의 시그니쳐를 해쉬한 값 중 앞 4바이트로 어떤 함수를 호출할지를 구분합니다.

해시라는 건 입력이 다르면 출력값도 다르겠죠. 즉  
`keccak256("burn(uint256)")` 의 결과 중 앞 4바이트를 추출한 값과  
`keccak256("_burn(uint)")`의 결과 중 앞 4바이트를 추출한 값은 당연히 다릅니다.

Caver에서 제공하는 기능은 KIP17 표준에 정의된 메서드를 편하게 호출할 수 있게 해주는 것인데 질문자분께서 사용하시는 컨트랙트는 KIP17의 표준에서 사용하고 있는 burn 이 아닌 다른 함수 시그니쳐로 사용을 하고 계시죠.  
Caver SDK에서는 `burn(uint256)` 함수를 호출하려하는데, 질문자분의 컨트랙트에는 해당 함수가 없으니 에러가 발생하고 있는 것이구요.

internal로 선언되어 있어서 호출이 안되겠지만, 그게 아니었다고 해도 이미 함수 시그니쳐 자체가 다르기에 Caver의 KIP17.burn 을 활용하셔서 호출하실 수는 없습니다.

---

<div class="post-metadata">

**Author:** ![eklee808](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/eklee808/32/1740_2.png) [@eklee808](https://devforum.kaia.io/u/eklee808)\
**Post date:** [3월 21, 2022, 4:51오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/14 "2022-03-21T16:51:44Z")

</div>

늦은 시간에 답변 정말 감사합니다. uint256과 uint를 혼용해서 사용했던 것은 제가 찾아내야 했던 이슈인데 정말 죄송합니다…

다만, caver.klay.kip17 api로 사용한 것은 kip17 instance, 그러니까 생성된 컨트랙트를 직접 인스턴스화한 후 burn api를 호출했으니 제가 팩토리 컨트랙트로부터 호출한 burnToken과는 별개로 테스트했던 것입니다. 이 경우 컨트랙트에 Burnable이 포함되어 있지 않아서 소각이 되지 않았던 것으로 보입니다.

말씀해주신대로 파라미터는 uint256으로 전달하고, 컨트랙트 전체에서 혼용하는 부분을 없앤 뒤 테스트했습니다. 팩토리 컨트랙트에서는 호출했더니 revert가 났고, 이 부분을 Klaytn IDE에서 직접 호출해보니 다음과 같은 메시지를 받았습니다.  
(테스트 환경을 위해 KIP17Burnable은 다시 제외했습니다)

 ![민팅안됨](https://canada1.discourse-cdn.com/flex035/uploads/klaytn/original/2X/0/0702d8f694f27243723bd21adce7a533898f196a.jpeg)

한가지 헷갈리는건, 이 경우 메소드 호출은 됐는데, require문 때문에 걸린 것인지, 아니면 말씀해주신대로 internal이기 때문에 호출이 되지 않은 것인데 IDE의 메시지만 저렇게 뜬 것인지 궁금합니다.

마지막으로, 팩토리에서 생성된 KIP17Full 컨트랙트의 ABI와 Address를 직접 가져와서, 다음과 같이 sendTransaction을 보내니 성공적으로 소각이 되었습니다.

```auto

          ret = await caver.klay.sendTransaction({
            type: 'SMART_CONTRACT_EXECUTION',
            from: minterAddress, //발행주소
            to: nftAddress, //배포된 컨트랙트 주소
            data: contract.methods.burnToken(tokenNum).encodeABI(),
            gas: '1000000'
          })

```

추측컨데 \_burn(uint256) 메소드가 internal이기 때문에 revert가 나지 않은 것인가 생각이 됩니다. 헌데, 팩토리 컨트랙트에서는 burnToken()메소드를 호출하지, \_burn(uint256)을 직접 호출하는 것이 아닌데 internal에 걸리는 것인지 궁금합니다.

답변 덕분에 문제되던 부분이 상당히 해결되었습니다. 늦은 시간까지 정말 감사하고 죄송합니다.

---

<div class="post-metadata">

**Author:** ![Denver](https://yyz1.discourse-cdn.com/flex035/user_avatar/devforum.kaia.io/denver/32/557_2.png) [@Denver](https://devforum.kaia.io/u/Denver)\
**Post date:** [3월 21, 2022, 11:52오후 UTC](https://devforum.kaia.io/t/caver-js-kip17/4272/15 "2022-03-21T23:52:59Z")

</div>

@eklee808

burnToken을 통해 internal 함수인 `_burn(uint256)` 을 호출하는 건 문제 없습니다.  
제가 말씀드린 호출이 안된다는 부분은 직접 호출하려는 시도(data 필드에 `_burn(uint256)`의 ABI를 넣어서 트랜잭션을 보내는 행위)가 안된다고 말씀 드린 거예요.

IDE의 메시지는 아래 `Returned error: execution reverted KIP17: burn of token that is not own` 으로 미루어보았을 때, require 문 조건에 걸리기 때문인 거 같습니다.
