ERC-5192 이란?
https://eips.ethereum.org/EIPS/eip-5192#copyright
https://it-timehacker.tistory.com/455
ERC-5192: Minimal Soulbound NFTs
Minimal interface for soulbinding EIP-721 NFTs
eips.ethereum.org
ERC-5192는 ERC-721을 기반으로 만들어졌으며 ERC-721의 기능을 상속한다.
여기에 ERC-5192는 전송이 불가능하다는 특성이 존재한다.
생성시에 잠금 여부를 한번 정하고 SBT면 잠김 상태로 모든 컨트렉트가 그 이후에는 작동을 하고 잠금 상태가 아니라면 일반적인 NFT처럼 작동을 하는 모델
- Locked 여부
- Locked와 Unlocked 이벤트

ERC-5192 실습-1 (vscode와 Sepolia 테스트넷)
NTT : Non-Transferable Token
- attestate/ERC5192 설치
https://github.com/attestate/ERC5192/tree/main
npm install erc5192
- openzeppelin 환경 설정
이때, openzeppelin의 v5 버전을 사용하면 아래와 같은 오류가 발생한다.
기존 버전 확인
npm list @openzeppelin/contracts
기존 버전 삭제
npm uninstall @openzeppelin/contracts
특정 버전으로 설치
npm install @openzeppelin/contracts@4.9
- 컨트랙트 작성
Contract NTT is ERC5192 : ERC5192를 상속받아 기능을 확장한 스마트 컨트랙트
ERC5192 Instance를 생성할 때(contract를 배포할 때) 이름, 토큰의 심볼, Locked 초기 잠금여부를 인수로 받는다.
constructor(string memory _name, string memory _symbol, bool _isLocked) ERC5192(_name, _symbol, _isLocked) {}
상속 관계일 때 생성자
부모 컨트랙트가 생성자를 가지고 있을 때, 자식 컨트랙트 생성자 안에서 부모 생성자를 명시적으로 호출해야 한다.
contract B{
constructor (){
}
}
컨트랙트 A는 B를 상속 받는다.
contract A is B {
constructor() B() {
}
}
- 코드 컴파일
truffle compile
- 컨트랙트 sepolia에 배포
deployer.deploy()는 첫 번째 매개변수로 계약 이름을 받고, 그 이후로 그 계약 생성자에 전달될 매개변수들을 받는다.
//migrations/2_deploy_contract.js
const NTT = artifacts.require("NTT");
const UseNTT = artifacts.require("UseNTT");
module.exports = async function (deployer, network, accounts) {
await deployer.deploy(UseNTT);
const useNTTInstance = await UseNTT.deployed();
console.log("UseNTT deployed at:", useNTTInstance.address);
};
//truffle-config.js
sepolia: {
provider: () =>
new HDWalletProvider(
[process.env.MNEMONIC], // Private Key
`https://eth-sepolia.g.alchemy.com/v2/${process.env.AICHEMY_PROJECT_ID}`
),
network_id: 11155111,
gas: //트랜잭션의 가스한도
maxFeePerGas: //(baseFee + maxPriorityFeePerGas)의 상한선
maxPriorityFeePerGas: //가스 단위당 지불할 최대 우선순위 수수료
EIP-1559
EIP-1559는 이더리움 네트워크에서 트랜잭션 비용을 예측 가능하게 만들기 위해 도입된 모델
gasPrice는 EIP-1559 이전의 방식이라 maxFeePerGas와 maxPriorityFeePerGas 로 대체되었다.
실제 가스 비용 = 사용된 가스 × (baseFee + 실제 지불된 priorityFee)
가스의 단위는 Gwei
1 ETH = 10^9 Gwei (1,000,000,000 Gwei)
1 ETH = 10^18Wei
- maxFeePerGas : 트랜잭션당 지불할 의향이 있는 총 최대 가스 가격(baseFee + priorityFee), baseFee(네트워크가 정하는 기본 수수료) + maxPriorityFeePerGas를 합한 값보다 커야 함. (baseFee + maxPriorityFeePerGas)의 상한선 역할을 한다.
- maxPriorityFeePerGas : 가스 단위당 지불할 최대 우선순위 수수료
truffle migrate --network sepolia
- useNTT 함수 호출하기
instance = await UseNTT.deployed();
instance.useNTT("NTT 받을 주소");
⚠️ Fail with error 'ERC721: transfer to non ERC721Receiver implementer'
openzeppelin ERC721 구현 내용
- _safeMint
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
ERC721Utils.checkOnERC721Received(_msgSender(), address(0), to, tokenId, data);
}
- _mint 함수
function _mint(address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner != address(0)) {
revert ERC721InvalidSender(address(0));
}
}
현재 NTT 계약에서 safeMint를 사용하고 있어서 발생하는 문제
_mint를 사용하는 방식으로 변경하였다
SBT 동작 검증
전송하려고 하니 예상된 오류 발생
SBT 토큰이 lock 되어있기 때문에 전송이 불가능 하다.

ERC-5192 실습-2 (Remix와 ganache)
NTT 계약 발급

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;
import "./ERC5192.sol";
contract NTT is ERC5192 {
bool private isLocked;
constructor(string memory _name, string memory _symbol, bool _isLocked)
ERC5192(_name, _symbol, _isLocked)
{
isLocked = _isLocked;
}
function Mint(address to, uint256 tokenId) external {
_mint(to, tokenId);
if (isLocked) emit Locked(tokenId);
}
function safeTransfer(address from, address to, uint256 tokenId) external {
require(!isLocked, "NTT: Token is locked and cannot be transferred.");
transferFrom(from, to, tokenId); // Calls ERC721's transferFrom
}
}

UseNTT 계약
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;
import "./NTT.sol";
contract UseNTT {
NTT public ntt;
constructor(address _nttAddress) {
ntt = NTT(_nttAddress);
}
function useNTT(address receiver) public {
uint256 tokenId = 0; // Define token ID
address minter = address(this);
ntt.Mint(minter, tokenId);
ntt.safeTransfer(minter, receiver, tokenId);
}
}

SBT 동작 검증
전송하려고 하니 예상된 오류 발생
SBT 토큰이 lock 되어있기 때문에 전송이 불가능 하다.

'Solidity' 카테고리의 다른 글
업그레이드 가능한 contract (Proxy) (0) | 2025.04.03 |
---|---|
Solidity의 event와 로그 저장 (0) | 2024.12.18 |