Skip to content

Commit

Permalink
Add NFT claim query API (#250)
Browse files Browse the repository at this point in the history
* fix error and add CacheDevController

* add NFT claim query API
  • Loading branch information
yuanmomo committed May 20, 2024
1 parent dfef458 commit c9ebd6b
Show file tree
Hide file tree
Showing 8 changed files with 1,003 additions and 125 deletions.
20 changes: 20 additions & 0 deletions src/main/java/com/dl/officialsite/activity/ActivityController.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.dl.officialsite.nft.constant.ContractNameEnum;
import com.dl.officialsite.nft.constant.EcdsaKeyTypeEnum;
import com.dl.officialsite.nft.dto.SignatureDto;
import com.dl.officialsite.nft.service.WarCraftContractService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -51,6 +52,8 @@ public class ActivityController {
private ChainConfig chainConfig;
@Autowired
private EcdsaKeyConfigService ecdsaKeyConfigService;
@Autowired
private WarCraftContractService warCraftContractService;

/**
* 获取活动中用户的状态
Expand Down Expand Up @@ -118,4 +121,21 @@ public BaseResponse verifyAndMint(@NotNull @RequestParam("chainId") String chain
return BaseResponse.failWithReason("1204", "Generate signature failed.");
}

/**
* 检查用户是否完成所有任务,如果完成,则生成 NFT mint 签名
*/
@GetMapping("/claim/result")
public BaseResponse claimResult(@NotNull @RequestParam("chainId") String chainIdParam,
@RequestParam(required = false) String addressForTesting,
@RequestParam(required = false, defaultValue = "WarCraft") String contractName,
HttpSession session) {
String chainId = Arrays.stream(chainConfig.getIds()).filter(id -> StringUtils.equalsIgnoreCase(chainIdParam, id))
.findFirst()
.orElseThrow(() -> new BizException(PARAM_ERROR.getCode(), String.format("Chain id %s not exists", chainIdParam)));

SessionUserInfo sessionUserInfo = HttpSessionUtils.getMember(session);
final String address = sessionUserInfo != null ? sessionUserInfo.getAddress() : addressForTesting;

return this.warCraftContractService.rank(address, ContractNameEnum.fromValue(contractName), chainId);
}
}
125 changes: 125 additions & 0 deletions src/main/java/com/dl/officialsite/common/utils/ECSignatureUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.dl.officialsite.common.utils;

import org.apache.commons.lang3.StringUtils;
import org.web3j.abi.TypeEncoder;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.DynamicStruct;
import org.web3j.abi.datatypes.Uint;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.utils.Numeric;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.SignatureException;

public class ECSignatureUtil {

private static final String MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n32";


public static String sign(Credentials credentials, String receiverAddress, BigInteger seed, BigInteger signedAt,
BigInteger chainId, String nftAddress) {
Sign.SignatureData signature =
Sign.signMessage(
toEthSignedMessageHash(sha3AbiEncodedData(receiverAddress, seed, signedAt, chainId, nftAddress)),
credentials.getEcKeyPair(), false);

byte[] retVal = new byte[65];
System.arraycopy(signature.getR(), 0, retVal, 0, 32);
System.arraycopy(signature.getS(), 0, retVal, 32, 32);
System.arraycopy(signature.getV(), 0, retVal, 64, 1);
return Numeric.toHexString(retVal);
}

private static byte[] toEthSignedMessageHash(String sha3AbiEncodedData) {
byte[] prefixBytes = MESSAGE_PREFIX.getBytes();
byte[] messageBytes = Numeric.hexStringToByteArray(sha3AbiEncodedData);

byte[] msgBytes = new byte[MESSAGE_PREFIX.getBytes().length + messageBytes.length];
System.arraycopy(prefixBytes, 0, msgBytes, 0, prefixBytes.length);
System.arraycopy(messageBytes, 0, msgBytes, prefixBytes.length, messageBytes.length);

return Hash.sha3(msgBytes);
}

public static BigInteger randomSeed() {
// Create a SecureRandom instance
SecureRandom random = new SecureRandom();

// Specify the bit length for the BigInteger
int bitLength = 128; // You can change this to any size you need

// Generate a random BigInteger
return new BigInteger(bitLength, random);
}

/**
* Sha3 == keccak256
*
* @param receiverAddress
* @param seed
* @param signedAt
* @param chainId
* @param nftAddress
* @return
*/
private static String sha3AbiEncodedData(String receiverAddress, BigInteger seed, BigInteger signedAt,
BigInteger chainId, String nftAddress) {
return Hash.sha3(abiEncode(receiverAddress, seed, signedAt, chainId, nftAddress));
}

private static String abiEncode(String receiverAddress, BigInteger seed, BigInteger signedAt,
BigInteger chainId, String nftAddress) {
return TypeEncoder.encode(new DynamicStruct(
new Address(receiverAddress),
new Uint256(seed),
new Uint256(signedAt),
new Uint(chainId),
new Address(nftAddress)
));
}

public static boolean verifySignature(String signature,
String expectedAddress,
String receiverAddress,
BigInteger seed,
BigInteger signedAt,
BigInteger chainId,
String nftAddress) {
byte[] ethSignedMessageHash = toEthSignedMessageHash(sha3AbiEncodedData(receiverAddress, seed, signedAt, chainId, nftAddress));

String extractedAddress = recoverAddress(ethSignedMessageHash, signature);

return StringUtils.equalsIgnoreCase(expectedAddress, extractedAddress);
}

public static String recoverAddress(byte[] digest, String signature) {
if (signature.startsWith("0x")) {
signature = signature.substring(2);
}

// No need to prepend these strings with 0x because
// Numeric.hexStringToByteArray() accepts both formats
String r = signature.substring(0, 64);
String s = signature.substring(64, 128);
String v = signature.substring(128, 130);

BigInteger pubkey = null;
try {
pubkey = Sign.signedMessageHashToKey(digest,
new Sign.SignatureData(
Numeric.hexStringToByteArray(v)[0],
Numeric.hexStringToByteArray(r),
Numeric.hexStringToByteArray(s)));
} catch (SignatureException e) {
throw new RuntimeException(e);
}

return "0x" + Keys.getAddress(pubkey);
}

}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,25 @@ public void startUpOrRefresh() {
}

public String getContractAddressByName(ContractNameEnum contractName, String chainId) {
return findByContractNameAndChainId(contractName, chainId)
.map(ContractAddressItem::getAddress)
.orElse("");
}

public String getRpcAddressByName(ContractNameEnum contractName, String chainId) {
return findByContractNameAndChainId(contractName, chainId)
.map(ContractAddressItem::getRpcAddress)
.orElse("");
}

private Optional<ContractAddressItem> findByContractNameAndChainId(ContractNameEnum contractName, String chainId){
return Optional.ofNullable(contractAddressConfig)
.map(ContractAddressConfig::getContractAddressItemList)
.flatMap(list -> list.stream()
.filter(item -> StringUtils.equalsIgnoreCase(item.getContractName(), contractName.getContractName()))
.filter(item -> StringUtils.equalsIgnoreCase(item.getChainId(), chainId))
.map(ContractAddressItem::getAddress)
.findFirst()
).orElse("");
);
}
}

