package com.izouma.nineth.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alipay.mychain.sdk.api.utils.Utils; import com.antfinancial.mychain.baas.tool.restclient.RestClient; import com.antfinancial.mychain.baas.tool.restclient.RestClientProperties; import com.antfinancial.mychain.baas.tool.restclient.model.CallRestBizParam; import com.antfinancial.mychain.baas.tool.restclient.model.Method; import com.antfinancial.mychain.baas.tool.restclient.model.ReceiptDecoration; import com.antfinancial.mychain.baas.tool.restclient.response.BaseResp; import com.izouma.nineth.config.Constants; import com.izouma.nineth.config.GeneralProperties; import com.izouma.nineth.dto.NFT; import com.izouma.nineth.dto.NFTAccount; import com.izouma.nineth.event.AccountCreatedEvent; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.utils.SnowflakeIdWorker; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Hex; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Service @Slf4j @AllArgsConstructor public class NFTService { private final RestClient restClient; private final RestClientProperties restClientProperties; private final GeneralProperties generalProperties; private final SnowflakeIdWorker snowflakeIdWorker; private final ApplicationContext context; private final Environment env; @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 5000), value = BusinessException.class) public NFTAccount createAccount(String username) { CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(String.valueOf(snowflakeIdWorker.nextId())) .bizid(Constants.bizId) .account(restClientProperties.getAccount()) .mykmsKeyId(restClientProperties.getKmsId()) .newAccountId(username) .newAccountKmsId(Constants.kmsKey) .method(Method.TENANTCREATEACCUNT) .gas(100000L).build(); try { BaseResp baseResp = restClient.chainCallForBiz(callRestBizParam); NFTAccount account = new NFTAccount(username, Constants.kmsKey, baseResp.getData()); if (baseResp.isSuccess() || "400123".equals(baseResp.getCode())) { log.info("创建账户成功 {}", account); return account; } else { throw new RuntimeException(baseResp.getCode()); } } catch (Exception e) { e.printStackTrace(); log.error("创建账户失败", e); throw new BusinessException("创建账户失败"); } } @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 5000), value = BusinessException.class) public synchronized NFT createToken(String toAccount, String tokenId) throws Exception { log.info("开始执行EVM合约mint, toAccount: {}, tokenId: {}", toAccount, tokenId); if (Arrays.asList(env.getActiveProfiles()).contains("notifytest")) { NFT nft = new NFT("1", tokenId, new BigInteger("1"), new BigInteger("1")); log.info("测试服生成假token {}", tokenId); return nft; } JSONArray jsonArray = new JSONArray(); jsonArray.add(Utils.getIdentityByName(toAccount)); jsonArray.add(new BigInteger(tokenId, 16)); CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(String.valueOf(snowflakeIdWorker.nextId())) .bizid(restClientProperties.getBizid()) .account(restClientProperties.getAccount()) .contractName(generalProperties.getContractName()) .methodSignature("mint(identity,uint256)") .inputParamListStr(jsonArray.toJSONString()) .outTypes("[]")//合约返回值类型 .mykmsKeyId(restClientProperties.getKmsId()) .method(Method.CALLCONTRACTBIZASYNC) .tenantid(restClientProperties.getTenantid()) .gas(500000L) .build(); BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam); if (!resp.isSuccess()) { log.info("EVM合约执行失败: code: {}, data: {}, toAccount: {}, tokenId: {}", resp.getCode(), resp.getData(), toAccount, tokenId); } if ("200".equals(resp.getCode())) { log.info("EVM合约执行成功"); // 合约调用交易回执内容 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); BigInteger gasUsed = txReceipt.getGasUsed(); long result = txReceipt.getResult(); log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result); NFT nft = new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed()); log.info("NFT生成成功 {}", nft); return nft; } else if ("211".equals(resp.getCode())) { log.error("EVM合约执行限流"); } else if ("10201".equals(resp.getCode())) { // 异步交易未成功需要根据状态码判断交易状态 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); String out = new String(matchAndReplaceNonEnglishChar(txReceipt.getOutput())); // 异步交易未成功需要根据状态码判断交易状态 log.error("EVM合约执行未成功: code: {}, out: {}, tokenId: {}", resp.getCode(), out, tokenId); if (out.endsWith("ERC721: token already minted")) { return new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed()); } } throw new BusinessException("执行EVM合约mint失败"); } public static byte[] matchAndReplaceNonEnglishChar(byte[] data) { List list = new ArrayList<>(); for (byte c : data) { if (c >= 32 && c <= 127) { list.add(c); } } byte[] bytes = new byte[list.size()]; for (int i = 0; i < list.size(); i++) { bytes[i] = list.get(i); } return bytes; } public String ownerOf(String tokenId) throws Exception { CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(String.valueOf(snowflakeIdWorker.nextId())) .bizid(restClientProperties.getBizid()) .account(restClientProperties.getAccount()) .contractName(generalProperties.getContractName()) .methodSignature("ownerOf(uint256)") .inputParamListStr("[" + new BigInteger(tokenId, 16) + "]") .outTypes("[identity]")//合约返回值类型 .mykmsKeyId(restClientProperties.getKmsId()) .method(Method.CALLCONTRACTBIZASYNC) .tenantid(restClientProperties.getTenantid()) .gas(500000L) .build(); BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam); if (!resp.isSuccess()) { log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData()); } if ("200".equals(resp.getCode())) { log.info("EVM合约执行成功"); // 合约调用交易回执内容 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); BigInteger gasUsed = txReceipt.getGasUsed(); long result = txReceipt.getResult(); log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result); String identity = Hex.encodeHexString(txReceipt.getOutput()); log.info("owner is: {}", identity); return identity; } else { // 异步交易未成功需要根据状态码判断交易状态 log.error("EVM合约执行未成功: " + resp.getCode()); throw new BusinessException("EVM合约执行未成功: " + resp.getCode()); } } public NFT transferToken(String tokenId, String fromAccount, String toAccount) throws Exception { JSONArray jsonArray = new JSONArray(); jsonArray.add(Utils.getIdentityByName("raex_official")); jsonArray.add(Utils.getIdentityByName("9th_HCWWflAZ_")); jsonArray.add("95057808871064671354760012409081314299"); CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(String.valueOf(snowflakeIdWorker.nextId())) .bizid(restClientProperties.getBizid()) .account(restClientProperties.getAccount()) .contractName("raex12") .methodSignature("ownerTransfer(identity,identity,uint256)") .inputParamListStr(jsonArray.toJSONString()) .outTypes("[]")//合约返回值类型 .mykmsKeyId(restClientProperties.getKmsId()) .method(Method.CALLCONTRACTBIZASYNC) .tenantid(restClientProperties.getTenantid()) .gas(500000L) .build(); BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam); if (!resp.isSuccess()) { log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData()); } if ("200".equals(resp.getCode())) { log.info("EVM合约执行成功"); // 合约调用交易回执内容 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); BigInteger gasUsed = txReceipt.getGasUsed(); long result = txReceipt.getResult(); log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result); return new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed()); } else { // 异步交易未成功需要根据状态码判断交易状态 log.error("EVM合约执行未成功: " + resp.getCode()); } throw new BusinessException("转移token失败"); } public NFT setApprovalForAll(String account) throws Exception { JSONArray jsonArray = new JSONArray(); jsonArray.add(Utils.getIdentityByName(account)); jsonArray.add(true); CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(String.valueOf(snowflakeIdWorker.nextId())) .bizid(restClientProperties.getBizid()) .account(restClientProperties.getAccount()) .contractName(generalProperties.getContractName()) .methodSignature("setApprovalForAll(identity,bool)") .inputParamListStr(jsonArray.toJSONString()) .outTypes("[]")//合约返回值类型 .mykmsKeyId(restClientProperties.getKmsId()) .method(Method.CALLCONTRACTBIZASYNC) .tenantid(restClientProperties.getTenantid()) .gas(500000L) .build(); BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam); if (!resp.isSuccess()) { log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData()); } if ("200".equals(resp.getCode())) { log.info("EVM合约执行成功"); // 合约调用交易回执内容 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); BigInteger gasUsed = txReceipt.getGasUsed(); long result = txReceipt.getResult(); log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result); } else { // 异步交易未成功需要根据状态码判断交易状态 log.error("EVM合约执行未成功: " + resp.getCode()); } throw new BusinessException("创建nft失败"); } public void deployContract(String name, String code) throws Exception { String orderId = "order_" + System.currentTimeMillis(); CallRestBizParam callRestBizParam = CallRestBizParam.builder() .orderId(orderId) .bizid(restClientProperties.getBizid()) .account(restClientProperties.getAccount()) .contractName(name) .contractCode(code) .mykmsKeyId(restClientProperties.getKmsId()) .method(Method.DEPLOYCONTRACTFORBIZ) .tenantid(restClientProperties.getTenantid()) .gas(1000000L).build(); BaseResp resp = restClient.chainCallForBiz(callRestBizParam); if (!resp.isSuccess()) { log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData()); } log.info("EVM合约执行成功"); // 合约调用交易回执内容 ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class); BigInteger gasUsed = txReceipt.getGasUsed(); long result = txReceipt.getResult(); log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result); } }