Updated on 2026-08-14
This commit is contained in:
parent
7cf462453b
commit
daa1b96c85
4 changed files with 493 additions and 3 deletions
|
|
@ -2,7 +2,18 @@ package com.tangem.wallet.ltc;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.local.PendingTransactionsStorage;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiSoChain;
|
||||
import com.tangem.data.network.model.BlockcypherFee;
|
||||
import com.tangem.data.network.model.BlockcypherResponse;
|
||||
import com.tangem.data.network.model.BlockcypherTx;
|
||||
import com.tangem.data.network.model.BlockcypherTxref;
|
||||
import com.tangem.data.network.model.SoChain;
|
||||
import com.tangem.wallet.BTCUtils;
|
||||
import com.tangem.wallet.BalanceValidator;
|
||||
import com.tangem.wallet.Base58;
|
||||
|
|
@ -21,6 +32,8 @@ import com.tangem.util.DecimalDigitsInputFilter;
|
|||
import com.tangem.util.DerEncodingUtil;
|
||||
import com.tangem.wallet.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
|
@ -478,6 +491,281 @@ public class LtcEngine extends BtcEngine {
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
|
||||
ctx.setError(null);
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.AddressBalance response) {
|
||||
try {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(Server.ApiSoChain.URL);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
|
||||
ctx.setError("FAIL METHOD_GetBalance JSONException");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
|
||||
ctx.setError("FAIL METHOD_GetBalance Exception");
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.TxUnspent response) {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
try {
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
coinData.getUnspentTransactions().clear();
|
||||
if (response.getData().getTxs() != null)
|
||||
for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = tx.getTxid();
|
||||
trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact();
|
||||
trUnspent.outputN = tx.getOutput_no();
|
||||
trUnspent.script = tx.getScript_hex();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
|
||||
// if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
|
||||
// } else {
|
||||
// ctx.setError("Terminated by user");
|
||||
// }
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
|
||||
}
|
||||
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: " + message);
|
||||
coinData.setUseBlockcypher(true);
|
||||
// ctx.setError(R.string.cannot_obtain_data_from_blockchain);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
serverApiSoChain.setAddressInfoListener(addressInfoListener);
|
||||
|
||||
serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet());
|
||||
serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet());
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
|
||||
Log.i(TAG, "onSuccess: " + method);
|
||||
try {
|
||||
String walletAddress = blockcypherResponse.getAddress();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
Long confBalance = blockcypherResponse.getBalance();
|
||||
Long unconfirmedBalance = blockcypherResponse.getUnconfirmed_balance();
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER);
|
||||
|
||||
coinData.getUnspentTransactions().clear();
|
||||
if (blockcypherResponse.getTxrefs() != null) {
|
||||
for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = txref.getTx_hash();
|
||||
trUnspent.amount = txref.getValue();
|
||||
trUnspent.outputN = txref.getTx_output_n();
|
||||
trUnspent.script = txref.getScript();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception");
|
||||
}
|
||||
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.GetTx response) {
|
||||
Log.i(TAG, "onSuccess: GetTx");
|
||||
try {
|
||||
if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: GetTx" + response);
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: GetTx " + message);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiSoChain.setTransactionInfoListener(listener);
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(BlockcypherTx response) {
|
||||
Log.i(TAG, "onSuccess: BlockcypherTx");
|
||||
try {
|
||||
if (response.getHex() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: BlockcypherTx" + response);
|
||||
}
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: BlockcypherTx " + message);
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setTxResponseListener(listener);
|
||||
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
|
||||
private final static BigDecimal relayFee = new BigDecimal(0.00001).setScale(8, RoundingMode.DOWN);
|
||||
|
||||
@Override
|
||||
|
|
@ -486,6 +774,92 @@ public class LtcEngine extends BtcEngine {
|
|||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final String txStr = BTCUtils.toHex(txForSend);
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.SendTx response) {
|
||||
try {
|
||||
if (response.getStatus() == null || response.getData() == null) {
|
||||
ctx.setError("Rejected by node: invalid answer received");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) {
|
||||
ctx.setError("Rejected by node: " + response.getData());
|
||||
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(String message) {
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiSoChain.setSendTxListener(listener);
|
||||
serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr);
|
||||
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
|
||||
String resultString = blockcypherResponse.toString();
|
||||
try {
|
||||
if (resultString.isEmpty()) {
|
||||
ctx.setError("No response from node");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else { // TODO: Make check for a valid send response
|
||||
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);
|
||||
Log.e(TAG, resultString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestSendTransaction");
|
||||
ctx.setError("Wrong response type for requestSendTransaction");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue