Updated on 2026-08-14
This commit is contained in:
parent
31d0b1fe8f
commit
14e5cc31e4
12 changed files with 518 additions and 90 deletions
11
app/src/main/java/com/tangem/data/network/BinanceApi.java
Normal file
11
app/src/main/java/com/tangem/data/network/BinanceApi.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
import com.tangem.data.network.model.BinanceFees;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
|
||||
public interface BinanceApi {
|
||||
@GET("/fees")
|
||||
Call<BinanceFees> binanceFees();
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ public class Server {
|
|||
public static final String URL_COINMARKET = ServerURL.API_COINMARKETCAP;
|
||||
|
||||
public static class Method {
|
||||
static final String V1_TICKER_CONVERT = URL_COINMARKET + "v1/ticker/?convert=USD&lmit=10";
|
||||
static final String V1_TICKER_CONVERT = URL_COINMARKET + "v1/ticker/?convert=USD&limit=10";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,13 +56,21 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
// public static class ApiBinance {
|
||||
// public static final String URL_BINANCE = ServerURL.API_BINANCE;
|
||||
//
|
||||
// public static class Method {
|
||||
// static final String API_V1 = URL_BINANCE + "api/v1";
|
||||
// }
|
||||
// }
|
||||
public static class ApiBinance {
|
||||
public static final String URL_BINANCE = ServerURL.API_BINANCE;
|
||||
|
||||
public static class Method {
|
||||
public static final String API_V1 = URL_BINANCE + "api/v1";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiBinanceTestnet {
|
||||
public static final String URL_BINANCE_TESTNET = ServerURL.API_BINANCE_TESTNET;
|
||||
|
||||
public static class Method {
|
||||
public static final String API_V1 = URL_BINANCE_TESTNET + "api/v1";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiBlockcypher {
|
||||
public static final String URL_BLOCKCYPHER = ServerURL.API_BLOCKCYPHER;
|
||||
|
|
|
|||
|
|
@ -8,4 +8,6 @@ class ServerURL {
|
|||
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";
|
||||
static final String API_ROOTSTOCK = "https://public-node.rsk.co/";
|
||||
static final String API_BLOCKCYPHER = "https://api.blockcypher.com/";
|
||||
static final String API_BINANCE = "https://dex.binance.org/";
|
||||
static final String API_BINANCE_TESTNET = "https://testnet-dex.binance.org/";
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class BinanceFees(
|
||||
@SerializedName("fixed_fee_params")
|
||||
var fixed_fee_params: BinanceFixedFee? = null
|
||||
)
|
||||
|
||||
data class BinanceFixedFee(
|
||||
@SerializedName("msg_type")
|
||||
var msg_type: String? = null,
|
||||
|
||||
@SerializedName("fee")
|
||||
var fee: Long? = null
|
||||
)
|
||||
|
|
@ -727,6 +727,7 @@ class LoadedWallet : androidx.fragment.app.Fragment(), NfcAdapter.ReaderCallback
|
|||
Blockchain.RootstockToken -> "bitcoin"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.Ripple -> "ripple"
|
||||
Blockchain.Binance -> "binance-coin"
|
||||
else -> {
|
||||
throw Exception("Can''t get rate for blockchain " + ctx.blockchainName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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
|
||||
|
|
@ -39,6 +40,7 @@ object CoinEngineFactory {
|
|||
Blockchain.RootstockToken -> RskTokenEngine()
|
||||
Blockchain.Cardano -> CardanoEngine()
|
||||
Blockchain.Ripple -> XrpEngine()
|
||||
Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +68,8 @@ object CoinEngineFactory {
|
|||
CardanoEngine(context)
|
||||
else if (Blockchain.Ripple == context.blockchain)
|
||||
XrpEngine(context)
|
||||
else if (Blockchain.Binance == context.blockchain || Blockchain.BinanceTestNet == context.blockchain)
|
||||
BinanceEngine(context)
|
||||
else
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,51 @@
|
|||
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;
|
||||
private String balance, chainId;
|
||||
private Long sequence;
|
||||
private Integer accountNumber;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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.putLong("AccountNumber", accountNumber);
|
||||
} 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;
|
||||
}
|
||||
|
||||
public CoinEngine.Amount getBalance() {
|
||||
return new CoinEngine.Amount(balance, "BNB");
|
||||
|
|
@ -17,4 +58,28 @@ public class BinanceData extends CoinData {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,19 @@ package com.tangem.wallet.binance;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.ripple.crypto.ecdsa.ECDSASignature;
|
||||
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.model.BinanceFees;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
import com.tangem.wallet.BalanceValidator;
|
||||
|
|
@ -16,24 +23,41 @@ 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.BinanceDexApiClientGenerator;
|
||||
import com.tangem.wallet.binance.client.BinanceDexApiRestClient;
|
||||
import com.tangem.wallet.binance.client.BinanceDexEnvironment;
|
||||
import com.tangem.wallet.binance.client.domain.Account;
|
||||
import com.tangem.wallet.binance.client.domain.Balance;
|
||||
import com.tangem.wallet.binance.client.domain.TransactionMetadata;
|
||||
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.TransactionRequestAssemblerExtSign;
|
||||
import com.tangem.wallet.binance.client.encoding.message.TransferMessage;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
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;
|
||||
BinanceDexApiRestClient client = null;
|
||||
|
||||
public BinanceEngine(TangemContext context) throws Exception {
|
||||
super(context);
|
||||
|
|
@ -43,7 +67,16 @@ public class BinanceEngine extends CoinEngine {
|
|||
} else if (context.getCoinData() instanceof BinanceData) {
|
||||
coinData = (BinanceData) context.getCoinData();
|
||||
} else {
|
||||
throw new Exception("Invalid type of Blockchain data for XrpEngine");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,15 +93,15 @@ public class BinanceEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitingConfirmation() { //TODO:check
|
||||
public boolean awaitingConfirmation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceHTML() { //TODO
|
||||
public String getBalanceHTML() {
|
||||
Amount balance = getBalance();
|
||||
if (balance != null) {
|
||||
return " " + balance.toDescriptionString(getDecimals()) + " <br><small><small>+ " + convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()) + " reserve</small></small>";
|
||||
return balance.toDescriptionString(getDecimals());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -147,7 +180,14 @@ public class BinanceEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public Uri getWalletExplorerUri() {
|
||||
return Uri.parse("https://testnet-explorer.binance.org/address/" + ctx.getCoinData().getWallet()); //TODO: add mainnet explorer
|
||||
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() {
|
||||
|
|
@ -258,7 +298,7 @@ public class BinanceEngine extends CoinEngine {
|
|||
} 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 " + TAG);
|
||||
throw new Exception("Invalid blockchain for BinanceEngine");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +313,8 @@ public class BinanceEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InternalAmount convertToInternalAmount(Amount amount) {;
|
||||
public InternalAmount convertToInternalAmount(Amount amount) {
|
||||
;
|
||||
return new InternalAmount(amount, getBalanceCurrency());
|
||||
}
|
||||
|
||||
|
|
@ -316,7 +357,7 @@ public class BinanceEngine extends CoinEngine {
|
|||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String amount, fee;
|
||||
String amount;
|
||||
|
||||
if (IncFee) {
|
||||
amount = amountValue.subtract(feeValue).setScale(getDecimals(), RoundingMode.DOWN).toPlainString();
|
||||
|
|
@ -324,82 +365,170 @@ public class BinanceEngine extends CoinEngine {
|
|||
amount = amountValue.setScale(getDecimals(), RoundingMode.DOWN).toPlainString();
|
||||
}
|
||||
|
||||
Transfer tx = new Transfer();
|
||||
Transfer transfer = new Transfer();
|
||||
transfer.setCoin("BNB");
|
||||
transfer.setFromAddress(ctx.getCoinData().getWallet());
|
||||
transfer.setToAddress(targetAddress);
|
||||
transfer.setAmount(amount);
|
||||
|
||||
tx.setCoin("BNB");
|
||||
tx.setFromAddress(ctx.getCoinData().getWallet());
|
||||
tx.setToAddress(targetAddress);
|
||||
tx.setAmount(amount);
|
||||
TransactionOption options = TransactionOption.DEFAULT_INSTANCE;
|
||||
|
||||
BinanceDexApiRestClient client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.TEST_NET.getBaseUrl());
|
||||
TransactionRequestAssemblerExtSign txAssembler = client.prepareTransfer(transfer, coinData, ctx.getCard().getWalletPublicKeyRar(), 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() {
|
||||
|
||||
// XrpPayment payment = new XrpPayment();
|
||||
//
|
||||
// // Put `as` AccountID field Account, `Object` o
|
||||
// payment.as(AccountID.Account, coinData.getWallet());
|
||||
// payment.as(AccountID.Destination, targetAddress);
|
||||
// payment.as(com.ripple.core.coretypes.Amount.Amount, amount);
|
||||
// payment.as(UInt32.Sequence, coinData.getSequence());
|
||||
// payment.as(com.ripple.core.coretypes.Amount.Fee, fee);
|
||||
//
|
||||
// XrpSignedTransaction signedTx = payment.prepare(canonisePubKey(ctx.getCard().getWalletPublicKeyRar()));
|
||||
//
|
||||
// return new SignTask.TransactionToSign() {
|
||||
//
|
||||
// @Override
|
||||
// public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
|
||||
// return signingMethod == TangemCard.SigningMethod.Sign_Hash;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public byte[][] getHashesToSign() throws Exception {
|
||||
// byte[][] dataForSign = new byte[1][];
|
||||
// if (ctx.getCard().getWalletPublicKeyRar().length == 33)
|
||||
// dataForSign[0] = HashUtils.halfSha512(signedTx.signingData);
|
||||
// else if (ctx.getCard().getWalletPublicKeyRar().length == 32)
|
||||
// dataForSign[0] = signedTx.signingData;
|
||||
// else
|
||||
// throw new Exception("Invalid pubkey length");
|
||||
// return dataForSign;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public byte[] getRawDataToSign() throws Exception {
|
||||
// throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String getHashAlgToSign() throws Exception {
|
||||
// throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
|
||||
// }
|
||||
//
|
||||
// @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 {
|
||||
// if (ctx.getCard().getWalletPublicKeyRar().length == 33) {
|
||||
// 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);
|
||||
// ECDSASignature sig = new ECDSASignature(r, s);
|
||||
// byte[] sigDer = sig.encodeToDER();
|
||||
// if (!ECDSASignature.isStrictlyCanonical(sigDer)) {
|
||||
// throw new IllegalStateException("Signature is not strictly canonical");
|
||||
// }
|
||||
// signedTx.addSign(sigDer);
|
||||
// } else if (ctx.getCard().getWalletPublicKeyRar().length == 32)
|
||||
// signedTx.addSign(signFromCard);
|
||||
// else
|
||||
// throw new Exception("Invalid pubkey length");
|
||||
// byte[] txForSend = BTCUtils.fromHex(signedTx.tx_blob);
|
||||
// notifyOnNeedSendTransaction(txForSend);
|
||||
// return txForSend;
|
||||
// }
|
||||
// };
|
||||
@Override
|
||||
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[][] getHashesToSign() {
|
||||
byte[][] hashForSign = new byte[1][];
|
||||
hashForSign[1] = CryptoUtil.doubleSha256(dataForSign);
|
||||
return hashForSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawDataToSign() {
|
||||
return dataForSign;
|
||||
}
|
||||
|
||||
@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 {
|
||||
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);
|
||||
ECKey.ECDSASignature sig = new ECKey.ECDSASignature(r, s);
|
||||
byte[] sigDer = sig.encodeToDER();
|
||||
if (!ECDSASignature.isStrictlyCanonical(sigDer)) {
|
||||
throw new IllegalStateException("Signature is not strictly canonical");
|
||||
}
|
||||
|
||||
// TransactionRequestAssembler.buildTransfer as reference
|
||||
byte[] signature = txAssembler.encodeSignature(sigDer);
|
||||
return txAssembler.encodeStdTx(msg, signature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
try {
|
||||
Account account = client.getAccount(ctx.getCoinData().getWallet());
|
||||
|
||||
for (Balance balance : account.getBalances()) {
|
||||
if (balance.getSymbol().equals("BNB")) {
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalance(balance.getFree());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
coinData.setAccountNumber(account.getAccountNumber());
|
||||
coinData.setSequence(account.getSequence());
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL RIPPLE_FEE Exception");
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
try {
|
||||
String baseUrl = null;
|
||||
|
||||
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<BinanceFees> call = binanceApi.binanceFees();
|
||||
call.enqueue(new Callback<BinanceFees>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<BinanceFees> call, @NonNull Response<BinanceFees> response) {
|
||||
if (response.code() == 200) {
|
||||
Long fee = response.body().getFixed_fee_params().getFee();
|
||||
Amount feeAmount = new Amount(BigDecimal.valueOf(fee).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<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 {
|
||||
List<TransactionMetadata> metadatas = client.broadcastNoWallet(requestBody, true);
|
||||
if (!metadatas.isEmpty() && metadatas.get(0).isOk()) {
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
} else {
|
||||
Log.e(TAG, "Transaction send error");
|
||||
ctx.setError("Transaction send error");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Transaction send error");
|
||||
ctx.setError("Transaction send error");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean allowSelectFeeInclusion() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
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();
|
||||
|
||||
|
|
@ -56,6 +61,8 @@ public interface BinanceDexApiRestClient {
|
|||
|
||||
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;
|
||||
|
||||
|
|
@ -65,6 +72,9 @@ public interface BinanceDexApiRestClient {
|
|||
List<TransactionMetadata> transfer(Transfer transfer, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
TransactionRequestAssemblerExtSign prepareTransfer(Transfer transfer, BinanceData binanceData, byte[] pubKey, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
List<TransactionMetadata> freeze(TokenFreeze freeze, Wallet wallet, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
* 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[] pubKey;
|
||||
private TransactionOption options;
|
||||
|
||||
public TransactionRequestAssemblerExtSign(BinanceData binanceData, byte[] pubKey, TransactionOption options) {
|
||||
this.binanceData = binanceData;
|
||||
this.pubKey = pubKey;
|
||||
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 byte[] prepareForSign(BinanceDexTransactionMessage msg)
|
||||
throws JsonProcessingException, NoSuchAlgorithmException {
|
||||
SignData sd = new SignData();
|
||||
sd.setChainId(binanceData.getChainId());
|
||||
sd.setAccountNumber(String.valueOf(binanceData.getAccountNumber()));
|
||||
sd.setSequence(String.valueOf(binanceData.getAccountNumber()));
|
||||
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(pubKey))
|
||||
.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, NoSuchAlgorithmException {
|
||||
TransferMessage msgBean = createTransferMessage(transfer);
|
||||
byte[] msg = encodeTransferMessage(msgBean);
|
||||
return prepareForSign(msgBean);
|
||||
// byte[] signature = encodeSignature(prepareForSign(msgBean));
|
||||
// byte[] stdTx = encodeStdTx(msg, signature);
|
||||
// return createRequestBody(stdTx);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
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;
|
||||
|
|
@ -94,6 +98,7 @@ public class BinanceDexApiAsyncRestClientImpl implements BinanceDexApiAsyncRestC
|
|||
new BinanceDexApiCallbackAdapter<>(callback));
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public void getClosedOrders(String address, BinanceDexApiCallback<OrderList> callback) {
|
||||
ClosedOrdersRequest request = new ClosedOrdersRequest();
|
||||
|
|
@ -101,6 +106,7 @@ public class BinanceDexApiAsyncRestClientImpl implements BinanceDexApiAsyncRestC
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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.*;
|
||||
|
|
@ -12,6 +13,8 @@ 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;
|
||||
|
|
@ -167,6 +170,10 @@ public class BinanceDexApiRestClientImpl implements BinanceDexApiRestClient {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -191,6 +198,14 @@ public class BinanceDexApiRestClientImpl implements BinanceDexApiRestClient {
|
|||
return broadcast(requestBody, sync, wallet);
|
||||
}
|
||||
|
||||
public TransactionRequestAssemblerExtSign prepareTransfer(Transfer transfer, BinanceData binanceData, byte[] pubKey, TransactionOption options, boolean sync)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
TransactionRequestAssemblerExtSign assembler = new TransactionRequestAssemblerExtSign(binanceData, pubKey, options);
|
||||
return assembler;
|
||||
// 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue