본문 바로가기

DAPP

Spring Boot Web3j Lib를 사용하여 이더리움 Contract 통신 방법

반응형

Web3j는 이더리움 블록체인과 상호작용하기 위한 자바 라이브러리입니다. 이러한 기술을 결합하여 이더리움 스마트 컨트랙트와 통신하는 방법을 소스로 보여드리겠습니다.

 

여기에서는 Infura API apiKey 및 smart contract wrapper java 파일이 준비되어 있어야 합니다.

 

1. Infura API apiKey는 아래 사이트에서 가입 후 얻으실 수 있습니다.

https://www.infura.io/

 

Ethereum API | IPFS API & Gateway | ETH Nodes as a Service

Infura's development suite provides instant, scalable API access to the Ethereum and IPFS networks. Connect your app to Ethereum and IPFS now, for free!

www.infura.io

2. smart contract wrapper 파일은 솔리디티의 ABI파일을 java로 변환한 파일입니다.

여기에서는 예제로 만들어놓은 NFT 솔리디티를 java로 변환하여 진행하였습니다.

 

전체 소스는 맨 아래 깃허브 주소를 남겨놓았으며 변환된 wrapper 파일은 아래 경로에서 확인하실 수 있습니다.

https://github.com/shan0325/blockchain/blob/master/back-web3j-ex01/src/main/java/com/blockchain/backweb3jex01/contract/NFT.java

 

예제로 만들어놓은 NFT 스마트컨트랙트 주소는 아래와 같습니다.

0x16a7d4f76197d5b77048189a4e31ae86f58a5eaa

 

3. 아래는 소스 내용입니다.

pom.xml

<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>core</artifactId>
    <version>4.9.6</version>
</dependency>

 

application.yml 파일

metamask:
  WALLET_ADDRESS: "" # 지갑 주소
  CONTRACT_ADDRESS: "0x16a7d4f76197d5b77048189a4e31ae86f58a5eaa" # 통신할 스마트컨트랙트 주소
  PRIVATE_KEY: "" # 지갑 private key

infura:
  API_URL: "https://goerli.infura.io/v3/[apiKey]" # infura goerli api url 정보

 

Web3jConfig.java 파일

@Configuration
public class Web3jConfig {

    @Value("${infura.API_URL}")
    private String INFURA_API_URL;

    @Value("${metamask.PRIVATE_KEY}")
    private String PRIVATE_KEY;

    @Value("${metamask.CONTRACT_ADDRESS}")
    private String CONTRACT_ADDRESS;

    @Bean
    public Web3j web3j() {
        return Web3j.build(new HttpService(INFURA_API_URL));
    }

    @Bean
    public Credentials credentials() {
        BigInteger privateKeyInBT = new BigInteger(PRIVATE_KEY, 16);
        return Credentials.create(ECKeyPair.create(privateKeyInBT));
    }

    @Bean
    public NFT nft() {
        BigInteger gasPrice = Contract.GAS_PRICE;
        BigInteger gasLimit = Contract.GAS_LIMIT;
        StaticGasProvider gasProvider = new StaticGasProvider(gasPrice, gasLimit);

        return NFT.load(CONTRACT_ADDRESS, web3j(), credentials(), gasProvider);
    }
}

 

Web3jService.java 파일

@Slf4j
@RequiredArgsConstructor
@Service("web3jService")
public class Web3jService {

    private final Web3j web3j;
    private final NFT nft;

    @Value("${metamask.WALLET_ADDRESS}")
    private String WALLET_ADDRESS;

    @Value("${metamask.CONTRACT_ADDRESS}")
    private String CONTRACT_ADDRESS;


    // 현재 블록 번호
    public EthBlockNumber getBlockNumber() throws ExecutionException, InterruptedException {
        return web3j.ethBlockNumber().sendAsync().get();
    }

    // 지정된 주소의 계정
    public EthAccounts getEthAccounts() throws ExecutionException, InterruptedException {
        return web3j.ethAccounts().sendAsync().get();
    }

    // 계좌 거래 건수
    public EthGetTransactionCount getTransactionCount() throws ExecutionException, InterruptedException {
        EthGetTransactionCount result = new EthGetTransactionCount();
        result = web3j.ethGetTransactionCount(WALLET_ADDRESS,
                        DefaultBlockParameter.valueOf("latest"))
                .sendAsync()
                .get();
        return result;
    }

    // 계정 잔액 조회
    public EthGetBalance getEthBalance() throws ExecutionException, InterruptedException {
        return web3j.ethGetBalance(WALLET_ADDRESS,
                        DefaultBlockParameter.valueOf("latest"))
                .sendAsync()
                .get();
    }

