Updated on 2026-08-14
This commit is contained in:
parent
25e76dbb18
commit
5e91f8ffa6
14 changed files with 408 additions and 3 deletions
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
|
|
@ -20,7 +20,9 @@ public enum Blockchain {
|
|||
Cardano("CARDANO", "ADA", 1000000.0, R.drawable.tangem2, "Cardano"),
|
||||
Ripple ("XRP", "XRP", 1000000.0, R.drawable.tangem2, "XRP"),
|
||||
Binance("BINANCE", "BNB", 100000000.0, R.drawable.tangem2, "Binance"),
|
||||
BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.tangem2, "Binance Testnet");
|
||||
BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.tangem2, "Binance Testnet"),
|
||||
Matic("MATIC", "MTX", 1.0, R.drawable.tangem2, "Matic"),
|
||||
MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet");
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
|
|||
15
app/src/main/java/com/tangem/data/network/MaticApi.java
Normal file
15
app/src/main/java/com/tangem/data/network/MaticApi.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
import com.tangem.data.network.model.InfuraBody;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface MaticApi {
|
||||
@Headers("Content-Type: application/json")
|
||||
@POST(Server.ApiMaticTesnet.Method.MAIN)
|
||||
Call<InfuraResponse> matic(@Body InfuraBody body);
|
||||
}
|
||||
|
|
@ -43,6 +43,17 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://testnet2.matic.network
|
||||
*/
|
||||
public static class ApiMaticTesnet {
|
||||
public static final String URL_MATIC_TESTNET = ServerURL.API_MATIC_TESTNET ;
|
||||
|
||||
public static class Method {
|
||||
static final String MAIN = URL_MATIC_TESTNET;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://estimatefee.com/
|
||||
*/
|
||||
|
|
|
|||
105
app/src/main/java/com/tangem/data/network/ServerApiMatic.java
Normal file
105
app/src/main/java/com/tangem/data/network/ServerApiMatic.java
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.network.model.InfuraBody;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ServerApiMatic {
|
||||
private static String TAG = ServerApiMatic.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* HTTP
|
||||
* Infura
|
||||
* <p>
|
||||
* eth_getBalance
|
||||
* eth_getTransactionCount
|
||||
* eth_call
|
||||
* eth_sendRawTransaction
|
||||
* eth_gasPrice
|
||||
*/
|
||||
public static final String MATIC_ETH_GET_BALANCE = "eth_getBalance";
|
||||
public static final String MATIC_ETH_GET_TRANSACTION_COUNT = "eth_getTransactionCount";
|
||||
public static final String MATIC_ETH_GET_PENDING_COUNT = "eth_getPendingCount";
|
||||
public static final String MATIC_ETH_CALL = "eth_call";
|
||||
public static final String MATIC_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction";
|
||||
public static final String MATIC_ETH_GAS_PRICE = "eth_gasPrice";
|
||||
|
||||
private int requestsCount=0;
|
||||
|
||||
public boolean isRequestsSequenceCompleted() {
|
||||
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
|
||||
return requestsCount <= 0;
|
||||
}
|
||||
|
||||
private ResponseListener responseListener;
|
||||
|
||||
public interface ResponseListener {
|
||||
void onSuccess(String method, InfuraResponse infuraResponse);
|
||||
|
||||
void onFail(String method, String message);
|
||||
}
|
||||
|
||||
public void setResponseListener(ResponseListener listener) {
|
||||
responseListener = listener;
|
||||
}
|
||||
|
||||
public void requestData(String method, int id, String wallet, String contract, String tx) {
|
||||
requestsCount++;
|
||||
MaticApi maticApi = App.Companion.getNetworkComponent().getRetrofitMaticTesnet().create(MaticApi.class);
|
||||
|
||||
InfuraBody infuraBody;
|
||||
switch (method) {
|
||||
case MATIC_ETH_GET_BALANCE:
|
||||
case MATIC_ETH_GET_TRANSACTION_COUNT:
|
||||
infuraBody = new InfuraBody(method, new String[]{wallet, "latest"}, id);
|
||||
break;
|
||||
case MATIC_ETH_GET_PENDING_COUNT:
|
||||
infuraBody = new InfuraBody(MATIC_ETH_GET_TRANSACTION_COUNT, new String[]{wallet, "pending"}, id);
|
||||
break;
|
||||
case MATIC_ETH_CALL:
|
||||
String address = wallet.substring(2);
|
||||
infuraBody = new InfuraBody(method, new Object[]{new InfuraBody.EthCallParams("0x70a08231000000000000000000000000" + address, contract), "latest"}, id);
|
||||
break;
|
||||
|
||||
case MATIC_ETH_SEND_RAW_TRANSACTION:
|
||||
infuraBody = new InfuraBody(method, new String[]{tx}, id);
|
||||
break;
|
||||
|
||||
case MATIC_ETH_GAS_PRICE:
|
||||
infuraBody = new InfuraBody(method, id);
|
||||
break;
|
||||
|
||||
default:
|
||||
infuraBody = new InfuraBody();
|
||||
}
|
||||
|
||||
Call<InfuraResponse> call = maticApi.matic(infuraBody);
|
||||
call.enqueue(new Callback<InfuraResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<InfuraResponse> call, @NonNull Response<InfuraResponse> response) {
|
||||
if (response.code() == 200) {
|
||||
requestsCount--;
|
||||
responseListener.onSuccess(method, response.body());
|
||||
Log.i(TAG, "requestData " + method + " onResponse " + response.code());
|
||||
} else {
|
||||
responseListener.onFail(method, String.valueOf(response.code()));
|
||||
Log.e(TAG, "requestData " + method + " onResponse " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<InfuraResponse> call, @NonNull Throwable t) {
|
||||
responseListener.onFail(method, String.valueOf(t.getMessage()));
|
||||
Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@ class ServerURL {
|
|||
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/";
|
||||
static final String API_MATIC_TESTNET = "https://testnet2.matic.network";
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ public class InfuraBody {
|
|||
private String method;
|
||||
private Object[] params;
|
||||
private int id;
|
||||
private String jsonrpc = "2.0";
|
||||
|
||||
public InfuraBody() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ interface NetworkComponent {
|
|||
@get:Named(Server.ApiInfura.URL_INFURA)
|
||||
val retrofitInfura: Retrofit
|
||||
|
||||
@get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
val retrofitMaticTesnet: Retrofit
|
||||
|
||||
@get:Named(Server.ApiEstimatefee.URL_ESTIMATEFEE)
|
||||
val retrofitEstimatefee: Retrofit
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ internal class NetworkModule {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
fun provideRetrofitMaticTestnet(): Retrofit {
|
||||
val builder = Retrofit.Builder()
|
||||
.baseUrl(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
if (BuildConfig.DEBUG)
|
||||
builder.client(createOkHttpClient())
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiEstimatefee.URL_ESTIMATEFEE)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class LoadedWalletViewModel : ViewModel() {
|
|||
Blockchain.Ripple -> "ripple"
|
||||
Blockchain.Binance -> "binance-coin"
|
||||
Blockchain.BinanceTestNet -> "binance-coin"
|
||||
Blockchain.Matic -> "bitcoin"
|
||||
Blockchain.MaticTestNet -> "bitcoin"
|
||||
else -> {
|
||||
throw Exception("Can''t get rate for blockchain " + ctx.blockchainName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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
|
||||
|
|
@ -41,6 +42,7 @@ object CoinEngineFactory {
|
|||
Blockchain.Cardano -> CardanoEngine()
|
||||
Blockchain.Ripple -> XrpEngine()
|
||||
Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine()
|
||||
Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +72,10 @@ object CoinEngineFactory {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ public class EthTransaction {
|
|||
Kovan(42),
|
||||
Ethereum_Classic_mainnet(61),
|
||||
Ethereum_Classic_testnet(62),
|
||||
Geth_private_chains(1337);
|
||||
Geth_private_chains(1337),
|
||||
Matic_Testnet(8995);
|
||||
|
||||
private int value;
|
||||
|
||||
|
|
|
|||
241
app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java
Normal file
241
app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
package com.tangem.wallet.matic;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.ServerApiMatic;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
import com.tangem.wallet.BTCUtils;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
import com.tangem.wallet.EthTransaction;
|
||||
import com.tangem.wallet.R;
|
||||
import com.tangem.wallet.TangemContext;
|
||||
import com.tangem.wallet.token.TokenEngine;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class MaticTokenEngine extends TokenEngine {
|
||||
|
||||
private static final String TAG = MaticTokenEngine.class.getSimpleName();
|
||||
|
||||
public MaticTokenEngine(TangemContext ctx) throws Exception {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public MaticTokenEngine() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Blockchain getBlockchain() {
|
||||
return ctx.getBlockchain();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChainIdNum() {
|
||||
return EthTransaction.ChainEnum.Matic_Testnet.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceHTML() {
|
||||
if (hasBalanceInfo()) {
|
||||
try {
|
||||
return " " + convertToAmount(coinData.getBalanceInInternalUnits()).toDescriptionString(getTokenDecimals());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBalanceEquivalent() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWalletExplorerUri() {
|
||||
return Uri.parse("https://explorer.testnet2.matic.network/account/" + ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getShareWalletUri() {
|
||||
if (ctx.getCard().getDenomination() != null) {
|
||||
return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
|
||||
} else {
|
||||
return Uri.parse(ctx.getCoinData().getWallet());
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
final ServerApiMatic serverApiMatic = new ServerApiMatic();
|
||||
// request requestData listener
|
||||
ServerApiMatic.ResponseListener responseListener = new ServerApiMatic.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, InfuraResponse infuraResponse) {
|
||||
switch (method) {
|
||||
case ServerApiMatic.MATIC_ETH_GET_BALANCE: {
|
||||
String balanceCap = infuraResponse.getResult();
|
||||
balanceCap = balanceCap.substring(2);
|
||||
BigInteger l = new BigInteger(balanceCap, 16);
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
|
||||
|
||||
// Log.i("$TAG eth_get_balance", balanceCap)
|
||||
}
|
||||
break;
|
||||
|
||||
case ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT: {
|
||||
String nonce = infuraResponse.getResult();
|
||||
nonce = nonce.substring(2);
|
||||
BigInteger count = new BigInteger(nonce, 16);
|
||||
coinData.setConfirmedTXCount(count);
|
||||
|
||||
|
||||
// Log.i("$TAG eth_getTransCount", nonce)
|
||||
}
|
||||
break;
|
||||
|
||||
case ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT: {
|
||||
String pending = infuraResponse.getResult();
|
||||
pending = pending.substring(2);
|
||||
BigInteger count = new BigInteger(pending, 16);
|
||||
coinData.setUnconfirmedTXCount(count);
|
||||
|
||||
// Log.i("$TAG eth_getPendingTxCount", pending)
|
||||
}
|
||||
break;
|
||||
//
|
||||
case ServerApiMatic.MATIC_ETH_CALL: {
|
||||
try {
|
||||
|
||||
String balanceCap = infuraResponse.getResult();
|
||||
balanceCap = balanceCap.substring(2);
|
||||
BigInteger l = new BigInteger(balanceCap, 16);
|
||||
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol));
|
||||
// Log.i("$TAG eth_call", balanceCap)
|
||||
|
||||
|
||||
if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
|
||||
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
|
||||
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
|
||||
} else {
|
||||
ctx.setError("Terminated by user");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
if (serverApiMatic.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
if (!serverApiMatic.isRequestsSequenceCompleted()) {
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiMatic.setResponseListener(responseListener);
|
||||
|
||||
if (validateAddress(getContractAddress(ctx.getCard()))) {
|
||||
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
|
||||
} else {
|
||||
ctx.setError("Smart contract address not defined");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
coinData.minFee = coinData.normalFee = coinData.maxFee = new Amount(0L, getFeeCurrency());
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
|
||||
|
||||
String txStr = String.format("0x%s", BTCUtils.toHex(txForSend));
|
||||
|
||||
final ServerApiMatic serverApiMatic = new ServerApiMatic();
|
||||
// request requestData listener
|
||||
ServerApiMatic.ResponseListener responseListener = new ServerApiMatic.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, InfuraResponse infuraResponse) {
|
||||
if (method.equals(ServerApiMatic.MATIC_ETH_SEND_RAW_TRANSACTION)) {
|
||||
if (infuraResponse.getResult()==null || infuraResponse.getResult().isEmpty()) {
|
||||
ctx.setError("Rejected by node: " + infuraResponse.getError());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
BigInteger nonce = coinData.getConfirmedTXCount();
|
||||
nonce = nonce.add(BigInteger.valueOf(1));
|
||||
coinData.setConfirmedTXCount(nonce);
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
if (method.equals(ServerApiMatic.MATIC_ETH_SEND_RAW_TRANSACTION)) {
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiMatic.setResponseListener(responseListener);
|
||||
|
||||
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needMultipleLinesForBalance() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowSelectFeeInclusion() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue