com.coinprism.model.APIClient.java Source code

Java tutorial

Introduction

Here is the source code for com.coinprism.model.APIClient.java

Source

/*
 * Copyright (c) 2014 Flavien Charlon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.coinprism.model;

import org.bitcoinj.core.Transaction;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;

/**
 * Provides functions for accessing the Coinprism API.
 */
public class APIClient {
    private final String baseUrl;
    private final HashMap<String, AssetDefinition> cache = new HashMap<String, AssetDefinition>();
    private final static String userAgent = "Coinprism Android";

    public APIClient(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public AssetDefinition getAssetDefinition(String address) {
        return cache.get(address);
    }

    public Collection<AssetDefinition> getAllAssetDefinitions() {
        return cache.values();
    }

    private AssetDefinition fetchAssetDefinition(String address) throws IOException, APIException {
        AssetDefinition definition = cache.get(address);
        if (definition == null) {
            String httpResponse = executeHttpGet(this.baseUrl + "/v1/assets/" + address);

            try {
                JSONObject jObject = new JSONObject(httpResponse);

                definition = new AssetDefinition(jObject.getString("asset_id"), jObject.getString("name"),
                        jObject.getString("name_short"), jObject.getInt("divisibility"),
                        jObject.getString("icon_url"));
            } catch (JSONException ex) {
                definition = new AssetDefinition(address);
            }

            cache.put(address, definition);
        }

        return definition;
    }

    /**
     * Gets the balance of an address from the Coinprism API.
     *
     * @param address the address for which to query the balance
     * @return the balance of the address
     */
    public AddressBalance getAddressBalance(String address) throws IOException, JSONException, APIException {
        String json = executeHttpGet(this.baseUrl + "/v1/addresses/" + address);

        JSONObject jObject = new JSONObject(json);
        JSONArray assets = jObject.getJSONArray("assets");
        Long bitcoinBalance = jObject.getLong("unconfirmed_balance") + jObject.getLong("balance");

        ArrayList<AssetBalance> assetBalances = new ArrayList<AssetBalance>();

        for (int i = 0; i < assets.length(); i++) {
            JSONObject assetObject = (JSONObject) assets.get(i);

            String assetId = assetObject.getString("id");

            BigInteger quantity = new BigInteger(assetObject.getString("balance"))
                    .add(new BigInteger(assetObject.getString("unconfirmed_balance")));

            assetBalances.add(new AssetBalance(fetchAssetDefinition(assetId), quantity));
        }

        return new AddressBalance(bitcoinBalance, assetBalances);
    }

    /**
     * Gets the list of recent transactions for a given address.
     *
     * @param address the address for which to query the recent transactions
     * @return a list of transactions
     */
    public List<SingleAssetTransaction> getTransactions(String address)
            throws IOException, JSONException, ParseException, APIException {
        String json = executeHttpGet(this.baseUrl + "/v1/addresses/" + address + "/transactions");

        JSONArray transactions = new JSONArray(json);

        ArrayList<SingleAssetTransaction> assetBalances = new ArrayList<SingleAssetTransaction>();

        for (int i = 0; i < transactions.length(); i++) {
            JSONObject transactionObject = (JSONObject) transactions.get(i);
            String transactionId = transactionObject.getString("hash");

            Date date = null;
            if (!transactionObject.isNull("block_time"))
                date = parseDate(transactionObject.getString("block_time"));

            HashMap<String, BigInteger> quantities = new HashMap<String, BigInteger>();
            BigInteger satoshiDelta = BigInteger.ZERO;

            JSONArray inputs = transactionObject.getJSONArray("inputs");
            for (int j = 0; j < inputs.length(); j++) {
                JSONObject input = (JSONObject) inputs.get(j);
                if (isAddress(input, address)) {
                    if (!input.isNull("asset_id"))
                        addQuantity(quantities, input.getString("asset_id"),
                                new BigInteger(input.getString("asset_quantity")).negate());

                    satoshiDelta = satoshiDelta.subtract(BigInteger.valueOf(input.getLong("value")));
                }
            }

            JSONArray outputs = transactionObject.getJSONArray("outputs");
            for (int j = 0; j < outputs.length(); j++) {
                JSONObject output = (JSONObject) outputs.get(j);
                if (isAddress(output, address)) {
                    if (!output.isNull("asset_id"))
                        addQuantity(quantities, output.getString("asset_id"),
                                new BigInteger(output.getString("asset_quantity")));

                    satoshiDelta = satoshiDelta.add(BigInteger.valueOf(output.getLong("value")));
                }
            }

            if (!satoshiDelta.equals(BigInteger.ZERO))
                assetBalances.add(new SingleAssetTransaction(transactionId, date, null, satoshiDelta));

            for (String key : quantities.keySet()) {
                assetBalances.add(new SingleAssetTransaction(transactionId, date, getAssetDefinition(key),
                        quantities.get(key)));
            }
        }

        return assetBalances;
    }

    /**
     * Calls the API to create a valid unsigned transaction.
     *
     * @param fromAddress the address from which to send the funds or assets
     * @param toAddress the address where to send the funds or assets
     * @param amount the amount to send, in asset units or satoshis
     * @param assetId the asset ID of the asset to send, or null for bitcoins
     * @param fees the fees to pay, in satoshis
     * @return the unsigned transaction for the requested operation
     */
    public Transaction buildTransaction(String fromAddress, String toAddress, String amount, String assetId,
            long fees) throws JSONException, IOException, APIException {
        try {
            JSONObject toObject = new JSONObject();
            toObject.put("address", toAddress);
            toObject.put("amount", amount);
            if (assetId != null)
                toObject.put("asset_id", assetId);

            JSONArray array = new JSONArray();
            array.put(toObject);
            JSONObject postData = new JSONObject();
            postData.put("fees", fees);
            postData.put("from", fromAddress);
            postData.put("to", array);

            String result;
            if (assetId != null)
                result = executeHttpPost(this.baseUrl + "/v1/sendasset?format=raw", postData.toString());
            else
                result = executeHttpPost(this.baseUrl + "/v1/sendbitcoin?format=raw", postData.toString());

            JSONObject jsonResponse = new JSONObject(result);

            byte[] data = hexStringToByteArray(jsonResponse.getString("raw"));
            Transaction transaction = new Transaction(
                    WalletState.getState().getConfiguration().getNetworkParameters(), data);
            transaction.ensureParsed();

            return transaction;
        } catch (UnsupportedEncodingException ex) {
            return null;
        }
    }

    private static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        final char[] hexArray = "0123456789ABCDEF".toCharArray();

        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    /**
     * Broadcasts a transaction to the Bitcoin network.
     *
     * @param transaction the transaction to broadcast
     * @return the transaction hash of the transaction
     */
    public String broadcastTransaction(Transaction transaction) throws IOException, JSONException, APIException {
        String serializedTransaction = byteArrayToHexString(transaction.bitcoinSerialize());

        try {
            String result = executeHttpPost(this.baseUrl + "/v1/sendrawtransaction",
                    "\"" + serializedTransaction + "\"");

            return result.substring(1, result.length() - 1);
        } catch (UnsupportedEncodingException ex) {
            return null;
        }
    }

    private void addQuantity(HashMap<String, BigInteger> map, String assetId, BigInteger quantity) {
        if (!map.containsKey(assetId))
            map.put(assetId, quantity);
        else
            map.put(assetId, quantity.add(map.get(assetId)));
    }

    private static Date parseDate(String input) throws java.text.ParseException {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

        return df.parse(input.substring(0, 21));
    }

    private boolean isAddress(JSONObject member, String localAddress) throws JSONException {
        JSONArray addresses = member.getJSONArray("addresses");

        return addresses.length() == 1 && addresses.getString(0).equals(localAddress);
    }

    private static String executeHttpGet(String url) throws IOException, APIException {
        URL target = new URL(url);
        HttpsURLConnection connection = (HttpsURLConnection) target.openConnection();

        connection.setRequestMethod("GET");
        connection.setRequestProperty("User-Agent", userAgent);

        return getHttpResponse(connection);
    }

    private static String executeHttpPost(String url, String body) throws IOException, APIException {
        URL target = new URL(url);
        HttpsURLConnection connection = (HttpsURLConnection) target.openConnection();

        connection.setRequestMethod("POST");
        connection.setRequestProperty("User-Agent", userAgent);
        connection.setRequestProperty("Content-Type", "application/json");

        OutputStream output = null;
        try {
            output = connection.getOutputStream();
            output.write(body.getBytes("UTF-8"));
        } finally {
            if (output != null)
                output.close();
        }

        return getHttpResponse(connection);
    }

    private static String getHttpResponse(HttpsURLConnection connection) throws IOException, APIException {
        int responseCode = connection.getResponseCode();

        if (responseCode < 400) {
            InputStream inputStream = new BufferedInputStream(connection.getInputStream());

            return readStream(inputStream);
        } else {
            InputStream inputStream = new BufferedInputStream(connection.getErrorStream());
            String response = readStream(inputStream);
            try {
                JSONObject error = new JSONObject(response);
                String errorCode = error.getString("ErrorCode");
                String subCode = error.optString("SubCode");

                throw new APIException(errorCode, subCode);
            } catch (JSONException exception) {
                throw new IOException(exception.getMessage());
            }
        }
    }

    private static String readStream(InputStream in) {
        BufferedReader reader = null;
        StringBuilder response = new StringBuilder();
        try {
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return response.toString();
    }
}