package network.thunder.core.communication.objects.messages.impl.blockchainlistener.bciapi.wallet; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import network.thunder.core.communication.objects.messages.impl.blockchainlistener.bciapi.APIException; import network.thunder.core.communication.objects.messages.impl.blockchainlistener.bciapi.HttpClient; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * This class reflects the functionality documented * at https://blockchain.info/api/blockchain_wallet_api. It allows users to interact * with their Blockchain.info wallet. If you have an API code, please set it via the * `setApiCode` method. */ public class Wallet { private JsonParser jsonParser; private String identifier; private String password; private String secondPassword; private String apiCode; /** * @param identifier Wallet identifier (GUID) * @param password Decryption password */ public Wallet (String identifier, String password) { this(identifier, password, null); } /** * @param identifier Wallet identifier (GUID) * @param password Decryption password * @param secondPassword Second password */ public Wallet (String identifier, String password, String secondPassword) { this.identifier = identifier; this.password = password; this.secondPassword = secondPassword; this.jsonParser = new JsonParser(); } @Override public boolean equals (Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Wallet wallet = (Wallet) o; return !(identifier != null ? !identifier.equals(wallet.identifier) : wallet.identifier != null); } @Override public int hashCode () { return identifier != null ? identifier.hashCode() : 0; } /** * Sends bitcoin from your wallet to a single address. * * @param toAddress Recipient bitcoin address * @param amount Amount to send (in satoshi) * @param fromAddress Specific address to send from (optional, nullable) * @param fee Transaction fee in satoshi. Must be greater than the default fee (optional, nullable). * @param note Public note to include with the transaction (optional, nullable) * @return An instance of the PaymentResponse class * @throws APIException If the server returns an error */ public PaymentResponse send (String toAddress, long amount, String fromAddress, Long fee, String note) throws APIException, IOException { Map<String, Long> recipient = new HashMap<String, Long>(); recipient.put(toAddress, amount); return sendMany(recipient, fromAddress, fee, note); } /** * Sends bitcoin from your wallet to multiple addresses. * * @param recipients Map with the structure of 'address':amount in satoshi (String:long) * @param fromAddress Specific address to send from (optional, nullable) * @param fee Transaction fee in satoshi. Must be greater than the default fee (optional, nullable). * @param note Public note to include with the transaction (optional, nullable) * @return An instance of the PaymentResponse class * @throws APIException If the server returns an error */ public PaymentResponse sendMany (Map<String, Long> recipients, String fromAddress, Long fee, String note) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); String method = null; if (recipients.size() == 1) { method = "payment"; Entry<String, Long> e = recipients.entrySet().iterator().next(); params.put("to", e.getKey()); params.put("amount", e.getValue().toString()); } else { method = "sendmany"; params.put("recipients", new Gson().toJson(recipients)); } if (fromAddress != null) { params.put("from", fromAddress); } if (fee != null) { params.put("fee", fee.toString()); } if (note != null) { params.put("note", note); } String response = HttpClient.getInstance().post(String.format("merchant/%s/%s", identifier, method), params); JsonObject topElem = parseResponse(response); return new PaymentResponse(topElem.get("message").getAsString(), topElem.get("tx_hash").getAsString(), topElem.has("notice") ? topElem.get("notice").getAsString() : null); } /** * Fetches the wallet balance. Includes unconfirmed transactions and * possibly double spends. * * @return Wallet balance in satoshi * @throws APIException If the server returns an error */ public long getBalance () throws APIException, IOException { String response = HttpClient.getInstance().get(String.format("merchant/%s/balance", identifier), buildBasicRequest()); JsonObject topElem = parseResponse(response); return topElem.get("balance").getAsLong(); } /** * Lists all active addresses in the wallet. * * @param confirmations Minimum number of confirmations transactions * must have before being included in the balance of addresses (can be 0) * @return A list of Address objects * @throws APIException If the server returns an error */ public List<Address> listAddresses (int confirmations) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); params.put("confirmations", String.valueOf(confirmations)); String response = HttpClient.getInstance().get(String.format("merchant/%s/list", identifier), params); JsonObject topElem = parseResponse(response); List<Address> addresses = new ArrayList<Address>(); for (JsonElement jAddr : topElem.get("addresses").getAsJsonArray()) { JsonObject a = jAddr.getAsJsonObject(); Address address = new Address(a.get("balance").getAsLong(), a.get("address").getAsString(), a.has("label") && !a.get("label").isJsonNull() ? a.get("label").getAsString() : null, a.get("total_received").getAsLong()); addresses.add(address); } return addresses; } /** * Retrieves an address from the wallet. * * @param address Address in the wallet to look up * @param confirmations Minimum number of confirmations transactions * must have before being included in the balance of an addresses (can be 0) * @return An instance of the Address class * @throws APIException If the server returns an error */ public Address getAddress (String address, int confirmations) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); params.put("address", address); params.put("confirmations", String.valueOf(confirmations)); String response = HttpClient.getInstance().get(String.format("merchant/%s/address_balance", identifier), params); JsonObject topElem = parseResponse(response); return new Address(topElem.get("balance").getAsLong(), topElem.get("address").getAsString(), topElem.has("label") && !topElem.get("label").isJsonNull() ? topElem.get("label").getAsString() : null, topElem.get("total_received").getAsLong()); } /** * Generates a new address and adds it to the wallet. * * @param label Label to attach to this address (optional, nullable) * @return An instance of the Address class * @throws APIException If the server returns an error */ public Address newAddress (String label) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); if (label != null) { params.put("label", label); } String response = HttpClient.getInstance().post(String.format("merchant/%s/new_address", identifier), params); JsonObject topElem = parseResponse(response); return new Address(0L, topElem.get("address").getAsString(), topElem.has("label") && !topElem.get("label").isJsonNull() ? topElem.get("label").getAsString() : null, 0L); } /** * Archives an address. * * @param address Address to archive * @return String representation of the archived address * @throws APIException If the server returns an error */ public String archiveAddress (String address) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); params.put("address", address); String response = HttpClient.getInstance().post(String.format("merchant/%s/archive_address", identifier), params); JsonObject topElem = parseResponse(response); return topElem.get("archived").getAsString(); } /** * Unarchives an address. * * @param address Address to unarchive * @return String representation of the unarchived address * @throws APIException If the server returns an error */ public String unarchiveAddress (String address) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); params.put("address", address); String response = HttpClient.getInstance().post(String.format("merchant/%s/unarchive_address", identifier), params); JsonObject topElem = parseResponse(response); return topElem.get("active").getAsString(); } /** * Consolidates the wallet addresses. * * @param days Addresses which have not received any * transactions in at least this many days will be consolidated. * @return A list of consolidated addresses in the string format * @throws APIException If the server returns an error */ public List<String> consolidate (int days) throws APIException, IOException { Map<String, String> params = buildBasicRequest(); params.put("days", String.valueOf(days)); String response = HttpClient.getInstance().post(String.format("merchant/%s/auto_consolidate", identifier), params); JsonObject topElem = parseResponse(response); List<String> addresses = new ArrayList<String>(); for (JsonElement jAddr : topElem.get("consolidated").getAsJsonArray()) { addresses.add(jAddr.getAsString()); } return addresses; } private Map<String, String> buildBasicRequest () { Map<String, String> params = new HashMap<String, String>(); params.put("password", password); if (secondPassword != null) { params.put("second_password", secondPassword); } if (apiCode != null) { params.put("api_code", apiCode); } return params; } private JsonObject parseResponse (String response) throws APIException { JsonObject topElem = jsonParser.parse(response).getAsJsonObject(); if (topElem.has("error")) { throw new APIException(topElem.get("error").getAsString()); } return topElem; } /** * Sets the Blockchain.info API code */ public void setApiCode (String apiCode) { this.apiCode = apiCode; } }