Expand All @@ -54,4 +65,5 @@ class ContractAddressItem {
private String chainId;
private String chainName;
private String address;
private String rpcAddress;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.dl.officialsite.nft.config;

import com.dl.officialsite.common.utils.SignatureGeneration;
import com.dl.officialsite.common.utils.ECSignatureUtil;
import com.dl.officialsite.config.bean.Configurable;
import com.dl.officialsite.config.bean.Refreshable;
import com.dl.officialsite.config.constant.ConfigEnum;
Expand Down Expand Up @@ -48,7 +48,7 @@ public SignatureDto sign(EcdsaKeyTypeEnum keyType, ContractNameEnum contractName
return null;
}

BigInteger seed = SignatureGeneration.randomSeed();
BigInteger seed = ECSignatureUtil.randomSeed();
long signedAt = Instant.now().getEpochSecond();

return Optional.ofNullable(ecdsaPrivateKeyConfig)
Expand All @@ -59,7 +59,7 @@ public SignatureDto sign(EcdsaKeyTypeEnum keyType, ContractNameEnum contractName
.map(privateKey ->
new SignatureDto(receiverAddress,
signedAt,
SignatureGeneration.sign(Credentials.create(privateKey), receiverAddress, seed, BigInteger.valueOf(signedAt),
ECSignatureUtil.sign(Credentials.create(privateKey), receiverAddress, seed, BigInteger.valueOf(signedAt),
BigInteger.valueOf(Long.parseLong(chainId)), contractAddress),
Numeric.toHexStringWithPrefix(seed),
chainId,
Expand Down
776 changes: 776 additions & 0 deletions src/main/java/com/dl/officialsite/nft/contract/WarCraftContract.java

Large diffs are not rendered by default.

Loading

0 comments on commit c9ebd6b

Please sign in to comment.