    // 스마트컨트랙트명 가져오기
    public String getContractName() throws Exception {
        return nft.name().send();
    }

    // nft 발행 건수
    public BigInteger currentCount() throws Exception {
        return nft.balanceOf(WALLET_ADDRESS).send();
    }

    // nft 발행
    public TransactionReceipt nftCreate() throws Exception {
        return nft.create(WALLET_ADDRESS, "ipfs://QmNZLXLk8nWG4PMdcCWAGpgW12hAhiV375YeFpaCLisfBi")
                .sendAsync()
                .get();
    }

    // nft 거래가 발생할 경우 subscribe에 등록한 함수가 실행됨
    public void transferEventFlowable() throws Exception {
        web3j.ethLogFlowable(getEthFilter())
                .subscribe(logData -> {
                    log.info("logData : {}", logData);
                    String data = logData.getData();
                    log.info("data : {}", data);
                    String address = logData.getAddress();
                    log.info("address : {}", address);
                });

        Thread.sleep(10000000);
    }

    // 이더리움 블록체인에서 발생하는 이벤트를 필터링하는데 사용(여기에서는 Transfer(거래)만 허용)
    private EthFilter getEthFilter() throws Exception {
        EthBlockNumber blockNumber = getBlockNumber();
        EthFilter ethFilter = new EthFilter(DefaultBlockParameter.valueOf(blockNumber.getBlockNumber()), DefaultBlockParameterName.LATEST, CONTRACT_ADDRESS);

        Event event = new Event("Transfer",
                Arrays.asList(
                        new TypeReference<Address>(true) {
                            // from
                        },
                        new TypeReference<Address>(true) {
                        },
                        new TypeReference<Uint256>(false) {
                            // amount
                        }
                ));
        String topicData = EventEncoder.encode(event);
        ethFilter.addSingleTopic(topicData);
        ethFilter.addNullTopic();// filter: event type (topic[0])
        //ethFilter.addOptionalTopics("0x"+ TypeEncoder.encode(new Address("")));

        return ethFilter;
    }
}

 

Web3jServiceTest.java Junit 테스트 파일 입니다.

@Slf4j
@SpringBootTest
class Web3jServiceTest {

    @Resource(name = "web3jService")
    private Web3jService web3jService;

    @Test
    public void getBlockNumber() throws ExecutionException, InterruptedException {
        EthBlockNumber ethBlockNumber = web3jService.getBlockNumber();
        long id = ethBlockNumber.getId();
        BigInteger blockNumber = ethBlockNumber.getBlockNumber();

        log.info("id : {}", id);
        log.info("blockNumber : {}", blockNumber);
    }

    @Test
    public void getEthAccounts() throws ExecutionException, InterruptedException {
        EthAccounts ethAccounts = web3jService.getEthAccounts();
        List<String> accounts = ethAccounts.getAccounts();

        log.info("accounts : {}", accounts);
    }

    @Test
    public void getTransactionCount() throws ExecutionException, InterruptedException {
        EthGetTransactionCount ethGetTransactionCount = web3jService.getTransactionCount();
        BigInteger transactionCount = ethGetTransactionCount.getTransactionCount();

        log.info("transactionCount : {}", transactionCount);
    }

    @Test
    public void getEthBalance() throws ExecutionException, InterruptedException {
        EthGetBalance ethGetBalance = web3jService.getEthBalance();
        BigInteger balance = ethGetBalance.getBalance();

        log.info("balance : {}", balance);
    }

    @Test
    public void getContractName() throws Exception {
        String contractName = web3jService.getContractName();

        log.info("contractName : {}", contractName);
    }

    @Test
    public void currentCount() throws Exception {
        BigInteger currentCount = web3jService.currentCount();

        log.info("currentCount : {}", currentCount);
    }

    @Test
    public void nftCreate() throws Exception {
        log.info("start time : {}", LocalDateTime.now());

        TransactionReceipt transactionReceipt = web3jService.nftCreate();
        log.info("transactionReceipt : {}", transactionReceipt);

        log.info("end time : {}", LocalDateTime.now());
    }

    @Test
    public void transferEventFlowable() throws Exception {
        web3jService.transferEventFlowable();
    }

}

 

 

전체 소스는 아래 깃 허브에 있습니다.

https://github.com/shan0325/blockchain/tree/master/back-web3j-ex01

 

GitHub - shan0325/blockchain

Contribute to shan0325/blockchain development by creating an account on GitHub.

github.com

 

반응형