Updated on 2026-08-14
This commit is contained in:
commit
bd8d84a85a
680 changed files with 51751 additions and 6799 deletions
345
app/src/main/java/com/tangem/wallet/BCHUtils.java
Normal file
345
app/src/main/java/com/tangem/wallet/BCHUtils.java
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 29.09.2017.
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.wallet.btc.BitcoinException;
|
||||
import com.tangem.wallet.btc.BitcoinOutputStream;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.FormatUtil;
|
||||
import com.tangem.card_common.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
|
||||
public final class BCHUtils {
|
||||
static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N
|
||||
public static final long MIN_FEE_PER_KB = 10000;
|
||||
public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1");
|
||||
public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000;
|
||||
public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L;
|
||||
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
|
||||
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
|
||||
|
||||
public static String toHex(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return "";
|
||||
}
|
||||
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
int v;
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
|
||||
int inputPos = currentInputPos;
|
||||
byte[] tx = buildPreimage(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
|
||||
return tx;
|
||||
}
|
||||
|
||||
public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, long amount, long change) throws BitcoinException, IOException {
|
||||
|
||||
int inputPos = -1;
|
||||
byte[] tx = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
|
||||
return tx;
|
||||
}
|
||||
|
||||
//BIP 143 as reference + script length added
|
||||
public static byte[] buildPreimage(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
|
||||
//nVersion of the transaction (4-byte little endian)
|
||||
BitcoinOutputStream forSign = new BitcoinOutputStream();
|
||||
//forSign.writeInt32(0x01);
|
||||
forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
|
||||
|
||||
//inputCount
|
||||
byte inputCount = (byte) unspentOutputs.size();
|
||||
|
||||
//hashPrevouts (32-byte hash)
|
||||
ByteArrayOutputStream prevouts = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < inputCount; ++i) {
|
||||
UnspentOutputInfo outPut = unspentOutputs.get(i);
|
||||
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID
|
||||
byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex
|
||||
prevouts.write(txHash);
|
||||
prevouts.write(txIndex);
|
||||
}
|
||||
byte[] hashPrevouts = CryptoUtil.doubleSha256(prevouts.toByteArray());
|
||||
forSign.write(hashPrevouts);
|
||||
|
||||
//hashSequence (32-byte hash), ffffffff only
|
||||
ByteArrayOutputStream sequences = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < inputCount; ++i) {
|
||||
sequences.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
|
||||
}
|
||||
byte[] hashSequence = CryptoUtil.doubleSha256(sequences.toByteArray());
|
||||
forSign.write(hashSequence);
|
||||
|
||||
//outpoint (32-byte hash + 4-byte little endian)
|
||||
UnspentOutputInfo outPut = unspentOutputs.get(inputPos);
|
||||
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID
|
||||
byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex
|
||||
forSign.write(txHash);
|
||||
forSign.write(txIndex);
|
||||
|
||||
//scriptCode of the input (serialized as scripts inside CTxOuts)
|
||||
byte[] scriptCode = Transaction.Script.buildOutput(changeAddress).bytes; //build change out
|
||||
byte[] scriptLength = Util.intToByteArray(scriptCode.length);
|
||||
forSign.write(scriptLength);
|
||||
forSign.write(scriptCode);
|
||||
|
||||
//value of the output spent by this input (8-byte little endian)
|
||||
byte[] outValue = BCHUtils.reverse(Util.longToByteArray8(outPut.value));
|
||||
forSign.write(outValue);
|
||||
|
||||
//nSequence of the input (4-byte little endian), ffffffff only
|
||||
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
|
||||
|
||||
//hashOutputs (32-byte hash)
|
||||
ByteArrayOutputStream outputs = new ByteArrayOutputStream();
|
||||
byte[] sendAmount = BCHUtils.reverse(Util.longToByteArray8(amount));
|
||||
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
|
||||
byte[] sendLength = Util.intToByteArray(sendScript.length);
|
||||
outputs.write(sendAmount);
|
||||
outputs.write(sendLength);
|
||||
outputs.write(sendScript);
|
||||
//output for change (if any)
|
||||
if (change != 0) {
|
||||
byte[] changeAmount = BCHUtils.reverse(Util.longToByteArray8(change));
|
||||
byte[] changeScript = Transaction.Script.buildOutput(changeAddress).bytes; //build change out
|
||||
byte[] changeLength = Util.intToByteArray(changeScript.length);
|
||||
outputs.write(changeAmount);
|
||||
outputs.write(changeLength);
|
||||
outputs.write(changeScript);
|
||||
}
|
||||
byte[] hashOutputs = CryptoUtil.doubleSha256(outputs.toByteArray());
|
||||
forSign.write(hashOutputs);
|
||||
|
||||
//nLocktime of the transaction (4-byte little endian)
|
||||
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
//sighash type of the signature (4-byte little endian)
|
||||
forSign.write(new byte[]{0x41, 0x00, 0x00, 0x00});
|
||||
|
||||
byte[] rawData = forSign.toByteArray();
|
||||
|
||||
Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
|
||||
|
||||
return rawData;
|
||||
|
||||
}
|
||||
public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
|
||||
//0200000000
|
||||
BitcoinOutputStream forSign = new BitcoinOutputStream();
|
||||
//forSign.writeInt32(0x01);
|
||||
forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
|
||||
|
||||
//01
|
||||
byte inputCount = (byte) unspentOutputs.size();
|
||||
forSign.write(inputCount); // input count
|
||||
//hex str hash prev btc
|
||||
|
||||
for (int i = 0; i < inputCount; ++i) {
|
||||
UnspentOutputInfo outPut = unspentOutputs.get(i);
|
||||
int outputIndex = outPut.outputIndex;
|
||||
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte);
|
||||
forSign.write(txHash);
|
||||
forSign.writeInt32(outputIndex); //output index in prev tx
|
||||
if (inputPos == -1 || i == inputPos) {
|
||||
// hex str 1976a914....88ac
|
||||
forSign.write((byte) outPut.scriptForBuild.length);
|
||||
forSign.write(outPut.scriptForBuild);
|
||||
} else {
|
||||
forSign.write(0x00);
|
||||
}
|
||||
//ffffffff
|
||||
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
|
||||
}
|
||||
|
||||
|
||||
//02
|
||||
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
|
||||
forSign.write(outputCount);
|
||||
|
||||
//8 bytes
|
||||
|
||||
forSign.writeInt64(amount); //amount
|
||||
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
|
||||
//hex str 1976a914....88ac
|
||||
forSign.write((byte) sendScript.length);
|
||||
forSign.write(sendScript);
|
||||
|
||||
if (change != 0) {
|
||||
//8 bytes
|
||||
forSign.writeInt64(change); // change
|
||||
//hex str 1976a914....88ac
|
||||
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
|
||||
forSign.write((byte) chancheScript.length);
|
||||
forSign.write(chancheScript);
|
||||
|
||||
}
|
||||
|
||||
//00000000
|
||||
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
//forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00});
|
||||
|
||||
byte[] rawData = forSign.toByteArray();
|
||||
|
||||
Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
|
||||
|
||||
//0200000000
|
||||
BitcoinOutputStream forSign = new BitcoinOutputStream();
|
||||
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
|
||||
|
||||
//01
|
||||
byte inputCount = 1;
|
||||
forSign.write(inputCount); // input count
|
||||
//hex str hash prev btc
|
||||
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte);
|
||||
forSign.write(txHash); //previos tx hash
|
||||
|
||||
//00000000
|
||||
//byte indexOutput = outputIndex;
|
||||
forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx
|
||||
//forSign.write(0x00);
|
||||
|
||||
// hex str 1976a914....88ac
|
||||
forSign.write((byte) script.length);
|
||||
forSign.write(script);
|
||||
|
||||
//ffffffff
|
||||
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
|
||||
|
||||
//02
|
||||
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
|
||||
forSign.write(outputCount);
|
||||
|
||||
//8 bytes
|
||||
|
||||
forSign.writeInt64(amount); //amount
|
||||
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
|
||||
//hex str 1976a914....88ac
|
||||
forSign.write((byte) sendScript.length);
|
||||
forSign.write(sendScript);
|
||||
|
||||
if (change != 0) {
|
||||
//8 bytes
|
||||
forSign.writeInt64(change); // change
|
||||
//hex str 1976a914....88ac
|
||||
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
|
||||
forSign.write((byte) chancheScript.length);
|
||||
forSign.write(chancheScript);
|
||||
|
||||
}
|
||||
|
||||
//00000000
|
||||
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
byte[] rawData = forSign.toByteArray();
|
||||
|
||||
//Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public static ArrayList<UnspentOutputInfo> getOutputs(List<BtcData.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
|
||||
for (BtcData.UnspentTransaction current : rawTxList) {
|
||||
byte[] rawTxByte = BCHUtils.fromHex(current.Raw);
|
||||
if (rawTxByte == null || current.Raw.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Transaction baseTx = new Transaction(rawTxByte);
|
||||
|
||||
if (baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
|
||||
throw new IllegalArgumentException("Unable to decode given transaction");
|
||||
|
||||
byte[] txHash = BCHUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
|
||||
String txHashForBuild = current.txID;
|
||||
byte[] sign = null;
|
||||
|
||||
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
|
||||
Transaction.Output output = baseTx.outputs[outputIndex];
|
||||
|
||||
// find outputs
|
||||
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
|
||||
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return unspentOutputs;
|
||||
}
|
||||
|
||||
public static byte[] fromHex(String s) {
|
||||
if (s != null) {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
if (!Character.isWhitespace(ch)) {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
s = sb.toString();
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int hi = (Character.digit(s.charAt(i), 16) << 4);
|
||||
int low = Character.digit(s.charAt(i + 1), 16);
|
||||
if (hi >= 256 || low < 0 || low >= 16) {
|
||||
return null;
|
||||
}
|
||||
data[i / 2] = (byte) (hi | low);
|
||||
}
|
||||
return data;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] reverse(byte[] bytes) {
|
||||
byte[] result = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
result[i] = bytes[bytes.length - i - 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] reverseInPlace(byte[] bytes) {
|
||||
int len = bytes.length / 2;
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte t = bytes[i];
|
||||
bytes[i] = bytes[bytes.length - i - 1];
|
||||
bytes[bytes.length - i - 1] = t;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
268
app/src/main/java/com/tangem/wallet/BTCUtils.java
Normal file
268
app/src/main/java/com/tangem/wallet/BTCUtils.java
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 29.09.2017.
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.wallet.btc.BitcoinException;
|
||||
import com.tangem.wallet.btc.BitcoinOutputStream;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.FormatUtil;
|
||||
import com.tangem.card_common.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
|
||||
public final class BTCUtils {
|
||||
static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N
|
||||
public static final long MIN_FEE_PER_KB = 10000;
|
||||
public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1");
|
||||
public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000;
|
||||
public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L;
|
||||
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
|
||||
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
|
||||
|
||||
public static String toHex(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return "";
|
||||
}
|
||||
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
int v;
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
unspentOutputs.get(currentInputPos).scriptForBuild = myScript;
|
||||
int inputPos = currentInputPos;
|
||||
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
os.write(body);
|
||||
os.write(new byte[]{0x01, 0x00, 0x00, 0x00});
|
||||
byte[] tx = os.toByteArray();
|
||||
return tx;
|
||||
}
|
||||
|
||||
public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, long amount, long change) throws BitcoinException, IOException {
|
||||
int inputPos = -1;
|
||||
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
os.write(body);
|
||||
byte[] tx = os.toByteArray();
|
||||
return tx;
|
||||
}
|
||||
|
||||
public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
|
||||
//0200000000
|
||||
BitcoinOutputStream forSign = new BitcoinOutputStream();
|
||||
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
|
||||
|
||||
//01
|
||||
byte inputCount = (byte) unspentOutputs.size();
|
||||
forSign.write(inputCount); // input count
|
||||
//hex str hash prev btc
|
||||
|
||||
for (int i = 0; i < inputCount; ++i) {
|
||||
UnspentOutputInfo outPut = unspentOutputs.get(i);
|
||||
int outputIndex = outPut.outputIndex;
|
||||
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte);
|
||||
forSign.write(txHash);
|
||||
forSign.writeInt32(outputIndex); //output index in prev tx
|
||||
if (inputPos == -1 || i == inputPos) {
|
||||
// hex str 1976a914....88ac
|
||||
forSign.write((byte) outPut.scriptForBuild.length);
|
||||
forSign.write(outPut.scriptForBuild);
|
||||
} else {
|
||||
forSign.write(0x00);
|
||||
}
|
||||
//ffffffff
|
||||
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
|
||||
}
|
||||
|
||||
|
||||
//02
|
||||
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
|
||||
forSign.write(outputCount);
|
||||
|
||||
//8 bytes
|
||||
|
||||
forSign.writeInt64(amount); //amount
|
||||
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
|
||||
//hex str 1976a914....88ac
|
||||
forSign.write((byte) sendScript.length);
|
||||
forSign.write(sendScript);
|
||||
|
||||
if (change != 0) {
|
||||
//8 bytes
|
||||
forSign.writeInt64(change); // change
|
||||
//hex str 1976a914....88ac
|
||||
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
|
||||
forSign.write((byte) chancheScript.length);
|
||||
forSign.write(chancheScript);
|
||||
|
||||
}
|
||||
|
||||
//00000000
|
||||
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
//forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00});
|
||||
|
||||
byte[] rawData = forSign.toByteArray();
|
||||
|
||||
Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
|
||||
|
||||
//0200000000
|
||||
BitcoinOutputStream forSign = new BitcoinOutputStream();
|
||||
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
|
||||
|
||||
//01
|
||||
byte inputCount = 1;
|
||||
forSign.write(inputCount); // input count
|
||||
//hex str hash prev btc
|
||||
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte);
|
||||
forSign.write(txHash); //previos tx hash
|
||||
|
||||
//00000000
|
||||
//byte indexOutput = outputIndex;
|
||||
forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx
|
||||
//forSign.write(0x00);
|
||||
|
||||
// hex str 1976a914....88ac
|
||||
forSign.write((byte) script.length);
|
||||
forSign.write(script);
|
||||
|
||||
//ffffffff
|
||||
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
|
||||
|
||||
//02
|
||||
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
|
||||
forSign.write(outputCount);
|
||||
|
||||
//8 bytes
|
||||
|
||||
forSign.writeInt64(amount); //amount
|
||||
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
|
||||
//hex str 1976a914....88ac
|
||||
forSign.write((byte) sendScript.length);
|
||||
forSign.write(sendScript);
|
||||
|
||||
if (change != 0) {
|
||||
//8 bytes
|
||||
forSign.writeInt64(change); // change
|
||||
//hex str 1976a914....88ac
|
||||
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
|
||||
forSign.write((byte) chancheScript.length);
|
||||
forSign.write(chancheScript);
|
||||
|
||||
}
|
||||
|
||||
//00000000
|
||||
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
byte[] rawData = forSign.toByteArray();
|
||||
|
||||
//Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public static ArrayList<UnspentOutputInfo> getOutputs(List<BtcData.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
|
||||
for (BtcData.UnspentTransaction current : rawTxList) {
|
||||
byte[] rawTxByte = BTCUtils.fromHex(current.Raw);
|
||||
if (rawTxByte == null || current.Raw.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Transaction baseTx = new Transaction(rawTxByte);
|
||||
|
||||
if (baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
|
||||
throw new IllegalArgumentException("Unable to decode given transaction");
|
||||
|
||||
byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
|
||||
String txHashForBuild = current.txID;
|
||||
byte[] sign = null;
|
||||
|
||||
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
|
||||
Transaction.Output output = baseTx.outputs[outputIndex];
|
||||
|
||||
// find outputs
|
||||
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
|
||||
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return unspentOutputs;
|
||||
}
|
||||
|
||||
public static byte[] fromHex(String s) {
|
||||
if (s != null) {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
if (!Character.isWhitespace(ch)) {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
s = sb.toString();
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int hi = (Character.digit(s.charAt(i), 16) << 4);
|
||||
int low = Character.digit(s.charAt(i + 1), 16);
|
||||
if (hi >= 256 || low < 0 || low >= 16) {
|
||||
return null;
|
||||
}
|
||||
data[i / 2] = (byte) (hi | low);
|
||||
}
|
||||
return data;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] reverse(byte[] bytes) {
|
||||
byte[] result = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
result[i] = bytes[bytes.length - i - 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] reverseInPlace(byte[] bytes) {
|
||||
int len = bytes.length / 2;
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte t = bytes[i];
|
||||
bytes[i] = bytes[bytes.length - i - 1];
|
||||
bytes[bytes.length - i - 1] = t;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
115
app/src/main/java/com/tangem/wallet/BalanceValidator.java
Normal file
115
app/src/main/java/com/tangem/wallet/BalanceValidator.java
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.card_common.data.TangemCard;
|
||||
|
||||
public class BalanceValidator {
|
||||
private String firstLine;
|
||||
private String secondLine;
|
||||
private int score;
|
||||
private boolean hasPending;
|
||||
|
||||
public String getFirstLine() {
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
public void setFirstLine(String value) {
|
||||
firstLine = value;
|
||||
}
|
||||
|
||||
public String getSecondLine(Boolean recommend) {
|
||||
if (!recommend) return secondLine;
|
||||
if (score > 89) {
|
||||
return "Safe to accept. " + secondLine;
|
||||
} else if (score > 74) {
|
||||
return "Not fully safe to accept. " + secondLine;
|
||||
} else if (score > 30) {
|
||||
return "Not safe to accept. " + secondLine;
|
||||
} else {
|
||||
return "Do not accept! " + secondLine;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSecondLine(String value) {
|
||||
secondLine = value;
|
||||
}
|
||||
|
||||
public void setScore(int score) {
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
if( hasPending )
|
||||
{
|
||||
return R.color.primary_dark;
|
||||
}
|
||||
if (score > 89) {
|
||||
return R.color.confirmed;
|
||||
} else if (score > 74) {
|
||||
return android.R.color.holo_orange_light;
|
||||
} else if (score > 0) {
|
||||
return android.R.color.holo_orange_dark;
|
||||
} else {
|
||||
return android.R.color.holo_red_light;
|
||||
}
|
||||
}
|
||||
|
||||
public void check(TangemContext ctx, Boolean attest) {
|
||||
firstLine = "Verification failed";
|
||||
secondLine = "";
|
||||
TangemCard card = ctx.getCard();
|
||||
CoinEngine engine = CoinEngineFactory.INSTANCE.create(ctx);
|
||||
|
||||
if (!engine.validateBalance(this)) return;
|
||||
|
||||
hasPending=App.pendingTransactionsStorage.hasTransactions(card);
|
||||
|
||||
if( hasPending )
|
||||
{
|
||||
firstLine = "Pending transaction...";
|
||||
secondLine = "Swipe down to refresh";
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify card?
|
||||
if (attest) {
|
||||
|
||||
if (!card.isWalletPublicKeyValid()) {
|
||||
score = 0;
|
||||
firstLine = "Verification failed";
|
||||
secondLine = "Wallet verification failed. Tap again.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (card.isOnlineVerified() != null && !card.isOnlineVerified()) {
|
||||
score = 0;
|
||||
firstLine = "Not genuine banknote";
|
||||
secondLine = "Tangem Attestation service says the banknote is not genuine.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (card.isCodeConfirmed() != null && !card.isCodeConfirmed()) {
|
||||
score = 0;
|
||||
firstLine = "Not genuine banknote";
|
||||
secondLine = "Firmware binary code verification failed";
|
||||
return;
|
||||
}
|
||||
|
||||
if (card.PIN2 == TangemCard.PIN2_Mode.CustomPIN2) {
|
||||
score = 0;
|
||||
firstLine = "Locked with PIN2";
|
||||
secondLine = "Ask the holder to disable PIN2 before accepting";
|
||||
return;
|
||||
}
|
||||
|
||||
// rule 2.b
|
||||
if (card.isOnlineVerified()) {
|
||||
secondLine += "Verified note identity. ";
|
||||
} else {
|
||||
score = 80;
|
||||
secondLine += "Card identity was not verified. Cannot reach Tangem attestation service. ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
115
app/src/main/java/com/tangem/wallet/Base58.java
Normal file
115
app/src/main/java/com/tangem/wallet/Base58.java
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 15.02.2018.
|
||||
*/
|
||||
|
||||
public class Base58 {
|
||||
private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||
|
||||
private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long
|
||||
private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS
|
||||
private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,
|
||||
-1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
|
||||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
|
||||
-1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
public static byte[] decodeBase58(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
input = input.trim();
|
||||
if (input.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
BigInteger resultNum = BigInteger.ZERO;
|
||||
int nLeadingZeros = 0;
|
||||
while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) {
|
||||
nLeadingZeros++;
|
||||
}
|
||||
long acc = 0;
|
||||
int nDigits = 0;
|
||||
int p = nLeadingZeros;
|
||||
while (p < input.length()) {
|
||||
int v = BASE58_VALUES[input.charAt(p) & 0xff];
|
||||
if (v >= 0) {
|
||||
acc *= 58;
|
||||
acc += v;
|
||||
nDigits++;
|
||||
if (nDigits == BASE58_CHUNK_DIGITS) {
|
||||
resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc));
|
||||
acc = 0;
|
||||
nDigits = 0;
|
||||
}
|
||||
p++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nDigits > 0) {
|
||||
long mul = 58;
|
||||
while (--nDigits > 0) {
|
||||
mul *= 58;
|
||||
}
|
||||
resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc));
|
||||
}
|
||||
final int BASE58_SPACE = -2;
|
||||
while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) {
|
||||
p++;
|
||||
}
|
||||
if (p < input.length()) {
|
||||
return null;
|
||||
}
|
||||
byte[] plainNumber = resultNum.toByteArray();
|
||||
int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0;
|
||||
byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs];
|
||||
System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String encodeBase58(byte[] input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1);
|
||||
BigInteger bn = new BigInteger(1, input);
|
||||
long rem;
|
||||
while (true) {
|
||||
BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD);
|
||||
bn = divideAndRemainder[0];
|
||||
rem = divideAndRemainder[1].longValue();
|
||||
if (bn.compareTo(BigInteger.ZERO) == 0) {
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) {
|
||||
str.append(BASE58[(int) (rem % 58)]);
|
||||
rem /= 58;
|
||||
}
|
||||
}
|
||||
while (rem != 0) {
|
||||
str.append(BASE58[(int) (rem % 58)]);
|
||||
rem /= 58;
|
||||
}
|
||||
str.reverse();
|
||||
int nLeadingZeros = 0;
|
||||
while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) {
|
||||
str.insert(0, BASE58[0]);
|
||||
nLeadingZeros++;
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
194
app/src/main/java/com/tangem/wallet/CoinData.java
Normal file
194
app/src/main/java/com/tangem/wallet/CoinData.java
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.data.Blockchain;
|
||||
|
||||
public abstract class CoinData {
|
||||
|
||||
public CoinData() {
|
||||
}
|
||||
|
||||
private String wallet;
|
||||
|
||||
public void setWallet(String wallet) {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
public String getWallet() {
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public String getShortWalletString() {
|
||||
if (wallet.length() < 22) {
|
||||
return wallet;
|
||||
} else {
|
||||
return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBalanceReceived() {
|
||||
return balanceReceived;
|
||||
}
|
||||
|
||||
private boolean balanceReceived = false;
|
||||
|
||||
public void setBalanceReceived(boolean value) {
|
||||
balanceReceived = value;
|
||||
}
|
||||
|
||||
public void loadFromBundle(Bundle B) {
|
||||
wallet = B.getString("Wallet");
|
||||
|
||||
if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived"));
|
||||
|
||||
validationNodeDescription = B.getString("validationNodeDescription");
|
||||
|
||||
// if (B.containsKey("FailedBalance"))
|
||||
// failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
|
||||
|
||||
if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual"));
|
||||
|
||||
if (B.containsKey("rate"))
|
||||
rate = B.getFloat("rate");
|
||||
if (B.containsKey("rateAlter"))
|
||||
rateAlter = B.getFloat("rateAlter");
|
||||
}
|
||||
|
||||
public void saveToBundle(Bundle B) {
|
||||
try {
|
||||
B.putString("Wallet", wallet);
|
||||
|
||||
if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
|
||||
|
||||
// if (failedBalanceRequestCounter != null)
|
||||
// B.putInt("FailedBalance", failedBalanceRequestCounter.get());
|
||||
|
||||
B.putFloat("rate", rate);
|
||||
B.putFloat("rateAlter", rateAlter);
|
||||
|
||||
B.putBoolean("balanceReceived", balanceReceived);
|
||||
B.putString("validationNodeDescription", validationNodeDescription);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Bundle asBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
saveToBundle(bundle);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static CoinData fromBundle(Blockchain blockchain, Bundle bundle) {
|
||||
CoinEngine engine= CoinEngineFactory.INSTANCE.create(blockchain);
|
||||
if( engine==null ) return null;
|
||||
CoinData result = engine.createCoinData();
|
||||
result.loadFromBundle(bundle);
|
||||
return result;
|
||||
}
|
||||
|
||||
//TODO - move all to special engines
|
||||
private float rate = 0;
|
||||
private float rateAlter = 0;
|
||||
|
||||
public float getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public float getRateAlter() {
|
||||
return rateAlter;
|
||||
}
|
||||
|
||||
public void setRate(float rate) {
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
public void setRateAlter(float rate) {
|
||||
this.rateAlter = rate;
|
||||
}
|
||||
|
||||
// public Double amountFromInternalUnits(Long internalAmount) {
|
||||
// // TODO java.lang.NullPointerException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
|
||||
// return ((double) internalAmount) /
|
||||
// getBlockchain().getMultiplier();
|
||||
// }
|
||||
//
|
||||
// public Double amountFromInternalUnits(Integer internalAmount) {
|
||||
// return ((double) internalAmount) / getBlockchain().getMultiplier();
|
||||
// }
|
||||
//
|
||||
// // TODO разобраться с балансами, привести к единому интерфейсу
|
||||
// public Long internalUnitsFromString(String caption) {
|
||||
// try {
|
||||
// return FormatUtil.ConvertStringToLong(caption);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public String getAmountDescription(Double amount) {
|
||||
// String output = FormatUtil.DoubleToString(amount);
|
||||
// return output + " " + getBlockchain().getCurrency();
|
||||
// }
|
||||
|
||||
public boolean getAmountEquivalentDescriptionAvailable() {
|
||||
return rate > 0;
|
||||
}
|
||||
|
||||
|
||||
public void clearInfo() {
|
||||
setIsBalanceEqual(false);
|
||||
setBalanceReceived(false);
|
||||
setValidationNodeDescription("");
|
||||
minFee=null;
|
||||
maxFee=null;
|
||||
normalFee=null;
|
||||
rate=0f;
|
||||
rateAlter=0f;
|
||||
}
|
||||
|
||||
// private AtomicInteger failedBalanceRequestCounter;
|
||||
//
|
||||
// public int incFailedBalanceRequestCounter() {
|
||||
// if (failedBalanceRequestCounter == null)
|
||||
// failedBalanceRequestCounter = new AtomicInteger(0);
|
||||
// return failedBalanceRequestCounter.incrementAndGet();
|
||||
// }
|
||||
//
|
||||
// public void resetFailedBalanceRequestCounter() {
|
||||
// failedBalanceRequestCounter = new AtomicInteger(0);
|
||||
// }
|
||||
//
|
||||
// public int getFailedBalanceRequestCounter() {
|
||||
// if (failedBalanceRequestCounter == null)
|
||||
// return 0;
|
||||
// return failedBalanceRequestCounter.get();
|
||||
// }
|
||||
|
||||
private Boolean balanceEqual;
|
||||
|
||||
public Boolean isBalanceEqual() {
|
||||
return balanceEqual;
|
||||
}
|
||||
|
||||
public void setIsBalanceEqual(boolean isEqual) {
|
||||
balanceEqual = isEqual;
|
||||
}
|
||||
|
||||
private String validationNodeDescription = "";
|
||||
|
||||
public String getValidationNodeDescription() {
|
||||
return validationNodeDescription;
|
||||
}
|
||||
|
||||
public void setValidationNodeDescription(String validationNodeDescription) {
|
||||
this.validationNodeDescription = validationNodeDescription;
|
||||
}
|
||||
|
||||
public CoinEngine.Amount minFee = null;
|
||||
public CoinEngine.Amount normalFee = null;
|
||||
public CoinEngine.Amount maxFee = null;
|
||||
}
|
||||
373
app/src/main/java/com/tangem/wallet/CoinEngine.java
Normal file
373
app/src/main/java/com/tangem/wallet/CoinEngine.java
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
|
||||
import com.tangem.card_common.reader.CardProtocol;
|
||||
import com.tangem.card_common.tasks.SignTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
|
||||
import co.nstant.in.cbor.CborException;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 15.02.2018.
|
||||
*/
|
||||
|
||||
public abstract class CoinEngine {
|
||||
|
||||
public static class InternalAmount extends BigDecimal {
|
||||
private String currency;
|
||||
|
||||
public InternalAmount() {
|
||||
super(0);
|
||||
currency = "";
|
||||
}
|
||||
|
||||
public InternalAmount(String amountString, String currency) {
|
||||
super(amountString.replace(',', '.'));
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public InternalAmount(long amount, String currency) {
|
||||
super(amount);
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public InternalAmount(BigDecimal amount, String currency) {
|
||||
super(amount.unscaledValue(), amount.scale());
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public InternalAmount(BigInteger amount, String currency) {
|
||||
super(new BigDecimal(amount).unscaledValue(), new BigDecimal(amount).scale());
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public boolean notZero() {
|
||||
return compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return compareTo(BigDecimal.ZERO) == 0;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
public String toValueString(int decimals) {
|
||||
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
|
||||
symbols.setDecimalSeparator('.');
|
||||
DecimalFormat df = new DecimalFormat();
|
||||
df.setDecimalFormatSymbols(symbols);
|
||||
df.setMaximumFractionDigits(decimals);
|
||||
|
||||
df.setMinimumFractionDigits(0);
|
||||
|
||||
df.setGroupingUsed(false);
|
||||
|
||||
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
|
||||
bd.setScale(decimals, ROUND_DOWN);
|
||||
return df.format(bd);
|
||||
}
|
||||
|
||||
public String toValueString() {
|
||||
return toValueString(scale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Amount extends BigDecimal {
|
||||
private String currency;
|
||||
|
||||
public Amount() {
|
||||
super(0);
|
||||
currency = "";
|
||||
}
|
||||
|
||||
public Amount(String amountString, String currency) {
|
||||
super(amountString.replace(',', '.'));
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public Amount(Long amount, String currency) {
|
||||
super(amount);
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public Amount(BigDecimal amount, String currency) {
|
||||
super(amount.unscaledValue(), amount.scale());
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " " + currency;
|
||||
}
|
||||
|
||||
public boolean notZero() {
|
||||
return compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
public String toDescriptionString(int decimals) {
|
||||
return toValueString(decimals) + " " + currency;
|
||||
}
|
||||
|
||||
public String toValueString(int decimals) {
|
||||
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
|
||||
symbols.setDecimalSeparator('.');
|
||||
DecimalFormat df = new DecimalFormat();
|
||||
df.setDecimalFormatSymbols(symbols);
|
||||
df.setMaximumFractionDigits(decimals);
|
||||
|
||||
df.setMinimumFractionDigits(0);
|
||||
|
||||
df.setGroupingUsed(false);
|
||||
|
||||
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
|
||||
bd.setScale(decimals, ROUND_DOWN);
|
||||
return df.format(bd);
|
||||
}
|
||||
|
||||
public String toValueString() {
|
||||
return toValueString(scale());
|
||||
}
|
||||
|
||||
public String toEquivalentString(double rateValue) {
|
||||
if (rateValue > 0) {
|
||||
BigDecimal biRate = new BigDecimal(rateValue);
|
||||
BigDecimal exchangeCurs = biRate.multiply(this);
|
||||
exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN);
|
||||
return "≈ USD " + exchangeCurs.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return compareTo(BigDecimal.ZERO) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected TangemContext ctx;
|
||||
|
||||
public CoinEngine() {
|
||||
|
||||
}
|
||||
|
||||
public CoinEngine(TangemContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public abstract boolean awaitingConfirmation();
|
||||
|
||||
public abstract boolean hasBalanceInfo();
|
||||
|
||||
public abstract boolean isBalanceNotZero();
|
||||
|
||||
// TODO - return string message
|
||||
public abstract boolean isExtractPossible();
|
||||
|
||||
public abstract Uri getWalletExplorerUri();
|
||||
|
||||
public abstract Uri getShareWalletUri();
|
||||
|
||||
public abstract boolean checkNewTransactionAmount(Amount amount);
|
||||
|
||||
public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded);
|
||||
|
||||
public abstract boolean validateBalance(BalanceValidator balanceValidator);
|
||||
|
||||
public abstract Amount getBalance();
|
||||
|
||||
public abstract String getBalanceHTML();
|
||||
|
||||
public abstract String getBalanceCurrency();
|
||||
|
||||
public abstract InputFilter[] getAmountInputFilters();
|
||||
|
||||
public abstract String getOfflineBalanceHTML();
|
||||
|
||||
public abstract String evaluateFeeEquivalent(String fee);
|
||||
|
||||
public abstract String getFeeCurrency();
|
||||
|
||||
public abstract boolean isNeedCheckNode();
|
||||
|
||||
public abstract String getBalanceEquivalent();
|
||||
|
||||
public abstract boolean validateAddress(String address);
|
||||
|
||||
public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException, Exception;
|
||||
|
||||
public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception;
|
||||
|
||||
public abstract Amount convertToAmount(String strAmount, String currency);
|
||||
|
||||
public abstract InternalAmount convertToInternalAmount(Amount amount) throws Exception;
|
||||
|
||||
public abstract InternalAmount convertToInternalAmount(byte[] bytes) throws Exception;
|
||||
|
||||
public abstract byte[] convertToByteArray(InternalAmount internalAmount) throws Exception;
|
||||
|
||||
public abstract CoinData createCoinData();
|
||||
|
||||
public abstract String getUnspentInputsDescription();
|
||||
|
||||
public void defineWallet() throws CardProtocol.TangemException {
|
||||
try {
|
||||
String wallet = calculateAddress(ctx.getCard().getWalletPublicKey());
|
||||
ctx.getCoinData().setWallet(wallet);
|
||||
} catch (Exception e) {
|
||||
ctx.getCoinData().setWallet("ERROR");
|
||||
throw new CardProtocol.TangemException("Can't define wallet address");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of {@link SignTask.TransactionToSign} used for transaction signing and sending
|
||||
*
|
||||
* Transaction processing sequence:
|
||||
* 1. User enter transaction attributes
|
||||
* 2. Application create instance of {@link SignTask.TransactionToSign} by call {@see constructTransaction}
|
||||
* 3. Application set notification when transaction were prepared {@see setOnNeedSendTransaction} and init {@link SignTask}
|
||||
* 4. User tap card and card sign transaction
|
||||
* 5. Application receive {@link OnNeedSendTransaction} notification with prepared raw transaction
|
||||
* 6. Application show user information that transaction ready for sending and init sending procedure by call {@see requestSendTransaction}
|
||||
* 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user
|
||||
*
|
||||
* @param amountValue - amount of desired transaction
|
||||
* @param feeValue - fee amount of desired transaction
|
||||
* @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction)
|
||||
* @param targetAddress - target address of transaction
|
||||
* @return instance of {@link SignTask.TransactionToSign}
|
||||
* @throws Exception if something goes wrong
|
||||
*/
|
||||
public abstract SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception;
|
||||
|
||||
/**
|
||||
* Interface used to notify main application when new transaction is prepared to send
|
||||
*/
|
||||
public interface OnNeedSendTransaction {
|
||||
void onTransactionPrepared(byte[] txForSend);
|
||||
}
|
||||
|
||||
protected OnNeedSendTransaction onNeedSendTransaction;
|
||||
|
||||
|
||||
/**
|
||||
* Set notification callback when new transaction is prepared to send
|
||||
*/
|
||||
public void setOnNeedSendTransaction(OnNeedSendTransaction onNeedSendTransaction) {
|
||||
this.onNeedSendTransaction = onNeedSendTransaction;
|
||||
}
|
||||
|
||||
protected void notifyOnNeedSendTransaction(byte[] txForSend) throws Exception {
|
||||
if (onNeedSendTransaction == null)
|
||||
throw new Exception("Transaction was signed but no callback defined to send!");
|
||||
onNeedSendTransaction.onTransactionPrepared(txForSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to notify/querying application during processing sequence of request to blockchain nodes/servers
|
||||
*/
|
||||
public interface BlockchainRequestsCallbacks {
|
||||
/**
|
||||
* Notification that the all requests in sequence completed
|
||||
* Call after a last request completed
|
||||
* If occurred error return in {@link TangemContext} {@see TangemContext.getError()}
|
||||
*
|
||||
* @param success -*
|
||||
*/
|
||||
void onComplete(Boolean success);
|
||||
|
||||
/**
|
||||
* Notification that a new part of data received and it's possible to update view
|
||||
* May call when some request in the sequence completed but there are still a few requests left
|
||||
*/
|
||||
void onProgress();
|
||||
|
||||
/**
|
||||
* Return flag that allow to add new or re-requests in the sequence
|
||||
* Call between requests or when request fail and before re-request
|
||||
*
|
||||
* @return true if not need terminate (e.g. activity is online)
|
||||
*/
|
||||
boolean allowAdvance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start sequence of request to blockchain nodes needed to get balance and other information (for example unspent transaction) needed to
|
||||
* show current state of wallet and prepare new withdrawal transaction
|
||||
* Save result in {@link CoinData}
|
||||
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
|
||||
* @param blockchainRequestsCallbacks - notifications
|
||||
* @throws Exception if something goes wrong
|
||||
*/
|
||||
public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception;
|
||||
|
||||
/**
|
||||
* Start sequence of request to blockchain nodes needed to get fee amount for a new transaction
|
||||
* Save result in {@link CoinData} minFee, maxFee, normalFee
|
||||
* @param blockchainRequestsCallbacks - notifications
|
||||
* @throws Exception if something goes wrong
|
||||
*/
|
||||
public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception;
|
||||
|
||||
/**
|
||||
* Start sequence of request to blockchain nodes needed to send new transaction
|
||||
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
|
||||
* @param blockchainRequestsCallbacks - notifications
|
||||
* @throws Exception if something goes wrong
|
||||
*/
|
||||
public abstract void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* @return true if blockchain need multiple lines to show balance (e.g. need show additional balance information, Token count for example)
|
||||
*/
|
||||
public boolean needMultipleLinesForBalance() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true to allow user select fee level - min, normal or priority
|
||||
*/
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true to allow user select include or exclude fee
|
||||
*/
|
||||
public boolean allowSelectFeeInclusion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isNftToken() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int pendingTransactionTimeoutInSeconds() { return 30; }
|
||||
|
||||
}
|
||||
109
app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
Normal file
109
app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package com.tangem.wallet
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import com.tangem.wallet.btc.BtcEngine
|
||||
import com.tangem.wallet.eth.EthEngine
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import com.tangem.wallet.bch.BtcCashEngine
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.wallet.binance.BinanceEngine
|
||||
import com.tangem.wallet.cardano.CardanoData
|
||||
import com.tangem.wallet.cardano.CardanoEngine
|
||||
import com.tangem.wallet.ltc.LtcEngine
|
||||
import com.tangem.wallet.matic.MaticTokenEngine
|
||||
import com.tangem.wallet.nftToken.NftTokenEngine
|
||||
import com.tangem.wallet.rsk.RskEngine
|
||||
import com.tangem.wallet.rsk.RskTokenEngine
|
||||
import com.tangem.wallet.xrp.XrpEngine
|
||||
|
||||
/**
|
||||
* Factory for create specific engine
|
||||
*
|
||||
* @param
|
||||
* Blockchain
|
||||
* @param TangemContext
|
||||
*
|
||||
*/
|
||||
|
||||
object CoinEngineFactory {
|
||||
private val TAG = CoinEngineFactory::class.java.simpleName
|
||||
|
||||
fun create(blockchain: Blockchain): CoinEngine? {
|
||||
return when (blockchain) {
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestNet -> BtcEngine()
|
||||
Blockchain.BitcoinCash -> BtcCashEngine()
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine()
|
||||
Blockchain.Token -> TokenEngine()
|
||||
Blockchain.NftToken -> NftTokenEngine()
|
||||
Blockchain.Litecoin -> LtcEngine()
|
||||
Blockchain.Rootstock -> RskEngine()
|
||||
Blockchain.RootstockToken -> RskTokenEngine()
|
||||
Blockchain.Cardano -> CardanoEngine()
|
||||
Blockchain.Ripple -> XrpEngine()
|
||||
Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine()
|
||||
Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun create(context: TangemContext): CoinEngine? {
|
||||
var result: CoinEngine?
|
||||
try {
|
||||
result = if (Blockchain.BitcoinCash == context.blockchain)
|
||||
BtcCashEngine(context)
|
||||
else if (Blockchain.Bitcoin == context.blockchain || Blockchain.BitcoinTestNet == context.blockchain)
|
||||
BtcEngine(context)
|
||||
else if (Blockchain.Ethereum == context.blockchain || Blockchain.EthereumTestNet == context.blockchain)
|
||||
EthEngine(context)
|
||||
else if (Blockchain.Token == context.blockchain)
|
||||
TokenEngine(context)
|
||||
else if (Blockchain.NftToken == context.blockchain)
|
||||
NftTokenEngine(context)
|
||||
else if (Blockchain.Litecoin == context.blockchain)
|
||||
LtcEngine(context)
|
||||
else if (Blockchain.Rootstock == context.blockchain)
|
||||
RskEngine(context)
|
||||
else if (Blockchain.RootstockToken == context.blockchain)
|
||||
RskTokenEngine(context)
|
||||
else if (Blockchain.Cardano == context.blockchain)
|
||||
CardanoEngine(context)
|
||||
else if (Blockchain.Ripple == context.blockchain)
|
||||
XrpEngine(context)
|
||||
else if (Blockchain.Binance == context.blockchain || Blockchain.BinanceTestNet == context.blockchain)
|
||||
BinanceEngine(context)
|
||||
else if (Blockchain.Matic == context.blockchain || Blockchain.MaticTestNet == context.blockchain)
|
||||
MaticTokenEngine(context
|
||||
|
||||
)
|
||||
else
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
result = null
|
||||
Log.e(TAG, "Can't create CoinEngine!")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun createCardano(context: TangemContext): CoinEngine? {
|
||||
var result: CoinEngine?
|
||||
try {
|
||||
result = CardanoEngine(context)//EthEngine(context)//
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
result = null
|
||||
Log.e(TAG, "Can't create Cardano CoinEngine!")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun createCardanoData(): CoinData? {
|
||||
return CardanoData()//EthData()// CardanoData()
|
||||
}
|
||||
|
||||
fun isCardano(blockchainID: String): Boolean {
|
||||
return blockchainID==Blockchain.Cardano.id//Ethereum.id
|
||||
}
|
||||
}
|
||||
114
app/src/main/java/com/tangem/wallet/Digest.java
Normal file
114
app/src/main/java/com/tangem/wallet/Digest.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 18.12.2017.
|
||||
*/
|
||||
|
||||
public interface Digest{
|
||||
|
||||
/**
|
||||
* Insert one more input data byte.
|
||||
*
|
||||
* @param in the input byte
|
||||
*/
|
||||
void update(byte in);
|
||||
|
||||
/**
|
||||
* Insert some more bytes.
|
||||
*
|
||||
* @param inbuf the data bytes
|
||||
*/
|
||||
void update(byte[] inbuf);
|
||||
|
||||
/**
|
||||
* Insert some more bytes.
|
||||
*
|
||||
* @param inbuf the data buffer
|
||||
* @param off the data offset in {@code inbuf}
|
||||
* @param len the data length (in bytes)
|
||||
*/
|
||||
void update(byte[] inbuf, int off, int len);
|
||||
|
||||
/**
|
||||
* Finalize the current hash computation and return the hash value
|
||||
* in a newly-allocated array. The object is resetted.
|
||||
*
|
||||
* @return the hash output
|
||||
*/
|
||||
byte[] digest();
|
||||
|
||||
/**
|
||||
* Input some bytes, then finalize the current hash computation
|
||||
* and return the hash value in a newly-allocated array. The object
|
||||
* is resetted.
|
||||
*
|
||||
* @param inbuf the input data
|
||||
* @return the hash output
|
||||
*/
|
||||
byte[] digest(byte[] inbuf);
|
||||
|
||||
/**
|
||||
* Finalize the current hash computation and store the hash value
|
||||
* in the provided output buffer. The {@code len} parameter
|
||||
* contains the maximum number of bytes that should be written;
|
||||
* no more bytes than the natural hash function output length will
|
||||
* be produced. If {@code len} is smaller than the natural
|
||||
* hash output length, the hash output is truncated to its first
|
||||
* {@code len} bytes. The object is resetted.
|
||||
*
|
||||
* @param outbuf the output buffer
|
||||
* @param off the output offset within {@code outbuf}
|
||||
* @param len the requested hash output length (in bytes)
|
||||
* @return the number of bytes actually written in {@code outbuf}
|
||||
*/
|
||||
int digest(byte[] outbuf, int off, int len);
|
||||
|
||||
/**
|
||||
* Get the natural hash function output length (in bytes).
|
||||
*
|
||||
* @return the digest output length (in bytes)
|
||||
*/
|
||||
int getDigestLength();
|
||||
|
||||
/**
|
||||
* Reset the object: this makes it suitable for a new hash
|
||||
* computation. The current computation, if any, is discarded.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Clone the current state. The returned object evolves independantly
|
||||
* of this object.
|
||||
*
|
||||
* @return the clone
|
||||
*/
|
||||
Digest copy();
|
||||
|
||||
/**
|
||||
* <p>Return the "block length" for the hash function. This
|
||||
* value is naturally defined for iterated hash functions
|
||||
* (Merkle-Damgard). It is used in HMAC (that's what the
|
||||
* <a href="http://tools.ietf.org/html/rfc2104">HMAC specification</a>
|
||||
* names the "{@code B}" parameter).</p>
|
||||
*
|
||||
* <p>If the function is "block-less" then this function may
|
||||
* return {@code -n} where {@code n} is an integer such that the
|
||||
* block length for HMAC ("{@code B}") will be inferred from the
|
||||
* key length, by selecting the smallest multiple of {@code n}
|
||||
* which is no smaller than the key length. For instance, for
|
||||
* the Fugue-xxx hash functions, this function returns -4: the
|
||||
* virtual block length B is the HMAC key length, rounded up to
|
||||
* the next multiple of 4.</p>
|
||||
*
|
||||
* @return the internal block length (in bytes), or {@code -n}
|
||||
*/
|
||||
int getBlockLength();
|
||||
|
||||
/**
|
||||
* <p>Get the display name for this function (e.g. {@code "SHA-1"}
|
||||
* for SHA-1).</p>
|
||||
*
|
||||
* @see Object
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
220
app/src/main/java/com/tangem/wallet/DigestEngine.java
Normal file
220
app/src/main/java/com/tangem/wallet/DigestEngine.java
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 18.12.2017.
|
||||
*/
|
||||
|
||||
public abstract class DigestEngine extends MessageDigest implements Digest {
|
||||
|
||||
/**
|
||||
* Reset the hash algorithm state.
|
||||
*/
|
||||
protected abstract void engineReset();
|
||||
|
||||
/**
|
||||
* Process one block of data.
|
||||
*
|
||||
* @param data the data block
|
||||
*/
|
||||
protected abstract void processBlock(byte[] data);
|
||||
|
||||
/**
|
||||
* Perform the final padding and store the result in the
|
||||
* provided buffer. This method shall call {@link #flush}
|
||||
* and then {@link #update} with the appropriate padding
|
||||
* data in order to get the full input data.
|
||||
*
|
||||
* @param buf the output buffer
|
||||
* @param off the output offset
|
||||
*/
|
||||
protected abstract void doPadding(byte[] buf, int off);
|
||||
|
||||
/**
|
||||
* This function is called at object creation time; the
|
||||
* implementation should use it to perform initialization tasks.
|
||||
* After this method is called, the implementation should be ready
|
||||
* to process data or meaningfully honour calls such as
|
||||
* {@link #engineGetDigestLength}
|
||||
*/
|
||||
protected abstract void doInit();
|
||||
|
||||
private int digestLen, blockLen, inputLen;
|
||||
private byte[] inputBuf, outputBuf;
|
||||
private long blockCount;
|
||||
|
||||
/**
|
||||
* Instantiate the engine.
|
||||
*/
|
||||
public DigestEngine(String alg)
|
||||
{
|
||||
super(alg);
|
||||
doInit();
|
||||
digestLen = engineGetDigestLength();
|
||||
blockLen = getInternalBlockLength();
|
||||
inputBuf = new byte[blockLen];
|
||||
outputBuf = new byte[digestLen];
|
||||
inputLen = 0;
|
||||
blockCount = 0;
|
||||
}
|
||||
|
||||
private void adjustDigestLen()
|
||||
{
|
||||
if (digestLen == 0) {
|
||||
digestLen = engineGetDigestLength();
|
||||
outputBuf = new byte[digestLen];
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] digest()
|
||||
{
|
||||
adjustDigestLen();
|
||||
byte[] result = new byte[digestLen];
|
||||
digest(result, 0, digestLen);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] digest(byte[] input)
|
||||
{
|
||||
update(input, 0, input.length);
|
||||
return digest();
|
||||
}
|
||||
|
||||
public int digest(byte[] buf, int offset, int len)
|
||||
{
|
||||
adjustDigestLen();
|
||||
if (len >= digestLen) {
|
||||
doPadding(buf, offset);
|
||||
reset();
|
||||
return digestLen;
|
||||
} else {
|
||||
doPadding(outputBuf, 0);
|
||||
System.arraycopy(outputBuf, 0, buf, offset, len);
|
||||
reset();
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
engineReset();
|
||||
inputLen = 0;
|
||||
blockCount = 0;
|
||||
}
|
||||
|
||||
public void update(byte input)
|
||||
{
|
||||
inputBuf[inputLen ++] = (byte)input;
|
||||
if (inputLen == blockLen) {
|
||||
processBlock(inputBuf);
|
||||
blockCount ++;
|
||||
inputLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(byte[] input)
|
||||
{
|
||||
update(input, 0, input.length);
|
||||
}
|
||||
|
||||
public void update(byte[] input, int offset, int len)
|
||||
{
|
||||
while (len > 0) {
|
||||
int copyLen = blockLen - inputLen;
|
||||
if (copyLen > len)
|
||||
copyLen = len;
|
||||
System.arraycopy(input, offset, inputBuf, inputLen,
|
||||
copyLen);
|
||||
offset += copyLen;
|
||||
inputLen += copyLen;
|
||||
len -= copyLen;
|
||||
if (inputLen == blockLen) {
|
||||
processBlock(inputBuf);
|
||||
blockCount ++;
|
||||
inputLen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal block length. This is the length (in
|
||||
* bytes) of the array which will be passed as parameter to
|
||||
* {@link #processBlock}. The default implementation of this
|
||||
* method calls {@link #getBlockLength} and returns the same
|
||||
* value. Overriding this method is useful when the advertised
|
||||
* block length (which is used, for instance, by HMAC) is
|
||||
* suboptimal with regards to internal buffering needs.
|
||||
*
|
||||
* @return the internal block length (in bytes)
|
||||
*/
|
||||
protected int getInternalBlockLength()
|
||||
{
|
||||
return getBlockLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush internal buffers, so that less than a block of data
|
||||
* may at most be upheld.
|
||||
*
|
||||
* @return the number of bytes still unprocessed after the flush
|
||||
*/
|
||||
protected final int flush()
|
||||
{
|
||||
return inputLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to an internal buffer with the same size
|
||||
* than a block. The contents of that buffer are defined only
|
||||
* immediately after a call to {@link #flush()}: if
|
||||
* {@link #flush()} return the value {@code n}, then the
|
||||
* first {@code n} bytes of the array returned by this method
|
||||
* are the {@code n} bytes of input data which are still
|
||||
* unprocessed. The values of the remaining bytes are
|
||||
* undefined and may be altered at will.
|
||||
*
|
||||
* @return a block-sized internal buffer
|
||||
*/
|
||||
protected final byte[] getBlockBuffer()
|
||||
{
|
||||
return inputBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "block count": this is the number of times the
|
||||
* {@link #processBlock} method has been invoked for the
|
||||
* current hash operation. That counter is incremented
|
||||
* <em>after</em> the call to {@link #processBlock}.
|
||||
*
|
||||
* @return the block count
|
||||
*/
|
||||
protected long getBlockCount()
|
||||
{
|
||||
return blockCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function copies the internal buffering state to some
|
||||
* other instance of a class extending {@code DigestEngine}.
|
||||
* It returns a reference to the copy. This method is intended
|
||||
* to be called by the implementation of the {@link #copy}
|
||||
* method.
|
||||
*
|
||||
* @param dest the copy
|
||||
* @return the value {@code dest}
|
||||
*/
|
||||
protected Digest copyState(DigestEngine dest)
|
||||
{
|
||||
dest.inputLen = inputLen;
|
||||
dest.blockCount = blockCount;
|
||||
System.arraycopy(inputBuf, 0, dest.inputBuf, 0,
|
||||
inputBuf.length);
|
||||
adjustDigestLen();
|
||||
dest.adjustDigestLen();
|
||||
System.arraycopy(outputBuf, 0, dest.outputBuf, 0,
|
||||
outputBuf.length);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
52
app/src/main/java/com/tangem/wallet/ECDSASignatureETH.java
Normal file
52
app/src/main/java/com/tangem/wallet/ECDSASignatureETH.java
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 07.01.2018.
|
||||
*/
|
||||
|
||||
public class ECDSASignatureETH {
|
||||
/**
|
||||
* The two components of the signature.
|
||||
*/
|
||||
public final BigInteger r, s;
|
||||
public byte v;
|
||||
|
||||
/**
|
||||
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
|
||||
*
|
||||
* @param r -
|
||||
* @param s -
|
||||
*/
|
||||
public ECDSASignatureETH(BigInteger r, BigInteger s) {
|
||||
this.r = r;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
/**
|
||||
*t
|
||||
* @param r
|
||||
* @param s
|
||||
* @return -
|
||||
*/
|
||||
private static ECDSASignatureETH fromComponents(byte[] r, byte[] s) {
|
||||
return new ECDSASignatureETH(new BigInteger(1, r), new BigInteger(1, s));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param r -
|
||||
* @param s -
|
||||
* @param v -
|
||||
* @return -
|
||||
*/
|
||||
public static ECDSASignatureETH fromComponents(byte[] r, byte[] s, byte v) {
|
||||
ECDSASignatureETH signature = fromComponents(r, s);
|
||||
signature.v = v;
|
||||
return signature;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
225
app/src/main/java/com/tangem/wallet/EthTransaction.java
Normal file
225
app/src/main/java/com/tangem/wallet/EthTransaction.java
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.util.ByteUtil;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.spongycastle.util.BigIntegers;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.tangem.util.ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 07.01.2018.
|
||||
*/
|
||||
|
||||
public class EthTransaction {
|
||||
byte[] nonce;
|
||||
byte[] gasPrice;
|
||||
byte[] gasLimit;
|
||||
byte[] receiveAddress;
|
||||
byte[] value;
|
||||
byte[] data;
|
||||
Integer chainId;
|
||||
byte[] rlpRaw;
|
||||
public ECDSASignatureETH signature;
|
||||
byte[] rlpEncoded;
|
||||
|
||||
private static final int CHAIN_ID_INC = 35;
|
||||
private static final int LOWER_REAL_V = 27;
|
||||
|
||||
public static EthTransaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, Integer chainId) {
|
||||
return new EthTransaction(BigIntegers.asUnsignedByteArray(nonce),
|
||||
BigIntegers.asUnsignedByteArray(gasPrice),
|
||||
BigIntegers.asUnsignedByteArray(gasLimit),
|
||||
Hex.decode(to),
|
||||
BigIntegers.asUnsignedByteArray(amount),
|
||||
null,
|
||||
chainId);
|
||||
}
|
||||
|
||||
public static EthTransaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, Integer chainId, byte[] data) {
|
||||
return new EthTransaction(BigIntegers.asUnsignedByteArray(nonce),
|
||||
BigIntegers.asUnsignedByteArray(gasPrice),
|
||||
BigIntegers.asUnsignedByteArray(gasLimit),
|
||||
Hex.decode(to),
|
||||
BigIntegers.asUnsignedByteArray(amount),
|
||||
data,
|
||||
chainId);
|
||||
}
|
||||
|
||||
public EthTransaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data, Integer chainId) {
|
||||
this.nonce = nonce;
|
||||
this.gasPrice = gasPrice;
|
||||
this.gasLimit = gasLimit;
|
||||
this.receiveAddress = receiveAddress;
|
||||
if (ByteUtil.isSingleZero(value)) {
|
||||
this.value = EMPTY_BYTE_ARRAY;
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
this.data = data;
|
||||
this.chainId = chainId;
|
||||
|
||||
if (receiveAddress == null) {
|
||||
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChainEnum {
|
||||
Mainnet(1),
|
||||
Morden(2),
|
||||
Ropsten(3),
|
||||
Rinkeby(4),
|
||||
Rootstock_mainnet(30),
|
||||
Rootstock_testnet(31),
|
||||
Kovan(42),
|
||||
Ethereum_Classic_mainnet(61),
|
||||
Ethereum_Classic_testnet(62),
|
||||
Geth_private_chains(1337),
|
||||
Matic_Testnet(8995);
|
||||
|
||||
private int value;
|
||||
|
||||
ChainEnum(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRawHash() {
|
||||
|
||||
byte[] plainMsg = this.getEncodedRaw();
|
||||
Keccak256 kec = new Keccak256();
|
||||
return kec.digest(plainMsg);
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
|
||||
byte[] plainMsg = this.getEncoded();
|
||||
Keccak256 kec = new Keccak256();
|
||||
return kec.digest(plainMsg);
|
||||
}
|
||||
|
||||
public int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
|
||||
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
|
||||
int recId = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
byte[] recK = CryptoUtil.recoverPubBytesFromSignature(i, sig, messageHash);
|
||||
|
||||
if (recK == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.e("ETH_k " + String.valueOf(i), BTCUtils.toHex(recK));
|
||||
if (Arrays.equals(recK, thisKey)) {
|
||||
recId = i;
|
||||
recId += 27;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return recId;
|
||||
}
|
||||
|
||||
public int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) {
|
||||
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
|
||||
int recId = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, false);
|
||||
|
||||
if (k == null)
|
||||
continue;
|
||||
byte[] recK = k.getPubKey();
|
||||
Log.e("ETH_k " + String.valueOf(i), BTCUtils.toHex(recK));
|
||||
if (k != null && Arrays.equals(recK, thisKey)) {
|
||||
recId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return recId;
|
||||
}
|
||||
|
||||
// signed TX
|
||||
public byte[] getEncoded() {
|
||||
|
||||
// parse null as 0 for nonce
|
||||
byte[] nonce = null;
|
||||
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
|
||||
nonce = RLP.encodeElement(null);
|
||||
} else {
|
||||
nonce = RLP.encodeElement(this.nonce);
|
||||
}
|
||||
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
|
||||
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
|
||||
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
|
||||
byte[] value = RLP.encodeElement(this.value);
|
||||
byte[] data = RLP.encodeElement(this.data);
|
||||
|
||||
byte[] v, r, s;
|
||||
|
||||
if (signature != null) {
|
||||
int encodeV;
|
||||
if (chainId == null) {
|
||||
encodeV = signature.v;
|
||||
} else {
|
||||
encodeV = signature.v - LOWER_REAL_V;
|
||||
encodeV += chainId * 2 + CHAIN_ID_INC;
|
||||
}
|
||||
v = RLP.encodeInt(encodeV);
|
||||
r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r));
|
||||
s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s));
|
||||
} else {
|
||||
// Since EIP-155 use chainId for v
|
||||
v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId);
|
||||
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
|
||||
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
|
||||
}
|
||||
|
||||
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
|
||||
receiveAddress, value, data, v, r, s);
|
||||
|
||||
//this.hash = this.getHash();
|
||||
|
||||
return rlpEncoded;
|
||||
}
|
||||
|
||||
// unsigned TX
|
||||
public byte[] getEncodedRaw() {
|
||||
// parse null as 0 for nonce
|
||||
byte[] nonce = null;
|
||||
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
|
||||
nonce = RLP.encodeElement(null);
|
||||
} else {
|
||||
nonce = RLP.encodeElement(this.nonce);
|
||||
}
|
||||
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
|
||||
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
|
||||
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
|
||||
byte[] value = RLP.encodeElement(this.value);
|
||||
byte[] data = RLP.encodeElement(this.data);
|
||||
|
||||
// Since EIP-155 use chainId for v
|
||||
if (chainId == null) {
|
||||
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
|
||||
value, data);
|
||||
} else {
|
||||
byte[] v, r, s;
|
||||
v = RLP.encodeInt(chainId);
|
||||
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
|
||||
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
|
||||
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
|
||||
value, data, v, r, s);
|
||||
}
|
||||
return rlpRaw;
|
||||
}
|
||||
|
||||
}
|
||||
39
app/src/main/java/com/tangem/wallet/Keccak256.java
Normal file
39
app/src/main/java/com/tangem/wallet/Keccak256.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 18.12.2017.
|
||||
*/
|
||||
|
||||
public class Keccak256 extends KeccakCore {
|
||||
|
||||
/**
|
||||
* Create the engine.
|
||||
*/
|
||||
public Keccak256()
|
||||
{
|
||||
super("eth-keccak-256");
|
||||
}
|
||||
|
||||
public Digest copy()
|
||||
{
|
||||
return copyState(new Keccak256());
|
||||
}
|
||||
|
||||
public int engineGetDigestLength()
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineDigest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] arg0, int arg1, int arg2) {
|
||||
}
|
||||
}
|
||||
546
app/src/main/java/com/tangem/wallet/KeccakCore.java
Normal file
546
app/src/main/java/com/tangem/wallet/KeccakCore.java
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Ilia on 18.12.2017.
|
||||
*/
|
||||
abstract class KeccakCore extends DigestEngine {
|
||||
KeccakCore(String alg)
|
||||
{
|
||||
super(alg);
|
||||
}
|
||||
|
||||
private long[] A;
|
||||
private byte[] tmpOut;
|
||||
|
||||
private static final long[] RC = {
|
||||
0x0000000000000001L, 0x0000000000008082L,
|
||||
0x800000000000808AL, 0x8000000080008000L,
|
||||
0x000000000000808BL, 0x0000000080000001L,
|
||||
0x8000000080008081L, 0x8000000000008009L,
|
||||
0x000000000000008AL, 0x0000000000000088L,
|
||||
0x0000000080008009L, 0x000000008000000AL,
|
||||
0x000000008000808BL, 0x800000000000008BL,
|
||||
0x8000000000008089L, 0x8000000000008003L,
|
||||
0x8000000000008002L, 0x8000000000000080L,
|
||||
0x000000000000800AL, 0x800000008000000AL,
|
||||
0x8000000080008081L, 0x8000000000008080L,
|
||||
0x0000000080000001L, 0x8000000080008008L
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the 64-bit word {@code val} into the array
|
||||
* {@code buf} at offset {@code off}, in little-endian
|
||||
* convention (least significant byte first).
|
||||
*
|
||||
* @param val the value to encode
|
||||
* @param buf the destination buffer
|
||||
* @param off the destination offset
|
||||
*/
|
||||
private static void encodeLELong(long val, byte[] buf, int off)
|
||||
{
|
||||
buf[off + 0] = (byte)val;
|
||||
buf[off + 1] = (byte)(val >>> 8);
|
||||
buf[off + 2] = (byte)(val >>> 16);
|
||||
buf[off + 3] = (byte)(val >>> 24);
|
||||
buf[off + 4] = (byte)(val >>> 32);
|
||||
buf[off + 5] = (byte)(val >>> 40);
|
||||
buf[off + 6] = (byte)(val >>> 48);
|
||||
buf[off + 7] = (byte)(val >>> 56);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a 64-bit little-endian word from the array {@code buf}
|
||||
* at offset {@code off}.
|
||||
*
|
||||
* @param buf the source buffer
|
||||
* @param off the source offset
|
||||
* @return the decoded value
|
||||
*/
|
||||
private static long decodeLELong(byte[] buf, int off)
|
||||
{
|
||||
return (buf[off + 0] & 0xFFL)
|
||||
| ((buf[off + 1] & 0xFFL) << 8)
|
||||
| ((buf[off + 2] & 0xFFL) << 16)
|
||||
| ((buf[off + 3] & 0xFFL) << 24)
|
||||
| ((buf[off + 4] & 0xFFL) << 32)
|
||||
| ((buf[off + 5] & 0xFFL) << 40)
|
||||
| ((buf[off + 6] & 0xFFL) << 48)
|
||||
| ((buf[off + 7] & 0xFFL) << 56);
|
||||
}
|
||||
|
||||
protected void engineReset()
|
||||
{
|
||||
doReset();
|
||||
}
|
||||
|
||||
protected void processBlock(byte[] data)
|
||||
{
|
||||
/* Input block */
|
||||
for (int i = 0; i < data.length; i += 8)
|
||||
A[i >>> 3] ^= decodeLELong(data, i);
|
||||
|
||||
long t0, t1, t2, t3, t4;
|
||||
long tt0, tt1, tt2, tt3, tt4;
|
||||
long t, kt;
|
||||
long c0, c1, c2, c3, c4, bnn;
|
||||
|
||||
/*
|
||||
* Unrolling four rounds kills performance big time
|
||||
* on Intel x86 Core2, in both 32-bit and 64-bit modes
|
||||
* (less than 1 MB/s instead of 55 MB/s on x86-64).
|
||||
* Unrolling two rounds appears to be fine.
|
||||
*/
|
||||
for (int j = 0; j < 24; j += 2) {
|
||||
|
||||
tt0 = A[ 1] ^ A[ 6];
|
||||
tt1 = A[11] ^ A[16];
|
||||
tt0 ^= A[21] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 4] ^ A[ 9];
|
||||
tt3 = A[14] ^ A[19];
|
||||
tt0 ^= A[24];
|
||||
tt2 ^= tt3;
|
||||
t0 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[ 2] ^ A[ 7];
|
||||
tt1 = A[12] ^ A[17];
|
||||
tt0 ^= A[22] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 0] ^ A[ 5];
|
||||
tt3 = A[10] ^ A[15];
|
||||
tt0 ^= A[20];
|
||||
tt2 ^= tt3;
|
||||
t1 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[ 3] ^ A[ 8];
|
||||
tt1 = A[13] ^ A[18];
|
||||
tt0 ^= A[23] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 1] ^ A[ 6];
|
||||
tt3 = A[11] ^ A[16];
|
||||
tt0 ^= A[21];
|
||||
tt2 ^= tt3;
|
||||
t2 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[ 4] ^ A[ 9];
|
||||
tt1 = A[14] ^ A[19];
|
||||
tt0 ^= A[24] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 2] ^ A[ 7];
|
||||
tt3 = A[12] ^ A[17];
|
||||
tt0 ^= A[22];
|
||||
tt2 ^= tt3;
|
||||
t3 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[ 0] ^ A[ 5];
|
||||
tt1 = A[10] ^ A[15];
|
||||
tt0 ^= A[20] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 3] ^ A[ 8];
|
||||
tt3 = A[13] ^ A[18];
|
||||
tt0 ^= A[23];
|
||||
tt2 ^= tt3;
|
||||
t4 = tt0 ^ tt2;
|
||||
|
||||
A[ 0] = A[ 0] ^ t0;
|
||||
A[ 5] = A[ 5] ^ t0;
|
||||
A[10] = A[10] ^ t0;
|
||||
A[15] = A[15] ^ t0;
|
||||
A[20] = A[20] ^ t0;
|
||||
A[ 1] = A[ 1] ^ t1;
|
||||
A[ 6] = A[ 6] ^ t1;
|
||||
A[11] = A[11] ^ t1;
|
||||
A[16] = A[16] ^ t1;
|
||||
A[21] = A[21] ^ t1;
|
||||
A[ 2] = A[ 2] ^ t2;
|
||||
A[ 7] = A[ 7] ^ t2;
|
||||
A[12] = A[12] ^ t2;
|
||||
A[17] = A[17] ^ t2;
|
||||
A[22] = A[22] ^ t2;
|
||||
A[ 3] = A[ 3] ^ t3;
|
||||
A[ 8] = A[ 8] ^ t3;
|
||||
A[13] = A[13] ^ t3;
|
||||
A[18] = A[18] ^ t3;
|
||||
A[23] = A[23] ^ t3;
|
||||
A[ 4] = A[ 4] ^ t4;
|
||||
A[ 9] = A[ 9] ^ t4;
|
||||
A[14] = A[14] ^ t4;
|
||||
A[19] = A[19] ^ t4;
|
||||
A[24] = A[24] ^ t4;
|
||||
A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36));
|
||||
A[10] = (A[10] << 3) | (A[10] >>> (64 - 3));
|
||||
A[15] = (A[15] << 41) | (A[15] >>> (64 - 41));
|
||||
A[20] = (A[20] << 18) | (A[20] >>> (64 - 18));
|
||||
A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1));
|
||||
A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44));
|
||||
A[11] = (A[11] << 10) | (A[11] >>> (64 - 10));
|
||||
A[16] = (A[16] << 45) | (A[16] >>> (64 - 45));
|
||||
A[21] = (A[21] << 2) | (A[21] >>> (64 - 2));
|
||||
A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62));
|
||||
A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6));
|
||||
A[12] = (A[12] << 43) | (A[12] >>> (64 - 43));
|
||||
A[17] = (A[17] << 15) | (A[17] >>> (64 - 15));
|
||||
A[22] = (A[22] << 61) | (A[22] >>> (64 - 61));
|
||||
A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28));
|
||||
A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55));
|
||||
A[13] = (A[13] << 25) | (A[13] >>> (64 - 25));
|
||||
A[18] = (A[18] << 21) | (A[18] >>> (64 - 21));
|
||||
A[23] = (A[23] << 56) | (A[23] >>> (64 - 56));
|
||||
A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27));
|
||||
A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20));
|
||||
A[14] = (A[14] << 39) | (A[14] >>> (64 - 39));
|
||||
A[19] = (A[19] << 8) | (A[19] >>> (64 - 8));
|
||||
A[24] = (A[24] << 14) | (A[24] >>> (64 - 14));
|
||||
bnn = ~A[12];
|
||||
kt = A[ 6] | A[12];
|
||||
c0 = A[ 0] ^ kt;
|
||||
kt = bnn | A[18];
|
||||
c1 = A[ 6] ^ kt;
|
||||
kt = A[18] & A[24];
|
||||
c2 = A[12] ^ kt;
|
||||
kt = A[24] | A[ 0];
|
||||
c3 = A[18] ^ kt;
|
||||
kt = A[ 0] & A[ 6];
|
||||
c4 = A[24] ^ kt;
|
||||
A[ 0] = c0;
|
||||
A[ 6] = c1;
|
||||
A[12] = c2;
|
||||
A[18] = c3;
|
||||
A[24] = c4;
|
||||
bnn = ~A[22];
|
||||
kt = A[ 9] | A[10];
|
||||
c0 = A[ 3] ^ kt;
|
||||
kt = A[10] & A[16];
|
||||
c1 = A[ 9] ^ kt;
|
||||
kt = A[16] | bnn;
|
||||
c2 = A[10] ^ kt;
|
||||
kt = A[22] | A[ 3];
|
||||
c3 = A[16] ^ kt;
|
||||
kt = A[ 3] & A[ 9];
|
||||
c4 = A[22] ^ kt;
|
||||
A[ 3] = c0;
|
||||
A[ 9] = c1;
|
||||
A[10] = c2;
|
||||
A[16] = c3;
|
||||
A[22] = c4;
|
||||
bnn = ~A[19];
|
||||
kt = A[ 7] | A[13];
|
||||
c0 = A[ 1] ^ kt;
|
||||
kt = A[13] & A[19];
|
||||
c1 = A[ 7] ^ kt;
|
||||
kt = bnn & A[20];
|
||||
c2 = A[13] ^ kt;
|
||||
kt = A[20] | A[ 1];
|
||||
c3 = bnn ^ kt;
|
||||
kt = A[ 1] & A[ 7];
|
||||
c4 = A[20] ^ kt;
|
||||
A[ 1] = c0;
|
||||
A[ 7] = c1;
|
||||
A[13] = c2;
|
||||
A[19] = c3;
|
||||
A[20] = c4;
|
||||
bnn = ~A[17];
|
||||
kt = A[ 5] & A[11];
|
||||
c0 = A[ 4] ^ kt;
|
||||
kt = A[11] | A[17];
|
||||
c1 = A[ 5] ^ kt;
|
||||
kt = bnn | A[23];
|
||||
c2 = A[11] ^ kt;
|
||||
kt = A[23] & A[ 4];
|
||||
c3 = bnn ^ kt;
|
||||
kt = A[ 4] | A[ 5];
|
||||
c4 = A[23] ^ kt;
|
||||
A[ 4] = c0;
|
||||
A[ 5] = c1;
|
||||
A[11] = c2;
|
||||
A[17] = c3;
|
||||
A[23] = c4;
|
||||
bnn = ~A[ 8];
|
||||
kt = bnn & A[14];
|
||||
c0 = A[ 2] ^ kt;
|
||||
kt = A[14] | A[15];
|
||||
c1 = bnn ^ kt;
|
||||
kt = A[15] & A[21];
|
||||
c2 = A[14] ^ kt;
|
||||
kt = A[21] | A[ 2];
|
||||
c3 = A[15] ^ kt;
|
||||
kt = A[ 2] & A[ 8];
|
||||
c4 = A[21] ^ kt;
|
||||
A[ 2] = c0;
|
||||
A[ 8] = c1;
|
||||
A[14] = c2;
|
||||
A[15] = c3;
|
||||
A[21] = c4;
|
||||
A[ 0] = A[ 0] ^ RC[j + 0];
|
||||
|
||||
tt0 = A[ 6] ^ A[ 9];
|
||||
tt1 = A[ 7] ^ A[ 5];
|
||||
tt0 ^= A[ 8] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[24] ^ A[22];
|
||||
tt3 = A[20] ^ A[23];
|
||||
tt0 ^= A[21];
|
||||
tt2 ^= tt3;
|
||||
t0 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[12] ^ A[10];
|
||||
tt1 = A[13] ^ A[11];
|
||||
tt0 ^= A[14] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 0] ^ A[ 3];
|
||||
tt3 = A[ 1] ^ A[ 4];
|
||||
tt0 ^= A[ 2];
|
||||
tt2 ^= tt3;
|
||||
t1 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[18] ^ A[16];
|
||||
tt1 = A[19] ^ A[17];
|
||||
tt0 ^= A[15] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[ 6] ^ A[ 9];
|
||||
tt3 = A[ 7] ^ A[ 5];
|
||||
tt0 ^= A[ 8];
|
||||
tt2 ^= tt3;
|
||||
t2 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[24] ^ A[22];
|
||||
tt1 = A[20] ^ A[23];
|
||||
tt0 ^= A[21] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[12] ^ A[10];
|
||||
tt3 = A[13] ^ A[11];
|
||||
tt0 ^= A[14];
|
||||
tt2 ^= tt3;
|
||||
t3 = tt0 ^ tt2;
|
||||
|
||||
tt0 = A[ 0] ^ A[ 3];
|
||||
tt1 = A[ 1] ^ A[ 4];
|
||||
tt0 ^= A[ 2] ^ tt1;
|
||||
tt0 = (tt0 << 1) | (tt0 >>> 63);
|
||||
tt2 = A[18] ^ A[16];
|
||||
tt3 = A[19] ^ A[17];
|
||||
tt0 ^= A[15];
|
||||
tt2 ^= tt3;
|
||||
t4 = tt0 ^ tt2;
|
||||
|
||||
A[ 0] = A[ 0] ^ t0;
|
||||
A[ 3] = A[ 3] ^ t0;
|
||||
A[ 1] = A[ 1] ^ t0;
|
||||
A[ 4] = A[ 4] ^ t0;
|
||||
A[ 2] = A[ 2] ^ t0;
|
||||
A[ 6] = A[ 6] ^ t1;
|
||||
A[ 9] = A[ 9] ^ t1;
|
||||
A[ 7] = A[ 7] ^ t1;
|
||||
A[ 5] = A[ 5] ^ t1;
|
||||
A[ 8] = A[ 8] ^ t1;
|
||||
A[12] = A[12] ^ t2;
|
||||
A[10] = A[10] ^ t2;
|
||||
A[13] = A[13] ^ t2;
|
||||
A[11] = A[11] ^ t2;
|
||||
A[14] = A[14] ^ t2;
|
||||
A[18] = A[18] ^ t3;
|
||||
A[16] = A[16] ^ t3;
|
||||
A[19] = A[19] ^ t3;
|
||||
A[17] = A[17] ^ t3;
|
||||
A[15] = A[15] ^ t3;
|
||||
A[24] = A[24] ^ t4;
|
||||
A[22] = A[22] ^ t4;
|
||||
A[20] = A[20] ^ t4;
|
||||
A[23] = A[23] ^ t4;
|
||||
A[21] = A[21] ^ t4;
|
||||
A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36));
|
||||
A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3));
|
||||
A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41));
|
||||
A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18));
|
||||
A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1));
|
||||
A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44));
|
||||
A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10));
|
||||
A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45));
|
||||
A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2));
|
||||
A[12] = (A[12] << 62) | (A[12] >>> (64 - 62));
|
||||
A[10] = (A[10] << 6) | (A[10] >>> (64 - 6));
|
||||
A[13] = (A[13] << 43) | (A[13] >>> (64 - 43));
|
||||
A[11] = (A[11] << 15) | (A[11] >>> (64 - 15));
|
||||
A[14] = (A[14] << 61) | (A[14] >>> (64 - 61));
|
||||
A[18] = (A[18] << 28) | (A[18] >>> (64 - 28));
|
||||
A[16] = (A[16] << 55) | (A[16] >>> (64 - 55));
|
||||
A[19] = (A[19] << 25) | (A[19] >>> (64 - 25));
|
||||
A[17] = (A[17] << 21) | (A[17] >>> (64 - 21));
|
||||
A[15] = (A[15] << 56) | (A[15] >>> (64 - 56));
|
||||
A[24] = (A[24] << 27) | (A[24] >>> (64 - 27));
|
||||
A[22] = (A[22] << 20) | (A[22] >>> (64 - 20));
|
||||
A[20] = (A[20] << 39) | (A[20] >>> (64 - 39));
|
||||
A[23] = (A[23] << 8) | (A[23] >>> (64 - 8));
|
||||
A[21] = (A[21] << 14) | (A[21] >>> (64 - 14));
|
||||
bnn = ~A[13];
|
||||
kt = A[ 9] | A[13];
|
||||
c0 = A[ 0] ^ kt;
|
||||
kt = bnn | A[17];
|
||||
c1 = A[ 9] ^ kt;
|
||||
kt = A[17] & A[21];
|
||||
c2 = A[13] ^ kt;
|
||||
kt = A[21] | A[ 0];
|
||||
c3 = A[17] ^ kt;
|
||||
kt = A[ 0] & A[ 9];
|
||||
c4 = A[21] ^ kt;
|
||||
A[ 0] = c0;
|
||||
A[ 9] = c1;
|
||||
A[13] = c2;
|
||||
A[17] = c3;
|
||||
A[21] = c4;
|
||||
bnn = ~A[14];
|
||||
kt = A[22] | A[ 1];
|
||||
c0 = A[18] ^ kt;
|
||||
kt = A[ 1] & A[ 5];
|
||||
c1 = A[22] ^ kt;
|
||||
kt = A[ 5] | bnn;
|
||||
c2 = A[ 1] ^ kt;
|
||||
kt = A[14] | A[18];
|
||||
c3 = A[ 5] ^ kt;
|
||||
kt = A[18] & A[22];
|
||||
c4 = A[14] ^ kt;
|
||||
A[18] = c0;
|
||||
A[22] = c1;
|
||||
A[ 1] = c2;
|
||||
A[ 5] = c3;
|
||||
A[14] = c4;
|
||||
bnn = ~A[23];
|
||||
kt = A[10] | A[19];
|
||||
c0 = A[ 6] ^ kt;
|
||||
kt = A[19] & A[23];
|
||||
c1 = A[10] ^ kt;
|
||||
kt = bnn & A[ 2];
|
||||
c2 = A[19] ^ kt;
|
||||
kt = A[ 2] | A[ 6];
|
||||
c3 = bnn ^ kt;
|
||||
kt = A[ 6] & A[10];
|
||||
c4 = A[ 2] ^ kt;
|
||||
A[ 6] = c0;
|
||||
A[10] = c1;
|
||||
A[19] = c2;
|
||||
A[23] = c3;
|
||||
A[ 2] = c4;
|
||||
bnn = ~A[11];
|
||||
kt = A[ 3] & A[ 7];
|
||||
c0 = A[24] ^ kt;
|
||||
kt = A[ 7] | A[11];
|
||||
c1 = A[ 3] ^ kt;
|
||||
kt = bnn | A[15];
|
||||
c2 = A[ 7] ^ kt;
|
||||
kt = A[15] & A[24];
|
||||
c3 = bnn ^ kt;
|
||||
kt = A[24] | A[ 3];
|
||||
c4 = A[15] ^ kt;
|
||||
A[24] = c0;
|
||||
A[ 3] = c1;
|
||||
A[ 7] = c2;
|
||||
A[11] = c3;
|
||||
A[15] = c4;
|
||||
bnn = ~A[16];
|
||||
kt = bnn & A[20];
|
||||
c0 = A[12] ^ kt;
|
||||
kt = A[20] | A[ 4];
|
||||
c1 = bnn ^ kt;
|
||||
kt = A[ 4] & A[ 8];
|
||||
c2 = A[20] ^ kt;
|
||||
kt = A[ 8] | A[12];
|
||||
c3 = A[ 4] ^ kt;
|
||||
kt = A[12] & A[16];
|
||||
c4 = A[ 8] ^ kt;
|
||||
A[12] = c0;
|
||||
A[16] = c1;
|
||||
A[20] = c2;
|
||||
A[ 4] = c3;
|
||||
A[ 8] = c4;
|
||||
A[ 0] = A[ 0] ^ RC[j + 1];
|
||||
t = A[ 5];
|
||||
A[ 5] = A[18];
|
||||
A[18] = A[11];
|
||||
A[11] = A[10];
|
||||
A[10] = A[ 6];
|
||||
A[ 6] = A[22];
|
||||
A[22] = A[20];
|
||||
A[20] = A[12];
|
||||
A[12] = A[19];
|
||||
A[19] = A[15];
|
||||
A[15] = A[24];
|
||||
A[24] = A[ 8];
|
||||
A[ 8] = t;
|
||||
t = A[ 1];
|
||||
A[ 1] = A[ 9];
|
||||
A[ 9] = A[14];
|
||||
A[14] = A[ 2];
|
||||
A[ 2] = A[13];
|
||||
A[13] = A[23];
|
||||
A[23] = A[ 4];
|
||||
A[ 4] = A[21];
|
||||
A[21] = A[16];
|
||||
A[16] = A[ 3];
|
||||
A[ 3] = A[17];
|
||||
A[17] = A[ 7];
|
||||
A[ 7] = t;
|
||||
}
|
||||
}
|
||||
|
||||
protected void doPadding(byte[] out, int off)
|
||||
{
|
||||
int ptr = flush();
|
||||
byte[] buf = getBlockBuffer();
|
||||
if ((ptr + 1) == buf.length) {
|
||||
buf[ptr] = (byte)0x81;
|
||||
} else {
|
||||
buf[ptr] = (byte)0x01;
|
||||
for (int i = ptr + 1; i < (buf.length - 1); i ++)
|
||||
buf[i] = 0;
|
||||
buf[buf.length - 1] = (byte)0x80;
|
||||
}
|
||||
processBlock(buf);
|
||||
A[ 1] = ~A[ 1];
|
||||
A[ 2] = ~A[ 2];
|
||||
A[ 8] = ~A[ 8];
|
||||
A[12] = ~A[12];
|
||||
A[17] = ~A[17];
|
||||
A[20] = ~A[20];
|
||||
int dlen = engineGetDigestLength();
|
||||
for (int i = 0; i < dlen; i += 8)
|
||||
encodeLELong(A[i >>> 3], tmpOut, i);
|
||||
System.arraycopy(tmpOut, 0, out, off, dlen);
|
||||
}
|
||||
|
||||
protected void doInit()
|
||||
{
|
||||
A = new long[25];
|
||||
tmpOut = new byte[(engineGetDigestLength() + 7) & ~7];
|
||||
doReset();
|
||||
}
|
||||
|
||||
public int getBlockLength()
|
||||
{
|
||||
return 200 - 2 * engineGetDigestLength();
|
||||
}
|
||||
|
||||
private final void doReset()
|
||||
{
|
||||
for (int i = 0; i < 25; i ++)
|
||||
A[i] = 0;
|
||||
A[ 1] = 0xFFFFFFFFFFFFFFFFL;
|
||||
A[ 2] = 0xFFFFFFFFFFFFFFFFL;
|
||||
A[ 8] = 0xFFFFFFFFFFFFFFFFL;
|
||||
A[12] = 0xFFFFFFFFFFFFFFFFL;
|
||||
A[17] = 0xFFFFFFFFFFFFFFFFL;
|
||||
A[20] = 0xFFFFFFFFFFFFFFFFL;
|
||||
}
|
||||
|
||||
|
||||
protected Digest copyState(KeccakCore dst)
|
||||
{
|
||||
System.arraycopy(A, 0, dst.A, 0, 25);
|
||||
return super.copyState(dst);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "Keccak-" + (engineGetDigestLength() << 3);
|
||||
}
|
||||
}
|
||||
228
app/src/main/java/com/tangem/wallet/RLP.java
Normal file
228
app/src/main/java/com/tangem/wallet/RLP.java
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 07.01.2018.
|
||||
*/
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.tangem.util.ByteUtil.isNullOrZeroArray;
|
||||
import static com.tangem.util.ByteUtil.isSingleZero;
|
||||
|
||||
public class RLP {
|
||||
public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]);
|
||||
|
||||
/**
|
||||
* Allow for content up to size of 2^64 bytes *
|
||||
*/
|
||||
private static final double MAX_ITEM_LENGTH = Math.pow(256, 8);
|
||||
|
||||
/**
|
||||
* Reason for threshold according to Vitalik Buterin:
|
||||
* - 56 bytes maximizes the benefit of both options
|
||||
* - if we went with 60 then we would have only had 4 slots for long strings
|
||||
* so RLP would not have been able to store objects above 4gb
|
||||
* - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much
|
||||
* - so 56 and 2^64 space seems like the right place to put the cutoff
|
||||
* - also, that's where Bitcoin's varint does the cutof
|
||||
*/
|
||||
private static final int SIZE_THRESHOLD = 56;
|
||||
|
||||
/** RLP encoding rules are defined as follows: */
|
||||
|
||||
/*
|
||||
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
|
||||
* its own RLP encoding.
|
||||
*/
|
||||
|
||||
/**
|
||||
* [0x80]
|
||||
* If a string is 0-55 bytes long, the RLP encoding consists of a single
|
||||
* byte with value 0x80 plus the length of the string followed by the
|
||||
* string. The range of the first byte is thus [0x80, 0xb7].
|
||||
*/
|
||||
private static final int OFFSET_SHORT_ITEM = 0x80;
|
||||
|
||||
/**
|
||||
* [0xb7]
|
||||
* If a string is more than 55 bytes long, the RLP encoding consists of a
|
||||
* single byte with value 0xb7 plus the length of the length of the string
|
||||
* in binary form, followed by the length of the string, followed by the
|
||||
* string. For example, a length-1024 string would be encoded as
|
||||
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
|
||||
* [0xb8, 0xbf].
|
||||
*/
|
||||
private static final int OFFSET_LONG_ITEM = 0xb7;
|
||||
|
||||
/**
|
||||
* [0xc0]
|
||||
* If the total payload of a list (i.e. the combined length of all its
|
||||
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
|
||||
* with value 0xc0 plus the length of the list followed by the concatenation
|
||||
* of the RLP encodings of the items. The range of the first byte is thus
|
||||
* [0xc0, 0xf7].
|
||||
*/
|
||||
private static final int OFFSET_SHORT_LIST = 0xc0;
|
||||
|
||||
public static byte[] encodeByte(byte singleByte) {
|
||||
if ((singleByte & 0xFF) == 0) {
|
||||
return new byte[]{(byte) OFFSET_SHORT_ITEM};
|
||||
} else if ((singleByte & 0xFF) <= 0x7F) {
|
||||
return new byte[]{singleByte};
|
||||
} else {
|
||||
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte};
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeShort(short singleShort) {
|
||||
if ((singleShort & 0xFF) == singleShort)
|
||||
return encodeByte((byte) singleShort);
|
||||
else {
|
||||
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2),
|
||||
(byte) (singleShort >> 8 & 0xFF),
|
||||
(byte) (singleShort >> 0 & 0xFF)};
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeInt(int singleInt) {
|
||||
if ((singleInt & 0xFF) == singleInt)
|
||||
return encodeByte((byte) singleInt);
|
||||
else if ((singleInt & 0xFFFF) == singleInt)
|
||||
return encodeShort((short) singleInt);
|
||||
else if ((singleInt & 0xFFFFFF) == singleInt)
|
||||
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3),
|
||||
(byte) (singleInt >>> 16),
|
||||
(byte) (singleInt >>> 8),
|
||||
(byte) singleInt};
|
||||
else {
|
||||
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4),
|
||||
(byte) (singleInt >>> 24),
|
||||
(byte) (singleInt >>> 16),
|
||||
(byte) (singleInt >>> 8),
|
||||
(byte) singleInt};
|
||||
}
|
||||
}
|
||||
|
||||
private static final int OFFSET_LONG_LIST = 0xf7;
|
||||
|
||||
public static byte[] encodeElement2(byte[] srcData) {
|
||||
if (srcData == null)
|
||||
return new byte[]{(byte) OFFSET_SHORT_ITEM};
|
||||
else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) {
|
||||
return srcData;
|
||||
} else if (srcData.length < SIZE_THRESHOLD) {
|
||||
// length = 8X
|
||||
byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length);
|
||||
byte[] data = Arrays.copyOf(srcData, srcData.length + 1);
|
||||
System.arraycopy(data, 0, data, 1, srcData.length);
|
||||
data[0] = length;
|
||||
|
||||
return data;
|
||||
} else {
|
||||
// length of length = BX
|
||||
// prefix = [BX, [length]]
|
||||
int tmpLength = srcData.length;
|
||||
byte byteNum = 0;
|
||||
while (tmpLength != 0) {
|
||||
++byteNum;
|
||||
tmpLength = tmpLength >> 8;
|
||||
}
|
||||
byte[] lenBytes = new byte[byteNum];
|
||||
for (int i = 0; i < byteNum; ++i) {
|
||||
lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF);
|
||||
}
|
||||
// first byte = F7 + bytes.length
|
||||
byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum);
|
||||
System.arraycopy(data, 0, data, 1 + byteNum, srcData.length);
|
||||
data[0] = (byte) (OFFSET_LONG_ITEM + byteNum);
|
||||
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
public static byte[] encodeElement(byte[] srcData) {
|
||||
|
||||
if (isNullOrZeroArray(srcData))
|
||||
return new byte[]{(byte) OFFSET_SHORT_ITEM};
|
||||
else if (isSingleZero(srcData))
|
||||
return srcData;
|
||||
else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) {
|
||||
return srcData;
|
||||
} else if (srcData.length < SIZE_THRESHOLD) {
|
||||
// length = 8X
|
||||
byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length);
|
||||
byte[] data = Arrays.copyOf(srcData, srcData.length + 1);
|
||||
System.arraycopy(data, 0, data, 1, srcData.length);
|
||||
data[0] = length;
|
||||
|
||||
return data;
|
||||
} else {
|
||||
// length of length = BX
|
||||
// prefix = [BX, [length]]
|
||||
int tmpLength = srcData.length;
|
||||
byte byteNum = 0;
|
||||
while (tmpLength != 0) {
|
||||
++byteNum;
|
||||
tmpLength = tmpLength >> 8;
|
||||
}
|
||||
byte[] lenBytes = new byte[byteNum];
|
||||
for (int i = 0; i < byteNum; ++i) {
|
||||
lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF);
|
||||
}
|
||||
// first byte = F7 + bytes.length
|
||||
byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum);
|
||||
System.arraycopy(data, 0, data, 1 + byteNum, srcData.length);
|
||||
data[0] = (byte) (OFFSET_LONG_ITEM + byteNum);
|
||||
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte[] encodeList(byte[]... elements) {
|
||||
|
||||
if (elements == null) {
|
||||
return new byte[]{(byte) OFFSET_SHORT_LIST};
|
||||
}
|
||||
|
||||
int totalLength = 0;
|
||||
for (byte[] element1 : elements) {
|
||||
totalLength += element1.length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
int copyPos;
|
||||
if (totalLength < SIZE_THRESHOLD) {
|
||||
|
||||
data = new byte[1 + totalLength];
|
||||
data[0] = (byte) (OFFSET_SHORT_LIST + totalLength);
|
||||
copyPos = 1;
|
||||
} else {
|
||||
// length of length = BX
|
||||
// prefix = [BX, [length]]
|
||||
int tmpLength = totalLength;
|
||||
byte byteNum = 0;
|
||||
while (tmpLength != 0) {
|
||||
++byteNum;
|
||||
tmpLength = tmpLength >> 8;
|
||||
}
|
||||
tmpLength = totalLength;
|
||||
byte[] lenBytes = new byte[byteNum];
|
||||
for (int i = 0; i < byteNum; ++i) {
|
||||
lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF);
|
||||
}
|
||||
// first byte = F7 + bytes.length
|
||||
data = new byte[1 + lenBytes.length + totalLength];
|
||||
data[0] = (byte) (OFFSET_LONG_LIST + byteNum);
|
||||
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
|
||||
|
||||
copyPos = lenBytes.length + 1;
|
||||
}
|
||||
for (byte[] element : elements) {
|
||||
System.arraycopy(element, 0, data, copyPos, element.length);
|
||||
copyPos += element.length;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/tangem/wallet/RLPElement.java
Normal file
12
app/src/main/java/com/tangem/wallet/RLPElement.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 07.01.2018.
|
||||
*/
|
||||
|
||||
public interface RLPElement extends Serializable {
|
||||
|
||||
byte[] getRLPData();
|
||||
}
|
||||
37
app/src/main/java/com/tangem/wallet/RLPList.java
Normal file
37
app/src/main/java/com/tangem/wallet/RLPList.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 07.01.2018.
|
||||
*/
|
||||
|
||||
public class RLPList extends ArrayList<RLPElement> implements RLPElement {
|
||||
|
||||
byte[] rlpData;
|
||||
|
||||
public void setRLPData(byte[] rlpData) {
|
||||
this.rlpData = rlpData;
|
||||
}
|
||||
|
||||
public byte[] getRLPData() {
|
||||
return rlpData;
|
||||
}
|
||||
|
||||
public static void recursivePrint(RLPElement element) {
|
||||
|
||||
if (element == null)
|
||||
throw new RuntimeException("RLPElement object can't be null");
|
||||
if (element instanceof RLPList) {
|
||||
|
||||
RLPList rlpList = (RLPList) element;
|
||||
System.out.print("[");
|
||||
for (RLPElement singleElement : rlpList)
|
||||
recursivePrint(singleElement);
|
||||
System.out.print("]");
|
||||
} else {
|
||||
String hex = BTCUtils.toHex(element.getRLPData());
|
||||
System.out.print(hex + ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
186
app/src/main/java/com/tangem/wallet/TangemContext.java
Normal file
186
app/src/main/java/com/tangem/wallet/TangemContext.java
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.tangem.Constant;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.card_android.data.TangemCardExtensionsKt;
|
||||
import com.tangem.card_common.data.TangemCard;
|
||||
|
||||
public class TangemContext {
|
||||
// public static final String EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA";
|
||||
private Context context;
|
||||
private TangemCard card;
|
||||
private CoinData coinData;
|
||||
private String error;
|
||||
private String message;
|
||||
|
||||
public TangemContext() {
|
||||
|
||||
}
|
||||
|
||||
public TangemContext(TangemCard card) {
|
||||
|
||||
this.card = card;
|
||||
}
|
||||
|
||||
public Blockchain getBlockchain() {
|
||||
if (card == null) return Blockchain.Unknown;
|
||||
Blockchain blockchain = Blockchain.fromId(card.getBlockchainID());
|
||||
if ((blockchain == Blockchain.Ethereum || blockchain == Blockchain.EthereumTestNet) && card.isToken()) {
|
||||
if (card.getTokenSymbol().startsWith("NFT:")) {
|
||||
return Blockchain.NftToken;
|
||||
} else {
|
||||
return Blockchain.Token;
|
||||
}
|
||||
}
|
||||
if ((blockchain == Blockchain.Rootstock) && card.isToken()) {
|
||||
return Blockchain.RootstockToken;
|
||||
}
|
||||
return blockchain;
|
||||
}
|
||||
|
||||
// public void setBlockchain(Blockchain blockchain) {
|
||||
// if (card == null) return;
|
||||
// card.setBlockchainID(blockchain.getID());
|
||||
// }
|
||||
|
||||
// private String blockchainName = "";
|
||||
|
||||
public String getBlockchainName() {
|
||||
Blockchain blockchain = getBlockchain();
|
||||
if (blockchain == Blockchain.Token || blockchain == Blockchain.RootstockToken) {
|
||||
String token = card.getTokenSymbol();
|
||||
return token + " <br><small><small> " + getBlockchain().getOfficialName() + " smart contract token</small></small>";
|
||||
}
|
||||
if (blockchain == Blockchain.NftToken) {
|
||||
return card.getTokenSymbol().substring(4) + " <br><small><small> " + getBlockchain().getOfficialName() + " NFT token</small></small>";
|
||||
}
|
||||
return blockchain.getOfficialName();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TangemCard getCard() {
|
||||
return card;
|
||||
}
|
||||
|
||||
public void setCard(TangemCard card) {
|
||||
this.card = card;
|
||||
}
|
||||
|
||||
public CoinData getCoinData() {
|
||||
return coinData;
|
||||
}
|
||||
|
||||
public void setCoinData(CoinData coinData) {
|
||||
this.coinData = coinData;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public void setError(int valueId) {
|
||||
this.error = getString(valueId);
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return error != null && !error.isEmpty();
|
||||
}
|
||||
|
||||
public void setMessage(String value) {
|
||||
this.message = value;
|
||||
}
|
||||
|
||||
public void setMessage(int valueId) {
|
||||
this.message = getString(valueId);
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
public static TangemContext loadFromBundle(Context context, Bundle bundle) {
|
||||
TangemContext tangemContext = new TangemContext();
|
||||
tangemContext.setContext(context);
|
||||
|
||||
if (bundle.containsKey(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID)) {
|
||||
tangemContext.card = new TangemCard(bundle.getString(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID));
|
||||
TangemCardExtensionsKt.loadFromBundle(tangemContext.card, bundle.getBundle(TangemCardExtensionsKt.EXTRA_TANGEM_CARD));
|
||||
}
|
||||
|
||||
if (tangemContext.getBlockchain() != null) {
|
||||
if (bundle.containsKey(Constant.EXTRA_BLOCKCHAIN_DATA)) {
|
||||
tangemContext.coinData = CoinData.fromBundle(tangemContext.getBlockchain(), bundle.getBundle(Constant.EXTRA_BLOCKCHAIN_DATA));
|
||||
} else {
|
||||
tangemContext.coinData = CoinEngineFactory.INSTANCE.create(tangemContext).createCoinData();
|
||||
}
|
||||
}
|
||||
tangemContext.error = bundle.getString("Error");
|
||||
tangemContext.message = bundle.getString("Message");
|
||||
|
||||
return tangemContext;
|
||||
}
|
||||
|
||||
public void saveToBundle(Bundle intent) {
|
||||
|
||||
if (card != null) {
|
||||
intent.putString(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID, card.getUID());
|
||||
intent.putBundle(TangemCardExtensionsKt.EXTRA_TANGEM_CARD, TangemCardExtensionsKt.getAsBundle(card));
|
||||
}
|
||||
|
||||
if (coinData != null) {
|
||||
intent.putBundle(Constant.EXTRA_BLOCKCHAIN_DATA, coinData.asBundle());
|
||||
}
|
||||
|
||||
intent.putString("Error", error);
|
||||
intent.putString("Message", message);
|
||||
}
|
||||
|
||||
public void saveToIntent(Intent intent) {
|
||||
|
||||
if (card != null) {
|
||||
intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID, card.getUID());
|
||||
intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD, TangemCardExtensionsKt.getAsBundle(card));
|
||||
}
|
||||
|
||||
if (coinData != null) {
|
||||
intent.putExtra(Constant.EXTRA_BLOCKCHAIN_DATA, coinData.asBundle());
|
||||
}
|
||||
|
||||
intent.putExtra("Error", error);
|
||||
intent.putExtra("Message", message);
|
||||
}
|
||||
|
||||
public String getString(int stringId) {
|
||||
if (context != null) return getContext().getResources().getString(stringId);
|
||||
return "context.resources.string[" + stringId + "]";
|
||||
}
|
||||
|
||||
public void setDenomination(byte[] denomination) {
|
||||
try {
|
||||
CoinEngine engine = CoinEngineFactory.INSTANCE.create(getBlockchain());
|
||||
CoinEngine.InternalAmount internalAmount = engine.convertToInternalAmount(denomination);
|
||||
CoinEngine.Amount amount = engine.convertToAmount(internalAmount);
|
||||
card.setDenomination(denomination, amount.toString());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
card.setDenomination(denomination, "N/A");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
635
app/src/main/java/com/tangem/wallet/Transaction.java
Normal file
635
app/src/main/java/com/tangem/wallet/Transaction.java
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 29.09.2017.
|
||||
*/
|
||||
|
||||
import com.tangem.wallet.btc.BitcoinException;
|
||||
import com.tangem.wallet.btc.BitcoinInputStream;
|
||||
import com.tangem.wallet.btc.BitcoinOutputStream;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Stack;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class Transaction {
|
||||
public final int version;
|
||||
public final Input[] inputs;
|
||||
public final Output[] outputs;
|
||||
public final int lockTime;
|
||||
|
||||
public Transaction(byte[] rawBytes) throws BitcoinException {
|
||||
if (rawBytes == null) {
|
||||
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "empty input");
|
||||
}
|
||||
BitcoinInputStream bais = null;
|
||||
try {
|
||||
bais = new BitcoinInputStream(rawBytes);
|
||||
version = bais.readInt32();
|
||||
if (version != 1 && version != 2 && version != 3 && version != -1273714314) {
|
||||
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unsupported TX version", version);
|
||||
}
|
||||
|
||||
|
||||
int inputsCount = 0;
|
||||
int first = bais.readByte();
|
||||
if(first == 0)
|
||||
{
|
||||
int skip = bais.readByte();
|
||||
inputsCount = bais.readByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
inputsCount = first;
|
||||
}
|
||||
//int inputsCount = (int) bais.readVarInt(); TODO:
|
||||
inputs = new Input[inputsCount];
|
||||
for (int i = 0; i < inputsCount; i++) {
|
||||
OutPoint outPoint = new OutPoint(BTCUtils.reverse(bais.readChars(32)), bais.readInt32());
|
||||
byte[] script = bais.readChars((int) bais.readVarInt());
|
||||
int sequence = bais.readInt32();
|
||||
inputs[i] = new Input(outPoint, new Script(script), sequence);
|
||||
}
|
||||
int outputsCount = (int) bais.readVarInt();
|
||||
outputs = new Output[outputsCount];
|
||||
for (int i = 0; i < outputsCount; i++) {
|
||||
long value = bais.readInt64();
|
||||
long scriptSize = bais.readVarInt();
|
||||
if (scriptSize < 0 || scriptSize > 10_000_000) {
|
||||
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Script size for output " + i +
|
||||
" is strange (" + scriptSize + " bytes).");
|
||||
}
|
||||
byte[] script = bais.readChars((int) scriptSize);
|
||||
outputs[i] = new Output(value, new Script(script));
|
||||
}
|
||||
lockTime = bais.readInt32();
|
||||
} catch (EOFException e) {
|
||||
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "TX incomplete");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Unable to read TX");
|
||||
} catch (Error e) {
|
||||
throw new IllegalArgumentException("Unable to read TX: " + e);
|
||||
} finally {
|
||||
if (bais != null) {
|
||||
try {
|
||||
bais.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction(Input[] inputs, Output[] outputs, int lockTime) {
|
||||
this.version = 1;
|
||||
this.inputs = inputs;
|
||||
this.outputs = outputs;
|
||||
this.lockTime = lockTime;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
BitcoinOutputStream baos = new BitcoinOutputStream();
|
||||
try {
|
||||
baos.writeInt32(version);
|
||||
baos.writeVarInt(inputs.length);
|
||||
for (Input input : inputs) {
|
||||
baos.write(BTCUtils.reverse(input.outPoint.hash));
|
||||
baos.writeInt32(input.outPoint.index);
|
||||
int scriptLen = input.script == null ? 0 : input.script.bytes.length;
|
||||
baos.writeVarInt(scriptLen);
|
||||
if (scriptLen > 0) {
|
||||
baos.write(input.script.bytes);
|
||||
}
|
||||
baos.writeInt32(input.sequence);
|
||||
}
|
||||
baos.writeVarInt(outputs.length);
|
||||
for (Output output : outputs) {
|
||||
baos.writeInt64(output.value);
|
||||
int scriptLen = output.script == null ? 0 : output.script.bytes.length;
|
||||
baos.writeVarInt(scriptLen);
|
||||
if (scriptLen > 0) {
|
||||
baos.write(output.script.bytes);
|
||||
}
|
||||
}
|
||||
baos.writeInt32(lockTime);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
baos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"\n\"inputs\":\n" + printAsJsonArray(inputs) +
|
||||
",\n\"outputs\":\n" + printAsJsonArray(outputs) +
|
||||
",\n\"lockTime\":\"" + lockTime + "\"}\n";
|
||||
}
|
||||
|
||||
private String printAsJsonArray(Object[] a) {
|
||||
if (a == null) {
|
||||
return "null";
|
||||
}
|
||||
if (a.length == 0) {
|
||||
return "[]";
|
||||
}
|
||||
int iMax = a.length - 1;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
for (int i = 0; ; i++) {
|
||||
sb.append(String.valueOf(a[i]));
|
||||
if (i == iMax)
|
||||
return sb.append(']').toString();
|
||||
sb.append(",\n");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Input {
|
||||
public final OutPoint outPoint;
|
||||
public final Script script;
|
||||
public final int sequence;
|
||||
|
||||
public Input(OutPoint outPoint, Script script, int sequence) {
|
||||
this.outPoint = outPoint;
|
||||
this.script = script;
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{\n\"outPoint\":" + outPoint + ",\n\"script\":\"" + script + "\",\n\"sequence\":\"" + Integer.toHexString(sequence) + "\"\n}\n";
|
||||
}
|
||||
}
|
||||
|
||||
public static class OutPoint {
|
||||
public final byte[] hash;//32-byte hash of the transaction from which we want to redeem an output
|
||||
public final int index;//Four-byte field denoting the output index we want to redeem from the transaction with the above hash (output number 2 = output index 1)
|
||||
|
||||
public OutPoint(byte[] hash, int index) {
|
||||
this.hash = hash;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + "\"hash\":\"" + BTCUtils.toHex(hash) + "\", \"index\":\"" + index + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Output {
|
||||
public final long value;
|
||||
public final Script script;
|
||||
|
||||
public Output(long value, Script script) {
|
||||
this.value = value;
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{\n\"value\":\"" + value * 1e-8 + "\",\"script\":\"" + script + "\"\n}";
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Script {
|
||||
|
||||
public static class ScriptInvalidException extends Exception {
|
||||
public ScriptInvalidException() {
|
||||
}
|
||||
|
||||
public ScriptInvalidException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static final byte OP_FALSE = 0;
|
||||
public static final byte OP_TRUE = 0x51;
|
||||
public static final byte OP_PUSHDATA1 = 0x4c;
|
||||
public static final byte OP_PUSHDATA2 = 0x4d;
|
||||
public static final byte OP_PUSHDATA4 = 0x4e;
|
||||
public static final byte OP_DUP = 0x76;//Duplicates the top stack item.
|
||||
public static final byte OP_DROP = 0x75;
|
||||
public static final byte OP_HASH160 = (byte) 0xA9;//The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
|
||||
public static final byte OP_VERIFY = 0x69;//Marks transaction as invalid if top stack value is not true. True is removed, but false is not.
|
||||
public static final byte OP_EQUAL = (byte) 0x87;//Returns 1 if the inputs are exactly equal, 0 otherwise.
|
||||
public static final byte OP_EQUALVERIFY = (byte) 0x88;//Same as OP_EQUAL, but runs OP_VERIFY afterward.
|
||||
public static final byte OP_CHECKSIG = (byte) 0xAC;//The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise.
|
||||
public static final byte OP_CHECKSIGVERIFY = (byte) 0xAD;
|
||||
public static final byte OP_NOP = 0x61;
|
||||
|
||||
public static final byte SIGHASH_ALL = 1;
|
||||
|
||||
public final byte[] bytes;
|
||||
|
||||
public Script(byte[] rawBytes) {
|
||||
bytes = rawBytes;
|
||||
}
|
||||
|
||||
public Script(byte[] data1, byte[] data2) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(data1.length + data2.length + 2);
|
||||
try {
|
||||
writeBytes(data1, baos);
|
||||
writeBytes(data2, baos);
|
||||
baos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
bytes = baos.toByteArray();
|
||||
}
|
||||
|
||||
private static void writeBytes(byte[] data, ByteArrayOutputStream baos) throws IOException {
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
baos.write(data.length);
|
||||
} else if (data.length < 0xff) {
|
||||
baos.write(OP_PUSHDATA1);
|
||||
baos.write(data.length);
|
||||
} else if (data.length < 0xffff) {
|
||||
baos.write(OP_PUSHDATA2);
|
||||
baos.write(data.length & 0xff);
|
||||
baos.write((data.length >> 8) & 0xff);
|
||||
} else {
|
||||
baos.write(OP_PUSHDATA4);
|
||||
baos.write(data.length & 0xff);
|
||||
baos.write((data.length >> 8) & 0xff);
|
||||
baos.write((data.length >> 16) & 0xff);
|
||||
baos.write((data.length >>> 24) & 0xff);
|
||||
}
|
||||
baos.write(data);
|
||||
}
|
||||
|
||||
public void run(Stack<byte[]> stack) throws ScriptInvalidException {
|
||||
run(0, null, stack);
|
||||
}
|
||||
|
||||
public void run(int inputIndex, Transaction tx, Stack<byte[]> stack) throws ScriptInvalidException {
|
||||
for (int pos = 0; pos < bytes.length; pos++) {
|
||||
switch (bytes[pos]) {
|
||||
case OP_NOP:
|
||||
break;
|
||||
case OP_DROP:
|
||||
if (stack.isEmpty()) {
|
||||
throw new IllegalArgumentException("stack empty on OP_DROP");
|
||||
}
|
||||
stack.pop();
|
||||
break;
|
||||
case OP_DUP:
|
||||
if (stack.isEmpty()) {
|
||||
throw new IllegalArgumentException("stack empty on OP_DUP");
|
||||
}
|
||||
stack.push(stack.peek());
|
||||
break;
|
||||
case OP_HASH160:
|
||||
if (stack.isEmpty()) {
|
||||
throw new IllegalArgumentException("stack empty on OP_HASH160");
|
||||
}
|
||||
stack.push(CryptoUtil.sha256ripemd160(stack.pop()));
|
||||
break;
|
||||
case OP_EQUAL:
|
||||
case OP_EQUALVERIFY:
|
||||
if (stack.size() < 2) {
|
||||
throw new IllegalArgumentException("not enough elements to perform OP_EQUAL");
|
||||
}
|
||||
stack.push(new byte[]{(byte) (Arrays.equals(stack.pop(), stack.pop()) ? 1 : 0)});
|
||||
if (bytes[pos] == OP_EQUALVERIFY) {
|
||||
if (verifyFails(stack)) {
|
||||
throw new ScriptInvalidException("wrong address");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_VERIFY:
|
||||
if (verifyFails(stack)) {
|
||||
throw new ScriptInvalidException();
|
||||
}
|
||||
break;
|
||||
case OP_CHECKSIG:
|
||||
case OP_CHECKSIGVERIFY:
|
||||
byte[] publicKey = stack.pop();
|
||||
byte[] signatureAndHashType = stack.pop();
|
||||
if (signatureAndHashType[signatureAndHashType.length - 1] != SIGHASH_ALL) {
|
||||
throw new IllegalArgumentException("I cannot check this sig type: " + signatureAndHashType[signatureAndHashType.length - 1]);
|
||||
}
|
||||
byte[] signature = new byte[signatureAndHashType.length - 1];
|
||||
System.arraycopy(signatureAndHashType, 0, signature, 0, signature.length);
|
||||
byte[] hash = hashTransaction(inputIndex, bytes, tx);
|
||||
//boolean valid = BTCUtils.verify(publicKey, signature, hash);
|
||||
if (bytes[pos] == OP_CHECKSIG) {
|
||||
stack.push(new byte[]{(byte) (1)});
|
||||
} else {
|
||||
if (verifyFails(stack)) {
|
||||
throw new ScriptInvalidException("Bad signature");
|
||||
}
|
||||
if (!stack.empty()) {
|
||||
throw new ScriptInvalidException("Bad signature - superfluous scriptSig operations");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_FALSE:
|
||||
stack.push(new byte[]{0});
|
||||
break;
|
||||
case OP_TRUE:
|
||||
stack.push(new byte[]{1});
|
||||
break;
|
||||
default:
|
||||
int op = bytes[pos] & 0xff;
|
||||
int len;
|
||||
if (op < OP_PUSHDATA1) {
|
||||
len = op;
|
||||
byte[] data = new byte[len];
|
||||
System.arraycopy(bytes, pos + 1, data, 0, len);
|
||||
stack.push(data);
|
||||
pos += data.length;
|
||||
} else if (op == OP_PUSHDATA1) {
|
||||
len = bytes[pos + 1] & 0xff;
|
||||
byte[] data = new byte[len];
|
||||
System.arraycopy(bytes, pos + 1, data, 0, len);
|
||||
stack.push(data);
|
||||
pos += 1 + data.length;
|
||||
} else {
|
||||
throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] hashTransaction(int inputIndex, byte[] subscript, Transaction tx) {
|
||||
Input[] unsignedInputs = new Input[tx.inputs.length];
|
||||
for (int i = 0; i < tx.inputs.length; i++) {
|
||||
Input txInput = tx.inputs[i];
|
||||
if (i == inputIndex) {
|
||||
unsignedInputs[i] = new Input(txInput.outPoint, new Script(subscript), txInput.sequence);
|
||||
} else {
|
||||
unsignedInputs[i] = new Input(txInput.outPoint, new Script(new byte[0]), txInput.sequence);
|
||||
}
|
||||
}
|
||||
Transaction unsignedTransaction = new Transaction(unsignedInputs, tx.outputs, tx.lockTime);
|
||||
return hashTransactionForSigning(unsignedTransaction);
|
||||
}
|
||||
|
||||
public static byte[] hashTransactionForSigning(Transaction unsignedTransaction) {
|
||||
byte[] txUnsignedBytes = unsignedTransaction.getBytes();
|
||||
BitcoinOutputStream baos = new BitcoinOutputStream();
|
||||
try {
|
||||
baos.write(txUnsignedBytes);
|
||||
baos.writeInt32(Script.SIGHASH_ALL);
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return CryptoUtil.doubleSha256(baos.toByteArray());
|
||||
}
|
||||
|
||||
public static boolean verifyFails(Stack<byte[]> stack) {
|
||||
byte[] input;
|
||||
boolean valid;
|
||||
input = stack.pop();
|
||||
if (input.length == 0 || (input.length == 1 && input[0] == OP_FALSE)) {
|
||||
//false
|
||||
stack.push(new byte[]{OP_FALSE});
|
||||
valid = false;
|
||||
} else {
|
||||
//true
|
||||
valid = true;
|
||||
}
|
||||
return !valid;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return convertBytesToReadableString(bytes);
|
||||
}
|
||||
|
||||
//converts something like "OP_DUP OP_HASH160 ba507bae8f1643d2556000ca26b9301b9069dc6b OP_EQUALVERIFY OP_CHECKSIG" into bytes
|
||||
public static byte[] convertReadableStringToBytes(String readableString) {
|
||||
String[] tokens = readableString.trim().split("\\s+");
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (String token : tokens) {
|
||||
switch (token) {
|
||||
case "OP_NOP":
|
||||
os.write(OP_NOP);
|
||||
break;
|
||||
case "OP_DROP":
|
||||
os.write(OP_DROP);
|
||||
break;
|
||||
case "OP_DUP":
|
||||
os.write(OP_DUP);
|
||||
break;
|
||||
case "OP_HASH160":
|
||||
os.write(OP_HASH160);
|
||||
break;
|
||||
case "OP_EQUAL":
|
||||
os.write(OP_EQUAL);
|
||||
break;
|
||||
case "OP_EQUALVERIFY":
|
||||
os.write(OP_EQUALVERIFY);
|
||||
break;
|
||||
case "OP_VERIFY":
|
||||
os.write(OP_VERIFY);
|
||||
break;
|
||||
case "OP_CHECKSIG":
|
||||
os.write(OP_CHECKSIG);
|
||||
break;
|
||||
case "OP_CHECKSIGVERIFY":
|
||||
os.write(OP_CHECKSIGVERIFY);
|
||||
break;
|
||||
case "OP_FALSE":
|
||||
os.write(OP_FALSE);
|
||||
break;
|
||||
case "OP_TRUE":
|
||||
os.write(OP_TRUE);
|
||||
break;
|
||||
default:
|
||||
if (token.startsWith("OP_")) {
|
||||
throw new IllegalArgumentException("I don't know this operation: " + token);
|
||||
}
|
||||
byte[] data = BTCUtils.fromHex(token);
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("I don't know what's this: " + token);
|
||||
}
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
os.write(data.length);
|
||||
try {
|
||||
os.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e);
|
||||
}
|
||||
} else if (data.length <= 255) {
|
||||
os.write(OP_PUSHDATA1);
|
||||
os.write(data.length);
|
||||
try {
|
||||
os.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("OP_PUSHDATA2 & OP_PUSHDATA4 are not supported");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public static String convertBytesToReadableString(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int pos = 0; pos < bytes.length; pos++) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
switch (bytes[pos]) {
|
||||
case OP_NOP:
|
||||
sb.append("OP_NOP");
|
||||
break;
|
||||
case OP_DROP:
|
||||
sb.append("OP_DROP");
|
||||
break;
|
||||
case OP_DUP:
|
||||
sb.append("OP_DUP");
|
||||
break;
|
||||
case OP_HASH160:
|
||||
sb.append("OP_HASH160");
|
||||
break;
|
||||
case OP_EQUAL:
|
||||
sb.append("OP_EQUAL");
|
||||
break;
|
||||
case OP_EQUALVERIFY:
|
||||
sb.append("OP_EQUALVERIFY");
|
||||
break;
|
||||
case OP_VERIFY:
|
||||
sb.append("OP_VERIFY");
|
||||
break;
|
||||
case OP_CHECKSIG:
|
||||
sb.append("OP_CHECKSIG");
|
||||
break;
|
||||
case OP_CHECKSIGVERIFY:
|
||||
sb.append("OP_CHECKSIGVERIFY");
|
||||
break;
|
||||
case OP_FALSE:
|
||||
sb.append("OP_FALSE");
|
||||
break;
|
||||
case OP_TRUE:
|
||||
sb.append("OP_TRUE");
|
||||
break;
|
||||
default:
|
||||
int op = bytes[pos] & 0xff;
|
||||
int len;
|
||||
if (op < OP_PUSHDATA1) {
|
||||
len = op;
|
||||
byte[] data = new byte[len];
|
||||
System.arraycopy(bytes, pos + 1, data, 0, len);
|
||||
sb.append(BTCUtils.toHex(data));
|
||||
pos += data.length;
|
||||
} else if (op == OP_PUSHDATA1) {
|
||||
len = bytes[pos + 1] & 0xff;
|
||||
byte[] data = new byte[len];
|
||||
System.arraycopy(bytes, pos + 1, data, 0, len);//FIXME I suspect there is off by one error...
|
||||
sb.append(BTCUtils.toHex(data));
|
||||
pos += 1 + data.length;
|
||||
} else {
|
||||
throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]) + " at " + pos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || !(o == null || getClass() != o.getClass()) && Arrays.equals(bytes, ((Script) o).bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(bytes);
|
||||
}
|
||||
|
||||
public static Script buildOutput(String address) throws BitcoinException {
|
||||
//noinspection TryWithIdenticalCatches
|
||||
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
|
||||
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48) { //0 for BTC/BCH 1 address | 48 for LTC L address
|
||||
return buildOutputP2H(address);
|
||||
}
|
||||
|
||||
if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4 || addressWithCheckSumAndNetworkCode[0] == 50) { //5 for BTC/BCH/LTC 3 address | 50 for LTC M address
|
||||
return buildOutputP2SH(address);
|
||||
}
|
||||
|
||||
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
|
||||
}
|
||||
public static Script buildOutputP2SH(String address) throws BitcoinException {
|
||||
try {
|
||||
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
|
||||
if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4 && addressWithCheckSumAndNetworkCode[0] != 50) {
|
||||
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
|
||||
}
|
||||
|
||||
byte[] bareAddress = new byte[20];
|
||||
System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length);
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(23);
|
||||
buf.write(OP_HASH160);
|
||||
writeBytes(bareAddress, buf);
|
||||
buf.write(OP_EQUAL);
|
||||
return new Script(buf.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Script buildOutputP2H(String address) throws BitcoinException {
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
|
||||
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48) {
|
||||
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
|
||||
}
|
||||
|
||||
byte[] bareAddress = new byte[20];
|
||||
System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length);
|
||||
|
||||
MessageDigest digestSha = MessageDigest.getInstance("SHA-256");
|
||||
digestSha.update(addressWithCheckSumAndNetworkCode, 0, addressWithCheckSumAndNetworkCode.length - 4);
|
||||
|
||||
byte[] calculatedDigest = digestSha.digest(digestSha.digest());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (calculatedDigest[i] != addressWithCheckSumAndNetworkCode[addressWithCheckSumAndNetworkCode.length - 4 + i]) {
|
||||
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Bad address", address);
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(25);
|
||||
buf.write(OP_DUP);
|
||||
buf.write(OP_HASH160);
|
||||
writeBytes(bareAddress, buf);
|
||||
buf.write(OP_EQUALVERIFY);
|
||||
buf.write(OP_CHECKSIG);
|
||||
return new Script(buf.toByteArray());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java
Normal file
26
app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.wallet;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 29.09.2017.
|
||||
*/
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class UnspentOutputInfo {
|
||||
public final byte[] txHash;
|
||||
public final Transaction.Script script;
|
||||
public final long value;
|
||||
public final int outputIndex;
|
||||
public final long confirmations;
|
||||
public String txHashForBuild;
|
||||
public byte[] scriptForBuild;
|
||||
|
||||
public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) {
|
||||
this.txHash = txHash;
|
||||
this.script = script;
|
||||
this.value = value;
|
||||
this.outputIndex = outputIndex;
|
||||
this.confirmations = confirmations;
|
||||
this.txHashForBuild = hashForBuild;
|
||||
this.scriptForBuild = sign;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet.bch;
|
||||
|
||||
// Helper class for CashAddr
|
||||
|
||||
public class BitcoinCashAddressDecodedParts {
|
||||
|
||||
String prefix;
|
||||
|
||||
BitcoinCashAddressType addressType;
|
||||
|
||||
byte[] hash;
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public BitcoinCashAddressType getAddressType() {
|
||||
return addressType;
|
||||
}
|
||||
|
||||
public void setAddressType(BitcoinCashAddressType addressType) {
|
||||
this.addressType = addressType;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void setHash(byte[] hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.wallet.bch;
|
||||
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018 Tobias Brandt
|
||||
*
|
||||
* Distributed under the MIT software license, see the accompanying file LICENSE
|
||||
* or http://www.opensource.org/licenses/mit-license.php.
|
||||
*/
|
||||
public enum BitcoinCashAddressType {
|
||||
|
||||
P2PKH((byte) 0), P2SH((byte) 8);
|
||||
|
||||
private final byte versionByte;
|
||||
|
||||
BitcoinCashAddressType(byte versionByte) {
|
||||
this.versionByte = versionByte;
|
||||
}
|
||||
|
||||
public byte getVersionByte() {
|
||||
return versionByte;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.wallet.bch;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018 Tobias Brandt
|
||||
*
|
||||
* Distributed under the MIT software license, see the accompanying file LICENSE
|
||||
* or http://www.opensource.org/licenses/mit-license.php.
|
||||
*/
|
||||
public class BitcoinCashBase32 {
|
||||
|
||||
public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
||||
private static final char[] CHARS = CHARSET.toCharArray();
|
||||
|
||||
private static Map<Character, Integer> charPositionMap;
|
||||
static {
|
||||
charPositionMap = new HashMap<>();
|
||||
for (int i = 0; i < CHARS.length; i++) {
|
||||
charPositionMap.put(CHARS[i], i);
|
||||
}
|
||||
if (charPositionMap.size() != 32) {
|
||||
throw new RuntimeException("The charset must contain 32 unique characters.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a byte array as base32 string. This method assumes that all bytes
|
||||
* are only from 0-31
|
||||
*
|
||||
* @param byteArray
|
||||
* @return
|
||||
*/
|
||||
public static String encode(byte[] byteArray) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < byteArray.length; i++) {
|
||||
int val = (int) byteArray[i];
|
||||
|
||||
if (val < 0 || val > 31) {
|
||||
throw new RuntimeException("This method assumes that all bytes are only from 0-31. Was: " + val);
|
||||
}
|
||||
|
||||
sb.append(CHARS[val]);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base32 string back to the byte array representation
|
||||
*
|
||||
* @param base32String
|
||||
* @return
|
||||
*/
|
||||
public static byte[] decode(String base32String) {
|
||||
byte[] bytes = new byte[base32String.length()];
|
||||
|
||||
char[] charArray = base32String.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i++) {
|
||||
Integer position = charPositionMap.get(charArray[i]);
|
||||
if (position == null) {
|
||||
throw new RuntimeException("There seems to be an invalid char: " + charArray[i]);
|
||||
}
|
||||
bytes[i] = (byte) ((int) position);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.wallet.bch;
|
||||
/**
|
||||
* Copyright (c) 2018 Tobias Brandt
|
||||
*
|
||||
* Copyright (c) 2017 Pieter Wuille
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
public class BitcoinCashBitArrayConverter {
|
||||
|
||||
public static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) {
|
||||
int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to)
|
||||
: Math.ceil((double) bytes8Bits.length * from / to));
|
||||
int mask = ((1 << to) - 1) & 0xff;
|
||||
byte[] result = new byte[length];
|
||||
int index = 0;
|
||||
int accumulator = 0;
|
||||
int bits = 0;
|
||||
for (int i = 0; i < bytes8Bits.length; i++) {
|
||||
byte value = bytes8Bits[i];
|
||||
accumulator = (((accumulator & 0xff) << from) | (value & 0xff));
|
||||
bits += from;
|
||||
while (bits >= to) {
|
||||
bits -= to;
|
||||
result[index] = (byte) ((accumulator >> bits) & mask);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
if (!strictMode) {
|
||||
if (bits > 0) {
|
||||
result[index] = (byte) ((accumulator << (to - bits)) & mask);
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) {
|
||||
throw new RuntimeException("Strict mode was used but input couldn't be converted without padding");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
14
app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt
Normal file
14
app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.wallet.bch
|
||||
|
||||
enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) {
|
||||
N_001("electrumx.hillsideinternet.com", 50002, "ssl"),
|
||||
// N_002("dedi.jochen-hoenicke.de", 51002, "ssl"),
|
||||
// N_003("crypto.mldlabs.com", 50002, "ssl"),
|
||||
// N_004("electroncash.cascharia.com", 50002, "ssl"),
|
||||
// N_005("bch.crypto.mldlabs.com", 50002, "ssl"),
|
||||
N_006("electron.coinucopia.io", 50002, "ssl"),
|
||||
N_007("blackie.c3-soft.com", 50002, "ssl"),
|
||||
N_008("electrum.imaginary.cash", 50002, "ssl"),
|
||||
// N_009("bitcoincash.quangld.com", 50002, "ssl"),
|
||||
// N_010("bch.stitthappens.com", 50002, "ssl"),
|
||||
}
|
||||
788
app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java
Normal file
788
app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java
Normal file
|
|
@ -0,0 +1,788 @@
|
|||
package com.tangem.wallet.bch;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.data.network.ElectrumRequest;
|
||||
import com.tangem.data.network.ServerApiElectrum;
|
||||
import com.tangem.wallet.BCHUtils;
|
||||
import com.tangem.wallet.BTCUtils;
|
||||
import com.tangem.card_common.reader.CardProtocol;
|
||||
import com.tangem.wallet.BalanceValidator;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.wallet.CoinData;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
import com.tangem.card_common.data.TangemCard;
|
||||
import com.tangem.wallet.TangemContext;
|
||||
import com.tangem.wallet.Transaction;
|
||||
import com.tangem.wallet.UnspentOutputInfo;
|
||||
import com.tangem.card_common.tasks.SignTask;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
import com.tangem.util.DerEncodingUtil;
|
||||
import com.tangem.card_common.util.Util;
|
||||
import com.tangem.wallet.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class BtcCashEngine extends CoinEngine {
|
||||
|
||||
private static final String TAG = BtcCashEngine.class.getSimpleName();
|
||||
public BtcData coinData = null;
|
||||
|
||||
public BtcCashEngine(TangemContext context) throws Exception {
|
||||
super(context);
|
||||
if (context.getCoinData() == null) {
|
||||
coinData = new BtcData();
|
||||
context.setCoinData(coinData);
|
||||
} else if (context.getCoinData() instanceof BtcData) {
|
||||
coinData = (BtcData) context.getCoinData();
|
||||
} else {
|
||||
throw new Exception("Invalid type of Blockchain data for BtcEngine");
|
||||
}
|
||||
}
|
||||
|
||||
public BtcCashEngine() {
|
||||
|
||||
}
|
||||
|
||||
private static int getDecimals() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
private void checkBlockchainDataExists() throws Exception {
|
||||
if (coinData == null) throw new Exception("No blockchain data");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitingConfirmation() {
|
||||
if (coinData == null) return false;
|
||||
return coinData.getBalanceUnconfirmed() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceHTML() {
|
||||
Amount balance = getBalance();
|
||||
if (balance != null) {
|
||||
return balance.toDescriptionString(getDecimals());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceCurrency() {
|
||||
return "BCH";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOfflineBalanceHTML() {
|
||||
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
|
||||
Amount offlineAmount = convertToAmount(offlineInternalAmount);
|
||||
return offlineAmount.toDescriptionString(getDecimals());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBalanceNotZero() {
|
||||
if (coinData == null) return false;
|
||||
if (coinData.getBalanceInInternalUnits() == null) return false;
|
||||
return coinData.getBalanceInInternalUnits().notZero();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBalanceInfo() {
|
||||
if (coinData == null) return false;
|
||||
return coinData.hasBalanceInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExtractPossible() {
|
||||
if (!hasBalanceInfo()) {
|
||||
ctx.setMessage(R.string.cannot_obtain_data_from_blockchain);
|
||||
} else if (!isBalanceNotZero()) {
|
||||
ctx.setMessage(R.string.wallet_empty);
|
||||
} else if (awaitingConfirmation()) {
|
||||
ctx.setMessage(R.string.please_wait_while_previous);
|
||||
} else if (coinData.getUnspentTransactions().size() == 0) {
|
||||
ctx.setMessage(R.string.please_wait_for_confirmation);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeeCurrency() {
|
||||
return "BCH";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
// if (address == null || address.isEmpty()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (address.length() < 25) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (address.length() > 35) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// byte[] decAddress = Base58.decodeBase58(address);
|
||||
//
|
||||
// if (decAddress == null || decAddress.length == 0) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// byte[] rip = new byte[21];
|
||||
// for (int i = 0; i < 21; ++i) {
|
||||
// rip[i] = decAddress[i];
|
||||
// }
|
||||
//
|
||||
// byte[] kcv = CryptoUtil.doubleSha256(rip);
|
||||
//
|
||||
// for (int i = 0; i < 4; ++i) {
|
||||
// if (kcv[i] != decAddress[21 + i])
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (ctx.getBlockchain() == Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
|
||||
return CashAddr.isValidCashAddress(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedCheckNode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWalletExplorerUri() {
|
||||
return Uri.parse("https://bch.btc.com/" + ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getShareWalletUri() {
|
||||
return Uri.parse(ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputFilter[] getAmountInputFilters() {
|
||||
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmount(Amount amount) {
|
||||
if (coinData == null) return false;
|
||||
if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) {
|
||||
InternalAmount fee;
|
||||
InternalAmount amount;
|
||||
|
||||
try {
|
||||
checkBlockchainDataExists();
|
||||
amount = convertToInternalAmount(amountValue);
|
||||
fee = convertToInternalAmount(feeValue);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fee == null || amount == null)
|
||||
return false;
|
||||
|
||||
if (fee.isZero() || amount.isZero())
|
||||
return false;
|
||||
|
||||
if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0))
|
||||
return false;
|
||||
|
||||
if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateBalance(BalanceValidator balanceValidator) {
|
||||
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
|
||||
balanceValidator.setScore(0);
|
||||
balanceValidator.setFirstLine("Unknown balance");
|
||||
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Workaround before new back-end
|
||||
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
|
||||
// firstLine = "Verified balance";
|
||||
// secondLine = "Balance confirmed in blockchain. ";
|
||||
// secondLine += "Verified note identity. ";
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (coinData.getBalanceUnconfirmed() != 0) {
|
||||
balanceValidator.setScore(0);
|
||||
balanceValidator.setFirstLine("Transaction in progress");
|
||||
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) {
|
||||
balanceValidator.setScore(100);
|
||||
balanceValidator.setFirstLine("Verified balance");
|
||||
balanceValidator.setSecondLine("Balance confirmed in blockchain");
|
||||
if (coinData.getBalanceInInternalUnits().isZero()) {
|
||||
balanceValidator.setFirstLine("Empty wallet");
|
||||
balanceValidator.setSecondLine("");
|
||||
}
|
||||
}
|
||||
|
||||
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
|
||||
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
|
||||
// {
|
||||
// score = 80;
|
||||
// firstLine = "Unguaranteed balance";
|
||||
// secondLine = "Potential unsent transaction. Redeem immediately if accept. ";
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
|
||||
balanceValidator.setScore(80);
|
||||
balanceValidator.setFirstLine("Verified offline balance");
|
||||
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
|
||||
}
|
||||
|
||||
// if(card.getFailedBalanceRequestCounter()!=0) {
|
||||
// score -= 5 * card.getFailedBalanceRequestCounter();
|
||||
// secondLine += "Not all nodes have returned balance. Swipe down or tap again. ";
|
||||
// if(score <= 0)
|
||||
// return;
|
||||
// }
|
||||
|
||||
//
|
||||
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
|
||||
// score = 0;
|
||||
// firstLine = "Disputed balance";
|
||||
// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later.";
|
||||
// return;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount getBalance() {
|
||||
return convertToAmount(coinData.getBalanceInInternalUnits());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluateFeeEquivalent(String fee) {
|
||||
if (!coinData.getAmountEquivalentDescriptionAvailable()) return "";
|
||||
try {
|
||||
Amount feeAmount = new Amount(fee, getFeeCurrency());
|
||||
return feeAmount.toEquivalentString(coinData.getRate());
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceEquivalent() {
|
||||
if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return "";
|
||||
Amount balance = getBalance();
|
||||
if (balance == null) return "";
|
||||
return balance.toEquivalentString(coinData.getRate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String calculateAddress(byte[] pubKey) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
// CashAddr format
|
||||
byte hash1[] = Util.calculateSHA256(pubKey);
|
||||
byte hash2[] = Util.calculateRIPEMD160(hash1);
|
||||
return CashAddr.toCashAddress(BitcoinCashAddressType.P2PKH, hash2);
|
||||
|
||||
}
|
||||
|
||||
public String calculateLegacyAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
// Legacy format (BTC)
|
||||
byte netSelectionByte = (byte) 0x00;
|
||||
|
||||
byte hash1[] = Util.calculateSHA256(pkUncompressed);
|
||||
byte hash2[] = Util.calculateRIPEMD160(hash1);
|
||||
|
||||
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
|
||||
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
|
||||
byte hash3[] = Util.calculateSHA256(BB.array());
|
||||
byte hash4[] = Util.calculateSHA256(hash3);
|
||||
|
||||
BB = ByteBuffer.allocate(hash2.length + 5);
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
BB.put(hash4[0]);
|
||||
BB.put(hash4[1]);
|
||||
BB.put(hash4[2]);
|
||||
BB.put(hash4[3]);
|
||||
|
||||
return org.bitcoinj.core.Base58.encode(BB.array());
|
||||
}
|
||||
|
||||
public String convertToLegacyAddress(String cashAddr) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
BitcoinCashAddressDecodedParts bcadp = CashAddr.decodeCashAddress(cashAddr);
|
||||
byte netSelectionByte = (byte) 0x00;
|
||||
byte hash2[] = bcadp.hash;
|
||||
|
||||
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
|
||||
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
|
||||
byte hash3[] = Util.calculateSHA256(BB.array());
|
||||
byte hash4[] = Util.calculateSHA256(hash3);
|
||||
|
||||
BB = ByteBuffer.allocate(hash2.length + 5);
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
BB.put(hash4[0]);
|
||||
BB.put(hash4[1]);
|
||||
BB.put(hash4[2]);
|
||||
BB.put(hash4[3]);
|
||||
|
||||
return org.bitcoinj.core.Base58.encode(BB.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount convertToAmount(InternalAmount internalAmount) {
|
||||
BigDecimal d = internalAmount.divide(new BigDecimal("100000000"));
|
||||
return new Amount(d, getBalanceCurrency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount convertToAmount(String strAmount, String currency) {
|
||||
return new Amount(strAmount, currency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAmount convertToInternalAmount(Amount amount) throws Exception {
|
||||
BigDecimal d = amount.multiply(new BigDecimal("100000000"));
|
||||
return new InternalAmount(d, getBalanceCurrency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAmount convertToInternalAmount(byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
byte[] reversed = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
|
||||
return new InternalAmount(Util.byteArrayToLong(reversed), "Satoshi");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception {
|
||||
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
|
||||
byte[] reversed = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
|
||||
return reversed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoinData createCoinData() {
|
||||
return new BtcData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return coinData.getUnspentInputsDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineWallet() throws CardProtocol.TangemException {
|
||||
try {
|
||||
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
|
||||
ctx.getCoinData().setWallet(wallet);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.getCoinData().setWallet("ERROR");
|
||||
throw new CardProtocol.TangemException("Can't define wallet address");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public String getAmountDescription(TangemCard mCard, String amount) throws Exception {
|
||||
// return mCard.getAmountDescription(Double.parseDouble(amount));
|
||||
// }
|
||||
|
||||
|
||||
@Override
|
||||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String srcLegacyAddress = convertToLegacyAddress(ctx.getCoinData().getWallet());
|
||||
String destLegacyAddress = convertToLegacyAddress(targetAddress);
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
|
||||
|
||||
// Build script for our address
|
||||
List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
|
||||
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(srcLegacyAddress).bytes;
|
||||
|
||||
// Collect unspent
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = BCHUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
|
||||
|
||||
long fullAmount = 0;
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
fullAmount += unspentOutputs.get(i).value;
|
||||
}
|
||||
|
||||
|
||||
long fees = convertToInternalAmount(feeValue).longValueExact();
|
||||
long amount = convertToInternalAmount(amountValue).longValueExact();
|
||||
long change = fullAmount - amount;
|
||||
if (IncFee) {
|
||||
amount = amount - fees;
|
||||
} else {
|
||||
change = change - fees;
|
||||
}
|
||||
|
||||
if (amount + fees > fullAmount) {
|
||||
throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount));
|
||||
}
|
||||
|
||||
final long amountFinal = amount;
|
||||
final long changeFinal = change;
|
||||
|
||||
byte[][] txForSign = new byte[unspentOutputs.size()][];
|
||||
byte[][] bodyHash = new byte[unspentOutputs.size()][];
|
||||
byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][];
|
||||
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
txForSign[i] = BCHUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change);
|
||||
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
|
||||
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
|
||||
}
|
||||
|
||||
return new SignTask.TransactionToSign() {
|
||||
|
||||
@Override
|
||||
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[][] getHashesToSign() throws Exception {
|
||||
byte[][] dataForSign = new byte[unspentOutputs.size()][];
|
||||
if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!");
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
dataForSign[i] = bodyDoubleHash[i];
|
||||
}
|
||||
return dataForSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawDataToSign() throws Exception {
|
||||
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < txForSign.length; i++) {
|
||||
if (i != 0 && txForSign[0].length != txForSign[i].length)
|
||||
throw new Exception("Hashes length must be identical!");
|
||||
bs.write(txForSign[i]);
|
||||
}
|
||||
|
||||
return bs.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgToSign() {
|
||||
return "sha-256x2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
|
||||
throw new Exception("Transaction validation by issuer not supported in this version!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
|
||||
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
|
||||
s = CryptoUtil.toCanonicalised(s);
|
||||
|
||||
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
|
||||
}
|
||||
|
||||
byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
|
||||
notifyOnNeedSendTransaction(txForSend);
|
||||
return txForSend;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
|
||||
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
|
||||
|
||||
ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(ElectrumRequest electrumRequest) {
|
||||
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) {
|
||||
try {
|
||||
Long confBalance = electrumRequest.getResult().getLong("confirmed");
|
||||
Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed");
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
|
||||
}
|
||||
}
|
||||
|
||||
if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) {
|
||||
try {
|
||||
String walletAddress = electrumRequest.getParams().getString(0);
|
||||
JSONArray jsUnspentArray = electrumRequest.getResultArray();
|
||||
try {
|
||||
coinData.getUnspentTransactions().clear();
|
||||
for (int i = 0; i < jsUnspentArray.length(); i++) {
|
||||
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = jsUnspent.getString("tx_hash");
|
||||
trUnspent.Amount = jsUnspent.getLong("value");
|
||||
trUnspent.Height = jsUnspent.getInt("height");
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
|
||||
}
|
||||
|
||||
for (int i = 0; i < jsUnspentArray.length(); i++) {
|
||||
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
|
||||
Integer height = jsUnspent.getInt("height");
|
||||
String hash = jsUnspent.getString("tx_hash");
|
||||
if (height != -1) {
|
||||
if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
|
||||
} else {
|
||||
ctx.setError("Terminated by user");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) {
|
||||
try {
|
||||
String txHash = electrumRequest.txHash;
|
||||
String raw = electrumRequest.getResultString();
|
||||
for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) {
|
||||
if (tx.txID.equals(txHash))
|
||||
tx.Raw = raw;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (serverApiElectrum.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}else{
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(ElectrumRequest electrumRequest) {
|
||||
Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError());
|
||||
ctx.setError(electrumRequest.getError());
|
||||
if (serverApiElectrum.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
}else{
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiElectrum.setResponseListener(electrumBodyListener);
|
||||
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet())));
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet())));
|
||||
}
|
||||
|
||||
private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) {
|
||||
try {
|
||||
SignTask.TransactionToSign ps= constructTransaction(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress );
|
||||
OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction;
|
||||
onNeedSendTransaction =(tx)->{}; // empty function to bypass exception
|
||||
|
||||
byte[][] hashesToSign=ps.getHashesToSign();
|
||||
byte[] signFromCard = new byte[64 * hashesToSign.length];
|
||||
Arrays.fill(signFromCard, (byte) 0x01);
|
||||
byte[] txForSend=ps.onSignCompleted(signFromCard);
|
||||
onNeedSendTransaction = onNeedSendTransactionBackup;
|
||||
Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length));
|
||||
return txForSend.length +1;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Can't calculate transaction size -> use default!");
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
|
||||
private final static BigDecimal relayFee = new BigDecimal(0.00001);
|
||||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
coinData.minFee=null;
|
||||
coinData.maxFee=null;
|
||||
coinData.normalFee=null;
|
||||
|
||||
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
|
||||
|
||||
final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(ElectrumRequest electrumRequest) {
|
||||
BigDecimal fee;
|
||||
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) {
|
||||
try {
|
||||
fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB
|
||||
|
||||
if (fee.equals(BigDecimal.ZERO)) {
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
|
||||
}
|
||||
|
||||
// if (calcSize != 0) {
|
||||
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
|
||||
// } else {
|
||||
// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
|
||||
// }
|
||||
|
||||
//compare fee to usual relay fee
|
||||
if (fee.compareTo(relayFee) < 0) {
|
||||
fee = relayFee;
|
||||
}
|
||||
fee = fee.setScale(8, RoundingMode.DOWN);
|
||||
|
||||
CoinEngine.Amount feeAmount = new CoinEngine.Amount(fee, ctx.getBlockchain().getCurrency());
|
||||
coinData.minFee = feeAmount;
|
||||
coinData.normalFee = feeAmount;
|
||||
coinData.maxFee = feeAmount;
|
||||
// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) {
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
// } else {
|
||||
// blockchainRequestsCallbacks.onProgress();
|
||||
// }
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(ElectrumRequest electrumRequest) {
|
||||
ctx.setError(electrumRequest.getError());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiElectrum.setResponseListener(electrumListener);
|
||||
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
|
||||
final String txStr = BTCUtils.toHex(txForSend);
|
||||
|
||||
ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(ElectrumRequest electrumRequest) {
|
||||
if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) {
|
||||
try {
|
||||
String resultString = electrumRequest.getResultString();
|
||||
if (resultString == null || resultString.isEmpty()) {
|
||||
ctx.setError("Rejected by node: " + electrumRequest.getError());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}else {
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
ctx.setError(e.getClass().getName());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(ElectrumRequest electrumRequest) {
|
||||
ctx.setError(electrumRequest.getError());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiElectrum.setResponseListener(electrumBodyListener);
|
||||
|
||||
|
||||
serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
226
app/src/main/java/com/tangem/wallet/bch/CashAddr.java
Normal file
226
app/src/main/java/com/tangem/wallet/bch/CashAddr.java
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package com.tangem.wallet.bch;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018 Tobias Brandt
|
||||
*
|
||||
* Distributed under the MIT software license, see the accompanying file LICENSE
|
||||
* or http://www.opensource.org/licenses/mit-license.php.
|
||||
*/
|
||||
|
||||
|
||||
public class CashAddr {
|
||||
|
||||
public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
||||
private static final char[] CHARS = CHARSET.toCharArray();
|
||||
|
||||
|
||||
public static final String SEPARATOR = ":";
|
||||
|
||||
public static final String MAIN_NET_PREFIX = "bitcoincash";
|
||||
|
||||
public static final String TEST_NET_PREFIX = "bchtest";
|
||||
|
||||
public static final String ASSUMED_DEFAULT_PREFIX = MAIN_NET_PREFIX;
|
||||
|
||||
private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] { new BigInteger("98f2bc8e61", 16),
|
||||
new BigInteger("79b76d99e2", 16), new BigInteger("f33e5fb3c4", 16), new BigInteger("ae2eabe2a8", 16),
|
||||
new BigInteger("1e4f43e470", 16) };
|
||||
|
||||
private static final BigInteger POLYMOD_AND_CONSTANT = new BigInteger("07ffffffff", 16);
|
||||
|
||||
public static String toCashAddress(BitcoinCashAddressType addressType, byte[] hash) {
|
||||
String prefixString = MAIN_NET_PREFIX;
|
||||
byte[] prefixBytes = getPrefixBytes(prefixString);
|
||||
byte[] payloadBytes = concatenateByteArrays(new byte[] { addressType.getVersionByte() }, hash);
|
||||
payloadBytes = convertBits(payloadBytes, 8, 5, false);
|
||||
byte[] allChecksumInput = concatenateByteArrays(
|
||||
concatenateByteArrays(concatenateByteArrays(prefixBytes, new byte[] { 0 }), payloadBytes),
|
||||
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
|
||||
byte[] checksumBytes = calculateChecksumBytesPolymod(allChecksumInput);
|
||||
checksumBytes = convertBits(checksumBytes, 8, 5, true);
|
||||
String cashAddress = BitcoinCashBase32.encode(concatenateByteArrays(payloadBytes, checksumBytes));
|
||||
//return prefixString + SEPARATOR + cashAddress;
|
||||
return cashAddress;
|
||||
}
|
||||
|
||||
public static BitcoinCashAddressDecodedParts decodeCashAddress(String bitcoinCashAddress) {
|
||||
if (!isValidCashAddress(bitcoinCashAddress)) {
|
||||
throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress);
|
||||
}
|
||||
|
||||
BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts();
|
||||
String[] addressParts = bitcoinCashAddress.split(SEPARATOR);
|
||||
if (addressParts.length == 2) {
|
||||
decoded.setPrefix(addressParts[0]);
|
||||
} else {
|
||||
decoded.setPrefix(MAIN_NET_PREFIX);
|
||||
}
|
||||
|
||||
byte[] addressData = BitcoinCashBase32.decode(addressParts[addressParts.length - 1]);
|
||||
addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8);
|
||||
addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true);
|
||||
byte versionByte = addressData[0];
|
||||
byte[] hash = Arrays.copyOfRange(addressData, 1, addressData.length);
|
||||
|
||||
decoded.setAddressType(getAddressTypeFromVersionByte(versionByte));
|
||||
decoded.setHash(hash);
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
private static BitcoinCashAddressType getAddressTypeFromVersionByte(byte versionByte) {
|
||||
for (BitcoinCashAddressType addressType : BitcoinCashAddressType.values()) {
|
||||
if (addressType.getVersionByte() == versionByte) {
|
||||
return addressType;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown version byte: " + versionByte);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean isValidCashAddress(String bitcoinCashAddress ) {
|
||||
try {
|
||||
if (!isSingleCase(bitcoinCashAddress))
|
||||
return false;
|
||||
|
||||
bitcoinCashAddress = bitcoinCashAddress.toLowerCase();
|
||||
String prefix;
|
||||
|
||||
if (bitcoinCashAddress.contains(SEPARATOR)) {
|
||||
String[] split = bitcoinCashAddress.split(SEPARATOR);
|
||||
prefix = split[0];
|
||||
if (!prefix.equals(MAIN_NET_PREFIX)) {return false;} //for now we use main net only
|
||||
bitcoinCashAddress = split[1];
|
||||
} else {
|
||||
prefix = MAIN_NET_PREFIX;
|
||||
}
|
||||
if (!bitcoinCashAddress.startsWith("q")) {return false;} //for now we use P2PKH addresses only
|
||||
|
||||
byte[] checksumData = concatenateByteArrays(
|
||||
concatenateByteArrays(getPrefixBytes(prefix ), new byte[] { 0x00 }),
|
||||
BitcoinCashBase32.decode(bitcoinCashAddress));
|
||||
|
||||
byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData);
|
||||
return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0;
|
||||
} catch (RuntimeException re) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static boolean isSingleCase(String bitcoinCashAddress) {
|
||||
if (bitcoinCashAddress.equals(bitcoinCashAddress.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
if (bitcoinCashAddress.equals(bitcoinCashAddress.toUpperCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checksumInput
|
||||
* @return Returns a 40 bits checksum in form of 5 8-bit arrays. This still has
|
||||
* to me mapped to 5-bit array representation
|
||||
*/
|
||||
private static byte[] calculateChecksumBytesPolymod(byte[] checksumInput) {
|
||||
BigInteger c = BigInteger.ONE;
|
||||
|
||||
for (int i = 0; i < checksumInput.length; i++) {
|
||||
byte c0 = c.shiftRight(35).byteValue();
|
||||
c = c.and(POLYMOD_AND_CONSTANT).shiftLeft(5)
|
||||
.xor(new BigInteger(String.format("%02x", checksumInput[i]), 16));
|
||||
|
||||
if ((c0 & 0x01) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[0]);
|
||||
if ((c0 & 0x02) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[1]);
|
||||
if ((c0 & 0x04) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[2]);
|
||||
if ((c0 & 0x08) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[3]);
|
||||
if ((c0 & 0x10) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[4]);
|
||||
}
|
||||
|
||||
byte[] checksum = c.xor(BigInteger.ONE).toByteArray();
|
||||
if (checksum.length == 5) {
|
||||
return checksum;
|
||||
} else {
|
||||
byte[] newChecksumArray = new byte[5];
|
||||
|
||||
System.arraycopy(checksum, Math.max(0, checksum.length - 5), newChecksumArray,
|
||||
Math.max(0, 5 - checksum.length), Math.min(5, checksum.length));
|
||||
|
||||
return newChecksumArray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static byte[] getPrefixBytes(String prefixString ) {
|
||||
byte[] prefixBytes = new byte[prefixString.length()];
|
||||
|
||||
char[] charArray = prefixString.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i++) {
|
||||
prefixBytes[i] = (byte) (charArray[i] & 0x1f);
|
||||
}
|
||||
|
||||
return prefixBytes;
|
||||
}
|
||||
|
||||
private static byte[] concatenateByteArrays(byte[] first, byte[] second) {
|
||||
byte[] concatenatedBytes = new byte[first.length + second.length];
|
||||
|
||||
System.arraycopy(first, 0, concatenatedBytes, 0, first.length);
|
||||
System.arraycopy(second, 0, concatenatedBytes, first.length, second.length);
|
||||
|
||||
return concatenatedBytes;
|
||||
}
|
||||
|
||||
private static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) {
|
||||
//Copyright (c) 2017 Pieter Wuille
|
||||
|
||||
int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to)
|
||||
: Math.ceil((double) bytes8Bits.length * from / to));
|
||||
int mask = ((1 << to) - 1) & 0xff;
|
||||
byte[] result = new byte[length];
|
||||
int index = 0;
|
||||
int accumulator = 0;
|
||||
int bits = 0;
|
||||
for (int i = 0; i < bytes8Bits.length; i++) {
|
||||
byte value = bytes8Bits[i];
|
||||
accumulator = (((accumulator & 0xff) << from) | (value & 0xff));
|
||||
bits += from;
|
||||
while (bits >= to) {
|
||||
bits -= to;
|
||||
result[index] = (byte) ((accumulator >> bits) & mask);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
if (!strictMode) {
|
||||
if (bits > 0) {
|
||||
result[index] = (byte) ((accumulator << (to - bits)) & mask);
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) {
|
||||
throw new RuntimeException("Strict mode was used but input couldn't be converted without padding");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
98
app/src/main/java/com/tangem/wallet/binance/BinanceData.java
Normal file
98
app/src/main/java/com/tangem/wallet/binance/BinanceData.java
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.wallet.binance;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.wallet.CoinData;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
|
||||
public class BinanceData extends CoinData {
|
||||
private String balance, chainId;
|
||||
private Long sequence;
|
||||
private Integer accountNumber;
|
||||
private boolean error404 = false;
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
super.loadFromBundle(B);
|
||||
|
||||
if (B.containsKey("Balance")) balance = B.getString("Balance");
|
||||
else balance = null;
|
||||
if (B.containsKey("ChainId")) chainId = B.getString("ChainId");
|
||||
else chainId = null;
|
||||
if (B.containsKey("Sequence")) sequence = B.getLong("Sequence");
|
||||
else sequence = null;
|
||||
if (B.containsKey("AccountNumber")) accountNumber = B.getInt("AccountNumber");
|
||||
else accountNumber = null;
|
||||
if (B.containsKey("Error404")) error404 = B.getBoolean("Error404");
|
||||
else error404 = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToBundle(Bundle B) {
|
||||
super.saveToBundle(B);
|
||||
try {
|
||||
if (balance != null) B.putString("Balance", balance);
|
||||
if (chainId != null) B.putString("ChainId", chainId);
|
||||
if (sequence != null) B.putLong("Sequence", sequence);
|
||||
if (accountNumber != null) B.putInt("AccountNumber", accountNumber);
|
||||
if (error404) B.putBoolean("Error404", true);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearInfo() {
|
||||
super.clearInfo();
|
||||
balance = null;
|
||||
chainId = null;
|
||||
sequence = null;
|
||||
accountNumber = null;
|
||||
error404 = false;
|
||||
}
|
||||
|
||||
public CoinEngine.Amount getBalance() {
|
||||
return new CoinEngine.Amount(balance, "BNB");
|
||||
}
|
||||
|
||||
public void setBalance(String balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
public boolean hasBalanceInfo() {
|
||||
return balance != null;
|
||||
}
|
||||
|
||||
public Integer getAccountNumber() {
|
||||
return accountNumber;
|
||||
}
|
||||
|
||||
public void setAccountNumber(Integer accountNumber) {
|
||||
this.accountNumber = accountNumber;
|
||||
}
|
||||
|
||||
public Long getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void setSequence(Long sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
public String getChainId() {
|
||||
return chainId;
|
||||
}
|
||||
|
||||
public void setChainId(String chain_id) {
|
||||
this.chainId = chain_id;
|
||||
}
|
||||
|
||||
public boolean isError404() {
|
||||
return error404;
|
||||
}
|
||||
|
||||
public void setError404(boolean error404) {
|
||||
this.error404 = error404;
|
||||
}
|
||||
}
|
||||
564
app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java
Normal file
564
app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
package com.tangem.wallet.binance;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tangem.card_common.data.TangemCard;
|
||||
import com.tangem.card_common.reader.CardProtocol;
|
||||
import com.tangem.card_common.tasks.SignTask;
|
||||
import com.tangem.card_common.util.Util;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.BinanceApi;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBinance;
|
||||
import com.tangem.data.network.model.BinanceFees;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
import com.tangem.wallet.BalanceValidator;
|
||||
import com.tangem.wallet.CoinData;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
import com.tangem.wallet.R;
|
||||
import com.tangem.wallet.TangemContext;
|
||||
import com.tangem.wallet.binance.client.BinanceDexApiClientFactory;
|
||||
import com.tangem.wallet.binance.client.BinanceDexApiRestClient;
|
||||
import com.tangem.wallet.binance.client.BinanceDexEnvironment;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.TransactionOption;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.Transfer;
|
||||
import com.tangem.wallet.binance.client.encoding.Bech32;
|
||||
import com.tangem.wallet.binance.client.encoding.Crypto;
|
||||
import com.tangem.wallet.binance.client.encoding.message.MessageType;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransferMessage;
|
||||
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
|
||||
public class BinanceEngine extends CoinEngine {
|
||||
private static final String TAG = BinanceEngine.class.getSimpleName();
|
||||
|
||||
public BinanceData coinData = null;
|
||||
private BinanceDexApiRestClient client = null;
|
||||
|
||||
public BinanceEngine(TangemContext context) throws Exception {
|
||||
super(context);
|
||||
if (context.getCoinData() == null) {
|
||||
coinData = new BinanceData();
|
||||
context.setCoinData(coinData);
|
||||
} else if (context.getCoinData() instanceof BinanceData) {
|
||||
coinData = (BinanceData) context.getCoinData();
|
||||
} else {
|
||||
throw new Exception("Invalid type of Blockchain data for " + TAG);
|
||||
}
|
||||
if (ctx.getBlockchain() == Blockchain.Binance) {
|
||||
client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.PROD.getBaseUrl());
|
||||
coinData.setChainId("Binance-Chain-Tigris");
|
||||
} else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) {
|
||||
client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.TEST_NET.getBaseUrl());
|
||||
coinData.setChainId("Binance-Chain-Nile");
|
||||
} else {
|
||||
throw new Exception("Invalid blockchain for BinanceEngine");
|
||||
}
|
||||
}
|
||||
|
||||
public BinanceEngine() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static int getDecimals() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
private void checkBlockchainDataExists() throws Exception {
|
||||
if (coinData == null) throw new Exception("No blockchain data");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitingConfirmation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceHTML() {
|
||||
Amount balance = getBalance();
|
||||
if (balance != null) {
|
||||
return balance.toDescriptionString(getDecimals());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceCurrency() {
|
||||
return "BNB";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOfflineBalanceHTML() { //TODO:check
|
||||
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
|
||||
Amount offlineAmount = convertToAmount(offlineInternalAmount);
|
||||
return offlineAmount.toDescriptionString(getDecimals());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBalanceNotZero() {
|
||||
if (coinData == null) return false;
|
||||
if (coinData.getBalance() == null) return false;
|
||||
return coinData.getBalance().notZero();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBalanceInfo() {
|
||||
if (coinData == null) return false;
|
||||
return coinData.hasBalanceInfo();
|
||||
}
|
||||
|
||||
public boolean isExtractPossible() {
|
||||
if (!hasBalanceInfo()) {
|
||||
ctx.setMessage(R.string.cannot_obtain_data_from_blockchain);
|
||||
} else if (!isBalanceNotZero()) {
|
||||
ctx.setMessage(R.string.wallet_empty);
|
||||
} else if (awaitingConfirmation()) {
|
||||
ctx.setMessage(R.string.please_wait_while_previous);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeeCurrency() {
|
||||
return "BNB";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
if (address == null || address.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Crypto.decodeAddress(address);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.Binance && !address.startsWith("bnb1")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.BinanceTestNet && !address.startsWith("tbnb1")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeedCheckNode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWalletExplorerUri() {
|
||||
if (ctx.getBlockchain() == Blockchain.Binance) {
|
||||
return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet());
|
||||
} else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) {
|
||||
return Uri.parse("https://testnet-explorer.binance.org/address/" + ctx.getCoinData().getWallet());
|
||||
} else {
|
||||
Log.e(TAG, "Invalid blockchain for BinanceEngine");
|
||||
return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet());
|
||||
}
|
||||
}
|
||||
|
||||
public Uri getShareWalletUri() {
|
||||
return Uri.parse(ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputFilter[] getAmountInputFilters() {
|
||||
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmount(Amount amount) {
|
||||
if (coinData == null) return false;
|
||||
return amount.compareTo(coinData.getBalance()) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) {
|
||||
try {
|
||||
checkBlockchainDataExists();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (feeValue == null || amountValue == null)
|
||||
return false;
|
||||
|
||||
if (feeValue.isZero() || amountValue.isZero())
|
||||
return false;
|
||||
|
||||
if (isIncludeFee && (amountValue.compareTo(coinData.getBalance()) > 0 || amountValue.compareTo(feeValue) < 0))
|
||||
return false;
|
||||
|
||||
if (!isIncludeFee && amountValue.add(feeValue).compareTo(coinData.getBalance()) > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateBalance(BalanceValidator balanceValidator) {
|
||||
try {
|
||||
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
|
||||
|
||||
if(coinData.isError404()) {
|
||||
balanceValidator.setScore(0);
|
||||
balanceValidator.setFirstLine("No account or network error");
|
||||
balanceValidator.setSecondLine("To create account send funds to this address");
|
||||
} else {
|
||||
balanceValidator.setScore(0);
|
||||
balanceValidator.setFirstLine("Unknown balance");
|
||||
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (coinData.isBalanceReceived()) {
|
||||
balanceValidator.setScore(100);
|
||||
balanceValidator.setFirstLine("Verified balance");
|
||||
balanceValidator.setSecondLine("Balance confirmed in blockchain");
|
||||
if (coinData.getBalance().isZero()) {
|
||||
balanceValidator.setFirstLine("Empty wallet");
|
||||
balanceValidator.setSecondLine("");
|
||||
}
|
||||
}
|
||||
|
||||
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalance().notZero()) {
|
||||
balanceValidator.setScore(80);
|
||||
balanceValidator.setFirstLine("Verified offline balance");
|
||||
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount getBalance() {
|
||||
if (!hasBalanceInfo()) return null;
|
||||
return coinData.getBalance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluateFeeEquivalent(String fee) {
|
||||
if (!coinData.getAmountEquivalentDescriptionAvailable()) return "";
|
||||
try {
|
||||
Amount feeAmount = new Amount(fee, getFeeCurrency());
|
||||
return feeAmount.toEquivalentString(coinData.getRate());
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceEquivalent() {
|
||||
if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return "";
|
||||
Amount balance = getBalance();
|
||||
if (balance == null) return "";
|
||||
return balance.toEquivalentString(coinData.getRate());
|
||||
}
|
||||
|
||||
public String calculateAddress(byte[] pkCompressed) throws Exception {
|
||||
byte[] pubKeyHash = Utils.sha256hash160(pkCompressed);
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.Binance) {
|
||||
return Bech32.encode("bnb", Crypto.convertBits(pubKeyHash, 0, pubKeyHash.length, 8, 5, false));
|
||||
} else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) {
|
||||
return Bech32.encode("tbnb", Crypto.convertBits(pubKeyHash, 0, pubKeyHash.length, 8, 5, false));
|
||||
} else {
|
||||
throw new Exception("Invalid blockchain for BinanceEngine");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount convertToAmount(InternalAmount internalAmount) {
|
||||
return new Amount(internalAmount, getBalanceCurrency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Amount convertToAmount(String strAmount, String currency) {
|
||||
return new Amount(strAmount, currency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAmount convertToInternalAmount(Amount amount) {
|
||||
return new InternalAmount(amount, getBalanceCurrency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAmount convertToInternalAmount(byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
byte[] reversed = new byte[bytes.length];
|
||||
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
|
||||
return new InternalAmount(Util.byteArrayToLong(reversed), getBalanceCurrency());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convertToByteArray(InternalAmount internalAmount) {
|
||||
return Util.longToByteArray(internalAmount.longValueExact());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoinData createCoinData() {
|
||||
return new BinanceData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineWallet() throws CardProtocol.TangemException {
|
||||
try {
|
||||
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
|
||||
ctx.getCoinData().setWallet(wallet);
|
||||
} catch (Exception e) {
|
||||
ctx.getCoinData().setWallet("ERROR");
|
||||
throw new CardProtocol.TangemException("Can't define wallet address");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String amount;
|
||||
|
||||
if (IncFee) {
|
||||
amount = amountValue.subtract(feeValue).setScale(getDecimals(), RoundingMode.DOWN).toPlainString();
|
||||
} else {
|
||||
amount = amountValue.setScale(getDecimals(), RoundingMode.DOWN).toPlainString();
|
||||
}
|
||||
|
||||
byte[] pubKey = ctx.getCard().getWalletPublicKeyRar();
|
||||
byte[] pubKeyPrefix = MessageType.PubKey.getTypePrefixBytes();
|
||||
byte[] pubKeyForSign = new byte[pubKey.length + pubKeyPrefix.length + 1];
|
||||
System.arraycopy(pubKeyPrefix, 0, pubKeyForSign, 0, pubKeyPrefix.length);
|
||||
pubKeyForSign[pubKeyPrefix.length] = (byte) 33;
|
||||
System.arraycopy(pubKey, 0, pubKeyForSign, pubKeyPrefix.length + 1, pubKey.length);
|
||||
|
||||
Transfer transfer = new Transfer();
|
||||
transfer.setCoin("BNB");
|
||||
transfer.setFromAddress(ctx.getCoinData().getWallet());
|
||||
transfer.setToAddress(targetAddress);
|
||||
transfer.setAmount(amount);
|
||||
|
||||
TransactionOption options = TransactionOption.DEFAULT_INSTANCE;
|
||||
|
||||
TransactionRequestAssemblerExtSign txAssembler = client.prepareTransfer(transfer, coinData, pubKeyForSign, options, true);
|
||||
// TransactionRequestAssembler.buildTransfer as reference
|
||||
TransferMessage msgBean = txAssembler.createTransferMessage(transfer);
|
||||
byte[] msg = txAssembler.encodeTransferMessage(msgBean);
|
||||
byte[] dataForSign = txAssembler.prepareForSign(msgBean);
|
||||
|
||||
return new SignTask.TransactionToSign() {
|
||||
|
||||
@Override
|
||||
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[][] getHashesToSign() throws NoSuchAlgorithmException {
|
||||
byte[][] hashForSign = new byte[1][];
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
hashForSign[0] = digest.digest(dataForSign);
|
||||
return hashForSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawDataToSign() {
|
||||
return dataForSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgToSign() {
|
||||
return "sha-256";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
|
||||
throw new Exception("Transaction validation by issuer not supported in this version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
|
||||
int size = signFromCard.length / 2;
|
||||
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, size));
|
||||
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, size, size * 2));
|
||||
s = CryptoUtil.toCanonicalised(s);
|
||||
|
||||
byte[] resultSig = new byte[64];
|
||||
System.arraycopy(Utils.bigIntegerToBytes(r, 32), 0, resultSig, 0, 32);
|
||||
System.arraycopy(Utils.bigIntegerToBytes(s, 32), 0, resultSig, 32, 32);
|
||||
|
||||
// TransactionRequestAssembler.buildTransfer as reference
|
||||
byte[] signature = txAssembler.encodeSignature(resultSig);
|
||||
byte[] txForSend = txAssembler.encodeStdTx(msg, signature);
|
||||
|
||||
notifyOnNeedSendTransaction(txForSend);
|
||||
return txForSend;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
try {
|
||||
ServerApiBinance serverApiBinance = new ServerApiBinance();
|
||||
|
||||
ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail() {
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiBinance.setResponseListener(responseListener);
|
||||
serverApiBinance.getBalance(ctx, client);
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.Binance) {
|
||||
coinData.setValidationNodeDescription(Server.ApiBinance.URL_BINANCE);
|
||||
} else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) {
|
||||
coinData.setValidationNodeDescription(Server.ApiBinanceTestnet.URL_BINANCE_TESTNET);
|
||||
} else {
|
||||
throw new Exception("Invalid blockchain for BinanceEngine");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL Binance balance exception");
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
try {
|
||||
String baseUrl;
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.Binance) {
|
||||
baseUrl = Server.ApiBinance.Method.API_V1;
|
||||
} else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) {
|
||||
baseUrl = Server.ApiBinanceTestnet.Method.API_V1;
|
||||
} else {
|
||||
throw new Exception("Invalid blockchain for BinanceEngine");
|
||||
}
|
||||
|
||||
Retrofit retrofitBinance = new Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
BinanceApi binanceApi = retrofitBinance.create(BinanceApi.class);
|
||||
Call<List<BinanceFees>> call = binanceApi.binanceFees();
|
||||
call.enqueue(new Callback<List<BinanceFees>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<List<BinanceFees>> call, @NonNull Response<List<BinanceFees>> response) {
|
||||
if (response.code() == 200) {
|
||||
for (BinanceFees fee : response.body()) {
|
||||
if (fee.getFixed_fee_params() != null) {
|
||||
Long longFee = Long.valueOf(fee.getFixed_fee_params().getFee());
|
||||
Amount feeAmount = new Amount(BigDecimal.valueOf(longFee).divide(BigDecimal.valueOf(100000000)).setScale(8, RoundingMode.DOWN), getFeeCurrency());
|
||||
coinData.minFee = coinData.normalFee = coinData.maxFee = feeAmount;
|
||||
Log.i(TAG, "requestFee onResponse " + response.code());
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.setError(response.code());
|
||||
Log.e(TAG, "requestFee onResponse " + response.code());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<List<BinanceFees>> call, @NonNull Throwable t) {
|
||||
ctx.setError(t.getMessage());
|
||||
Log.e(TAG, "requestFee onFailure " + t.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL Binance fee exception");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
|
||||
// RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend);
|
||||
try {
|
||||
ServerApiBinance serverApiBinance = new ServerApiBinance();
|
||||
|
||||
ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail() {
|
||||
ctx.setError("Broadcast error");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiBinance.setResponseListener(responseListener);
|
||||
serverApiBinance.sendTransaction(txForSend, client);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
public int pendingTransactionTimeoutInSeconds() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.tangem.wallet.binance.client.domain.*;
|
||||
import okhttp3.RequestBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BinanceDexApi {
|
||||
@GET("/api/v1/time")
|
||||
Call<Time> getTime();
|
||||
|
||||
@GET("/api/v1/node-info")
|
||||
Call<Infos> getNodeInfo();
|
||||
|
||||
@GET("/api/v1/validators")
|
||||
Call<Validators> getValidators();
|
||||
|
||||
@GET("/api/v1/peers")
|
||||
Call<List<Peer>> getPeers();
|
||||
|
||||
@GET("/api/v1/account/{address}")
|
||||
Call<Account> getAccount(@Path("address") String address);
|
||||
|
||||
@GET("/api/v1/account/{address}/sequence")
|
||||
Call<AccountSequence> getAccountSequence(@Path("address") String address);
|
||||
|
||||
@GET("/api/v1/tx/{hash}")
|
||||
Call<TransactionMetadata> getTransactionMetadata(@Path("hash") String hash);
|
||||
|
||||
@GET("/api/v1/tokens")
|
||||
Call<List<Token>> getTokens();
|
||||
|
||||
@GET("/api/v1/markets")
|
||||
Call<List<Market>> getMarkets();
|
||||
|
||||
|
||||
@GET("/api/v1/depth")
|
||||
Call<OrderBook> getOrderBook(@Query("symbol") String symbol, @Query("limit") Integer limit);
|
||||
|
||||
@GET("/api/v1/klines")
|
||||
Call<List<Candlestick>> getCandlestickBars(@Query("symbol") String symbol, @Query("interval") String interval,
|
||||
@Query("limit") Integer limit, @Query("startTime") Long startTime,
|
||||
@Query("endTime") Long endTime);
|
||||
|
||||
@GET("/api/v1/orders/open")
|
||||
Call<OrderList> getOpenOrders(@Query("address") String address, @Query("limit") Integer limit,
|
||||
@Query("offset") Integer offset, @Query("symbol") String symbol,
|
||||
@Query("total") Integer total);
|
||||
|
||||
@GET("/api/v1/orders/closed")
|
||||
Call<OrderList> getClosedOrders(@Query("address") String address, @Query("end") Long end,
|
||||
@Query("limit") Integer limit, @Query("offset") Integer offset,
|
||||
@Query("side") String side, @Query("start") Long start,
|
||||
@Query("status") List<String> status, @Query("symbol") String symbol,
|
||||
@Query("total") Integer total);
|
||||
|
||||
@GET("/api/v1/orders/{id}")
|
||||
Call<Order> getOrder(@Path("id") String id);
|
||||
|
||||
@GET("/api/v1/ticker/24hr")
|
||||
Call<List<TickerStatistics>> get24HrPriceStatistics();
|
||||
|
||||
@GET("/api/v1/trades")
|
||||
Call<TradePage> getTrades(@Query("address") String address,
|
||||
@Query("buyerOrderId") String buyerOrderId, @Query("end") Long end,
|
||||
@Query("height") Long height, @Query("limit") Integer limit,
|
||||
@Query("offset") Integer offset, @Query("quoteAsset") String quoteAsset,
|
||||
@Query("sellerOrderId") String sellerOrderId, @Query("side") String side,
|
||||
@Query("start") Long start, @Query("symbol") String symbol, @Query("total") Integer total);
|
||||
|
||||
@GET("/api/v1/transactions")
|
||||
Call<TransactionPage> getTransactions(@Query("address") String address, @Query("blockHeight") Long blockHeight,
|
||||
@Query("endTime") Long endTime, @Query("limit") Integer limit,
|
||||
@Query("offset") Integer offset, @Query("side") String side,
|
||||
@Query("startTime") Long startTime, @Query("txAsset") String txAsset,
|
||||
@Query("txType") String txType);
|
||||
|
||||
@POST("/api/v1/broadcast")
|
||||
Call<List<TransactionMetadata>> broadcast(@Query("sync") boolean sync, @Body RequestBody transaction);
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.tangem.wallet.binance.client.domain.*;
|
||||
import com.tangem.wallet.binance.client.domain.request.ClosedOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.OpenOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TradesRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TransactionsRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BinanceDexApiAsyncRestClient {
|
||||
void getTime(BinanceDexApiCallback<Time> callback);
|
||||
|
||||
void getNodeInfo(BinanceDexApiCallback<Infos> callback);
|
||||
|
||||
void getValidators(BinanceDexApiCallback<Validators> callback);
|
||||
|
||||
void getPeers(BinanceDexApiCallback<List<Peer>> callback);
|
||||
|
||||
void getMarkets(BinanceDexApiCallback<List<Market>> callback);
|
||||
|
||||
void getAccount(String address, BinanceDexApiCallback<Account> callback);
|
||||
|
||||
void getAccountSequence(String address, BinanceDexApiCallback<AccountSequence> callback);
|
||||
|
||||
void getTransactionMetadata(String hash, BinanceDexApiCallback<TransactionMetadata> callback);
|
||||
|
||||
void getTokens(BinanceDexApiCallback<List<Token>> callback);
|
||||
|
||||
void getOrderBook(String symbol, Integer limit, BinanceDexApiCallback<OrderBook> callback);
|
||||
|
||||
void getCandleStickBars(String symbol, CandlestickInterval interval,
|
||||
BinanceDexApiCallback<List<Candlestick>> callback);
|
||||
|
||||
void getCandleStickBars(String symbol, CandlestickInterval interval, Integer limit, Long startTime, Long endTime,
|
||||
BinanceDexApiCallback<List<Candlestick>> callback);
|
||||
|
||||
void getOpenOrders(String address, BinanceDexApiCallback<OrderList> callback);
|
||||
|
||||
void getOpenOrders(OpenOrdersRequest request, BinanceDexApiCallback<OrderList> callback);
|
||||
|
||||
void getClosedOrders(String address, BinanceDexApiCallback<OrderList> callback);
|
||||
|
||||
void getClosedOrders(ClosedOrdersRequest request, BinanceDexApiCallback<OrderList> callback);
|
||||
|
||||
void getOrder(String id, BinanceDexApiCallback<Order> callback);
|
||||
|
||||
void get24HrPriceStatistics(BinanceDexApiCallback<List<TickerStatistics>> callback);
|
||||
|
||||
void getTrades(BinanceDexApiCallback<TradePage> callback);
|
||||
|
||||
void getTrades(TradesRequest request, BinanceDexApiCallback<TradePage> callback);
|
||||
|
||||
void getTransactions(String address, BinanceDexApiCallback<TransactionPage> callback);
|
||||
|
||||
void getTransactions(TransactionsRequest request, BinanceDexApiCallback<TransactionPage> callback);
|
||||
|
||||
// Do not support async broadcast due to account sequence
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
/**
|
||||
* BinanceDexApiCallback is a functional interface used together with the BinanceApiAsyncClient to provide a non-blocking REST client.
|
||||
*
|
||||
* @param <T> the return type from the callback
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BinanceDexApiCallback<T> {
|
||||
|
||||
/**
|
||||
* Called whenever a response comes back from the Binance API.
|
||||
*
|
||||
* @param response the expected response object
|
||||
*/
|
||||
void onResponse(T response);
|
||||
|
||||
/**
|
||||
* Called whenever an error occurs.
|
||||
*
|
||||
* @param cause the cause of the failure
|
||||
*/
|
||||
default void onFailure(Throwable cause) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.tangem.wallet.binance.client.BinanceDexApiClientGenerator.getBinanceApiError;
|
||||
|
||||
/**
|
||||
* An adapter/wrapper which transforms a Callback from Retrofit into a BinanceDexApiCallback which is exposed to the client.
|
||||
*/
|
||||
public class BinanceDexApiCallbackAdapter<T> implements Callback<T> {
|
||||
|
||||
private final BinanceDexApiCallback<T> callback;
|
||||
|
||||
public BinanceDexApiCallbackAdapter(BinanceDexApiCallback<T> callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void onResponse(Call<T> call, Response<T> response) {
|
||||
if (response.isSuccessful()) {
|
||||
callback.onResponse(response.body());
|
||||
} else {
|
||||
if (response.code() == 504) {
|
||||
// HTTP 504 return code is used when the API successfully sent the message but not get a response within the timeout period.
|
||||
// It is important to NOT treat this as a failure; the execution status is UNKNOWN and could have been a success.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BinanceDexApiError apiError = getBinanceApiError(response);
|
||||
onFailure(call, new BinanceDexApiException(apiError));
|
||||
} catch (IOException e) {
|
||||
onFailure(call, new BinanceDexApiException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<T> call, Throwable throwable) {
|
||||
if (throwable instanceof BinanceDexApiException) {
|
||||
callback.onFailure(throwable);
|
||||
} else {
|
||||
callback.onFailure(new BinanceDexApiException(throwable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.tangem.wallet.binance.client.impl.BinanceDexApiAsyncRestClientImpl;
|
||||
import com.tangem.wallet.binance.client.impl.BinanceDexApiRestClientImpl;
|
||||
|
||||
public class BinanceDexApiClientFactory {
|
||||
private BinanceDexApiClientFactory() {
|
||||
}
|
||||
|
||||
public static BinanceDexApiClientFactory newInstance() {
|
||||
return new BinanceDexApiClientFactory();
|
||||
}
|
||||
|
||||
public BinanceDexApiRestClient newRestClient() {
|
||||
return newRestClient(BinanceDexEnvironment.PROD.getBaseUrl());
|
||||
}
|
||||
|
||||
public BinanceDexApiRestClient newRestClient(String baseUrl) {
|
||||
return new BinanceDexApiRestClientImpl(baseUrl);
|
||||
}
|
||||
|
||||
public BinanceDexApiAsyncRestClient newAsyncRestClient() {
|
||||
return newAsyncRestClient(BinanceDexEnvironment.PROD.getBaseUrl());
|
||||
}
|
||||
|
||||
public BinanceDexApiAsyncRestClient newAsyncRestClient(String baseUrl) {
|
||||
return new BinanceDexApiAsyncRestClientImpl(baseUrl);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Converter;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BinanceDexApiClientGenerator {
|
||||
private static final OkHttpClient sharedClient = new OkHttpClient.Builder()
|
||||
.pingInterval(20, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private static final Converter.Factory converterFactory =
|
||||
JacksonConverterFactory.create(new ObjectMapper().registerModule(new JodaModule()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Converter<ResponseBody, BinanceDexApiError> errorBodyConverter =
|
||||
(Converter<ResponseBody, BinanceDexApiError>) converterFactory.responseBodyConverter(
|
||||
BinanceDexApiError.class, new Annotation[0], null);
|
||||
|
||||
public static <S> S createService(Class<S> serviceClass, String baseUrl) {
|
||||
Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(converterFactory);
|
||||
|
||||
retrofitBuilder.client(sharedClient);
|
||||
|
||||
Retrofit retrofit = retrofitBuilder.build();
|
||||
|
||||
return retrofit.create(serviceClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a REST call and block until the response is received.
|
||||
*/
|
||||
public static <T> T executeSync(Call<T> call) {
|
||||
try {
|
||||
Response<T> response = call.execute();
|
||||
if (response.isSuccessful()) {
|
||||
return response.body();
|
||||
} else {
|
||||
try {
|
||||
BinanceDexApiError apiError = getBinanceApiError(response);
|
||||
throw new BinanceDexApiException(apiError);
|
||||
} catch (IOException e) {
|
||||
throw new BinanceDexApiException(response.toString(), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new BinanceDexApiException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and converts the response error body into an object.
|
||||
*/
|
||||
public static BinanceDexApiError getBinanceApiError(Response<?> response) throws IOException {
|
||||
return errorBodyConverter.convert(response.errorBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shared OkHttpClient instance.
|
||||
*/
|
||||
public static OkHttpClient getSharedClient() {
|
||||
return sharedClient;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BinanceDexApiError {
|
||||
private int code;
|
||||
private String message;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("code", code)
|
||||
.append("message", message)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
|
||||
public class BinanceDexApiException extends RuntimeException {
|
||||
private static final long serialVersionUID = 3788669840036201041L;
|
||||
private BinanceDexApiError error;
|
||||
|
||||
public BinanceDexApiException(BinanceDexApiError error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public BinanceDexApiException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BinanceDexApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BinanceDexApiError getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
if (error != null) {
|
||||
return error.getMessage();
|
||||
}
|
||||
return super.getMessage();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.tangem.wallet.TangemContext;
|
||||
import com.tangem.wallet.binance.BinanceData;
|
||||
import com.tangem.wallet.binance.client.domain.*;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.*;
|
||||
import com.tangem.wallet.binance.client.domain.request.ClosedOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.OpenOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TradesRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TransactionsRequest;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
public interface BinanceDexApiRestClient {
|
||||
Time getTime();
|
||||
|
||||
Infos getNodeInfo();
|
||||
|
||||
Validators getValidators();
|
||||
|
||||
List<Peer> getPeers();
|
||||
|
||||
List<Market> getMarkets();
|
||||
|
||||
Account getAccount(String address);
|
||||
|
||||
AccountSequence getAccountSequence(String address);
|
||||
|
||||
TransactionMetadata getTransactionMetadata(String hash);
|
||||
|
||||
List<Token> getTokens();
|
||||
|
||||
OrderBook getOrderBook(String symbol, Integer limit);
|
||||
|
||||
List<Candlestick> getCandleStickBars(String symbol, CandlestickInterval interval);
|
||||
|
||||
List<Candlestick> getCandleStickBars(String symbol, CandlestickInterval interval, Integer limit, Long startTime, Long endTime);
|
||||
|
||||
OrderList getOpenOrders(String address);
|
||||
|
||||
OrderList getOpenOrders(OpenOrdersRequest request);
|
||||
|
||||
OrderList getClosedOrders(String address);
|
||||
|
||||
OrderList getClosedOrders(ClosedOrdersRequest request);
|
||||
|
||||
Order getOrder(String id);
|
||||
|
||||
List<TickerStatistics> get24HrPriceStatistics();
|
||||
|
||||
TradePage getTrades();
|
||||
|
||||
TradePage getTrades(TradesRequest request);
|
||||
|
||||
TransactionPage getTransactions(String address);
|
||||
|
||||
TransactionPage getTransactions(TransactionsRequest request);
|
||||
|
||||
public List<TransactionMetadata> broadcastNoWallet(RequestBody requestBody, boolean sync) throws BinanceDexApiException;
|
||||
|
||||
List<TransactionMetadata> newOrder(NewOrder newOrder, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
List<TransactionMetadata> cancelOrder(CancelOrder cancelOrder, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
List<TransactionMetadata> transfer(Transfer transfer, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
TransactionRequestAssemblerExtSign prepareTransfer(Transfer transfer, BinanceData binanceData, byte[] pubKeyFroSign, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
List<TransactionMetadata> freeze(TokenFreeze freeze, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
List<TransactionMetadata> unfreeze(TokenUnfreeze unfreeze, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
public class BinanceDexConstants {
|
||||
|
||||
/**
|
||||
* Identifier of this client.
|
||||
*/
|
||||
public static final long BINANCE_DEX_API_CLIENT_JAVA_SOURCE = 3L;
|
||||
|
||||
/**
|
||||
* Default ToStringStyle used by toString methods.
|
||||
* Override this to change the output format of the overridden toString methods.
|
||||
* - Example ToStringStyle.JSON_STYLE
|
||||
*/
|
||||
public static final ToStringStyle BINANCE_DEX_TO_STRING_STYLE = ToStringStyle.SHORT_PREFIX_STYLE;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
public enum BinanceDexEnvironment {
|
||||
PROD(
|
||||
"https://dex.binance.org",
|
||||
"wss://dex.binance.org/api/",
|
||||
"bnb"
|
||||
),
|
||||
TEST_NET(
|
||||
"https://testnet-dex.binance.org",
|
||||
"wss://testnet-dex.binance.org/api/",
|
||||
"tbnb"
|
||||
);
|
||||
// Rest API base URL
|
||||
private String baseUrl;
|
||||
// Websocket API base URL
|
||||
private String wsBaseUrl;
|
||||
// Address human readable part prefix
|
||||
private String hrp;
|
||||
|
||||
private BinanceDexEnvironment(String baseUrl, String wsBaseUrl, String hrp) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.wsBaseUrl = wsBaseUrl;
|
||||
this.hrp = hrp;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public String getWsBaseUrl() {
|
||||
return wsBaseUrl;
|
||||
}
|
||||
|
||||
public String getHrp() {
|
||||
return hrp;
|
||||
}
|
||||
}
|
||||
166
app/src/main/java/com/tangem/wallet/binance/client/Wallet.java
Normal file
166
app/src/main/java/com/tangem/wallet/binance/client/Wallet.java
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package com.tangem.wallet.binance.client;
|
||||
|
||||
import com.tangem.wallet.binance.client.domain.Account;
|
||||
import com.tangem.wallet.binance.client.domain.AccountSequence;
|
||||
import com.tangem.wallet.binance.client.domain.Infos;
|
||||
import com.tangem.wallet.binance.client.encoding.Crypto;
|
||||
import com.tangem.wallet.binance.client.encoding.message.MessageType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Wallet {
|
||||
private final static Map<BinanceDexEnvironment, String> CHAIN_IDS = new HashMap<>();
|
||||
private String privateKey;
|
||||
private String address;
|
||||
private ECKey ecKey;
|
||||
private byte[] addressBytes;
|
||||
private byte[] pubKeyForSign;
|
||||
private Integer accountNumber;
|
||||
private Long sequence = null;
|
||||
private BinanceDexEnvironment env;
|
||||
|
||||
private String chainId;
|
||||
|
||||
public Wallet(String privateKey, BinanceDexEnvironment env) {
|
||||
if (!StringUtils.isEmpty(privateKey)) {
|
||||
this.privateKey = privateKey;
|
||||
this.env = env;
|
||||
this.ecKey = ECKey.fromPrivate(new BigInteger(privateKey, 16));
|
||||
this.address = Crypto.getAddressFromECKey(this.ecKey, env.getHrp());
|
||||
this.addressBytes = Crypto.decodeAddress(this.address);
|
||||
byte[] pubKey = ecKey.getPubKeyPoint().getEncoded(true);
|
||||
byte[] pubKeyPrefix = MessageType.PubKey.getTypePrefixBytes();
|
||||
this.pubKeyForSign = new byte[pubKey.length + pubKeyPrefix.length + 1];
|
||||
System.arraycopy(pubKeyPrefix, 0, this.pubKeyForSign, 0, pubKeyPrefix.length);
|
||||
pubKeyForSign[pubKeyPrefix.length] = (byte) 33;
|
||||
System.arraycopy(pubKey, 0, this.pubKeyForSign, pubKeyPrefix.length + 1, pubKey.length);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Private key cannot be empty.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Wallet createRandomWallet(BinanceDexEnvironment env) throws IOException {
|
||||
return createWalletFromMnemonicCode(Crypto.generateMnemonicCode(), env);
|
||||
}
|
||||
|
||||
public static Wallet createWalletFromMnemonicCode(List<String> words, BinanceDexEnvironment env) throws IOException {
|
||||
String privateKey = Crypto.getPrivateKeyFromMnemonicCode(words);
|
||||
return new Wallet(privateKey, env);
|
||||
}
|
||||
|
||||
public synchronized void initAccount(BinanceDexApiRestClient client) {
|
||||
Account account = client.getAccount(this.address);
|
||||
if (account != null) {
|
||||
this.accountNumber = account.getAccountNumber();
|
||||
this.sequence = account.getSequence();
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot get account information for address " + this.address);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadAccountSequence(BinanceDexApiRestClient client) {
|
||||
AccountSequence accountSequence = client.getAccountSequence(this.address);
|
||||
this.sequence = accountSequence.getSequence();
|
||||
}
|
||||
|
||||
public synchronized void increaseAccountSequence() {
|
||||
if (this.sequence != null)
|
||||
this.sequence++;
|
||||
}
|
||||
|
||||
public synchronized void decreaseAccountSequence() {
|
||||
if (this.sequence != null)
|
||||
this.sequence--;
|
||||
}
|
||||
|
||||
public synchronized long getSequence() {
|
||||
if (sequence == null)
|
||||
throw new IllegalStateException("Account sequence is not initialized.");
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public synchronized void setAccountNumber(Integer accountNumber) {
|
||||
this.accountNumber = accountNumber;
|
||||
}
|
||||
|
||||
public synchronized void setSequence(Long sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
public synchronized void setChainId(String chainId) {
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
public synchronized void invalidAccountSequence() {
|
||||
this.sequence = null;
|
||||
}
|
||||
|
||||
public synchronized void ensureWalletIsReady(BinanceDexApiRestClient client) {
|
||||
if (accountNumber == null) {
|
||||
initAccount(client);
|
||||
} else if (sequence == null) {
|
||||
reloadAccountSequence(client);
|
||||
}
|
||||
|
||||
if (chainId == null) {
|
||||
chainId = CHAIN_IDS.get(chainId);
|
||||
if (chainId == null) {
|
||||
initChainId(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void initChainId(BinanceDexApiRestClient client) {
|
||||
Infos info = client.getNodeInfo();
|
||||
chainId = info.getNodeInfo().getNetwork();
|
||||
CHAIN_IDS.put(env, chainId);
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public ECKey getEcKey() {
|
||||
return ecKey;
|
||||
}
|
||||
|
||||
public byte[] getPubKeyForSign() {
|
||||
return pubKeyForSign;
|
||||
}
|
||||
|
||||
public int getAccountNumber() {
|
||||
return accountNumber;
|
||||
}
|
||||
|
||||
public String getChainId() {
|
||||
return chainId;
|
||||
}
|
||||
|
||||
public byte[] getAddressBytes() {
|
||||
return addressBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("addressBytes", addressBytes)
|
||||
.append("address", address)
|
||||
.append("ecKey", ecKey)
|
||||
.append("pubKeyForSign", pubKeyForSign)
|
||||
.append("accountNumber", accountNumber)
|
||||
.append("sequence", sequence)
|
||||
.append("chainId", chainId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Account {
|
||||
@JsonProperty("account_number")
|
||||
private Integer accountNumber;
|
||||
private String address;
|
||||
private List<Balance> balances;
|
||||
@JsonProperty("public_key")
|
||||
private List<Integer> publicKey;
|
||||
private Long sequence;
|
||||
|
||||
public Integer getAccountNumber() {
|
||||
return accountNumber;
|
||||
}
|
||||
|
||||
public void setAccountNumber(Integer accountNumber) {
|
||||
this.accountNumber = accountNumber;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public List<Balance> getBalances() {
|
||||
return balances;
|
||||
}
|
||||
|
||||
public void setBalances(List<Balance> balances) {
|
||||
this.balances = balances;
|
||||
}
|
||||
|
||||
public List<Integer> getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(List<Integer> publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public Long getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void setSequence(Long sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("accountNumber", accountNumber)
|
||||
.append("address", address)
|
||||
.append("balances", balances)
|
||||
.append("publicKey", publicKey)
|
||||
.append("sequence", sequence)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AccountSequence {
|
||||
private Long sequence;
|
||||
|
||||
public Long getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void setSequence(Long sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("sequence", sequence)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Balance {
|
||||
private String symbol;
|
||||
private String free;
|
||||
private String locked;
|
||||
private String frozen;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getFree() {
|
||||
return free;
|
||||
}
|
||||
|
||||
public void setFree(String free) {
|
||||
this.free = free;
|
||||
}
|
||||
|
||||
public String getLocked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public void setLocked(String locked) {
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
public String getFrozen() {
|
||||
return frozen;
|
||||
}
|
||||
|
||||
public void setFrozen(String frozen) {
|
||||
this.frozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("free", free)
|
||||
.append("locked", locked)
|
||||
.append("frozen", frozen)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* Kline/Candlestick bars for a symbol. Klines are uniquely identified by their open time.
|
||||
*/
|
||||
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
|
||||
@JsonPropertyOrder()
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Candlestick {
|
||||
|
||||
private Long openTime;
|
||||
|
||||
private String open;
|
||||
|
||||
private String high;
|
||||
|
||||
private String low;
|
||||
|
||||
private String close;
|
||||
|
||||
private String volume;
|
||||
|
||||
private Long closeTime;
|
||||
|
||||
private String quoteAssetVolume;
|
||||
|
||||
private Long numberOfTrades;
|
||||
|
||||
public Long getOpenTime() {
|
||||
return openTime;
|
||||
}
|
||||
|
||||
public void setOpenTime(Long openTime) {
|
||||
this.openTime = openTime;
|
||||
}
|
||||
|
||||
public String getOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
public void setOpen(String open) {
|
||||
this.open = open;
|
||||
}
|
||||
|
||||
public String getHigh() {
|
||||
return high;
|
||||
}
|
||||
|
||||
public void setHigh(String high) {
|
||||
this.high = high;
|
||||
}
|
||||
|
||||
public String getLow() {
|
||||
return low;
|
||||
}
|
||||
|
||||
public void setLow(String low) {
|
||||
this.low = low;
|
||||
}
|
||||
|
||||
public String getClose() {
|
||||
return close;
|
||||
}
|
||||
|
||||
public void setClose(String close) {
|
||||
this.close = close;
|
||||
}
|
||||
|
||||
public String getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(String volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public Long getCloseTime() {
|
||||
return closeTime;
|
||||
}
|
||||
|
||||
public void setCloseTime(Long closeTime) {
|
||||
this.closeTime = closeTime;
|
||||
}
|
||||
|
||||
public String getQuoteAssetVolume() {
|
||||
return quoteAssetVolume;
|
||||
}
|
||||
|
||||
public void setQuoteAssetVolume(String quoteAssetVolume) {
|
||||
this.quoteAssetVolume = quoteAssetVolume;
|
||||
}
|
||||
|
||||
public Long getNumberOfTrades() {
|
||||
return numberOfTrades;
|
||||
}
|
||||
|
||||
public void setNumberOfTrades(Long numberOfTrades) {
|
||||
this.numberOfTrades = numberOfTrades;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("openTime", openTime)
|
||||
.append("open", open)
|
||||
.append("high", high)
|
||||
.append("low", low)
|
||||
.append("close", close)
|
||||
.append("volume", volume)
|
||||
.append("closeTime", closeTime)
|
||||
.append("quoteAssetVolume", quoteAssetVolume)
|
||||
.append("numberOfTrades", numberOfTrades)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/**
|
||||
* Kline/Candlestick intervals.
|
||||
* m -> minutes; h -> hours; d -> days; w -> weeks; M -> months
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public enum CandlestickInterval {
|
||||
ONE_MINUTE("1m"),
|
||||
THREE_MINUTES("3m"),
|
||||
FIVE_MINUTES("5m"),
|
||||
FIFTEEN_MINUTES("15m"),
|
||||
HALF_HOURLY("30m"),
|
||||
HOURLY("1h"),
|
||||
TWO_HOURLY("2h"),
|
||||
FOUR_HOURLY("4h"),
|
||||
SIX_HOURLY("6h"),
|
||||
EIGHT_HOURLY("8h"),
|
||||
TWELVE_HOURLY("12h"),
|
||||
DAILY("1d"),
|
||||
THREE_DAILY("3d"),
|
||||
WEEKLY("1w"),
|
||||
MONTHLY("1M");
|
||||
|
||||
private final String intervalId;
|
||||
|
||||
CandlestickInterval(String intervalId) {
|
||||
this.intervalId = intervalId;
|
||||
}
|
||||
|
||||
public String getIntervalId() {
|
||||
return intervalId;
|
||||
}
|
||||
|
||||
public static CandlestickInterval fromIntervalId(String intervalId) {
|
||||
if (intervalId == null) {
|
||||
throw new IllegalArgumentException("Null interval id");
|
||||
}
|
||||
String id = intervalId.toLowerCase();
|
||||
for (CandlestickInterval interval : values()) {
|
||||
if (id.equals(interval.getIntervalId())) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown interval id: " + intervalId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Infos {
|
||||
@JsonProperty("node_info")
|
||||
private NodeInfo nodeInfo;
|
||||
@JsonProperty("sync_info")
|
||||
private SyncInfo syncInfo;
|
||||
@JsonProperty("validator_info")
|
||||
private ValidatorInfo validatorInfo;
|
||||
|
||||
public NodeInfo getNodeInfo() {
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
public void setNodeInfo(NodeInfo nodeInfo) {
|
||||
this.nodeInfo = nodeInfo;
|
||||
}
|
||||
|
||||
public SyncInfo getSyncInfo() {
|
||||
return syncInfo;
|
||||
}
|
||||
|
||||
public void setSyncInfo(SyncInfo syncInfo) {
|
||||
this.syncInfo = syncInfo;
|
||||
}
|
||||
|
||||
public ValidatorInfo getValidatorInfo() {
|
||||
return validatorInfo;
|
||||
}
|
||||
|
||||
public void setValidatorInfo(ValidatorInfo validatorInfo) {
|
||||
this.validatorInfo = validatorInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("nodeInfo", nodeInfo)
|
||||
.append("syncInfo", syncInfo)
|
||||
.append("validatorInfo", validatorInfo)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Market {
|
||||
public String baseAssetSymbol;
|
||||
public String quoteAssetSymbol;
|
||||
public String price;
|
||||
public String tickSize;
|
||||
public String lotSize;
|
||||
|
||||
@JsonProperty("base_asset_symbol")
|
||||
public String getBaseAssetSymbol() {
|
||||
return baseAssetSymbol;
|
||||
}
|
||||
|
||||
public void setBaseAssetSymbol(String baseAssetSymbol) {
|
||||
this.baseAssetSymbol = baseAssetSymbol;
|
||||
}
|
||||
|
||||
@JsonProperty("quote_asset_symbol")
|
||||
public String getQuoteAssetSymbol() {
|
||||
return quoteAssetSymbol;
|
||||
}
|
||||
|
||||
public void setQuoteAssetSymbol(String quoteAssetSymbol) {
|
||||
this.quoteAssetSymbol = quoteAssetSymbol;
|
||||
}
|
||||
|
||||
@JsonProperty("price")
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(String price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
@JsonProperty("tick_size")
|
||||
public String getTickSize() {
|
||||
return tickSize;
|
||||
}
|
||||
|
||||
public void setTickSize(String tickSize) {
|
||||
this.tickSize = tickSize;
|
||||
}
|
||||
|
||||
@JsonProperty("lot_size")
|
||||
public String getLotSize() {
|
||||
return lotSize;
|
||||
}
|
||||
|
||||
public void setLotSize(String lotSize) {
|
||||
this.lotSize = lotSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("baseAssetSymbol", baseAssetSymbol)
|
||||
.append("quoteAssetSymbol", quoteAssetSymbol)
|
||||
.append("price", price)
|
||||
.append("tickSize", tickSize)
|
||||
.append("lotSize", lotSize)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class NodeInfo {
|
||||
private String id;
|
||||
@JsonProperty("listen_addr")
|
||||
private String listenAddr;
|
||||
private String network;
|
||||
private String version;
|
||||
private String channels;
|
||||
private String moniker;
|
||||
private Map<String, Object> other;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getListenAddr() {
|
||||
return listenAddr;
|
||||
}
|
||||
|
||||
public void setListenAddr(String listenAddr) {
|
||||
this.listenAddr = listenAddr;
|
||||
}
|
||||
|
||||
public String getNetwork() {
|
||||
return network;
|
||||
}
|
||||
|
||||
public void setNetwork(String network) {
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getChannels() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
public void setChannels(String channels) {
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
public String getMoniker() {
|
||||
return moniker;
|
||||
}
|
||||
|
||||
public void setMoniker(String moniker) {
|
||||
this.moniker = moniker;
|
||||
}
|
||||
|
||||
public Map<String, Object> getOther() {
|
||||
return other;
|
||||
}
|
||||
|
||||
public void setOther(Map<String, Object> other) {
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("id", id)
|
||||
.append("listenAddr", listenAddr)
|
||||
.append("network", network)
|
||||
.append("version", version)
|
||||
.append("channels", channels)
|
||||
.append("moniker", moniker)
|
||||
.append("other", other)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Order {
|
||||
private String orderId;
|
||||
private String symbol;
|
||||
private String owner;
|
||||
private String price;
|
||||
private String quantity;
|
||||
private String cumulateQuantity;
|
||||
private String fee;
|
||||
private DateTime orderCreateTime;
|
||||
private DateTime transactionTime;
|
||||
private OrderStatus status;
|
||||
private TimeInForce timeInForce;
|
||||
private OrderSide side;
|
||||
private OrderType type;
|
||||
private String tradeId;
|
||||
private String lastExecutedPrice;
|
||||
private String lastExecutedQuantity;
|
||||
private String transactionHash;
|
||||
|
||||
public String getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(String orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(String price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(String quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public String getCumulateQuantity() {
|
||||
return cumulateQuantity;
|
||||
}
|
||||
|
||||
public void setCumulateQuantity(String cumulateQuantity) {
|
||||
this.cumulateQuantity = cumulateQuantity;
|
||||
}
|
||||
|
||||
public String getFee() {
|
||||
return fee;
|
||||
}
|
||||
|
||||
public void setFee(String fee) {
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public DateTime getOrderCreateTime() {
|
||||
return orderCreateTime;
|
||||
}
|
||||
|
||||
public void setOrderCreateTime(DateTime orderCreateTime) {
|
||||
this.orderCreateTime = orderCreateTime;
|
||||
}
|
||||
|
||||
public DateTime getTransactionTime() {
|
||||
return transactionTime;
|
||||
}
|
||||
|
||||
public void setTransactionTime(DateTime transactionTime) {
|
||||
this.transactionTime = transactionTime;
|
||||
}
|
||||
|
||||
public OrderStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(OrderStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public TimeInForce getTimeInForce() {
|
||||
return timeInForce;
|
||||
}
|
||||
|
||||
public void setTimeInForce(TimeInForce timeInForce) {
|
||||
this.timeInForce = timeInForce;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public OrderType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(OrderType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getTradeId() {
|
||||
return tradeId;
|
||||
}
|
||||
|
||||
public void setTradeId(String tradeId) {
|
||||
this.tradeId = tradeId;
|
||||
}
|
||||
|
||||
public String getLastExecutedPrice() {
|
||||
return lastExecutedPrice;
|
||||
}
|
||||
|
||||
public void setLastExecutedPrice(String lastExecutedPrice) {
|
||||
this.lastExecutedPrice = lastExecutedPrice;
|
||||
}
|
||||
|
||||
public String getLastExecutedQuantity() {
|
||||
return lastExecutedQuantity;
|
||||
}
|
||||
|
||||
public void setLastExecutedQuantity(String lastExecutedQuantity) {
|
||||
this.lastExecutedQuantity = lastExecutedQuantity;
|
||||
}
|
||||
|
||||
public String getTransactionHash() {
|
||||
return transactionHash;
|
||||
}
|
||||
|
||||
public void setTransactionHash(String transactionHash) {
|
||||
this.transactionHash = transactionHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("orderId", orderId)
|
||||
.append("symbol", symbol)
|
||||
.append("owner", owner)
|
||||
.append("price", price)
|
||||
.append("quantity", quantity)
|
||||
.append("cumulateQuantity", cumulateQuantity)
|
||||
.append("fee", fee)
|
||||
.append("orderCreateTime", orderCreateTime)
|
||||
.append("transactionTime", transactionTime)
|
||||
.append("status", status)
|
||||
.append("timeInForce", timeInForce)
|
||||
.append("side", side)
|
||||
.append("type", type)
|
||||
.append("tradeId", tradeId)
|
||||
.append("lastExecutedPrice", lastExecutedPrice)
|
||||
.append("lastExecutedQuantity", lastExecutedQuantity)
|
||||
.append("transactionHash", transactionHash)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OrderBook {
|
||||
private List<OrderBookEntry> asks;
|
||||
private List<OrderBookEntry> bids;
|
||||
private long height;
|
||||
|
||||
public List<OrderBookEntry> getAsks() {
|
||||
return asks;
|
||||
}
|
||||
|
||||
public void setAsks(List<OrderBookEntry> asks) {
|
||||
this.asks = asks;
|
||||
}
|
||||
|
||||
public List<OrderBookEntry> getBids() {
|
||||
return bids;
|
||||
}
|
||||
|
||||
public void setBids(List<OrderBookEntry> bids) {
|
||||
this.bids = bids;
|
||||
}
|
||||
|
||||
public long getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(long height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("asks", asks)
|
||||
.append("bids", bids)
|
||||
.append("height", height)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonDeserialize(using = OrderBookEntryDeserializer.class)
|
||||
@JsonSerialize(using = OrderBookEntrySerializer.class)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OrderBookEntry {
|
||||
private String price;
|
||||
private String quantity;
|
||||
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(String price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(String quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("price", price)
|
||||
.append("quantity", quantity)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.ObjectCodec;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class OrderBookEntryDeserializer extends JsonDeserializer<OrderBookEntry> {
|
||||
@Override
|
||||
public OrderBookEntry deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
|
||||
ObjectCodec oc = jp.getCodec();
|
||||
JsonNode node = oc.readTree(jp);
|
||||
final String price = node.get(0).asText();
|
||||
final String qty = node.get(1).asText();
|
||||
|
||||
OrderBookEntry orderBookEntry = new OrderBookEntry();
|
||||
orderBookEntry.setPrice(price);
|
||||
orderBookEntry.setQuantity(qty);
|
||||
return orderBookEntry;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class OrderBookEntrySerializer extends JsonSerializer<OrderBookEntry> {
|
||||
@Override
|
||||
public void serialize(OrderBookEntry orderBookEntry, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeStartArray();
|
||||
gen.writeString(orderBookEntry.getPrice());
|
||||
gen.writeString(orderBookEntry.getQuantity());
|
||||
gen.writeEndArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OrderList {
|
||||
private List<Order> order;
|
||||
private Long total;
|
||||
|
||||
public List<Order> getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(List<Order> order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public Long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("order", order)
|
||||
.append("total", total)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum OrderSide {
|
||||
BUY(1L), SELL(2L);
|
||||
|
||||
private long value;
|
||||
|
||||
OrderSide(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static OrderSide fromValue(long value) {
|
||||
for (OrderSide os : OrderSide.values()) {
|
||||
if (os.value == value) {
|
||||
return os;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public long toValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
public enum OrderStatus {
|
||||
Ack,
|
||||
PartialFill,
|
||||
IocNoFill,
|
||||
FullyFill,
|
||||
Canceled,
|
||||
Expired,
|
||||
Unknown
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum OrderType {
|
||||
LIMIT(2L);
|
||||
|
||||
private long value;
|
||||
|
||||
OrderType(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static OrderType fromValue(long value) {
|
||||
for (OrderType ot : OrderType.values()) {
|
||||
if (ot.value == value) {
|
||||
return ot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public long toValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Peer {
|
||||
private Boolean accelerated;
|
||||
@JsonProperty("access_addr")
|
||||
private String accessAddress;
|
||||
private List<String> capabilities;
|
||||
private String id;
|
||||
@JsonProperty("listen_addr")
|
||||
private String listenAddress;
|
||||
private String moniker;
|
||||
private String network;
|
||||
@JsonProperty("stream_addr")
|
||||
private String streamAddress;
|
||||
private String version;
|
||||
|
||||
public Boolean getAccelerated() {
|
||||
return accelerated;
|
||||
}
|
||||
|
||||
public void setAccelerated(Boolean accelerated) {
|
||||
this.accelerated = accelerated;
|
||||
}
|
||||
|
||||
public String getAccessAddress() {
|
||||
return accessAddress;
|
||||
}
|
||||
|
||||
public void setAccessAddress(String accessAddress) {
|
||||
this.accessAddress = accessAddress;
|
||||
}
|
||||
|
||||
public List<String> getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(List<String> capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getListenAddress() {
|
||||
return listenAddress;
|
||||
}
|
||||
|
||||
public void setListenAddress(String listenAddress) {
|
||||
this.listenAddress = listenAddress;
|
||||
}
|
||||
|
||||
public String getMoniker() {
|
||||
return moniker;
|
||||
}
|
||||
|
||||
public void setMoniker(String moniker) {
|
||||
this.moniker = moniker;
|
||||
}
|
||||
|
||||
public String getNetwork() {
|
||||
return network;
|
||||
}
|
||||
|
||||
public void setNetwork(String network) {
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
public String getStreamAddress() {
|
||||
return streamAddress;
|
||||
}
|
||||
|
||||
public void setStreamAddress(String streamAddress) {
|
||||
this.streamAddress = streamAddress;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("accelerated", accelerated)
|
||||
.append("accessAddress", accessAddress)
|
||||
.append("capabilities", capabilities)
|
||||
.append("id", id)
|
||||
.append("listenAddress", listenAddress)
|
||||
.append("moniker", moniker)
|
||||
.append("network", network)
|
||||
.append("streamAddress", streamAddress)
|
||||
.append("version", version)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SyncInfo {
|
||||
@JsonProperty("latest_block_hash")
|
||||
private String latestBlockHash;
|
||||
@JsonProperty("latest_app_hash")
|
||||
private String latestAppHash;
|
||||
@JsonProperty("latest_block_height")
|
||||
private Long latestBlockHeight;
|
||||
@JsonProperty("latest_block_time")
|
||||
private DateTime latestBlockTime;
|
||||
@JsonProperty("catching_up")
|
||||
private Boolean catchingUp;
|
||||
|
||||
public String getLatestBlockHash() {
|
||||
return latestBlockHash;
|
||||
}
|
||||
|
||||
public void setLatestBlockHash(String latestBlockHash) {
|
||||
this.latestBlockHash = latestBlockHash;
|
||||
}
|
||||
|
||||
public String getLatestAppHash() {
|
||||
return latestAppHash;
|
||||
}
|
||||
|
||||
public void setLatestAppHash(String latestAppHash) {
|
||||
this.latestAppHash = latestAppHash;
|
||||
}
|
||||
|
||||
public Long getLatestBlockHeight() {
|
||||
return latestBlockHeight;
|
||||
}
|
||||
|
||||
public void setLatestBlockHeight(Long latestBlockHeight) {
|
||||
this.latestBlockHeight = latestBlockHeight;
|
||||
}
|
||||
|
||||
public DateTime getLatestBlockTime() {
|
||||
return latestBlockTime;
|
||||
}
|
||||
|
||||
public void setLatestBlockTime(DateTime latestBlockTime) {
|
||||
this.latestBlockTime = latestBlockTime;
|
||||
}
|
||||
|
||||
public Boolean getCatchingUp() {
|
||||
return catchingUp;
|
||||
}
|
||||
|
||||
public void setCatchingUp(Boolean catchingUp) {
|
||||
this.catchingUp = catchingUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("latestBlockHash", latestBlockHash)
|
||||
.append("latestAppHash", latestAppHash)
|
||||
.append("latestBlockHeight", latestBlockHeight)
|
||||
.append("latestBlockTime", latestBlockTime)
|
||||
.append("catchingUp", catchingUp)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TickerStatistics {
|
||||
private String symbol;
|
||||
private String priceChange;
|
||||
private String priceChangePercent;
|
||||
private String prevClosePrice;
|
||||
private String lastPrice;
|
||||
private String lastQuantity;
|
||||
private String openPrice;
|
||||
private String highPrice;
|
||||
private String lowPrice;
|
||||
private Long openTime;
|
||||
private Long closeTime;
|
||||
private String firstId;
|
||||
private String lastId;
|
||||
private String bidPrice;
|
||||
private String bidQuantity;
|
||||
private String askPrice;
|
||||
private String askQuantity;
|
||||
private String weightedAvgPrice;
|
||||
private String volume;
|
||||
private String quoteVolume;
|
||||
private Long count;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getPriceChange() {
|
||||
return priceChange;
|
||||
}
|
||||
|
||||
public void setPriceChange(String priceChange) {
|
||||
this.priceChange = priceChange;
|
||||
}
|
||||
|
||||
public String getPriceChangePercent() {
|
||||
return priceChangePercent;
|
||||
}
|
||||
|
||||
public void setPriceChangePercent(String priceChangePercent) {
|
||||
this.priceChangePercent = priceChangePercent;
|
||||
}
|
||||
|
||||
public String getPrevClosePrice() {
|
||||
return prevClosePrice;
|
||||
}
|
||||
|
||||
public void setPrevClosePrice(String prevClosePrice) {
|
||||
this.prevClosePrice = prevClosePrice;
|
||||
}
|
||||
|
||||
public String getLastPrice() {
|
||||
return lastPrice;
|
||||
}
|
||||
|
||||
public void setLastPrice(String lastPrice) {
|
||||
this.lastPrice = lastPrice;
|
||||
}
|
||||
|
||||
public String getLastQuantity() {
|
||||
return lastQuantity;
|
||||
}
|
||||
|
||||
public void setLastQuantity(String lastQuantity) {
|
||||
this.lastQuantity = lastQuantity;
|
||||
}
|
||||
|
||||
public String getOpenPrice() {
|
||||
return openPrice;
|
||||
}
|
||||
|
||||
public void setOpenPrice(String openPrice) {
|
||||
this.openPrice = openPrice;
|
||||
}
|
||||
|
||||
public String getHighPrice() {
|
||||
return highPrice;
|
||||
}
|
||||
|
||||
public void setHighPrice(String highPrice) {
|
||||
this.highPrice = highPrice;
|
||||
}
|
||||
|
||||
public String getLowPrice() {
|
||||
return lowPrice;
|
||||
}
|
||||
|
||||
public void setLowPrice(String lowPrice) {
|
||||
this.lowPrice = lowPrice;
|
||||
}
|
||||
|
||||
public Long getOpenTime() {
|
||||
return openTime;
|
||||
}
|
||||
|
||||
public void setOpenTime(Long openTime) {
|
||||
this.openTime = openTime;
|
||||
}
|
||||
|
||||
public Long getCloseTime() {
|
||||
return closeTime;
|
||||
}
|
||||
|
||||
public void setCloseTime(Long closeTime) {
|
||||
this.closeTime = closeTime;
|
||||
}
|
||||
|
||||
public String getFirstId() {
|
||||
return firstId;
|
||||
}
|
||||
|
||||
public void setFirstId(String firstId) {
|
||||
this.firstId = firstId;
|
||||
}
|
||||
|
||||
public String getLastId() {
|
||||
return lastId;
|
||||
}
|
||||
|
||||
public void setLastId(String lastId) {
|
||||
this.lastId = lastId;
|
||||
}
|
||||
|
||||
public String getBidPrice() {
|
||||
return bidPrice;
|
||||
}
|
||||
|
||||
public void setBidPrice(String bidPrice) {
|
||||
this.bidPrice = bidPrice;
|
||||
}
|
||||
|
||||
public String getBidQuantity() {
|
||||
return bidQuantity;
|
||||
}
|
||||
|
||||
public void setBidQuantity(String bidQuantity) {
|
||||
this.bidQuantity = bidQuantity;
|
||||
}
|
||||
|
||||
public String getAskPrice() {
|
||||
return askPrice;
|
||||
}
|
||||
|
||||
public void setAskPrice(String askPrice) {
|
||||
this.askPrice = askPrice;
|
||||
}
|
||||
|
||||
public String getAskQuantity() {
|
||||
return askQuantity;
|
||||
}
|
||||
|
||||
public void setAskQuantity(String askQuantity) {
|
||||
this.askQuantity = askQuantity;
|
||||
}
|
||||
|
||||
public String getWeightedAvgPrice() {
|
||||
return weightedAvgPrice;
|
||||
}
|
||||
|
||||
public void setWeightedAvgPrice(String weightedAvgPrice) {
|
||||
this.weightedAvgPrice = weightedAvgPrice;
|
||||
}
|
||||
|
||||
public String getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(String volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public String getQuoteVolume() {
|
||||
return quoteVolume;
|
||||
}
|
||||
|
||||
public void setQuoteVolume(String quoteVolume) {
|
||||
this.quoteVolume = quoteVolume;
|
||||
}
|
||||
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Long count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("priceChange", priceChange)
|
||||
.append("priceChangePercent", priceChangePercent)
|
||||
.append("prevClosePrice", prevClosePrice)
|
||||
.append("lastPrice", lastPrice)
|
||||
.append("lastQuantity", lastQuantity)
|
||||
.append("openPrice", openPrice)
|
||||
.append("highPrice", highPrice)
|
||||
.append("lowPrice", lowPrice)
|
||||
.append("openTime", openTime)
|
||||
.append("closeTime", closeTime)
|
||||
.append("firstId", firstId)
|
||||
.append("lastId", lastId)
|
||||
.append("bidPrice", bidPrice)
|
||||
.append("bidQuantity", bidQuantity)
|
||||
.append("askPrice", askPrice)
|
||||
.append("askQuantity", askQuantity)
|
||||
.append("weightedAvgPrice", weightedAvgPrice)
|
||||
.append("volume", volume)
|
||||
.append("quoteVolume", quoteVolume)
|
||||
.append("count", count)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Time {
|
||||
@JsonProperty("ap_time")
|
||||
private DateTime apTime;
|
||||
@JsonProperty("block_time")
|
||||
private DateTime blockTime;
|
||||
|
||||
public DateTime getApTime() {
|
||||
return apTime;
|
||||
}
|
||||
|
||||
public void setApTime(DateTime apTime) {
|
||||
this.apTime = apTime;
|
||||
}
|
||||
|
||||
public DateTime getBlockTime() {
|
||||
return blockTime;
|
||||
}
|
||||
|
||||
public void setBlockTime(DateTime blockTime) {
|
||||
this.blockTime = blockTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("apTime", apTime)
|
||||
.append("blockTime", blockTime)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum TimeInForce {
|
||||
GTE(1L), IOC(3L);
|
||||
|
||||
private long value;
|
||||
|
||||
TimeInForce(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static TimeInForce fromValue(long value) {
|
||||
for (TimeInForce tif : TimeInForce.values()) {
|
||||
if (tif.value == value) {
|
||||
return tif;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public long toValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Token {
|
||||
private String name;
|
||||
private String symbol;
|
||||
@JsonProperty("original_symbol")
|
||||
private String originalSymbol;
|
||||
@JsonProperty("total_supply")
|
||||
private String totalSupply;
|
||||
private String owner;
|
||||
private boolean mintable;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getOriginalSymbol() {
|
||||
return originalSymbol;
|
||||
}
|
||||
|
||||
public void setOriginalSymbol(String originalSymbol) {
|
||||
this.originalSymbol = originalSymbol;
|
||||
}
|
||||
|
||||
public String getTotalSupply() {
|
||||
return totalSupply;
|
||||
}
|
||||
|
||||
public void setTotalSupply(String totalSupply) {
|
||||
this.totalSupply = totalSupply;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public boolean isMintable() {
|
||||
return mintable;
|
||||
}
|
||||
|
||||
public void setMintable(boolean mintable) {
|
||||
this.mintable = mintable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("name", name)
|
||||
.append("symbol", symbol)
|
||||
.append("originalSymbol", originalSymbol)
|
||||
.append("totalSupply", totalSupply)
|
||||
.append("owner", owner)
|
||||
.append("mintable", mintable)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Trade {
|
||||
private String baseAsset;
|
||||
private Long blockHeight;
|
||||
private String buyFee;
|
||||
private String buyerId;
|
||||
private String buyerOrderId;
|
||||
private String price;
|
||||
private String quantity;
|
||||
private String quoteAsset;
|
||||
private String sellFee;
|
||||
private String sellerId;
|
||||
private String sellerOrderId;
|
||||
private String symbol;
|
||||
private Long time;
|
||||
private String tradeId;
|
||||
|
||||
public String getBaseAsset() {
|
||||
return baseAsset;
|
||||
}
|
||||
|
||||
public void setBaseAsset(String baseAsset) {
|
||||
this.baseAsset = baseAsset;
|
||||
}
|
||||
|
||||
public Long getBlockHeight() {
|
||||
return blockHeight;
|
||||
}
|
||||
|
||||
public void setBlockHeight(Long blockHeight) {
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public String getBuyFee() {
|
||||
return buyFee;
|
||||
}
|
||||
|
||||
public void setBuyFee(String buyFee) {
|
||||
this.buyFee = buyFee;
|
||||
}
|
||||
|
||||
public String getBuyerId() {
|
||||
return buyerId;
|
||||
}
|
||||
|
||||
public void setBuyerId(String buyerId) {
|
||||
this.buyerId = buyerId;
|
||||
}
|
||||
|
||||
public String getBuyerOrderId() {
|
||||
return buyerOrderId;
|
||||
}
|
||||
|
||||
public void setBuyerOrderId(String buyerOrderId) {
|
||||
this.buyerOrderId = buyerOrderId;
|
||||
}
|
||||
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(String price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(String quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public String getQuoteAsset() {
|
||||
return quoteAsset;
|
||||
}
|
||||
|
||||
public void setQuoteAsset(String quoteAsset) {
|
||||
this.quoteAsset = quoteAsset;
|
||||
}
|
||||
|
||||
public String getSellFee() {
|
||||
return sellFee;
|
||||
}
|
||||
|
||||
public void setSellFee(String sellFee) {
|
||||
this.sellFee = sellFee;
|
||||
}
|
||||
|
||||
public String getSellerId() {
|
||||
return sellerId;
|
||||
}
|
||||
|
||||
public void setSellerId(String sellerId) {
|
||||
this.sellerId = sellerId;
|
||||
}
|
||||
|
||||
public String getSellerOrderId() {
|
||||
return sellerOrderId;
|
||||
}
|
||||
|
||||
public void setSellerOrderId(String sellerOrderId) {
|
||||
this.sellerOrderId = sellerOrderId;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public Long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(Long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getTradeId() {
|
||||
return tradeId;
|
||||
}
|
||||
|
||||
public void setTradeId(String tradeId) {
|
||||
this.tradeId = tradeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("baseAsset", baseAsset)
|
||||
.append("blockHeight", blockHeight)
|
||||
.append("buyFee", buyFee)
|
||||
.append("buyerId", buyerId)
|
||||
.append("buyerOrderId", buyerOrderId)
|
||||
.append("price", price)
|
||||
.append("quantity", quantity)
|
||||
.append("quoteAsset", quoteAsset)
|
||||
.append("sellFee", sellFee)
|
||||
.append("sellerId", sellerId)
|
||||
.append("sellerOrderId", sellerOrderId)
|
||||
.append("symbol", symbol)
|
||||
.append("time", time)
|
||||
.append("tradeId", tradeId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TradePage {
|
||||
private Long total;
|
||||
private List<Trade> trade;
|
||||
|
||||
public Long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<Trade> getTrade() {
|
||||
return trade;
|
||||
}
|
||||
|
||||
public void setTrade(List<Trade> trade) {
|
||||
this.trade = trade;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("total", total)
|
||||
.append("trade", trade)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TradeStatistics {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Transaction {
|
||||
private Long blockHeight;
|
||||
private Integer code;
|
||||
private Long confirmBlocks;
|
||||
private String data;
|
||||
private String fromAddr;
|
||||
private String orderId;
|
||||
private String timeStamp;
|
||||
private String toAddr;
|
||||
private Long txAge;
|
||||
private String txAsset;
|
||||
private String txFee;
|
||||
private String txHash;
|
||||
private String txType;
|
||||
private String value;
|
||||
|
||||
public Long getBlockHeight() {
|
||||
return blockHeight;
|
||||
}
|
||||
|
||||
public void setBlockHeight(Long blockHeight) {
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Long getConfirmBlocks() {
|
||||
return confirmBlocks;
|
||||
}
|
||||
|
||||
public void setConfirmBlocks(Long confirmBlocks) {
|
||||
this.confirmBlocks = confirmBlocks;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getFromAddr() {
|
||||
return fromAddr;
|
||||
}
|
||||
|
||||
public void setFromAddr(String fromAddr) {
|
||||
this.fromAddr = fromAddr;
|
||||
}
|
||||
|
||||
public String getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(String orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
public String getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
public void setTimeStamp(String timeStamp) {
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
public String getToAddr() {
|
||||
return toAddr;
|
||||
}
|
||||
|
||||
public void setToAddr(String toAddr) {
|
||||
this.toAddr = toAddr;
|
||||
}
|
||||
|
||||
public Long getTxAge() {
|
||||
return txAge;
|
||||
}
|
||||
|
||||
public void setTxAge(Long txAge) {
|
||||
this.txAge = txAge;
|
||||
}
|
||||
|
||||
public String getTxAsset() {
|
||||
return txAsset;
|
||||
}
|
||||
|
||||
public void setTxAsset(String txAsset) {
|
||||
this.txAsset = txAsset;
|
||||
}
|
||||
|
||||
public String getTxFee() {
|
||||
return txFee;
|
||||
}
|
||||
|
||||
public void setTxFee(String txFee) {
|
||||
this.txFee = txFee;
|
||||
}
|
||||
|
||||
public String getTxHash() {
|
||||
return txHash;
|
||||
}
|
||||
|
||||
public void setTxHash(String txHash) {
|
||||
this.txHash = txHash;
|
||||
}
|
||||
|
||||
public String getTxType() {
|
||||
return txType;
|
||||
}
|
||||
|
||||
public void setTxType(String txType) {
|
||||
this.txType = txType;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("blockHeight", blockHeight)
|
||||
.append("code", code)
|
||||
.append("confirmBlocks", confirmBlocks)
|
||||
.append("data", data)
|
||||
.append("fromAddr", fromAddr)
|
||||
.append("orderId", orderId)
|
||||
.append("timeStamp", timeStamp)
|
||||
.append("toAddr", toAddr)
|
||||
.append("txAge", txAge)
|
||||
.append("txAsset", txAsset)
|
||||
.append("txFee", txFee)
|
||||
.append("txHash", txHash)
|
||||
.append("txType", txType)
|
||||
.append("value", value)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TransactionMetadata {
|
||||
private int code;
|
||||
private String data;
|
||||
private String hash;
|
||||
private String log;
|
||||
private boolean ok;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return ok;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("code", code)
|
||||
.append("data", data)
|
||||
.append("hash", hash)
|
||||
.append("log", log)
|
||||
.append("ok", ok)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TransactionPage {
|
||||
private Long total;
|
||||
private List<Transaction> tx;
|
||||
|
||||
public Long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<Transaction> getTx() {
|
||||
return tx;
|
||||
}
|
||||
|
||||
public void setTx(List<Transaction> tx) {
|
||||
this.tx = tx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("total", total)
|
||||
.append("tx", tx)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
public enum TransactionType {
|
||||
NEW_ORDER,
|
||||
ISSUE_TOKEN,
|
||||
BURN_TOKEN,
|
||||
LIST_TOKEN,
|
||||
CANCEL_ORDER,
|
||||
FREEZE_TOKEN,
|
||||
UN_FREEZE_TOKEN,
|
||||
TRANSFER,
|
||||
PROPOSAL,
|
||||
VOTE;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ValidatorInfo {
|
||||
private String address;
|
||||
@JsonProperty("pub_key")
|
||||
private List<Integer> pubKey;
|
||||
@JsonProperty("voting_power")
|
||||
private Long votingPower;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public List<Integer> getPubKey() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
public void setPubKey(List<Integer> pubKey) {
|
||||
this.pubKey = pubKey;
|
||||
}
|
||||
|
||||
public Long getVotingPower() {
|
||||
return votingPower;
|
||||
}
|
||||
|
||||
public void setVotingPower(Long votingPower) {
|
||||
this.votingPower = votingPower;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("pubKey", pubKey)
|
||||
.append("votingPower", votingPower)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.wallet.binance.client.domain;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Validators {
|
||||
@JsonProperty("block_height")
|
||||
private Long blockHeight;
|
||||
private List<ValidatorInfo> validators;
|
||||
|
||||
public Long getBlockHeight() {
|
||||
return blockHeight;
|
||||
}
|
||||
|
||||
public void setBlockHeight(Long blockHeight) {
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public List<ValidatorInfo> getValidators() {
|
||||
return validators;
|
||||
}
|
||||
|
||||
public void setValidators(List<ValidatorInfo> validators) {
|
||||
this.validators = validators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("blockHeight", blockHeight)
|
||||
.append("validators", validators)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class CancelOrder {
|
||||
private String symbol;
|
||||
@JsonProperty("refid")
|
||||
private String refId;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getRefId() {
|
||||
return refId;
|
||||
}
|
||||
|
||||
public void setRefId(String refId) {
|
||||
this.refId = refId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("refId", refId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.tangem.wallet.binance.client.domain.OrderSide;
|
||||
import com.tangem.wallet.binance.client.domain.OrderType;
|
||||
import com.tangem.wallet.binance.client.domain.TimeInForce;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class NewOrder {
|
||||
private String symbol;
|
||||
private OrderType orderType;
|
||||
private OrderSide side;
|
||||
private String price;
|
||||
private String quantity;
|
||||
private TimeInForce timeInForce;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public OrderType getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
public void setOrderType(OrderType orderType) {
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(String price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(String quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public TimeInForce getTimeInForce() {
|
||||
return timeInForce;
|
||||
}
|
||||
|
||||
public void setTimeInForce(TimeInForce timeInForce) {
|
||||
this.timeInForce = timeInForce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("orderType", orderType)
|
||||
.append("side", side)
|
||||
.append("price", price)
|
||||
.append("quantity", quantity)
|
||||
.append("timeInForce", timeInForce)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class TokenFreeze {
|
||||
private String symbol;
|
||||
private String amount;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(String amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class TokenUnfreeze {
|
||||
private String symbol;
|
||||
private String amount;
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(String amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("symbol", symbol)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* Optional fields for Bianace DEX standard transaction
|
||||
*/
|
||||
public class TransactionOption {
|
||||
|
||||
public static final TransactionOption DEFAULT_INSTANCE =
|
||||
new TransactionOption("", BinanceDexConstants.BINANCE_DEX_API_CLIENT_JAVA_SOURCE, null);
|
||||
|
||||
private String memo;
|
||||
private long source;
|
||||
private byte[] data;
|
||||
|
||||
public TransactionOption(String memo, long source, byte[] data) {
|
||||
this.memo = memo;
|
||||
this.source = source;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getMemo() {
|
||||
return memo;
|
||||
}
|
||||
|
||||
public void setMemo(String memo) {
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
public long getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(long source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("memo", memo)
|
||||
.append("source", source)
|
||||
.append("data", data)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.wallet.binance.client.domain.broadcast;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class Transfer {
|
||||
private String fromAddress;
|
||||
private String toAddress;
|
||||
private String coin;
|
||||
private String amount;
|
||||
|
||||
public String getFromAddress() {
|
||||
return fromAddress;
|
||||
}
|
||||
|
||||
public void setFromAddress(String fromAddress) {
|
||||
this.fromAddress = fromAddress;
|
||||
}
|
||||
|
||||
public String getToAddress() {
|
||||
return toAddress;
|
||||
}
|
||||
|
||||
public void setToAddress(String toAddress) {
|
||||
this.toAddress = toAddress;
|
||||
}
|
||||
|
||||
public String getCoin() {
|
||||
return coin;
|
||||
}
|
||||
|
||||
public void setCoin(String coin) {
|
||||
this.coin = coin;
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(String amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("fromAddress", fromAddress)
|
||||
.append("toAddress", toAddress)
|
||||
.append("coin", coin)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.wallet.binance.client.domain.request;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.tangem.wallet.binance.client.domain.OrderSide;
|
||||
import com.tangem.wallet.binance.client.domain.OrderStatus;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ClosedOrdersRequest {
|
||||
private String address;
|
||||
private Long end;
|
||||
private Integer limit;
|
||||
private Integer offset;
|
||||
private OrderSide side;
|
||||
private Long start;
|
||||
private List<OrderStatus> status;
|
||||
private String symbol;
|
||||
private Integer total;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Long end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public Integer getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(Integer offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public Long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Long start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public List<OrderStatus> getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(List<OrderStatus> status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public Integer getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Integer total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("end", end)
|
||||
.append("limit", limit)
|
||||
.append("offset", offset)
|
||||
.append("side", side)
|
||||
.append("start", start)
|
||||
.append("status", status)
|
||||
.append("symbol", symbol)
|
||||
.append("total", total)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.wallet.binance.client.domain.request;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class OpenOrdersRequest {
|
||||
private String address;
|
||||
private Integer limit;
|
||||
private Integer offset;
|
||||
private String symbol;
|
||||
private Integer total;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public Integer getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(Integer offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public Integer getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Integer total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("limit", limit)
|
||||
.append("offset", offset)
|
||||
.append("symbol", symbol)
|
||||
.append("total", total)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.wallet.binance.client.domain.request;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.tangem.wallet.binance.client.domain.OrderSide;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class TradesRequest {
|
||||
private String address;
|
||||
private String buyerOrderId;
|
||||
private Long end;
|
||||
private Long height;
|
||||
private Integer limit;
|
||||
private Integer offset;
|
||||
private String quoteAsset;
|
||||
private String sellerOrderId;
|
||||
private OrderSide side;
|
||||
private Long start;
|
||||
private String symbol;
|
||||
private Integer total;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getBuyerOrderId() {
|
||||
return buyerOrderId;
|
||||
}
|
||||
|
||||
public void setBuyerOrderId(String buyerOrderId) {
|
||||
this.buyerOrderId = buyerOrderId;
|
||||
}
|
||||
|
||||
public Long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Long end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Long getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(Long height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public Integer getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(Integer offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public String getQuoteAsset() {
|
||||
return quoteAsset;
|
||||
}
|
||||
|
||||
public void setQuoteAsset(String quoteAsset) {
|
||||
this.quoteAsset = quoteAsset;
|
||||
}
|
||||
|
||||
public String getSellerOrderId() {
|
||||
return sellerOrderId;
|
||||
}
|
||||
|
||||
public void setSellerOrderId(String sellerOrderId) {
|
||||
this.sellerOrderId = sellerOrderId;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public Long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Long start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public Integer getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Integer total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("buyerOrderId", buyerOrderId)
|
||||
.append("end", end)
|
||||
.append("height", height)
|
||||
.append("limit", limit)
|
||||
.append("offset", offset)
|
||||
.append("quoteAsset", quoteAsset)
|
||||
.append("sellerOrderId", sellerOrderId)
|
||||
.append("side", side)
|
||||
.append("start", start)
|
||||
.append("symbol", symbol)
|
||||
.append("total", total)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.wallet.binance.client.domain.request;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.tangem.wallet.binance.client.domain.OrderSide;
|
||||
import com.tangem.wallet.binance.client.domain.TransactionType;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
public class TransactionsRequest {
|
||||
private String address;
|
||||
private Long blockHeight;
|
||||
private Long endTime;
|
||||
private Integer limit;
|
||||
private Integer offset;
|
||||
private OrderSide side;
|
||||
private Long startTime;
|
||||
private String txAsset;
|
||||
private TransactionType txType;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Long getBlockHeight() {
|
||||
return blockHeight;
|
||||
}
|
||||
|
||||
public void setBlockHeight(Long blockHeight) {
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public Long getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(Long endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public Integer getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(Integer offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public Long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(Long startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public String getTxAsset() {
|
||||
return txAsset;
|
||||
}
|
||||
|
||||
public void setTxAsset(String txAsset) {
|
||||
this.txAsset = txAsset;
|
||||
}
|
||||
|
||||
public TransactionType getTxType() {
|
||||
return txType;
|
||||
}
|
||||
|
||||
public void setTxType(TransactionType txType) {
|
||||
this.txType = txType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("blockHeight", blockHeight)
|
||||
.append("endTime", endTime)
|
||||
.append("limit", limit)
|
||||
.append("offset", offset)
|
||||
.append("side", side)
|
||||
.append("startTime", startTime)
|
||||
.append("txAsset", txAsset)
|
||||
.append("txType", txType)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2015 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tangem.wallet.binance.client.encoding;
|
||||
|
||||
import org.bitcoinj.core.Base58;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
// TODO: Copied from https://github.com/bitcoinj/bitcoinj. Remove these files after they are included in a new bitconj release
|
||||
public class AddressFormatException extends IllegalArgumentException {
|
||||
public AddressFormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AddressFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and a character isn't valid. You shouldn't allow the user to proceed in this
|
||||
* case.
|
||||
*/
|
||||
public static class InvalidCharacter extends AddressFormatException {
|
||||
public final char character;
|
||||
public final int position;
|
||||
|
||||
public InvalidCharacter(char character, int position) {
|
||||
super("Invalid character '" + Character.toString(character) + "' at position " + position);
|
||||
this.character = character;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and the data isn't of the right size. You shouldn't allow the user to proceed
|
||||
* in this case.
|
||||
*/
|
||||
public static class InvalidDataLength extends AddressFormatException {
|
||||
public InvalidDataLength() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidDataLength(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and the checksum isn't valid. You shouldn't allow the user to proceed in this
|
||||
* case.
|
||||
*/
|
||||
public static class InvalidChecksum extends AddressFormatException {
|
||||
public InvalidChecksum() {
|
||||
super("Checksum does not validate");
|
||||
}
|
||||
|
||||
public InvalidChecksum(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by the {@link PrefixedChecksummedBytes} hierarchy of classes when you try and decode an
|
||||
* address or private key with an invalid prefix (version header or human-readable part). You shouldn't allow the
|
||||
* user to proceed in this case.
|
||||
*/
|
||||
public static class InvalidPrefix extends AddressFormatException {
|
||||
public InvalidPrefix() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidPrefix(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by the {@link PrefixedChecksummedBytes} hierarchy of classes when you try and decode an
|
||||
* address with a prefix (version header or human-readable part) that used by another network (usually: mainnet vs
|
||||
* testnet). You shouldn't allow the user to proceed in this case as they are trying to send money across different
|
||||
* chains, an operation that is guaranteed to destroy the money.
|
||||
*/
|
||||
public static class WrongNetwork extends InvalidPrefix {
|
||||
public WrongNetwork(int versionHeader) {
|
||||
super("Version code of address did not match acceptable versions for network: " + versionHeader);
|
||||
}
|
||||
|
||||
public WrongNetwork(String hrp) {
|
||||
super("Human readable part of address did not match acceptable HRPs for network: " + hrp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright 2018 Coinomi Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tangem.wallet.binance.client.encoding;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
// TODO: Copied from https://github.com/bitcoinj/bitcoinj. Remove these files after they are included in a new bitconj release
|
||||
public class Bech32 {
|
||||
/**
|
||||
* The io.nayuki.bitcoin.crypto.Bech32 character set for encoding.
|
||||
*/
|
||||
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
||||
/**
|
||||
* The io.nayuki.bitcoin.crypto.Bech32 character set for decoding.
|
||||
*/
|
||||
private static final byte[] CHARSET_REV = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
|
||||
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
||||
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
|
||||
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
||||
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
|
||||
};
|
||||
|
||||
public static class Bech32Data {
|
||||
final String hrp;
|
||||
final byte[] data;
|
||||
|
||||
private Bech32Data(final String hrp, final byte[] data) {
|
||||
this.hrp = hrp;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getHrp() {
|
||||
return hrp;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the polynomial with value coefficients mod the generator as 30-bit.
|
||||
*/
|
||||
private static int polymod(final byte[] values) {
|
||||
int c = 1;
|
||||
for (byte v_i : values) {
|
||||
int c0 = (c >>> 25) & 0xff;
|
||||
c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff);
|
||||
if ((c0 & 1) != 0) c ^= 0x3b6a57b2;
|
||||
if ((c0 & 2) != 0) c ^= 0x26508e6d;
|
||||
if ((c0 & 4) != 0) c ^= 0x1ea119fa;
|
||||
if ((c0 & 8) != 0) c ^= 0x3d4233dd;
|
||||
if ((c0 & 16) != 0) c ^= 0x2a1462b3;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a HRP for use in checksum computation.
|
||||
*/
|
||||
private static byte[] expandHrp(final String hrp) {
|
||||
int hrpLength = hrp.length();
|
||||
byte ret[] = new byte[hrpLength * 2 + 1];
|
||||
for (int i = 0; i < hrpLength; ++i) {
|
||||
int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII
|
||||
ret[i] = (byte) ((c >>> 5) & 0x07);
|
||||
ret[i + hrpLength + 1] = (byte) (c & 0x1f);
|
||||
}
|
||||
ret[hrpLength] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a checksum.
|
||||
*/
|
||||
private static boolean verifyChecksum(final String hrp, final byte[] values) {
|
||||
byte[] hrpExpanded = expandHrp(hrp);
|
||||
byte[] combined = new byte[hrpExpanded.length + values.length];
|
||||
System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length);
|
||||
System.arraycopy(values, 0, combined, hrpExpanded.length, values.length);
|
||||
return polymod(combined) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a checksum.
|
||||
*/
|
||||
private static byte[] createChecksum(final String hrp, final byte[] values) {
|
||||
byte[] hrpExpanded = expandHrp(hrp);
|
||||
byte[] enc = new byte[hrpExpanded.length + values.length + 6];
|
||||
System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length);
|
||||
System.arraycopy(values, 0, enc, hrpExpanded.length, values.length);
|
||||
int mod = polymod(enc) ^ 1;
|
||||
byte[] ret = new byte[6];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a io.nayuki.bitcoin.crypto.Bech32 string.
|
||||
*/
|
||||
public static String encode(final Bech32Data bech32) {
|
||||
return encode(bech32.hrp, bech32.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a io.nayuki.bitcoin.crypto.Bech32 string.
|
||||
*/
|
||||
public static String encode(String hrp, final byte[] values) {
|
||||
checkArgument(hrp.length() >= 1, "Human-readable part is too short");
|
||||
checkArgument(hrp.length() <= 83, "Human-readable part is too long");
|
||||
hrp = hrp.toLowerCase(Locale.ROOT);
|
||||
byte[] checksum = createChecksum(hrp, values);
|
||||
byte[] combined = new byte[values.length + checksum.length];
|
||||
System.arraycopy(values, 0, combined, 0, values.length);
|
||||
System.arraycopy(checksum, 0, combined, values.length, checksum.length);
|
||||
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
|
||||
sb.append(hrp);
|
||||
sb.append('1');
|
||||
for (byte b : combined) {
|
||||
sb.append(CHARSET.charAt(b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a io.nayuki.bitcoin.crypto.Bech32 string.
|
||||
*/
|
||||
public static Bech32Data decode(final String str) throws AddressFormatException {
|
||||
boolean lower = false, upper = false;
|
||||
if (str.length() < 8)
|
||||
throw new AddressFormatException.InvalidDataLength("Input too short: " + str.length());
|
||||
if (str.length() > 90)
|
||||
throw new AddressFormatException.InvalidDataLength("Input too long: " + str.length());
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
char c = str.charAt(i);
|
||||
if (c < 33 || c > 126) throw new AddressFormatException.InvalidCharacter(c, i);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
if (upper)
|
||||
throw new AddressFormatException.InvalidCharacter(c, i);
|
||||
lower = true;
|
||||
}
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
if (lower)
|
||||
throw new AddressFormatException.InvalidCharacter(c, i);
|
||||
upper = true;
|
||||
}
|
||||
}
|
||||
final int pos = str.lastIndexOf('1');
|
||||
if (pos < 1) throw new AddressFormatException.InvalidPrefix("Missing human-readable part");
|
||||
final int dataPartLength = str.length() - 1 - pos;
|
||||
if (dataPartLength < 6)
|
||||
throw new AddressFormatException.InvalidDataLength("Data part too short: " + dataPartLength);
|
||||
byte[] values = new byte[dataPartLength];
|
||||
for (int i = 0; i < dataPartLength; ++i) {
|
||||
char c = str.charAt(i + pos + 1);
|
||||
if (CHARSET_REV[c] == -1) throw new AddressFormatException.InvalidCharacter(c, i + pos + 1);
|
||||
values[i] = CHARSET_REV[c];
|
||||
}
|
||||
String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT);
|
||||
if (!verifyChecksum(hrp, values)) throw new AddressFormatException.InvalidChecksum();
|
||||
return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.wallet.binance.client.encoding;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.crypto.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
|
||||
public class Crypto {
|
||||
|
||||
private static final String HD_PATH = "44H/714H/0H/0/0";
|
||||
|
||||
public static byte[] sign(byte[] msg, String privateKey) throws NoSuchAlgorithmException {
|
||||
ECKey k = ECKey.fromPrivate(new BigInteger(privateKey, 16));
|
||||
|
||||
return sign(msg, k);
|
||||
}
|
||||
|
||||
public static byte[] sign(byte[] msg, ECKey k) throws NoSuchAlgorithmException {
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] msgHash = digest.digest(msg);
|
||||
|
||||
ECKey.ECDSASignature signature = k.sign(Sha256Hash.wrap(msgHash));
|
||||
|
||||
byte[] result = new byte[64];
|
||||
System.arraycopy(Utils.bigIntegerToBytes(signature.r, 32), 0, result, 0, 32);
|
||||
System.arraycopy(Utils.bigIntegerToBytes(signature.s, 32), 0, result, 32, 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] decodeAddress(String address) throws SegwitAddressException {
|
||||
byte[] dec = Bech32.decode(address).getData();
|
||||
return convertBits(dec, 0, dec.length, 5, 8, false);
|
||||
}
|
||||
|
||||
public static String getAddressFromPrivateKey(String privateKey, String hrp) {
|
||||
ECKey ecKey = ECKey.fromPrivate(new BigInteger(privateKey, 16));
|
||||
return getAddressFromECKey(ecKey, hrp);
|
||||
}
|
||||
|
||||
public static String getAddressFromECKey(ECKey ecKey, String hrp) {
|
||||
byte[] hash = ecKey.getPubKeyHash();
|
||||
return Bech32.encode(hrp, convertBits(hash, 0, hash.length, 8, 5, false));
|
||||
}
|
||||
|
||||
public static String getPrivateKeyFromMnemonicCode(List<String> words) {
|
||||
byte[] seed = MnemonicCode.INSTANCE.toSeed(words, "");
|
||||
DeterministicKey key = HDKeyDerivation.createMasterPrivateKey(seed);
|
||||
|
||||
List<ChildNumber> childNumbers = HDUtils.parsePath(HD_PATH);
|
||||
for (ChildNumber cn : childNumbers) {
|
||||
key = HDKeyDerivation.deriveChildKey(key, cn);
|
||||
}
|
||||
return key.getPrivateKeyAsHex();
|
||||
}
|
||||
|
||||
public static List<String> generateMnemonicCode() {
|
||||
byte[] entrophy = new byte[256 / 8];
|
||||
new SecureRandom().nextBytes(entrophy);
|
||||
try {
|
||||
return MnemonicCode.INSTANCE.toMnemonic(entrophy);
|
||||
} catch (MnemonicException.MnemonicLengthException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SegwitAddressException extends IllegalArgumentException {
|
||||
SegwitAddressException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
SegwitAddressException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* see https://github.com/sipa/bech32/pull/40/files
|
||||
*/
|
||||
public static byte[] convertBits(final byte[] in, final int inStart, final int inLen,
|
||||
final int fromBits, final int toBits, final boolean pad)
|
||||
throws SegwitAddressException {
|
||||
int acc = 0;
|
||||
int bits = 0;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(64);
|
||||
final int maxv = (1 << toBits) - 1;
|
||||
final int max_acc = (1 << (fromBits + toBits - 1)) - 1;
|
||||
for (int i = 0; i < inLen; i++) {
|
||||
int value = in[i + inStart] & 0xff;
|
||||
if ((value >>> fromBits) != 0) {
|
||||
throw new SegwitAddressException(String.format(
|
||||
"Input value '%X' exceeds '%d' bit size", value, fromBits));
|
||||
}
|
||||
acc = ((acc << fromBits) | value) & max_acc;
|
||||
bits += fromBits;
|
||||
while (bits >= toBits) {
|
||||
bits -= toBits;
|
||||
out.write((acc >>> bits) & maxv);
|
||||
}
|
||||
}
|
||||
if (pad) {
|
||||
if (bits > 0) out.write((acc << (toBits - bits)) & maxv);
|
||||
} else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) != 0) {
|
||||
throw new SegwitAddressException("Could not convert bits, invalid padding");
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.wallet.binance.client.encoding;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.google.protobuf.CodedOutputStream;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class EncodeUtils {
|
||||
private static final ObjectWriter OBJECT_WRITER;
|
||||
|
||||
static {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
OBJECT_WRITER = mapper.writer();
|
||||
}
|
||||
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
return Hex.decode(s);
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
return Hex.toHexString(bytes);
|
||||
}
|
||||
|
||||
public static String toJsonStringSortKeys(Object object) throws JsonProcessingException {
|
||||
return OBJECT_WRITER.writeValueAsString(object);
|
||||
}
|
||||
|
||||
public static byte[] toJsonEncodeBytes(Object object) throws JsonProcessingException {
|
||||
return toJsonStringSortKeys(object).getBytes(Charset.forName("UTF-8"));
|
||||
}
|
||||
|
||||
public static byte[] aminoWrap(byte[] raw, byte[] typePrefix, boolean isPrefixLength) throws IOException {
|
||||
int totalLen = raw.length + typePrefix.length;
|
||||
if (isPrefixLength)
|
||||
totalLen += CodedOutputStream.computeUInt64SizeNoTag(totalLen);
|
||||
|
||||
byte[] msg = new byte[totalLen];
|
||||
CodedOutputStream cos = CodedOutputStream.newInstance(msg);
|
||||
if (isPrefixLength)
|
||||
cos.writeUInt64NoTag(raw.length + typePrefix.length);
|
||||
cos.write(typePrefix, 0, typePrefix.length);
|
||||
cos.write(raw, 0, raw.length);
|
||||
cos.flush();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
public interface BinanceDexTransactionMessage {
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class CancelOrderMessage implements BinanceDexTransactionMessage {
|
||||
private String sender;
|
||||
private String symbol;
|
||||
@JsonProperty("refid")
|
||||
private String refId;
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public void setSender(String sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String getRefId() {
|
||||
return refId;
|
||||
}
|
||||
|
||||
public void setRefId(String refId) {
|
||||
this.refId = refId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("sender", sender)
|
||||
.append("symbol", symbol)
|
||||
.append("refId", refId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class InputOutput {
|
||||
private String address;
|
||||
private List<Token> coins;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public List<Token> getCoins() {
|
||||
return coins;
|
||||
}
|
||||
|
||||
public void setCoins(List<Token> coins) {
|
||||
this.coins = coins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("address", address)
|
||||
.append("coins", coins)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.encoding.EncodeUtils;
|
||||
|
||||
/**
|
||||
* Binance dex standard transactiont types.
|
||||
*/
|
||||
public enum MessageType {
|
||||
Send("2A2C87FA"),
|
||||
NewOrder("CE6DC043"),
|
||||
CancelOrder("166E681B"),
|
||||
TokenFreeze("E774B32D"),
|
||||
TokenUnfreeze("6515FF0D"),
|
||||
StdSignature(null),
|
||||
PubKey("EB5AE987"),
|
||||
StdTx("F0625DEE");
|
||||
|
||||
private byte[] typePrefixBytes;
|
||||
|
||||
MessageType(String typePrefix) {
|
||||
if (typePrefix == null) {
|
||||
this.typePrefixBytes = new byte[0];
|
||||
} else
|
||||
this.typePrefixBytes = EncodeUtils.hexStringToByteArray(typePrefix);
|
||||
}
|
||||
|
||||
public byte[] getTypePrefixBytes() {
|
||||
return typePrefixBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.domain.OrderSide;
|
||||
import com.tangem.wallet.binance.client.domain.OrderType;
|
||||
import com.tangem.wallet.binance.client.domain.TimeInForce;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class NewOrderMessage implements BinanceDexTransactionMessage {
|
||||
private String id;
|
||||
@JsonProperty("ordertype")
|
||||
private OrderType orderType;
|
||||
private long price;
|
||||
private long quantity;
|
||||
private String sender;
|
||||
private OrderSide side;
|
||||
private String symbol;
|
||||
@JsonProperty("timeinforce")
|
||||
private TimeInForce timeInForce;
|
||||
|
||||
private NewOrderMessage() {
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public void setSender(String sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public OrderType getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
public void setOrderType(OrderType orderType) {
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public void setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(long price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(long quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public TimeInForce getTimeInForce() {
|
||||
return timeInForce;
|
||||
}
|
||||
|
||||
public void setTimeInForce(TimeInForce timeInForce) {
|
||||
this.timeInForce = timeInForce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("sender", sender)
|
||||
.append("id", id)
|
||||
.append("symbol", symbol)
|
||||
.append("orderType", orderType)
|
||||
.append("side", side)
|
||||
.append("price", price)
|
||||
.append("quantity", quantity)
|
||||
.append("timeInForce", timeInForce)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static NewOrderBuilder newBuilder() {
|
||||
return new NewOrderBuilder();
|
||||
}
|
||||
|
||||
public NewOrderBuilder toBuilder() {
|
||||
return newBuilder()
|
||||
.setSender(this.sender)
|
||||
.setId(this.id)
|
||||
.setSymbol(this.symbol)
|
||||
.setOrderType(this.orderType)
|
||||
.setSide(this.side)
|
||||
.setPrice(TransactionRequestAssembler.longToDouble(this.price))
|
||||
.setQuantity(TransactionRequestAssembler.longToDouble(this.quantity))
|
||||
.setTimeInForce(this.timeInForce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for NewOrderMessage transaction. It handles price/quantity conversion from double to long.
|
||||
*/
|
||||
public static class NewOrderBuilder {
|
||||
private String id;
|
||||
private OrderType orderType;
|
||||
private String price;
|
||||
private String quantity;
|
||||
private String sender;
|
||||
private OrderSide side;
|
||||
private String symbol;
|
||||
private TimeInForce timeInForce;
|
||||
|
||||
public NewOrderMessage build() {
|
||||
NewOrderMessage newOrder = new NewOrderMessage();
|
||||
newOrder.setId(id);
|
||||
newOrder.setOrderType(orderType);
|
||||
newOrder.setPrice(TransactionRequestAssembler.doubleToLong(price));
|
||||
newOrder.setQuantity(TransactionRequestAssembler.doubleToLong(quantity));
|
||||
newOrder.setSender(sender);
|
||||
newOrder.setSide(side);
|
||||
newOrder.setSymbol(symbol);
|
||||
newOrder.setTimeInForce(timeInForce);
|
||||
return newOrder;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderType getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setOrderType(OrderType orderType) {
|
||||
this.orderType = orderType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setPrice(String price) {
|
||||
this.price = price;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setQuantity(String quantity) {
|
||||
this.quantity = quantity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setSender(String sender) {
|
||||
this.sender = sender;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderSide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setSide(OrderSide side) {
|
||||
this.side = side;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TimeInForce getTimeInForce() {
|
||||
return timeInForce;
|
||||
}
|
||||
|
||||
public NewOrderBuilder setTimeInForce(TimeInForce timeInForce) {
|
||||
this.timeInForce = timeInForce;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class SignData {
|
||||
@JsonProperty("chain_id")
|
||||
private String chainId;
|
||||
@JsonProperty("account_number")
|
||||
private String accountNumber;
|
||||
private String sequence;
|
||||
private String memo;
|
||||
private BinanceDexTransactionMessage[] msgs;
|
||||
private String source;
|
||||
private byte[] data;
|
||||
|
||||
public String getChainId() {
|
||||
return chainId;
|
||||
}
|
||||
|
||||
public void setChainId(String chainId) {
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
public String getAccountNumber() {
|
||||
return accountNumber;
|
||||
}
|
||||
|
||||
public void setAccountNumber(String accountNumber) {
|
||||
this.accountNumber = accountNumber;
|
||||
}
|
||||
|
||||
public String getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public void setSequence(String sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
public String getMemo() {
|
||||
return memo;
|
||||
}
|
||||
|
||||
public void setMemo(String memo) {
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
public BinanceDexTransactionMessage[] getMsgs() {
|
||||
return msgs;
|
||||
}
|
||||
|
||||
public void setMsgs(BinanceDexTransactionMessage[] msgs) {
|
||||
this.msgs = msgs;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("chainId", chainId)
|
||||
.append("accountNumber", accountNumber)
|
||||
.append("sequence", sequence)
|
||||
.append("memo", memo)
|
||||
.append("msgs", msgs)
|
||||
.append("source", source)
|
||||
.append("data", data)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class Token {
|
||||
private String denom;
|
||||
private Long amount;
|
||||
|
||||
public String getDenom() {
|
||||
return denom;
|
||||
}
|
||||
|
||||
public void setDenom(String denom) {
|
||||
this.denom = denom;
|
||||
}
|
||||
|
||||
public Long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("denom", denom)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class TokenFreezeMessage implements BinanceDexTransactionMessage {
|
||||
private String from;
|
||||
private String symbol;
|
||||
private long amount;
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("from", from)
|
||||
.append("symbol", symbol)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class TokenUnfreezeMessage implements BinanceDexTransactionMessage {
|
||||
private String from;
|
||||
private String symbol;
|
||||
|
||||
private long amount;
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("from", from)
|
||||
.append("symbol", symbol)
|
||||
.append("amount", amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.Wallet;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.TokenFreeze;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.TokenUnfreeze;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.TransactionOption;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.Transfer;
|
||||
import com.tangem.wallet.binance.client.encoding.Crypto;
|
||||
import com.tangem.wallet.binance.client.encoding.EncodeUtils;
|
||||
import com.tangem.wallet.binance.proto.StdSignature;
|
||||
import com.tangem.wallet.binance.proto.StdTx;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.protobuf.ByteString;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Assemble a transaction message body.
|
||||
* https://testnet-dex.binance.org/doc/encoding.html
|
||||
*/
|
||||
public class TransactionRequestAssembler {
|
||||
private static final okhttp3.MediaType MEDIA_TYPE = okhttp3.MediaType.parse("text/plain; charset=utf-8");
|
||||
private static final BigDecimal MULTIPLY_FACTOR = BigDecimal.valueOf(1e8);
|
||||
private static final BigDecimal MAX_NUMBER = new BigDecimal(Long.MAX_VALUE);
|
||||
|
||||
private Wallet wallet;
|
||||
private TransactionOption options;
|
||||
|
||||
public TransactionRequestAssembler(Wallet wallet, TransactionOption options) {
|
||||
this.wallet = wallet;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public static long doubleToLong(String d) {
|
||||
BigDecimal encodeValue = new BigDecimal(d).multiply(MULTIPLY_FACTOR);
|
||||
if (encodeValue.compareTo(MAX_NUMBER) > 0) {
|
||||
throw new IllegalArgumentException(d + " is too large.");
|
||||
}
|
||||
return encodeValue.longValue();
|
||||
|
||||
}
|
||||
|
||||
public static String longToDouble(long l) {
|
||||
return BigDecimal.valueOf(l).divide(MULTIPLY_FACTOR).toString();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] sign(BinanceDexTransactionMessage msg)
|
||||
throws JsonProcessingException, NoSuchAlgorithmException {
|
||||
SignData sd = new SignData();
|
||||
sd.setChainId(wallet.getChainId());
|
||||
sd.setAccountNumber(String.valueOf(wallet.getAccountNumber()));
|
||||
sd.setSequence(String.valueOf(wallet.getSequence()));
|
||||
sd.setMsgs(new BinanceDexTransactionMessage[]{msg});
|
||||
|
||||
sd.setMemo(options.getMemo());
|
||||
sd.setSource(String.valueOf(options.getSource()));
|
||||
sd.setData(options.getData());
|
||||
return Crypto.sign(EncodeUtils.toJsonEncodeBytes(sd), wallet.getEcKey());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeSignature(byte[] signatureBytes) throws IOException {
|
||||
StdSignature stdSignature = StdSignature.newBuilder().setPubKey(ByteString.copyFrom(wallet.getPubKeyForSign()))
|
||||
.setSignature(ByteString.copyFrom(signatureBytes))
|
||||
.setAccountNumber(wallet.getAccountNumber())
|
||||
.setSequence(wallet.getSequence())
|
||||
.build();
|
||||
|
||||
return EncodeUtils.aminoWrap(
|
||||
stdSignature.toByteArray(), MessageType.StdSignature.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeStdTx(byte[] msg, byte[] signature) throws IOException {
|
||||
StdTx.Builder stdTxBuilder = StdTx.newBuilder()
|
||||
.addMsgs(ByteString.copyFrom(msg))
|
||||
.addSignatures(ByteString.copyFrom(signature))
|
||||
.setMemo(options.getMemo())
|
||||
.setSource(options.getSource());
|
||||
if (options.getData() != null) {
|
||||
stdTxBuilder = stdTxBuilder.setData(ByteString.copyFrom(options.getData()));
|
||||
}
|
||||
StdTx stdTx = stdTxBuilder.build();
|
||||
return EncodeUtils.aminoWrap(stdTx.toByteArray(), MessageType.StdTx.getTypePrefixBytes(), true);
|
||||
}
|
||||
|
||||
private RequestBody createRequestBody(byte[] stdTx) {
|
||||
return RequestBody.create(MEDIA_TYPE, EncodeUtils.bytesToHex(stdTx));
|
||||
}
|
||||
|
||||
private String generateOrderId() {
|
||||
return EncodeUtils.bytesToHex(wallet.getAddressBytes()).toUpperCase() + "-" + (wallet.getSequence() + 1);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
NewOrderMessage createNewOrderMessage(
|
||||
com.tangem.wallet.binance.client.domain.broadcast.NewOrder newOrder) {
|
||||
return NewOrderMessage.newBuilder()
|
||||
.setId(generateOrderId())
|
||||
.setOrderType(newOrder.getOrderType())
|
||||
.setPrice(newOrder.getPrice())
|
||||
.setQuantity(newOrder.getQuantity())
|
||||
.setSender(wallet.getAddress())
|
||||
.setSide(newOrder.getSide())
|
||||
.setSymbol(newOrder.getSymbol())
|
||||
.setTimeInForce(newOrder.getTimeInForce())
|
||||
.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeNewOrderMessage(NewOrderMessage newOrder)
|
||||
throws IOException {
|
||||
com.tangem.wallet.binance.proto.NewOrder proto = com.tangem.wallet.binance.proto.NewOrder.newBuilder()
|
||||
.setSender(ByteString.copyFrom(wallet.getAddressBytes()))
|
||||
.setId(newOrder.getId())
|
||||
.setSymbol(newOrder.getSymbol())
|
||||
.setOrdertype(newOrder.getOrderType().toValue())
|
||||
.setSide(newOrder.getSide().toValue())
|
||||
.setPrice(newOrder.getPrice())
|
||||
.setQuantity(newOrder.getQuantity())
|
||||
.setTimeinforce(newOrder.getTimeInForce().toValue())
|
||||
.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.NewOrder.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public RequestBody buildNewOrder(com.tangem.wallet.binance.client.domain.broadcast.NewOrder newOrder)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
NewOrderMessage msgBean = createNewOrderMessage(newOrder);
|
||||
byte[] msg = encodeNewOrderMessage(msgBean);
|
||||
byte[] signature = encodeSignature(sign(msgBean));
|
||||
byte[] stdTx = encodeStdTx(msg, signature);
|
||||
return createRequestBody(stdTx);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CancelOrderMessage createCancelOrderMessage(
|
||||
com.tangem.wallet.binance.client.domain.broadcast.CancelOrder cancelOrder) {
|
||||
CancelOrderMessage bean =
|
||||
new CancelOrderMessage();
|
||||
bean.setRefId(cancelOrder.getRefId());
|
||||
bean.setSymbol(cancelOrder.getSymbol());
|
||||
bean.setSender(wallet.getAddress());
|
||||
return bean;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeCancelOrderMessage(CancelOrderMessage cancelOrder)
|
||||
throws IOException {
|
||||
com.tangem.wallet.binance.proto.CancelOrder proto = com.tangem.wallet.binance.proto.CancelOrder.newBuilder()
|
||||
.setSender(ByteString.copyFrom(wallet.getAddressBytes()))
|
||||
.setSymbol(cancelOrder.getSymbol())
|
||||
.setRefid(cancelOrder.getRefId())
|
||||
.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.CancelOrder.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public RequestBody buildCancelOrder(com.tangem.wallet.binance.client.domain.broadcast.CancelOrder cancelOrder)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
CancelOrderMessage msgBean = createCancelOrderMessage(cancelOrder);
|
||||
byte[] msg = encodeCancelOrderMessage(msgBean);
|
||||
byte[] signature = encodeSignature(sign(msgBean));
|
||||
byte[] stdTx = encodeStdTx(msg, signature);
|
||||
return createRequestBody(stdTx);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TransferMessage createTransferMessage(Transfer transfer) {
|
||||
Token token = new Token();
|
||||
token.setDenom(transfer.getCoin());
|
||||
token.setAmount(doubleToLong(transfer.getAmount()));
|
||||
List<Token> coins = Collections.singletonList(token);
|
||||
|
||||
InputOutput input = new InputOutput();
|
||||
input.setAddress(transfer.getFromAddress());
|
||||
input.setCoins(coins);
|
||||
InputOutput output = new InputOutput();
|
||||
output.setAddress(transfer.getToAddress());
|
||||
output.setCoins(coins);
|
||||
|
||||
TransferMessage msgBean = new TransferMessage();
|
||||
msgBean.setInputs(Collections.singletonList(input));
|
||||
msgBean.setOutputs(Collections.singletonList(output));
|
||||
return msgBean;
|
||||
}
|
||||
|
||||
private com.tangem.wallet.binance.proto.Send.Input toProtoInput(InputOutput input) {
|
||||
byte[] address = Crypto.decodeAddress(input.getAddress());
|
||||
com.tangem.wallet.binance.proto.Send.Input.Builder builder =
|
||||
com.tangem.wallet.binance.proto.Send.Input.newBuilder().setAddress(ByteString.copyFrom(address));
|
||||
|
||||
for (Token coin : input.getCoins()) {
|
||||
com.tangem.wallet.binance.proto.Send.Token protCoin =
|
||||
com.tangem.wallet.binance.proto.Send.Token.newBuilder().setAmount(coin.getAmount())
|
||||
.setDenom(coin.getDenom()).build();
|
||||
builder.addCoins(protCoin);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private com.tangem.wallet.binance.proto.Send.Output toProtoOutput(InputOutput output) {
|
||||
byte[] address = Crypto.decodeAddress(output.getAddress());
|
||||
com.tangem.wallet.binance.proto.Send.Output.Builder builder =
|
||||
com.tangem.wallet.binance.proto.Send.Output.newBuilder().setAddress(ByteString.copyFrom(address));
|
||||
|
||||
for (Token coin : output.getCoins()) {
|
||||
com.tangem.wallet.binance.proto.Send.Token protCoin =
|
||||
com.tangem.wallet.binance.proto.Send.Token.newBuilder().setAmount(coin.getAmount())
|
||||
.setDenom(coin.getDenom()).build();
|
||||
builder.addCoins(protCoin);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeTransferMessage(TransferMessage msg)
|
||||
throws IOException {
|
||||
com.tangem.wallet.binance.proto.Send.Builder builder = com.tangem.wallet.binance.proto.Send.newBuilder();
|
||||
for (InputOutput input : msg.getInputs()) {
|
||||
builder.addInputs(toProtoInput(input));
|
||||
}
|
||||
for (InputOutput output : msg.getOutputs()) {
|
||||
builder.addOutputs(toProtoOutput(output));
|
||||
}
|
||||
com.tangem.wallet.binance.proto.Send proto = builder.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.Send.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public RequestBody buildTransfer(Transfer transfer)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
TransferMessage msgBean = createTransferMessage(transfer);
|
||||
byte[] msg = encodeTransferMessage(msgBean);
|
||||
byte[] signature = encodeSignature(sign(msgBean));
|
||||
byte[] stdTx = encodeStdTx(msg, signature);
|
||||
return createRequestBody(stdTx);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TokenFreezeMessage createTokenFreezeMessage(TokenFreeze freeze) {
|
||||
TokenFreezeMessage msg = new TokenFreezeMessage();
|
||||
msg.setAmount(doubleToLong(freeze.getAmount()));
|
||||
msg.setFrom(wallet.getAddress());
|
||||
msg.setSymbol(freeze.getSymbol());
|
||||
return msg;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeTokenFreezeMessage(TokenFreezeMessage freeze) throws IOException {
|
||||
byte[] address = Crypto.decodeAddress(freeze.getFrom());
|
||||
com.tangem.wallet.binance.proto.TokenFreeze proto =
|
||||
com.tangem.wallet.binance.proto.TokenFreeze.newBuilder().setFrom(ByteString.copyFrom(address))
|
||||
.setAmount(freeze.getAmount())
|
||||
.setSymbol(freeze.getSymbol())
|
||||
.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.TokenFreeze.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public RequestBody buildTokenFreeze(TokenFreeze freeze)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
TokenFreezeMessage msgBean = createTokenFreezeMessage(freeze);
|
||||
byte[] msg = encodeTokenFreezeMessage(msgBean);
|
||||
byte[] signature = encodeSignature(sign(msgBean));
|
||||
byte[] stdTx = encodeStdTx(msg, signature);
|
||||
return createRequestBody(stdTx);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TokenUnfreezeMessage createTokenUnfreezeMessage(TokenUnfreeze unfreeze) {
|
||||
TokenUnfreezeMessage msg = new TokenUnfreezeMessage();
|
||||
msg.setAmount(doubleToLong(unfreeze.getAmount()));
|
||||
msg.setFrom(wallet.getAddress());
|
||||
msg.setSymbol(unfreeze.getSymbol());
|
||||
return msg;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] encodeTokenUnfreezeMessage(TokenUnfreezeMessage unfreeze) throws IOException {
|
||||
byte[] address = Crypto.decodeAddress(unfreeze.getFrom());
|
||||
com.tangem.wallet.binance.proto.TokenUnfreeze proto =
|
||||
com.tangem.wallet.binance.proto.TokenUnfreeze.newBuilder().setFrom(ByteString.copyFrom(address))
|
||||
.setAmount(unfreeze.getAmount())
|
||||
.setSymbol(unfreeze.getSymbol())
|
||||
.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.TokenUnfreeze.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public RequestBody buildTokenUnfreeze(TokenUnfreeze unfreeze)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
TokenUnfreezeMessage msgBean = createTokenUnfreezeMessage(unfreeze);
|
||||
byte[] msg = encodeTokenUnfreezeMessage(msgBean);
|
||||
byte[] signature = encodeSignature(sign(msgBean));
|
||||
byte[] stdTx = encodeStdTx(msg, signature);
|
||||
return createRequestBody(stdTx);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.tangem.wallet.binance.BinanceData;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.TransactionOption;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.Transfer;
|
||||
import com.tangem.wallet.binance.client.encoding.Crypto;
|
||||
import com.tangem.wallet.binance.client.encoding.EncodeUtils;
|
||||
import com.tangem.wallet.binance.proto.StdSignature;
|
||||
import com.tangem.wallet.binance.proto.StdTx;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
/**
|
||||
* Assemble a transaction message body with external signature
|
||||
* https://testnet-dex.binance.org/doc/encoding.html
|
||||
*/
|
||||
public class TransactionRequestAssemblerExtSign {
|
||||
private static final okhttp3.MediaType MEDIA_TYPE = okhttp3.MediaType.parse("text/plain; charset=utf-8");
|
||||
private static final BigDecimal MULTIPLY_FACTOR = BigDecimal.valueOf(1e8);
|
||||
private static final BigDecimal MAX_NUMBER = new BigDecimal(Long.MAX_VALUE);
|
||||
|
||||
//private Wallet wallet;
|
||||
private BinanceData binanceData;
|
||||
private byte[] pubKeyForSign;
|
||||
private TransactionOption options;
|
||||
|
||||
public TransactionRequestAssemblerExtSign(BinanceData binanceData, byte[] pubKeyForSign, TransactionOption options) {
|
||||
this.binanceData = binanceData;
|
||||
this.pubKeyForSign = pubKeyForSign;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
private static long doubleToLong(String d) {
|
||||
BigDecimal encodeValue = new BigDecimal(d).multiply(MULTIPLY_FACTOR);
|
||||
if (encodeValue.compareTo(MAX_NUMBER) > 0) {
|
||||
throw new IllegalArgumentException(d + " is too large.");
|
||||
}
|
||||
return encodeValue.longValue();
|
||||
|
||||
}
|
||||
|
||||
public byte[] prepareForSign(BinanceDexTransactionMessage msg)
|
||||
throws JsonProcessingException {
|
||||
SignData sd = new SignData();
|
||||
sd.setChainId(binanceData.getChainId());
|
||||
sd.setAccountNumber(String.valueOf(binanceData.getAccountNumber()));
|
||||
sd.setSequence(String.valueOf(binanceData.getSequence()));
|
||||
sd.setMsgs(new BinanceDexTransactionMessage[]{msg});
|
||||
|
||||
sd.setMemo(options.getMemo());
|
||||
sd.setSource(String.valueOf(options.getSource()));
|
||||
sd.setData(options.getData());
|
||||
return EncodeUtils.toJsonEncodeBytes(sd);
|
||||
}
|
||||
|
||||
public byte[] encodeSignature(byte[] signatureBytes) throws IOException {
|
||||
StdSignature stdSignature = StdSignature.newBuilder().setPubKey(ByteString.copyFrom(pubKeyForSign))
|
||||
.setSignature(ByteString.copyFrom(signatureBytes))
|
||||
.setAccountNumber(binanceData.getAccountNumber())
|
||||
.setSequence(binanceData.getSequence())
|
||||
.build();
|
||||
|
||||
return EncodeUtils.aminoWrap(
|
||||
stdSignature.toByteArray(), MessageType.StdSignature.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public byte[] encodeStdTx(byte[] msg, byte[] signature) throws IOException {
|
||||
StdTx.Builder stdTxBuilder = StdTx.newBuilder()
|
||||
.addMsgs(ByteString.copyFrom(msg))
|
||||
.addSignatures(ByteString.copyFrom(signature))
|
||||
.setMemo(options.getMemo())
|
||||
.setSource(options.getSource());
|
||||
if (options.getData() != null) {
|
||||
stdTxBuilder = stdTxBuilder.setData(ByteString.copyFrom(options.getData()));
|
||||
}
|
||||
StdTx stdTx = stdTxBuilder.build();
|
||||
return EncodeUtils.aminoWrap(stdTx.toByteArray(), MessageType.StdTx.getTypePrefixBytes(), true);
|
||||
}
|
||||
|
||||
public static RequestBody createRequestBody(byte[] stdTx) {
|
||||
return RequestBody.create(MEDIA_TYPE, EncodeUtils.bytesToHex(stdTx));
|
||||
}
|
||||
|
||||
public TransferMessage createTransferMessage(Transfer transfer) {
|
||||
Token token = new Token();
|
||||
token.setDenom(transfer.getCoin());
|
||||
token.setAmount(doubleToLong(transfer.getAmount()));
|
||||
List<Token> coins = Collections.singletonList(token);
|
||||
|
||||
InputOutput input = new InputOutput();
|
||||
input.setAddress(transfer.getFromAddress());
|
||||
input.setCoins(coins);
|
||||
InputOutput output = new InputOutput();
|
||||
output.setAddress(transfer.getToAddress());
|
||||
output.setCoins(coins);
|
||||
|
||||
TransferMessage msgBean = new TransferMessage();
|
||||
msgBean.setInputs(Collections.singletonList(input));
|
||||
msgBean.setOutputs(Collections.singletonList(output));
|
||||
return msgBean;
|
||||
}
|
||||
|
||||
private com.tangem.wallet.binance.proto.Send.Input toProtoInput(InputOutput input) {
|
||||
byte[] address = Crypto.decodeAddress(input.getAddress());
|
||||
com.tangem.wallet.binance.proto.Send.Input.Builder builder =
|
||||
com.tangem.wallet.binance.proto.Send.Input.newBuilder().setAddress(ByteString.copyFrom(address));
|
||||
|
||||
for (Token coin : input.getCoins()) {
|
||||
com.tangem.wallet.binance.proto.Send.Token protCoin =
|
||||
com.tangem.wallet.binance.proto.Send.Token.newBuilder().setAmount(coin.getAmount())
|
||||
.setDenom(coin.getDenom()).build();
|
||||
builder.addCoins(protCoin);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private com.tangem.wallet.binance.proto.Send.Output toProtoOutput(InputOutput output) {
|
||||
byte[] address = Crypto.decodeAddress(output.getAddress());
|
||||
com.tangem.wallet.binance.proto.Send.Output.Builder builder =
|
||||
com.tangem.wallet.binance.proto.Send.Output.newBuilder().setAddress(ByteString.copyFrom(address));
|
||||
|
||||
for (Token coin : output.getCoins()) {
|
||||
com.tangem.wallet.binance.proto.Send.Token protCoin =
|
||||
com.tangem.wallet.binance.proto.Send.Token.newBuilder().setAmount(coin.getAmount())
|
||||
.setDenom(coin.getDenom()).build();
|
||||
builder.addCoins(protCoin);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public byte[] encodeTransferMessage(TransferMessage msg)
|
||||
throws IOException {
|
||||
com.tangem.wallet.binance.proto.Send.Builder builder = com.tangem.wallet.binance.proto.Send.newBuilder();
|
||||
for (InputOutput input : msg.getInputs()) {
|
||||
builder.addInputs(toProtoInput(input));
|
||||
}
|
||||
for (InputOutput output : msg.getOutputs()) {
|
||||
builder.addOutputs(toProtoOutput(output));
|
||||
}
|
||||
com.tangem.wallet.binance.proto.Send proto = builder.build();
|
||||
return EncodeUtils.aminoWrap(proto.toByteArray(), MessageType.Send.getTypePrefixBytes(), false);
|
||||
}
|
||||
|
||||
public byte[] buildTransfer(Transfer transfer)
|
||||
throws IOException {
|
||||
TransferMessage msgBean = createTransferMessage(transfer);
|
||||
byte[] msg = encodeTransferMessage(msgBean);
|
||||
return prepareForSign(msgBean);
|
||||
// byte[] signature = encodeSignature(prepareForSign(msgBean));
|
||||
// byte[] stdTx = encodeStdTx(msg, signature);
|
||||
// return createRequestBody(stdTx);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.wallet.binance.client.encoding.message;
|
||||
|
||||
import com.tangem.wallet.binance.client.BinanceDexConstants;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder(alphabetic = true)
|
||||
public class TransferMessage implements BinanceDexTransactionMessage {
|
||||
private List<InputOutput> inputs;
|
||||
private List<InputOutput> outputs;
|
||||
|
||||
public List<InputOutput> getInputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public void setInputs(List<InputOutput> inputs) {
|
||||
this.inputs = inputs;
|
||||
}
|
||||
|
||||
public List<InputOutput> getOutputs() {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
public void setOutputs(List<InputOutput> outputs) {
|
||||
this.outputs = outputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, BinanceDexConstants.BINANCE_DEX_TO_STRING_STYLE)
|
||||
.append("inputs", inputs)
|
||||
.append("outputs", outputs)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package com.tangem.wallet.binance.client.impl;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.tangem.wallet.binance.client.*;
|
||||
import com.tangem.wallet.binance.client.domain.*;
|
||||
import com.tangem.wallet.binance.client.domain.request.ClosedOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.OpenOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TradesRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TransactionsRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BinanceDexApiAsyncRestClientImpl implements BinanceDexApiAsyncRestClient {
|
||||
private BinanceDexApi binanceDexApi;
|
||||
|
||||
public BinanceDexApiAsyncRestClientImpl(String baseUrl) {
|
||||
this.binanceDexApi = BinanceDexApiClientGenerator.createService(BinanceDexApi.class, baseUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTime(BinanceDexApiCallback<Time> callback) {
|
||||
binanceDexApi.getTime().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getNodeInfo(BinanceDexApiCallback<Infos> callback) {
|
||||
binanceDexApi.getNodeInfo().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getValidators(BinanceDexApiCallback<Validators> callback) {
|
||||
binanceDexApi.getValidators().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPeers(BinanceDexApiCallback<List<Peer>> callback) {
|
||||
binanceDexApi.getPeers().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMarkets(BinanceDexApiCallback<List<Market>> callback) {
|
||||
binanceDexApi.getMarkets().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAccount(String address, BinanceDexApiCallback<Account> callback) {
|
||||
binanceDexApi.getAccount(address).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAccountSequence(String address, BinanceDexApiCallback<AccountSequence> callback) {
|
||||
binanceDexApi.getAccountSequence(address).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTransactionMetadata(String hash, BinanceDexApiCallback<TransactionMetadata> callback) {
|
||||
binanceDexApi.getTransactionMetadata(hash).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTokens(BinanceDexApiCallback<List<Token>> callback) {
|
||||
binanceDexApi.getTokens().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOrderBook(String symbol, Integer limit, BinanceDexApiCallback<OrderBook> callback) {
|
||||
binanceDexApi.getOrderBook(symbol, limit).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCandleStickBars(String symbol, CandlestickInterval interval,
|
||||
BinanceDexApiCallback<List<Candlestick>> callback) {
|
||||
getCandleStickBars(symbol, interval, null, null, null, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCandleStickBars(String symbol, CandlestickInterval interval, Integer limit, Long startTime,
|
||||
Long endTime, BinanceDexApiCallback<List<Candlestick>> callback) {
|
||||
binanceDexApi.getCandlestickBars(symbol, interval.getIntervalId(), limit, startTime, endTime)
|
||||
.enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOpenOrders(String address, BinanceDexApiCallback<OrderList> callback) {
|
||||
OpenOrdersRequest request = new OpenOrdersRequest();
|
||||
request.setAddress(address);
|
||||
getOpenOrders(address, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOpenOrders(OpenOrdersRequest request, BinanceDexApiCallback<OrderList> callback) {
|
||||
binanceDexApi.getOpenOrders(request.getAddress(), request.getLimit(),
|
||||
request.getOffset(), request.getSymbol(), request.getTotal()).enqueue(
|
||||
new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public void getClosedOrders(String address, BinanceDexApiCallback<OrderList> callback) {
|
||||
ClosedOrdersRequest request = new ClosedOrdersRequest();
|
||||
request.setAddress(address);
|
||||
getClosedOrders(request, callback);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public void getClosedOrders(ClosedOrdersRequest request, BinanceDexApiCallback<OrderList> callback) {
|
||||
String sidStr = request.getSide() == null ? null : request.getSide().name();
|
||||
List<String> statusStrList = null;
|
||||
if (request.getStatus() != null)
|
||||
statusStrList = request.getStatus().stream().map(s -> s.name()).collect(Collectors.toList());
|
||||
binanceDexApi.getClosedOrders(request.getAddress(), request.getEnd(), request.getLimit(),
|
||||
request.getLimit(), sidStr, request.getStart(), statusStrList, request.getSymbol(),
|
||||
request.getTotal()).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOrder(String id, BinanceDexApiCallback<Order> callback) {
|
||||
binanceDexApi.getOrder(id).enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void get24HrPriceStatistics(BinanceDexApiCallback<List<TickerStatistics>> callback) {
|
||||
binanceDexApi.get24HrPriceStatistics().enqueue(new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTrades(BinanceDexApiCallback<TradePage> callback) {
|
||||
TradesRequest request = new TradesRequest();
|
||||
getTrades(request, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTrades(TradesRequest request, BinanceDexApiCallback<TradePage> callback) {
|
||||
String sideStr = request.getSide() == null ? null : request.getSide().name();
|
||||
binanceDexApi.getTrades(
|
||||
request.getAddress(), request.getBuyerOrderId(),
|
||||
request.getEnd(), request.getHeight(), request.getLimit(), request.getOffset(),
|
||||
request.getQuoteAsset(), request.getSellerOrderId(), sideStr,
|
||||
request.getStart(), request.getSymbol(), request.getTotal()).enqueue(
|
||||
new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTransactions(String address, BinanceDexApiCallback<TransactionPage> callback) {
|
||||
TransactionsRequest request = new TransactionsRequest();
|
||||
request.setAddress(address);
|
||||
getTransactions(request, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTransactions(TransactionsRequest request, BinanceDexApiCallback<TransactionPage> callback) {
|
||||
String sideStr = request.getSide() == null ? null : request.getSide().name();
|
||||
String txTypeStr = request.getTxType() != null ? request.getTxType().name() : null;
|
||||
binanceDexApi.getTransactions(
|
||||
request.getAddress(), request.getBlockHeight(), request.getEndTime(),
|
||||
request.getLimit(), request.getOffset(), sideStr,
|
||||
request.getStartTime(), request.getTxAsset(), txTypeStr).enqueue(
|
||||
new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
package com.tangem.wallet.binance.client.impl;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.tangem.wallet.binance.BinanceData;
|
||||
import com.tangem.wallet.binance.client.*;
|
||||
import com.tangem.wallet.binance.client.domain.*;
|
||||
import com.tangem.wallet.binance.client.domain.broadcast.*;
|
||||
import com.tangem.wallet.binance.client.domain.request.ClosedOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.OpenOrdersRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TradesRequest;
|
||||
import com.tangem.wallet.binance.client.domain.request.TransactionsRequest;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssembler;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Binance DEX API rest client, supporting synchronous/blocking access Binance DEX's REST API.
|
||||
*/
|
||||
public class BinanceDexApiRestClientImpl implements BinanceDexApiRestClient {
|
||||
private BinanceDexApi binanceDexApi;
|
||||
|
||||
public BinanceDexApiRestClientImpl(String baseUrl) {
|
||||
this.binanceDexApi = BinanceDexApiClientGenerator.createService(BinanceDexApi.class, baseUrl);
|
||||
}
|
||||
|
||||
public Time getTime() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getTime());
|
||||
}
|
||||
|
||||
public Infos getNodeInfo() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getNodeInfo());
|
||||
}
|
||||
|
||||
public Validators getValidators() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getValidators());
|
||||
}
|
||||
|
||||
public List<Peer> getPeers() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getPeers());
|
||||
}
|
||||
|
||||
public List<Market> getMarkets() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getMarkets());
|
||||
}
|
||||
|
||||
public Account getAccount(String address) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getAccount(address));
|
||||
}
|
||||
|
||||
public AccountSequence getAccountSequence(String address) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getAccountSequence(address));
|
||||
}
|
||||
|
||||
public TransactionMetadata getTransactionMetadata(String hash) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getTransactionMetadata(hash));
|
||||
}
|
||||
|
||||
public List<Token> getTokens() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getTokens());
|
||||
}
|
||||
|
||||
public OrderBook getOrderBook(String symbol, Integer limit) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getOrderBook(symbol, limit));
|
||||
}
|
||||
|
||||
public List<Candlestick> getCandleStickBars(String symbol, CandlestickInterval interval) {
|
||||
return getCandleStickBars(symbol, interval, null, null, null);
|
||||
}
|
||||
|
||||
public List<Candlestick> getCandleStickBars(String symbol, CandlestickInterval interval, Integer limit, Long startTime, Long endTime) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getCandlestickBars(symbol, interval.getIntervalId(), limit, startTime, endTime));
|
||||
}
|
||||
|
||||
public OrderList getOpenOrders(String address) {
|
||||
OpenOrdersRequest request = new OpenOrdersRequest();
|
||||
request.setAddress(address);
|
||||
return getOpenOrders(request);
|
||||
}
|
||||
|
||||
public OrderList getOpenOrders(OpenOrdersRequest request) {
|
||||
return BinanceDexApiClientGenerator.executeSync(
|
||||
binanceDexApi.getOpenOrders(request.getAddress(), request.getLimit(),
|
||||
request.getOffset(), request.getSymbol(), request.getTotal()));
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public OrderList getClosedOrders(String address) {
|
||||
ClosedOrdersRequest request = new ClosedOrdersRequest();
|
||||
request.setAddress(address);
|
||||
return getClosedOrders(request);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public OrderList getClosedOrders(ClosedOrdersRequest request) {
|
||||
String sidStr = request.getSide() == null ? null : request.getSide().name();
|
||||
List<String> statusStrList = null;
|
||||
if (request.getStatus() != null)
|
||||
statusStrList = request.getStatus().stream().map(s -> s.name()).collect(Collectors.toList());
|
||||
return BinanceDexApiClientGenerator.executeSync(
|
||||
binanceDexApi.getClosedOrders(request.getAddress(), request.getEnd(), request.getLimit(),
|
||||
request.getLimit(), sidStr, request.getStart(), statusStrList, request.getSymbol(),
|
||||
request.getTotal()));
|
||||
}
|
||||
|
||||
public Order getOrder(String id) {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.getOrder(id));
|
||||
}
|
||||
|
||||
public List<TickerStatistics> get24HrPriceStatistics() {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.get24HrPriceStatistics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradePage getTrades() {
|
||||
TradesRequest request = new TradesRequest();
|
||||
return getTrades(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradePage getTrades(TradesRequest request) {
|
||||
String sideStr = request.getSide() == null ? null : request.getSide().name();
|
||||
return BinanceDexApiClientGenerator.executeSync(
|
||||
binanceDexApi.getTrades(
|
||||
request.getAddress(), request.getBuyerOrderId(),
|
||||
request.getEnd(), request.getHeight(), request.getLimit(), request.getOffset(),
|
||||
request.getQuoteAsset(), request.getSellerOrderId(), sideStr,
|
||||
request.getStart(), request.getSymbol(), request.getTotal()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionPage getTransactions(String address) {
|
||||
TransactionsRequest request = new TransactionsRequest();
|
||||
request.setAddress(address);
|
||||
return getTransactions(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionPage getTransactions(TransactionsRequest request) {
|
||||
String sideStr = request.getSide() != null ? request.getSide().name() : null;
|
||||
String txTypeStr = request.getTxType() != null ? request.getTxType().name() : null;
|
||||
return BinanceDexApiClientGenerator.executeSync(
|
||||
binanceDexApi.getTransactions(
|
||||
request.getAddress(), request.getBlockHeight(), request.getEndTime(),
|
||||
request.getLimit(), request.getOffset(), sideStr,
|
||||
request.getStartTime(), request.getTxAsset(), txTypeStr));
|
||||
}
|
||||
|
||||
// Broadcast and handle account sequence
|
||||
private List<TransactionMetadata> broadcast(RequestBody requestBody, boolean sync, Wallet wallet) {
|
||||
try {
|
||||
List<TransactionMetadata> metadatas =
|
||||
BinanceDexApiClientGenerator.executeSync(binanceDexApi.broadcast(sync, requestBody));
|
||||
if (!metadatas.isEmpty() && metadatas.get(0).isOk()) {
|
||||
wallet.increaseAccountSequence();
|
||||
}
|
||||
return metadatas;
|
||||
} catch (BinanceDexApiException e) {
|
||||
wallet.invalidAccountSequence();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> broadcastNoWallet(RequestBody requestBody, boolean sync) throws BinanceDexApiException {
|
||||
return BinanceDexApiClientGenerator.executeSync(binanceDexApi.broadcast(sync, requestBody));
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> newOrder(NewOrder newOrder, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
wallet.ensureWalletIsReady(this);
|
||||
TransactionRequestAssembler assembler = new TransactionRequestAssembler(wallet, options);
|
||||
RequestBody requestBody = assembler.buildNewOrder(newOrder);
|
||||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> cancelOrder(CancelOrder cancelOrder, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
wallet.ensureWalletIsReady(this);
|
||||
TransactionRequestAssembler assembler = new TransactionRequestAssembler(wallet, options);
|
||||
RequestBody requestBody = assembler.buildCancelOrder(cancelOrder);
|
||||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> transfer(Transfer transfer, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
wallet.ensureWalletIsReady(this);
|
||||
TransactionRequestAssembler assembler = new TransactionRequestAssembler(wallet, options);
|
||||
RequestBody requestBody = assembler.buildTransfer(transfer);
|
||||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public TransactionRequestAssemblerExtSign prepareTransfer(Transfer transfer, BinanceData binanceData, byte[] pubKeyForSign, TransactionOption options, boolean sync) {
|
||||
return new TransactionRequestAssemblerExtSign(binanceData, pubKeyForSign, options);
|
||||
// RequestBody requestBody = assembler.buildTransfer(transfer);
|
||||
// return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> freeze(TokenFreeze freeze, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
wallet.ensureWalletIsReady(this);
|
||||
TransactionRequestAssembler assembler = new TransactionRequestAssembler(wallet, options);
|
||||
RequestBody requestBody = assembler.buildTokenFreeze(freeze);
|
||||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public List<TransactionMetadata> unfreeze(TokenUnfreeze unfreeze, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
wallet.ensureWalletIsReady(this);
|
||||
TransactionRequestAssembler assembler = new TransactionRequestAssembler(wallet, options);
|
||||
RequestBody requestBody = assembler.buildTokenUnfreeze(unfreeze);
|
||||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue