diff --git a/src/main/java/org/qortal/crosschain/AddressInfo.java b/src/main/java/org/qortal/crosschain/AddressInfo.java index d1226d5a..56234537 100644 --- a/src/main/java/org/qortal/crosschain/AddressInfo.java +++ b/src/main/java/org/qortal/crosschain/AddressInfo.java @@ -21,15 +21,18 @@ public class AddressInfo { private int transactionCount; + private boolean isSpendable; + public AddressInfo() { } - public AddressInfo(String address, List path, long value, String pathAsString, int transactionCount) { + public AddressInfo(String address, List path, long value, String pathAsString, int transactionCount, boolean isSpendable) { this.address = address; this.path = path; this.value = value; this.pathAsString = pathAsString; this.transactionCount = transactionCount; + this.isSpendable = isSpendable; } public String getAddress() { @@ -52,17 +55,21 @@ public class AddressInfo { return transactionCount; } + public boolean isSpendable() { + return isSpendable; + } + @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); + return value == that.value && transactionCount == that.transactionCount && isSpendable == that.isSpendable && address.equals(that.address) && path.equals(that.path) && pathAsString.equals(that.pathAsString); } @Override public int hashCode() { - return Objects.hash(address, path, value, pathAsString, transactionCount); + return Objects.hash(address, path, value, pathAsString, transactionCount, isSpendable); } @Override @@ -73,6 +80,7 @@ public class AddressInfo { ", value=" + value + ", pathAsString='" + pathAsString + '\'' + ", transactionCount=" + transactionCount + + ", isSpendable=" + isSpendable + '}'; } } diff --git a/src/main/java/org/qortal/crosschain/Bitcoiny.java b/src/main/java/org/qortal/crosschain/Bitcoiny.java index cac37d64..1749cee9 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoiny.java +++ b/src/main/java/org/qortal/crosschain/Bitcoiny.java @@ -335,6 +335,30 @@ public abstract class Bitcoiny implements ForeignBlockchain { } } + /** + * Get Spending Candidate Addresses + * + * @param key58 public master key + * @return the addresses this instance will look at when building a spend + * @throws ForeignBlockchainException + */ + public List getSpendingCandidateAddresses(String key58) throws ForeignBlockchainException { + + Wallet wallet = Wallet.fromWatchingKeyB58(this.params, key58, DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS); + wallet.setUTXOProvider(new WalletAwareUTXOProvider(this, wallet)); + + // from Wallet.getStoredOutputsFromUTXOProvider() + List spendingKeys = wallet.getImportedKeys(); + spendingKeys.addAll(wallet.getActiveKeyChain().getLeafKeys()); + + List spendingCandidateAddresses + = spendingKeys.stream() + .map(spendingKey -> Address.fromKey(this.params, spendingKey, ScriptType.P2PKH ).toString()) + .collect(Collectors.toList()); + + return spendingCandidateAddresses; + } + /** * Returns bitcoinj transaction sending amount to recipient using default fees. * @@ -478,8 +502,10 @@ public abstract class Bitcoiny implements ForeignBlockchain { public List getWalletAddressInfos(String key58) throws ForeignBlockchainException { List infos = new ArrayList<>(); + List candidates = this.getSpendingCandidateAddresses(key58); + for(DeterministicKey key : getWalletKeys(key58)) { - infos.add(buildAddressInfo(key)); + infos.add(buildAddressInfo(key, candidates)); } return infos.stream() @@ -487,7 +513,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { .collect(Collectors.toList()); } - public AddressInfo buildAddressInfo(DeterministicKey key) throws ForeignBlockchainException { + public AddressInfo buildAddressInfo(DeterministicKey key, List candidates) throws ForeignBlockchainException { Address address = Address.fromKey(this.params, key, ScriptType.P2PKH); @@ -498,7 +524,8 @@ public abstract class Bitcoiny implements ForeignBlockchain { toIntegerList( key.getPath()), summingUnspentOutputs(address.toString()), key.getPathAsString(), - transactionCount); + transactionCount, + candidates.contains(address.toString())); } private static List toIntegerList(ImmutableList path) { diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java index 684f1cd6..e83afac2 100644 --- a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java @@ -32,6 +32,11 @@ public class BitcoinTests extends BitcoinyTests { return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; } + @Override + protected String getDeterministicPublicKey58() { + return "tpubDCxs3oB9X7XJYkQGU6gfPwd4h3NEiBGA8mfD1aEbZiG5x3BTH4cJqszDP6dtoHPPjZNEj5jPxuSWHCvjg9AHz4dNg6w5vQhv1B8KwWKpxoz"; + } + @Override protected String getRecipient() { return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; diff --git a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java index d95f1bd5..9e932bbf 100644 --- a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java +++ b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java @@ -32,6 +32,11 @@ public class DigibyteTests extends BitcoinyTests { return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; } + @Override + protected String getDeterministicPublicKey58() { + return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; + } + @Override protected String getRecipient() { return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; diff --git a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java index 62982437..7ec2b341 100644 --- a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java @@ -32,6 +32,11 @@ public class DogecoinTests extends BitcoinyTests { return "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; } + @Override + protected String getDeterministicPublicKey58() { + return "dgub8rqf3khHiPeYE3cNn3Y4DQQ411nAnFpuSUPt5k5GJZQsydsTLkaf4onaWn4N8pHvrV3oNMEATKoPGTFZwm2Uhh7Dy9gYwA7rkSv6oLofbag"; + } + @Override protected String getRecipient() { return null; diff --git a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java index 66c631e5..e88eaa48 100644 --- a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java @@ -32,6 +32,11 @@ public class LitecoinTests extends BitcoinyTests { return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; } + @Override + protected String getDeterministicPublicKey58() { + return "tpubDCxs3oB9X7XJYkQGU6gfPwd4h3NEiBGA8mfD1aEbZiG5x3BTH4cJqszDP6dtoHPPjZNEj5jPxuSWHCvjg9AHz4dNg6w5vQhv1B8KwWKpxoz"; + } + @Override protected String getRecipient() { return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java index cf4bff25..a2c2a9ec 100644 --- a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -43,6 +43,11 @@ public class PirateChainTests extends BitcoinyTests { return null; } + @Override + protected String getDeterministicPublicKey58() { + return null; + } + @Override protected String getRecipient() { return null; @@ -250,4 +255,8 @@ public class PirateChainTests extends BitcoinyTests { @Test @Ignore(value = "Needs adapting for Pirate Chain") public void testWalletAddressInfos() throws ForeignBlockchainException {} + + @Test + @Ignore(value = "Needs adapting for Pirate Chain") + public void testWalletSpendingCandidateAddresses() throws ForeignBlockchainException {} } \ No newline at end of file diff --git a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java index d7581f74..3d590199 100644 --- a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java @@ -32,6 +32,11 @@ public class RavencoinTests extends BitcoinyTests { return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; } + @Override + protected String getDeterministicPublicKey58() { + return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; + } + @Override protected String getRecipient() { return null;