java Web3j 이용한 transaction 생성

java Web3j 이용한 transaction 생성

지갑 App 개발 및 신규 서비스인 launchpad(토큰 프리세일즈) 작업했었던.. 기록

·

11 min read

서비스 개발팀 팀장 담당하면서 팀 리딩 뿐아니라 개발까지 직접 참여 한것중 신규 컨트렉트에 transaction 생성하여 mainnet에 참여 기록 및 프로젝트 생성 관리등 개발했던 내용을 기록 하고자 합니다.

Our Mainnet 공통 transaction 관리 컴포넌트

앞서.. Needs 는 아래와 같았습니다.

1. As-is 소스중 반복적인 코드 발견 (rawTransaction 생성등)
2. 반복적인 코드로 인해 소스 커맨드 라인 길어짐 (가독성 저하)
3. 새로운 함수 추가시 또 반복적인 소스 추가되는 문제

git history (저만 직접 작업했었군요...ㅠ)

[코드]


@Service
class KsEthereumCommonService(
        private val web3j: Web3j,
        private val transactionManageDao: TransactionManageDAO,
        private val commonUserUtil: CommonUserUtil,
        private val octetApiService: OctetApiService<*>,
        private val octetWalletService: OctetWalletService,
) {
    private val log = LoggerFactory.getLogger(javaClass)
    private val TX_SUCCESS = "0x1"

    @Value("\${octet.keycurrency.symbol}")
    private val keyCurrencySymbol: String? = null

    /**
     * transaction 상태 조회
     */
    fun getTransactionStatus(txHash: String): KsTransactionStatusVO {
        var result = KsTransactionStatusVO(
                txHash=txHash
        )
        try {
            val transactionReceipt = web3j.ethGetTransactionReceipt(txHash).send()

            if (transactionReceipt.transactionReceipt.isPresent) {
                val receipt = transactionReceipt.result
                val status = receipt.status

                // "0x1" 또는 "0x0"으로 결과가 나올 수 있습니다.
                // "0x1"은 성공, "0x0"은 실패를 나타냅니다.
                if (TX_SUCCESS == status) {
                    log.info("[txHash: $txHash] Transaction was successful.")
                    result.txHash = txHash
                    result.status = true
                } else {
                    log.info("[txHash: $txHash] Transaction failed.")
                    result.txHash = txHash
                    result.status = false
                }
            } else {
                log.info("[txHash: $txHash] Transaction receipt is not available yet.")
            }
            return result
        }
        catch (e: Exception){
            log.error("getTransactionStatus ::: $e")
            return result
        } finally {
            web3j.shutdown()
        }

    }

    /**
     * Transaction Receipt 조회]
     */
    @Throws(Exception::class)
    fun getTransactionReceipt(transactionHash: String): KsTransactionReceiptVO  = getTransactionReceiptV2(transactionHash)

    @Throws(Exception::class)
    fun getTransactionReceiptV2(txHash: String): KsTransactionReceiptVO {
        val receiptProcessor: TransactionReceiptProcessor = PollingTransactionReceiptProcessor(web3j,
                1000L,
                TransactionManager.DEFAULT_POLLING_ATTEMPTS_PER_TX_HASH)

        val receipt = receiptProcessor.waitForTransactionReceipt(txHash)

        var revertReason: String? = null
        if(!receipt.isStatusOK && StringUtils.isNotEmpty( receipt.revertReason )){
            revertReason = _decodeRevertReason(receipt.revertReason)
            if(revertReason != null){
                revertReason = revertReason.replace("\\u0000", "")
                revertReason = revertReason.replace("\u0000", "")
            }
        }

        return  KsTransactionReceiptVO(
                isSuccess = receipt.isStatusOK,
                functionName = "",
                transactionReceipt=receipt,
                revertReason=revertReason
        )
    }

    private fun _decodeRevertReason(input: String): String? {
        // Remove the first 10 characters (0x08c379a0) which is the "Error(string)" signature
        val encodedRevertReason = input.substring(10)
        val bytes = Numeric.hexStringToByteArray(encodedRevertReason)
        val reason = Numeric.toHexStringNoPrefix(bytes)
        return String(Numeric.hexStringToByteArray(reason))
    }

    /**
     * 공통 트랜젝션 (serialized and Octet Signning Transaction
     */
    fun commonOctetSendTransaction(from: String, to: String, value: BigInteger?, data: String, nonce: BigInteger, gasPrice: BigInteger, gasLimit: BigInteger): String {
        var serializedTx = serialized(to, value, data, nonce, gasPrice, gasLimit)
        return signOctetTransaction(from, serializedTx)
    }

    /**
     * 직접 Signning 후 호출
     */

    @Throws(Exception::class)
    fun sendSignningTransaction(credentials: Credentials, to: String, data: String, nonce: BigInteger,
                               gasPrice: BigInteger, gasLimit: BigInteger) : KsTransactionReceiptVO{
        try {
            log.info("### from address : ${credentials.address}")
            val txManager: TransactionManager = RawTransactionManager(web3j, credentials)
            val txHash = txManager.sendTransaction(gasPrice, gasLimit,to, data, BigInteger.ZERO).transactionHash
            log.info("### txHash::: $txHash")
            val transactionReceipt: KsTransactionReceiptVO = getTransactionReceipt(txHash)
            log.info("### transactionReceipt::: $transactionReceipt")

            return transactionReceipt
        }
        catch (e: Exception){
            log.error("#### sendSignningTransaction ::: $e")
            throw e
        }
    }

    fun ethCallTransaction(from: String, to: String, functionName: String, inputParameters: List<Type<*>>, outputParameters: List<TypeReference<*>>): List<Type<*>>{
        try{
            val function = Function(functionName, inputParameters, outputParameters)

            val encodedFunction = FunctionEncoder.encode(function)
            val ethCallTransaction = Transaction.createEthCallTransaction(from, to, encodedFunction)
            // ETH Call 트랜잭션 실행
            val ethCall = web3j.ethCall(ethCallTransaction, DefaultBlockParameterName.LATEST).send()
            if(ethCall.hasError()){
                val resultError = ethCall.error
                log.error("#### ethCallTransaction ERROR ::: $resultError")
                return listOf()
            }

            val result = ethCall.result
            // 반환값 디코딩
            return FunctionReturnDecoder.decode(result, outputParameters as MutableList<TypeReference<Type<Any>>>)
        }
        catch (e: Exception){
            log.error("#### ethCallTransaction ::: $e")
        }
        finally {
            web3j.shutdown()
        }
        return listOf()

    }


    /**
     * serialized transaction
     */
    fun serialized(to: String, value: BigInteger?, data: String, nonce: BigInteger, gasPrice: BigInteger, gasLimit: BigInteger): String {
        var rawTransaction = if (value != null) {
            RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data)
        } else {
            RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, data)
        }

        val encodedRawTx = TransactionEncoder.encode(rawTransaction)
        val hexValue = Numeric.toHexString(encodedRawTx)

        log.debug("_serialized() hexValue : $hexValue")

        return hexValue
    }

    /**
     * Octet signning Tx
     */
    fun signOctetTransaction(from: String, serializedTx: String): String {
        val loginUser: TokenPayloadVo = commonUserUtil.loginUserInfo
        // 2. 로우 트랜잭션 서명(Step.1)
        val signTxDto = signTxDto()
        signTxDto.serializedTx = serializedTx
        signTxDto.address = from

        val resultMap: MutableMap<String, Object> = octetApiService.post("/v2/$keyCurrencySymbol/transaction/signTx", null, signTxDto).body as MutableMap<String, Object>
        log.info("### signTx result ::: $resultMap")
        // 3. 서명 트랜잭션 전송(Step.2)
        val sendSignedTxDto = sendSignedTxDto()
        sendSignedTxDto.serializedTx = if(resultMap["signedSerializedTx"] != null){
            resultMap["signedSerializedTx"].toString()
        } else {
            null
        }

        sendSignedTxDto.reqId = octetWalletService.generateReqId(" ksEthereumCommonService", loginUser.userId)
        val octSendSignedTxVo: MutableMap<String, Object> = octetApiService.post("/v2/$keyCurrencySymbol/transaction/sendSignedTx", null, sendSignedTxDto).body as MutableMap<String, Object>
        log.info("### sendSignedTx result ::: $octSendSignedTxVo")

        return if (octSendSignedTxVo["transactionHash"] != null){
            octSendSignedTxVo["transactionHash"].toString()
        }
        else {
            ""
        }
    }


    /**
     * Address By Nonce
     */
    fun getNonceByAddress(address: String): BigInteger {
        var nonce = BigInteger("0")
        try {
            val ethGetTransactionCount = web3j.ethGetTransactionCount(
                    address, DefaultBlockParameterName.PENDING).sendAsync().get()
            nonce = ethGetTransactionCount.transactionCount
            log.info("#### [address={}] nonce ::: {}", address, nonce)
        } catch (e: Exception) {
            log.error("_getNonceByAddress ::: $e")
        } finally {
            web3j.shutdown()
        }
        return nonce
    }


    /**
     * Address By gasPrice
     */
    fun getGasPrice(address: String?): BigInteger {
        var gasPrice = BigInteger("0")
        try {
            val ethGasPrice = web3j.ethGasPrice().send()
            gasPrice = ethGasPrice.gasPrice
        } catch (e: Exception) {
            log.error("_getGasPrice ::: $e")
        } finally {
            web3j.shutdown()
        }
        return gasPrice
    }

    /**
     * Address GasLimit (DB)
     */
    fun getEstimateGasByDb(methodName: String?, initGasLimit: String?): BigInteger {
        var gasLimit = BigInteger("0")
        try {
            val estimateGasDto = estimateGas()
            estimateGasDto.methodName = methodName
            var estimageGas: String? = transactionManageDao.getEstimateGasByDb(estimateGasDto)
            if ("" == estimageGas || estimageGas == null) {
                estimageGas = initGasLimit
            }
            gasLimit = BigInteger(estimageGas)
        } catch (e: java.lang.Exception) {
            log.error("_getGasPrice ::: $e")
        }
        return gasLimit
    }
}
    1. transaction Receipt 조회 개발

      1. sign send Transaction
        - signOctetTransaction : 사용자 wallet 키의 경우 SaaS 사용(Hexlant) 직접 Signning하지 못하고 API로 Signnig 하고 send하는 기능을 수행합니다.
        - sendSignningTransaction : 저희측에서 직접 private key 를 관리하는 경우 (Contract Owner 나 Admin Wallet이 이에 해당 됩니다.) 직접 Signning후 저희 메인넷에 직접 transaction을 보냅니다.

      2. 공통 구현
        - gas price / limit 조회 함수
        - address nones 조회함수
        - 트랜젝션 데이터 serialize 함수
        - rawTransaction 생성 함수(ethCall 함수)

      3. 추가로 블록 번호 조회해오는 함수도 필요할것으로 보이는데 아직 사용하지 않아서 ㅎ

