Updated on 2026-08-14

This commit is contained in:
Tangem 2019-06-13 17:23:45 +03:00
commit bd8d84a85a
680 changed files with 51751 additions and 6799 deletions

View 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;
}
}

View 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;
}
}

View 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. ";
}
}
}
}

View 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();
}
}

View 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;
}

View 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; }
}

View 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
}
}

View 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();
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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) {
}
}

View 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);
}
}

View 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;
}
}

View 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();
}

View 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 + ", ");
}
}
}

View 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");
}
}
}

View 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);
}
}
}
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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"),
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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);
}

View file

@ -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
}

View file

@ -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) {
}
}

View file

@ -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));
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View 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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.wallet.binance.client.domain;
public enum OrderStatus {
Ack,
PartialFill,
IocNoFill,
FullyFill,
Canceled,
Expired,
Unknown
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.wallet.binance.client.domain;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class TradeStatistics {
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,4 @@
package com.tangem.wallet.binance.client.encoding.message;
public interface BinanceDexTransactionMessage {
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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