forked from Qortal-Forker/qortal
		
	@@ -0,0 +1,17 @@
 | 
			
		||||
package org.qortal.api.model.crosschain;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
 | 
			
		||||
import javax.xml.bind.annotation.XmlAccessType;
 | 
			
		||||
import javax.xml.bind.annotation.XmlAccessorType;
 | 
			
		||||
 | 
			
		||||
@XmlAccessorType(XmlAccessType.FIELD)
 | 
			
		||||
public class AddressRequest {
 | 
			
		||||
 | 
			
		||||
	@Schema(description = "Litecoin BIP32 extended public key", example = "tpub___________________________________________________________________________________________________________")
 | 
			
		||||
	public String xpub58;
 | 
			
		||||
 | 
			
		||||
	public AddressRequest() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,9 @@ import org.qortal.api.ApiError;
 | 
			
		||||
import org.qortal.api.ApiErrors;
 | 
			
		||||
import org.qortal.api.ApiExceptionFactory;
 | 
			
		||||
import org.qortal.api.Security;
 | 
			
		||||
import org.qortal.api.model.crosschain.AddressRequest;
 | 
			
		||||
import org.qortal.api.model.crosschain.BitcoinSendRequest;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.Bitcoin;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.SimpleTransaction;
 | 
			
		||||
@@ -148,6 +150,44 @@ public class CrossChainBitcoinResource {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/addressinfos")
 | 
			
		||||
	@Operation(
 | 
			
		||||
			summary = "Returns information for each address for a hierarchical, deterministic BIP32 wallet",
 | 
			
		||||
			description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
 | 
			
		||||
			requestBody = @RequestBody(
 | 
			
		||||
					required = true,
 | 
			
		||||
					content = @Content(
 | 
			
		||||
							mediaType = MediaType.APPLICATION_JSON,
 | 
			
		||||
							schema = @Schema(
 | 
			
		||||
									implementation = AddressRequest.class
 | 
			
		||||
							)
 | 
			
		||||
					)
 | 
			
		||||
			),
 | 
			
		||||
			responses = {
 | 
			
		||||
					@ApiResponse(
 | 
			
		||||
							content = @Content(array = @ArraySchema( schema = @Schema( implementation = AddressInfo.class ) ) )
 | 
			
		||||
					)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	)
 | 
			
		||||
	@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
 | 
			
		||||
	@SecurityRequirement(name = "apiKey")
 | 
			
		||||
	public List<AddressInfo> getBitcoinAddressInfos(@HeaderParam(Security.API_KEY_HEADER) String apiKey, AddressRequest addressRequest) {
 | 
			
		||||
		Security.checkApiCallAllowed(request);
 | 
			
		||||
 | 
			
		||||
		Bitcoin bitcoin = Bitcoin.getInstance();
 | 
			
		||||
 | 
			
		||||
		if (!bitcoin.isValidDeterministicKey(addressRequest.xpub58))
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			return bitcoin.getWalletAddressInfos(addressRequest.xpub58);
 | 
			
		||||
		} catch (ForeignBlockchainException e) {
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/unusedaddress")
 | 
			
		||||
	@Operation(
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,9 @@ import org.qortal.api.ApiError;
 | 
			
		||||
import org.qortal.api.ApiErrors;
 | 
			
		||||
import org.qortal.api.ApiExceptionFactory;
 | 
			
		||||
import org.qortal.api.Security;
 | 
			
		||||
import org.qortal.api.model.crosschain.AddressRequest;
 | 
			
		||||
import org.qortal.api.model.crosschain.DigibyteSendRequest;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.Digibyte;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.SimpleTransaction;
 | 
			
		||||
@@ -148,6 +150,44 @@ public class CrossChainDigibyteResource {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/addressinfos")
 | 
			
		||||
	@Operation(
 | 
			
		||||
			summary = "Returns information for each address for a hierarchical, deterministic BIP32 wallet",
 | 
			
		||||
			description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
 | 
			
		||||
			requestBody = @RequestBody(
 | 
			
		||||
					required = true,
 | 
			
		||||
					content = @Content(
 | 
			
		||||
							mediaType = MediaType.APPLICATION_JSON,
 | 
			
		||||
							schema = @Schema(
 | 
			
		||||
									implementation = AddressRequest.class
 | 
			
		||||
							)
 | 
			
		||||
					)
 | 
			
		||||
			),
 | 
			
		||||
			responses = {
 | 
			
		||||
					@ApiResponse(
 | 
			
		||||
							content = @Content(array = @ArraySchema( schema = @Schema( implementation = AddressInfo.class ) ) )
 | 
			
		||||
					)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	)
 | 
			
		||||
	@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
 | 
			
		||||
	@SecurityRequirement(name = "apiKey")
 | 
			
		||||
	public List<AddressInfo> getDigibyteAddressInfos(@HeaderParam(Security.API_KEY_HEADER) String apiKey, AddressRequest addressRequest) {
 | 
			
		||||
		Security.checkApiCallAllowed(request);
 | 
			
		||||
 | 
			
		||||
		Digibyte digibyte = Digibyte.getInstance();
 | 
			
		||||
 | 
			
		||||
		if (!digibyte.isValidDeterministicKey(addressRequest.xpub58))
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			return digibyte.getWalletAddressInfos(addressRequest.xpub58);
 | 
			
		||||
		} catch (ForeignBlockchainException e) {
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/unusedaddress")
 | 
			
		||||
	@Operation(
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,9 @@ import org.qortal.api.ApiError;
 | 
			
		||||
import org.qortal.api.ApiErrors;
 | 
			
		||||
import org.qortal.api.ApiExceptionFactory;
 | 
			
		||||
import org.qortal.api.Security;
 | 
			
		||||
import org.qortal.api.model.crosschain.AddressRequest;
 | 
			
		||||
import org.qortal.api.model.crosschain.DogecoinSendRequest;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.Dogecoin;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.SimpleTransaction;
 | 
			
		||||
@@ -148,6 +150,44 @@ public class CrossChainDogecoinResource {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/addressinfos")
 | 
			
		||||
	@Operation(
 | 
			
		||||
			summary = "Returns information for each address for a hierarchical, deterministic BIP32 wallet",
 | 
			
		||||
			description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
 | 
			
		||||
			requestBody = @RequestBody(
 | 
			
		||||
					required = true,
 | 
			
		||||
					content = @Content(
 | 
			
		||||
							mediaType = MediaType.APPLICATION_JSON,
 | 
			
		||||
							schema = @Schema(
 | 
			
		||||
									implementation = AddressRequest.class
 | 
			
		||||
							)
 | 
			
		||||
					)
 | 
			
		||||
			),
 | 
			
		||||
			responses = {
 | 
			
		||||
					@ApiResponse(
 | 
			
		||||
							content = @Content(array = @ArraySchema( schema = @Schema( implementation = AddressInfo.class ) ) )
 | 
			
		||||
					)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	)
 | 
			
		||||
	@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
 | 
			
		||||
	@SecurityRequirement(name = "apiKey")
 | 
			
		||||
	public List<AddressInfo> getDogecoinAddressInfos(@HeaderParam(Security.API_KEY_HEADER) String apiKey, AddressRequest addressRequest) {
 | 
			
		||||
		Security.checkApiCallAllowed(request);
 | 
			
		||||
 | 
			
		||||
		Dogecoin dogecoin = Dogecoin.getInstance();
 | 
			
		||||
 | 
			
		||||
		if (!dogecoin.isValidDeterministicKey(addressRequest.xpub58))
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			return dogecoin.getWalletAddressInfos(addressRequest.xpub58);
 | 
			
		||||
		} catch (ForeignBlockchainException e) {
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/unusedaddress")
 | 
			
		||||
	@Operation(
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,9 @@ import org.qortal.api.ApiError;
 | 
			
		||||
import org.qortal.api.ApiErrors;
 | 
			
		||||
import org.qortal.api.ApiExceptionFactory;
 | 
			
		||||
import org.qortal.api.Security;
 | 
			
		||||
import org.qortal.api.model.crosschain.AddressRequest;
 | 
			
		||||
import org.qortal.api.model.crosschain.LitecoinSendRequest;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.Litecoin;
 | 
			
		||||
import org.qortal.crosschain.SimpleTransaction;
 | 
			
		||||
@@ -148,6 +150,44 @@ public class CrossChainLitecoinResource {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/addressinfos")
 | 
			
		||||
	@Operation(
 | 
			
		||||
			summary = "Returns information for each address for a hierarchical, deterministic BIP32 wallet",
 | 
			
		||||
			description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
 | 
			
		||||
			requestBody = @RequestBody(
 | 
			
		||||
					required = true,
 | 
			
		||||
					content = @Content(
 | 
			
		||||
							mediaType = MediaType.APPLICATION_JSON,
 | 
			
		||||
							schema = @Schema(
 | 
			
		||||
									implementation = AddressRequest.class
 | 
			
		||||
							)
 | 
			
		||||
					)
 | 
			
		||||
			),
 | 
			
		||||
			responses = {
 | 
			
		||||
					@ApiResponse(
 | 
			
		||||
							content = @Content(array = @ArraySchema( schema = @Schema( implementation = AddressInfo.class ) ) )
 | 
			
		||||
					)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	)
 | 
			
		||||
	@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
 | 
			
		||||
	@SecurityRequirement(name = "apiKey")
 | 
			
		||||
	public List<AddressInfo> getLitecoinAddressInfos(@HeaderParam(Security.API_KEY_HEADER) String apiKey, AddressRequest addressRequest) {
 | 
			
		||||
		Security.checkApiCallAllowed(request);
 | 
			
		||||
 | 
			
		||||
		Litecoin litecoin = Litecoin.getInstance();
 | 
			
		||||
 | 
			
		||||
		if (!litecoin.isValidDeterministicKey(addressRequest.xpub58))
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			return litecoin.getWalletAddressInfos(addressRequest.xpub58);
 | 
			
		||||
		} catch (ForeignBlockchainException e) {
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/unusedaddress")
 | 
			
		||||
	@Operation(
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,9 @@ import org.qortal.api.ApiError;
 | 
			
		||||
import org.qortal.api.ApiErrors;
 | 
			
		||||
import org.qortal.api.ApiExceptionFactory;
 | 
			
		||||
import org.qortal.api.Security;
 | 
			
		||||
import org.qortal.api.model.crosschain.AddressRequest;
 | 
			
		||||
import org.qortal.api.model.crosschain.RavencoinSendRequest;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.Ravencoin;
 | 
			
		||||
import org.qortal.crosschain.SimpleTransaction;
 | 
			
		||||
@@ -148,6 +150,44 @@ public class CrossChainRavencoinResource {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/addressinfos")
 | 
			
		||||
	@Operation(
 | 
			
		||||
			summary = "Returns information for each address for a hierarchical, deterministic BIP32 wallet",
 | 
			
		||||
			description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
 | 
			
		||||
			requestBody = @RequestBody(
 | 
			
		||||
					required = true,
 | 
			
		||||
					content = @Content(
 | 
			
		||||
							mediaType = MediaType.APPLICATION_JSON,
 | 
			
		||||
							schema = @Schema(
 | 
			
		||||
									implementation = AddressRequest.class
 | 
			
		||||
							)
 | 
			
		||||
					)
 | 
			
		||||
			),
 | 
			
		||||
			responses = {
 | 
			
		||||
					@ApiResponse(
 | 
			
		||||
							content = @Content(array = @ArraySchema( schema = @Schema( implementation = AddressInfo.class ) ) )
 | 
			
		||||
					)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	)
 | 
			
		||||
	@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
 | 
			
		||||
	@SecurityRequirement(name = "apiKey")
 | 
			
		||||
	public List<AddressInfo> getRavencoinAddressInfos(@HeaderParam(Security.API_KEY_HEADER) String apiKey, AddressRequest addressRequest) {
 | 
			
		||||
		Security.checkApiCallAllowed(request);
 | 
			
		||||
 | 
			
		||||
		Ravencoin ravencoin = Ravencoin.getInstance();
 | 
			
		||||
 | 
			
		||||
		if (!ravencoin.isValidDeterministicKey(addressRequest.xpub58))
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			return ravencoin.getWalletAddressInfos(addressRequest.xpub58);
 | 
			
		||||
		} catch (ForeignBlockchainException e) {
 | 
			
		||||
			throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@POST
 | 
			
		||||
	@Path("/unusedaddress")
 | 
			
		||||
	@Operation(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								src/main/java/org/qortal/crosschain/AddressInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/main/java/org/qortal/crosschain/AddressInfo.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
package org.qortal.crosschain;
 | 
			
		||||
 | 
			
		||||
import javax.xml.bind.annotation.XmlAccessType;
 | 
			
		||||
import javax.xml.bind.annotation.XmlAccessorType;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AddressInfo
 | 
			
		||||
 */
 | 
			
		||||
@XmlAccessorType(XmlAccessType.FIELD)
 | 
			
		||||
public class AddressInfo {
 | 
			
		||||
 | 
			
		||||
    private String address;
 | 
			
		||||
 | 
			
		||||
    private List<Integer> path;
 | 
			
		||||
 | 
			
		||||
    private long value;
 | 
			
		||||
 | 
			
		||||
    private String pathAsString;
 | 
			
		||||
 | 
			
		||||
    private int transactionCount;
 | 
			
		||||
 | 
			
		||||
    public AddressInfo() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AddressInfo(String address, List<Integer> path, long value, String pathAsString, int transactionCount) {
 | 
			
		||||
        this.address = address;
 | 
			
		||||
        this.path = path;
 | 
			
		||||
        this.value = value;
 | 
			
		||||
        this.pathAsString = pathAsString;
 | 
			
		||||
        this.transactionCount = transactionCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getAddress() {
 | 
			
		||||
        return address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<Integer> getPath() {
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getValue() {
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getPathAsString() {
 | 
			
		||||
        return pathAsString;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getTransactionCount() {
 | 
			
		||||
        return transactionCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
        if (o == null || getClass() != o.getClass()) return false;
 | 
			
		||||
        AddressInfo that = (AddressInfo) o;
 | 
			
		||||
        return value == that.value && transactionCount == that.transactionCount && Objects.equals(address, that.address) && Objects.equals(path, that.path) && Objects.equals(pathAsString, that.pathAsString);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hash(address, path, value, pathAsString, transactionCount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "AddressInfo{" +
 | 
			
		||||
                "address='" + address + '\'' +
 | 
			
		||||
                ", path=" + path +
 | 
			
		||||
                ", value=" + value +
 | 
			
		||||
                ", pathAsString='" + pathAsString + '\'' +
 | 
			
		||||
                ", transactionCount=" + transactionCount +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package org.qortal.crosschain;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.hash.HashCode;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
@@ -474,6 +475,37 @@ public abstract class Bitcoiny implements ForeignBlockchain {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public List<AddressInfo> getWalletAddressInfos(String key58) throws ForeignBlockchainException {
 | 
			
		||||
		List<AddressInfo> infos = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
		for(DeterministicKey key : getWalletKeys(key58)) {
 | 
			
		||||
			infos.add(buildAddressInfo(key));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return infos.stream()
 | 
			
		||||
				.sorted(new PathComparator(1))
 | 
			
		||||
				.collect(Collectors.toList());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public AddressInfo buildAddressInfo(DeterministicKey key) throws ForeignBlockchainException  {
 | 
			
		||||
 | 
			
		||||
		Address address = Address.fromKey(this.params, key, ScriptType.P2PKH);
 | 
			
		||||
 | 
			
		||||
		int transactionCount = getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).size();
 | 
			
		||||
 | 
			
		||||
		return new AddressInfo(
 | 
			
		||||
				address.toString(),
 | 
			
		||||
				toIntegerList( key.getPath()),
 | 
			
		||||
				summingUnspentOutputs(address.toString()),
 | 
			
		||||
				key.getPathAsString(),
 | 
			
		||||
				transactionCount);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static  List<Integer> toIntegerList(ImmutableList<ChildNumber> path) {
 | 
			
		||||
 | 
			
		||||
		return path.stream().map(ChildNumber::num).collect(Collectors.toList());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Set<String> getWalletAddresses(String key58) throws ForeignBlockchainException {
 | 
			
		||||
		synchronized (this) {
 | 
			
		||||
			Context.propagate(bitcoinjContext);
 | 
			
		||||
@@ -532,6 +564,61 @@ public abstract class Bitcoiny implements ForeignBlockchain {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<DeterministicKey> getWalletKeys(String key58) throws ForeignBlockchainException {
 | 
			
		||||
		synchronized (this) {
 | 
			
		||||
			Context.propagate(bitcoinjContext);
 | 
			
		||||
 | 
			
		||||
			Wallet wallet = walletFromDeterministicKey58(key58);
 | 
			
		||||
			DeterministicKeyChain keyChain = wallet.getActiveKeyChain();
 | 
			
		||||
 | 
			
		||||
			keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT);
 | 
			
		||||
			keyChain.maybeLookAhead();
 | 
			
		||||
 | 
			
		||||
			List<DeterministicKey> keys = new ArrayList<>(keyChain.getLeafKeys());
 | 
			
		||||
 | 
			
		||||
			int unusedCounter = 0;
 | 
			
		||||
			int ki = 0;
 | 
			
		||||
			do {
 | 
			
		||||
				boolean areAllKeysUnused = true;
 | 
			
		||||
 | 
			
		||||
				for (; ki < keys.size(); ++ki) {
 | 
			
		||||
					DeterministicKey dKey = keys.get(ki);
 | 
			
		||||
 | 
			
		||||
					// Check for transactions
 | 
			
		||||
					Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
 | 
			
		||||
					byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
 | 
			
		||||
 | 
			
		||||
					// Ask for transaction history - if it's empty then key has never been used
 | 
			
		||||
					List<TransactionHash> historicTransactionHashes = this.getAddressTransactions(script, false);
 | 
			
		||||
 | 
			
		||||
					if (!historicTransactionHashes.isEmpty()) {
 | 
			
		||||
						areAllKeysUnused = false;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (areAllKeysUnused) {
 | 
			
		||||
					// No transactions
 | 
			
		||||
					if (unusedCounter >= Settings.getInstance().getGapLimit()) {
 | 
			
		||||
						// ... and we've hit our search limit
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
					// We haven't hit our search limit yet so increment the counter and keep looking
 | 
			
		||||
					unusedCounter += WALLET_KEY_LOOKAHEAD_INCREMENT;
 | 
			
		||||
				} else {
 | 
			
		||||
					// Some keys in this batch were used, so reset the counter
 | 
			
		||||
					unusedCounter = 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Generate some more keys
 | 
			
		||||
				keys.addAll(generateMoreKeys(keyChain));
 | 
			
		||||
 | 
			
		||||
				// Process new keys
 | 
			
		||||
			} while (true);
 | 
			
		||||
 | 
			
		||||
			return keys;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected SimpleTransaction convertToSimpleTransaction(BitcoinyTransaction t, Set<String> keySet) {
 | 
			
		||||
		long amount = 0;
 | 
			
		||||
		long total = 0L;
 | 
			
		||||
@@ -804,6 +891,13 @@ public abstract class Bitcoiny implements ForeignBlockchain {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Long summingUnspentOutputs(String walletAddress) throws ForeignBlockchainException {
 | 
			
		||||
		return this.getUnspentOutputs(walletAddress).stream()
 | 
			
		||||
				.map(TransactionOutput::getValue)
 | 
			
		||||
				.mapToLong(Coin::longValue)
 | 
			
		||||
				.sum();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Utility methods for others
 | 
			
		||||
 | 
			
		||||
	public static List<SimpleForeignTransaction> simplifyWalletTransactions(List<BitcoinyTransaction> transactions) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								src/main/java/org/qortal/crosschain/PathComparator.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/main/java/org/qortal/crosschain/PathComparator.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
package org.qortal.crosschain;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class PathComparator
 | 
			
		||||
 */
 | 
			
		||||
public class PathComparator implements java.util.Comparator<AddressInfo> {
 | 
			
		||||
 | 
			
		||||
    private int max;
 | 
			
		||||
 | 
			
		||||
    public PathComparator(int max) {
 | 
			
		||||
        this.max = max;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int compare(AddressInfo info1, AddressInfo info2) {
 | 
			
		||||
        return compareAtLevel(info1, info2, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int compareAtLevel(AddressInfo info1, AddressInfo info2, int level) {
 | 
			
		||||
 | 
			
		||||
        if( level < 0 ) return 0;
 | 
			
		||||
 | 
			
		||||
        int compareTo = info1.getPath().get(level).compareTo(info2.getPath().get(level));
 | 
			
		||||
 | 
			
		||||
        if(compareTo != 0 || level == max) return compareTo;
 | 
			
		||||
 | 
			
		||||
        return compareAtLevel(info1, info2,level + 1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -221,7 +221,7 @@ public class Settings {
 | 
			
		||||
	public long recoveryModeTimeout = 9999999999999L;
 | 
			
		||||
 | 
			
		||||
	/** Minimum peer version number required in order to sync with them */
 | 
			
		||||
	private String minPeerVersion = "4.3.1";
 | 
			
		||||
	private String minPeerVersion = "4.3.2";
 | 
			
		||||
	/** Whether to allow connections with peers below minPeerVersion
 | 
			
		||||
	 * If true, we won't sync with them but they can still sync with us, and will show in the peers list
 | 
			
		||||
	 * If false, sync will be blocked both ways, and they will not appear in the peers list */
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import org.bitcoinj.core.Transaction;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.qortal.crosschain.AddressInfo;
 | 
			
		||||
import org.qortal.crosschain.Bitcoiny;
 | 
			
		||||
import org.qortal.crosschain.BitcoinyHTLC;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
@@ -11,6 +12,8 @@ import org.qortal.repository.DataException;
 | 
			
		||||
import org.qortal.test.common.Common;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
@@ -127,4 +130,36 @@ public abstract class BitcoinyTests extends Common {
 | 
			
		||||
		System.out.println(address);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testGenerateRootKeyForTesting() {
 | 
			
		||||
 | 
			
		||||
		String rootKey = BitcoinyTestsUtils.generateBip32RootKey( this.bitcoiny.getNetworkParameters() );
 | 
			
		||||
 | 
			
		||||
		System.out.println(String.format(getCoinName() + " generated BIP32 Root Key: " + rootKey));
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testGetWalletAddresses() throws ForeignBlockchainException {
 | 
			
		||||
 | 
			
		||||
		String xprv58 = getDeterministicKey58();
 | 
			
		||||
 | 
			
		||||
		Set<String> addresses = this.bitcoiny.getWalletAddresses(xprv58);
 | 
			
		||||
 | 
			
		||||
		System.out.println( "root key = " + xprv58 );
 | 
			
		||||
		System.out.println( "keys ...");
 | 
			
		||||
		addresses.stream().forEach(System.out::println);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testWalletAddressInfos() throws ForeignBlockchainException {
 | 
			
		||||
 | 
			
		||||
		String xprv58 = getDeterministicKey58();
 | 
			
		||||
 | 
			
		||||
		List<AddressInfo> addressInfos = this.bitcoiny.getWalletAddressInfos(xprv58);
 | 
			
		||||
 | 
			
		||||
		System.out.println("address count = " + addressInfos.size() );
 | 
			
		||||
		System.out.println( "address infos ..." );
 | 
			
		||||
		addressInfos.forEach( System.out::println );
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
package org.qortal.test.crosschain;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import org.bitcoinj.core.NetworkParameters;
 | 
			
		||||
import org.bitcoinj.crypto.ChildNumber;
 | 
			
		||||
import org.bitcoinj.crypto.DeterministicKey;
 | 
			
		||||
import org.bitcoinj.script.Script;
 | 
			
		||||
import org.bitcoinj.wallet.DeterministicKeyChain;
 | 
			
		||||
import org.bitcoinj.wallet.DeterministicSeed;
 | 
			
		||||
import org.bitcoinj.wallet.Wallet;
 | 
			
		||||
import org.qortal.crosschain.ForeignBlockchainException;
 | 
			
		||||
import org.qortal.crosschain.Litecoin;
 | 
			
		||||
import org.qortal.repository.DataException;
 | 
			
		||||
import org.qortal.test.common.Common;
 | 
			
		||||
 | 
			
		||||
public class BitcoinyTestsUtils {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) throws DataException, ForeignBlockchainException {
 | 
			
		||||
 | 
			
		||||
        Common.useDefaultSettings();
 | 
			
		||||
 | 
			
		||||
        final String rootKey = generateBip32RootKey( Litecoin.LitecoinNet.TEST3.getParams());
 | 
			
		||||
        String address = Litecoin.getInstance().getUnusedReceiveAddress(rootKey);
 | 
			
		||||
 | 
			
		||||
        System.out.println("rootKey = " + rootKey);
 | 
			
		||||
        System.out.println("address = " + address);
 | 
			
		||||
 | 
			
		||||
        System.exit(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String generateBip32RootKey(NetworkParameters networkParameters) {
 | 
			
		||||
 | 
			
		||||
        final Wallet wallet = Wallet.createDeterministic(networkParameters, Script.ScriptType.P2PKH);
 | 
			
		||||
        final DeterministicSeed seed = wallet.getKeyChainSeed();
 | 
			
		||||
        final DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
 | 
			
		||||
        final ImmutableList<ChildNumber> path = keyChain.getAccountPath();
 | 
			
		||||
        final DeterministicKey parent = keyChain.getKeyByPath(path, true);
 | 
			
		||||
        final String rootKey = parent.serializePrivB58(networkParameters);
 | 
			
		||||
 | 
			
		||||
        return rootKey;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,7 @@ import org.qortal.crypto.Crypto;
 | 
			
		||||
import org.qortal.transform.TransformationException;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
 | 
			
		||||
@@ -241,4 +242,12 @@ public class PirateChainTests extends BitcoinyTests {
 | 
			
		||||
	@Test
 | 
			
		||||
	@Ignore(value = "Needs adapting for Pirate Chain")
 | 
			
		||||
	public void testGetUnusedReceiveAddress() {}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@Ignore(value = "Needs adapting for Pirate Chain")
 | 
			
		||||
	public void testGetWalletAddresses() throws ForeignBlockchainException {}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@Ignore(value = "Needs adapting for Pirate Chain")
 | 
			
		||||
	public void testWalletAddressInfos() throws ForeignBlockchainException {}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user