런치패드 Contract 호출용 서비스

[코드]


@Service
class KsEthereumLaunchpadService(
        private val contractAddressService: ContractAddressService,
        private val ksEthereumCommonService: KsEthereumCommonService,
        private val accountRepository: AccountRepository
) {
    private val log = LoggerFactory.getLogger(javaClass)
    private val LAUNCHPAD_CONTRACT_NAME = "LaunchPad" // LaunchPadErc20
    private val LAUNCHPAD_ERC20_CONTRACT_NAME = "LaunchPadErc20"

    @Value("\${private.key.launchpad.owner}")
    private lateinit var PRIVATE_KEY_LAUNCHPAD_OWNER: String

    private val INIT_GAS_LIMIT = "10000000"


    //================================================
    // currency 런치패드 !!
    //================================================
    /**
     * launchpad investment
     */
    @Throws(Exception::class)
    fun launchpadInvestment(from: String, amount: BigDecimal, symbol: String): String {
        val functionName = "investment"
        //input
        var _symbol = Utf8String(symbol)
        val inputParameters: List<Type<*>> = arrayListOf(_symbol)
        val outputParameters: List<TypeReference<*>> = ArrayList()
        val function = Function(functionName, inputParameters, outputParameters)
        var data = CommonUtil.fromHex(FunctionEncoder.encode(function))

        val contract: DeployedContractVO = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)
        log.info("### launchpad contract ::: $contract")
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)
        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(functionName, "210000")

        var resultTransactionHash = ksEthereumCommonService.commonOctetSendTransaction(from, contract.address, amount.toBigInteger(), data, nonce, gasPrice, gasLimit)
        log.info("### [launchpadInvestment] resultTransactionHash ::: $resultTransactionHash")
        return resultTransactionHash
    }

    /**
     * project getTotalInvested Contract call (GET_
     */
    fun getTotalInvested(symbol: String): BigDecimal {
        val functionName = "getTotalInvested"
        var _symbol = Utf8String(symbol)
        val inputParameters: List<Type<*>> = arrayListOf(_symbol)
        var outputParam = object : TypeReference<Uint256>() {}
        val outputParameters: List<TypeReference<*>> = arrayListOf(outputParam)
        val contract: DeployedContractVO = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)
        val adminVote: AccountEntity = accountRepository.findByPublicNameTag(
                ContractAdminWalletConstants.ADMIN_WALLET_NAME_VOTE)

        var result = ksEthereumCommonService.ethCallTransaction(adminVote.address, contract.address, functionName, inputParameters, outputParameters)
        val totalInvestment = result.get(0).value as BigInteger
        log.info("#### totalInvestment ::: $totalInvestment")
        // System.out.println("balanceValue Value: " + balanceValue);
        val value: BigDecimal = Convert.fromWei(BigDecimal(totalInvestment), Convert.Unit.WEI)
        log.info("#### value ::: $value")

        return value
    }

    @Throws(Exception::class)
    fun setProjectMeta(tokenSymbol: String, title: String, startedBlockNumber: Long,
                             minInvestmentPerUser: String, maxInvestmentPerUser: String): KsTransactionReceiptVO {
        val methodName = "setProjectMeta"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Utf8String(title))
        inputParameters.add(Uint256(BigInteger(
                startedBlockNumber.toString())))
        inputParameters.add(Uint256(BigInteger(
                minInvestmentPerUser)))
        inputParameters.add(Uint256(BigInteger(
                maxInvestmentPerUser)))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials: Credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val encodedFunction = FunctionEncoder.encode(function)
        val data = CommonUtil.fromHex(encodedFunction)

        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    @Throws(Exception::class)
    fun setProjectTokenMetadata(tokenSymbol: String, tokenAddress: String,
                                  quantityOfCoinToSellOutTokens: String, tokenPriceInCoin: String):KsTransactionReceiptVO {
        val methodName = "setTokenMetaData"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()

        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Address(tokenAddress))
        inputParameters.add(Uint256(BigInteger(
                quantityOfCoinToSellOutTokens.toString())))
        inputParameters.add(Uint256(BigInteger(
                tokenPriceInCoin)))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)

        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    fun openProject(tokenSymbol: String, tokenProvider:String): KsTransactionReceiptVO {
        val methodName = "openProject"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Address(tokenProvider))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    fun setMaxBatchSize(maxBatchSize: Long): KsTransactionReceiptVO{
        val methodName = "setMaxBatchSize"
        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Uint256(maxBatchSize!!))

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address

        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    @Throws(Exception::class)
    fun closeProject(tokenSymbol: String, result: Int): KsTransactionReceiptVO{
        val methodName = if (result == 0) "closeProjectFail" else "closeProjectSuccess"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Utf8String(tokenSymbol))
        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }


    fun executeBatchAirDropToken(tokenSymbol: String, batchIndex: Long): KsTransactionReceiptVO{
        val methodName = "executeBatchAirDropToken"
        val contract = contractAddressService.getContractByName(LAUNCHPAD_CONTRACT_NAME)

        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Uint8(batchIndex))

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    //================================================
    // erc20 런치패드 !!
    //================================================

    /**
     * 프로젝트 메타데이터
     */
    @Throws(Exception::class)
    fun setProjectMeta(investTokenCA: String,
                            tokenSymbol: String, title: String, startedBlockNumber: Long,
                            minInvestmentPerUser: String, maxInvestmentPerUser: String): KsTransactionReceiptVO {
        val methodName = "setProjectMeta"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Address(investTokenCA)) //_investTokenCA
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Utf8String(title))
        inputParameters.add(Uint256(BigInteger(
                startedBlockNumber.toString())))
        inputParameters.add(Uint256(BigInteger(
                minInvestmentPerUser)))
        inputParameters.add(Uint256(BigInteger(
                maxInvestmentPerUser)))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials: Credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val encodedFunction = FunctionEncoder.encode(function)
        val data = CommonUtil.fromHex(encodedFunction)

        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    /**
     * 토큰 메타 데이터
     */
    @Throws(Exception::class)
    fun setProjectTokenMetadata(investTokenCA: String, tokenSymbol: String, tokenAddress: String,
                                  quantityOfCoinToSellOutTokens: String, tokenPriceInCoin: String):KsTransactionReceiptVO {
        val methodName = "setTokenMetaData"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Address(investTokenCA)) //_investTokenCA

        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Address(tokenAddress))
        inputParameters.add(Uint256(BigInteger(
                quantityOfCoinToSellOutTokens.toString())))
        inputParameters.add(Uint256(BigInteger(
                tokenPriceInCoin)))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)

        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    /**
     * 오픈 프로젝트
     */
    fun openProject(investTokenCA: String, tokenSymbol: String, tokenProvider:String): KsTransactionReceiptVO{
        val methodName = "openProject"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Address(investTokenCA)) //_investTokenCA
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Address(tokenProvider))

        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    fun setMaxBatchSizeByErc20(maxBatchSize: Long): KsTransactionReceiptVO{
        val methodName = "setMaxBatchSize"
        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Uint256(maxBatchSize!!))

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    @Throws(Exception::class)
    fun closeProject(investTokenCA: String, tokenSymbol: String, result: Int): KsTransactionReceiptVO{
        val methodName = if (result == 0) "closeProjectFail" else "closeProjectSuccess"
        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Address(investTokenCA)) //_investTokenCA
        inputParameters.add(Utf8String(tokenSymbol))
        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }

    fun executeBatchAirDropToken(investTokenCA: String, tokenSymbol: String, batchIndex: Long): KsTransactionReceiptVO{
        val methodName = "executeBatchAirDropToken"
        val contract = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)

        val inputParameters: MutableList<Type<*>> = java.util.ArrayList()
        inputParameters.add(Address(investTokenCA)) //_investTokenCA
        inputParameters.add(Utf8String(tokenSymbol))
        inputParameters.add(Uint8(batchIndex))

        val outputParameters: List<TypeReference<*>> = java.util.ArrayList()
        val function = Function(methodName, inputParameters, outputParameters)

        val credentials = Credentials.create(PRIVATE_KEY_LAUNCHPAD_OWNER)
        val from = credentials.address
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)

        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(methodName, INIT_GAS_LIMIT)

        val encodedFunction = FunctionEncoder.encode(function)

        val data = CommonUtil.fromHex(encodedFunction)

        return ksEthereumCommonService.sendSignningTransaction(credentials, contract.address,
                data, nonce, gasPrice, gasLimit
        )
    }


    @Throws(Exception::class)
    fun launchpadInvestment(investTokenCA: String, from: String, amount: BigDecimal, symbol: String): String {
        val functionName = "investment"
        //input
        val _symbol = Utf8String(symbol)
        val _investmentTokenCA = Address(investTokenCA)

        val inputParameters: List<Type<*>> = arrayListOf(_symbol, _investmentTokenCA)
        val outputParameters: List<TypeReference<*>> = ArrayList()
        val function = Function(functionName, inputParameters, outputParameters)
        var data = CommonUtil.fromHex(FunctionEncoder.encode(function))

        val contract: DeployedContractVO = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)
        log.info("### launchpad contract ::: $contract")
        val nonce: BigInteger = ksEthereumCommonService.getNonceByAddress(from)
        val gasPrice: BigInteger = ksEthereumCommonService.getGasPrice(from)
        var gasLimit: BigInteger = ksEthereumCommonService.getEstimateGasByDb(functionName, "210000")

        var resultTransactionHash = ksEthereumCommonService.commonOctetSendTransaction(from, contract.address, amount.toBigInteger(), data, nonce, gasPrice, gasLimit)
        log.info("### [launchpadInvestment] resultTransactionHash ::: $resultTransactionHash")
        return resultTransactionHash
    }

    fun getTotalInvested(investTokenCA: String, symbol: String): BigDecimal {
        val functionName = "getTotalInvested"
        var _symbol = Utf8String(symbol)
        val _investmentTokenCA = Address(investTokenCA)

        val inputParameters: List<Type<*>> = arrayListOf(_symbol, _investmentTokenCA)
        var outputParam = object : TypeReference<Uint256>() {}
        val outputParameters: List<TypeReference<*>> = arrayListOf(outputParam)
        val contract: DeployedContractVO = contractAddressService.getContractByName(LAUNCHPAD_ERC20_CONTRACT_NAME)
        val adminVote: AccountEntity = accountRepository.findByPublicNameTag(
                ContractAdminWalletConstants.ADMIN_WALLET_NAME_VOTE)

        var result = ksEthereumCommonService.ethCallTransaction(adminVote.address, contract.address, functionName, inputParameters, outputParameters)
        val totalInvestment = result.get(0).value as BigInteger
        log.info("#### totalInvestment ::: $totalInvestment")
        // System.out.println("balanceValue Value: " + balanceValue);
        val value: BigDecimal = Convert.fromWei(BigDecimal(totalInvestment), Convert.Unit.WEI)
        log.info("#### value ::: $value")

        return value
    }
}

해당 서비스에는 2개의 contract가 존재하여 관련 함수 호출 하는 기능을 수행합니다. (기축통화 투자용 런치패드, erc20 Token 투자용 런치패드)

  1. sendSignningTransaction 함수를 사용하는 경우 저희가 직접 signning 하여 메인넷에 트랜잭션을 보냅니다.

  2. commonOctetSendTransaction 함수를 사용하는 경우에는 SaaS API를 이용해 메인넷에 트랜잭션을 보냅니다. (고객의 private key 는 자사에서 직접 관리되지 않아 이에 해당됩니다.)

여기까지 컨트랙트 호출하는 서비스를 구현한 모습이고 이외 이미 구현된 Contract Call 하는 부분도 가독성있게 서비스를 분리하는 작업을 진행할 예정입니다.

감사합니다.