diff --git a/app/src/main/cpp/keys.c b/app/src/main/cpp/keys.c index ba19fd8d6a..7dc3e68d77 100644 --- a/app/src/main/cpp/keys.c +++ b/app/src/main/cpp/keys.c @@ -266,6 +266,17 @@ Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getOkLinkKey( JNIEnv* env #endif } +JNIEXPORT jstring JNICALL +Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getOkLBKey( JNIEnv* env, jclass thiz ) +{ +#if (HAS_KEYS == 1) + return getDecryptedKey(env, oklbKey); +#else + const jstring key = ""; + return (*env)->NewStringUTF(env, key); +#endif +} + JNIEXPORT jstring JNICALL Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getBlockPiBaobabKey( JNIEnv* env, jclass thiz ) { diff --git a/app/src/main/java/com/alphawallet/app/C.java b/app/src/main/java/com/alphawallet/app/C.java index eb18b43056..fb89ee7f06 100644 --- a/app/src/main/java/com/alphawallet/app/C.java +++ b/app/src/main/java/com/alphawallet/app/C.java @@ -58,6 +58,10 @@ public abstract class C { public static final String LINEA_TESTNET_NAME = LINEA_NAME + " (Test)"; public static final String HOLESKY_TESTNET_NAME = "Holesky (Test)"; + public static final String AMOY_TESTNET_NAME = "Amoy (Test)"; + public static final String BASE_MAINNET_NAME = "Base"; + public static final String BASE_TESTNET_NAME = "Base Sepolia (Test)"; + public static final String ETHEREUM_TICKER_NAME = "ethereum"; public static final String CLASSIC_TICKER_NAME = "ethereum-classic"; public static final String XDAI_TICKER_NAME = "dai"; @@ -91,6 +95,8 @@ public abstract class C { public static final String ROOTSTOCK_TEST_SYMBOL = "tBTC"; public static final String HOLESKY_TEST_SYMBOL = "Hol" + ETH_SYMBOL; + public static final String AMOY_TESTNET_SYMBOL = "Am" + ETH_SYMBOL; + public static final String BURN_ADDRESS = "0x0000000000000000000000000000000000000000"; //some important known contracts - NB must be all lower case for switch statement diff --git a/app/src/main/java/com/alphawallet/app/di/RepositoriesModule.java b/app/src/main/java/com/alphawallet/app/di/RepositoriesModule.java index 3e677819b9..cc51883696 100644 --- a/app/src/main/java/com/alphawallet/app/di/RepositoriesModule.java +++ b/app/src/main/java/com/alphawallet/app/di/RepositoriesModule.java @@ -39,6 +39,7 @@ import com.alphawallet.app.service.KeyService; import com.alphawallet.app.service.KeystoreAccountService; import com.alphawallet.app.service.NotificationService; +import com.alphawallet.app.service.OkLinkService; import com.alphawallet.app.service.OpenSeaService; import com.alphawallet.app.service.RealmManager; import com.alphawallet.app.service.SwapService; @@ -198,9 +199,10 @@ TokensService provideTokensServices(EthereumNetworkRepositoryType ethereumNetwor TokenRepositoryType tokenRepository, TickerService tickerService, OpenSeaService openseaService, - AnalyticsServiceType analyticsService) + AnalyticsServiceType analyticsService, + OkHttpClient client) { - return new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, openseaService, analyticsService); + return new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, openseaService, analyticsService, client); } @Singleton diff --git a/app/src/main/java/com/alphawallet/app/entity/okx/OkProtocolType.java b/app/src/main/java/com/alphawallet/app/entity/okx/OkProtocolType.java new file mode 100644 index 0000000000..3a9dd0f1e3 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/okx/OkProtocolType.java @@ -0,0 +1,43 @@ +package com.alphawallet.app.entity.okx; + +import com.alphawallet.app.entity.ContractType; + +public enum OkProtocolType +{ + ERC_20("token_20"), + ERC_721("token_721"), + ERC_1155("token_1155"); + + private final String type; + + OkProtocolType(String type) + { + this.type = type; + } + + public static ContractType getStandardType(OkProtocolType type) + { + switch (type) + { + case ERC_20 -> + { + return ContractType.ERC20; + } + case ERC_721 -> + { + return ContractType.ERC721; + } + case ERC_1155 -> + { + return ContractType.ERC1155; + } + } + + return ContractType.ERC20; + } + + public String getValue() + { + return type; + } +} diff --git a/app/src/main/java/com/alphawallet/app/entity/okx/TransactionListResponse.java b/app/src/main/java/com/alphawallet/app/entity/okx/OkServiceResponse.java similarity index 88% rename from app/src/main/java/com/alphawallet/app/entity/okx/TransactionListResponse.java rename to app/src/main/java/com/alphawallet/app/entity/okx/OkServiceResponse.java index c2c303699d..ddc352e0ca 100644 --- a/app/src/main/java/com/alphawallet/app/entity/okx/TransactionListResponse.java +++ b/app/src/main/java/com/alphawallet/app/entity/okx/OkServiceResponse.java @@ -6,7 +6,7 @@ import java.util.List; -public class TransactionListResponse +public class OkServiceResponse { @SerializedName("code") @Expose @@ -45,5 +45,9 @@ public static class Data @SerializedName("transactionLists") @Expose public List transactionLists; + + @SerializedName("tokenList") + @Expose + public List tokenList; } } diff --git a/app/src/main/java/com/alphawallet/app/entity/okx/OkToken.java b/app/src/main/java/com/alphawallet/app/entity/okx/OkToken.java new file mode 100644 index 0000000000..35487037a2 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/okx/OkToken.java @@ -0,0 +1,33 @@ +package com.alphawallet.app.entity.okx; + +import com.alphawallet.app.entity.tokens.TokenInfo; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class OkToken +{ + @SerializedName("symbol") + @Expose + public String symbol; + + @SerializedName("tokenContractAddress") + @Expose + public String tokenContractAddress; + + @SerializedName("holdingAmount") + @Expose + public String holdingAmount; + + @SerializedName("priceUsd") + @Expose + public String priceUsd; + + @SerializedName("tokenId") + @Expose + public String tokenId; + + public TokenInfo createInfo(long chainId) + { + return new TokenInfo(tokenContractAddress, "", symbol, 0, true, chainId); + } +} diff --git a/app/src/main/java/com/alphawallet/app/entity/okx/OkTokenCheck.java b/app/src/main/java/com/alphawallet/app/entity/okx/OkTokenCheck.java new file mode 100644 index 0000000000..36ea8fffa4 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/okx/OkTokenCheck.java @@ -0,0 +1,13 @@ +package com.alphawallet.app.entity.okx; + +public class OkTokenCheck +{ + public final long chainId; + public final OkProtocolType type; + + public OkTokenCheck(long chainId, OkProtocolType type) + { + this.chainId = chainId; + this.type = type; + } +} diff --git a/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenTicker.java b/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenTicker.java index 32d777c26d..9ed52b9cf8 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenTicker.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenTicker.java @@ -73,4 +73,9 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(priceSymbol); dest.writeLong(updateTime); } + + public long getTickerAgeMillis() + { + return (System.currentTimeMillis() - updateTime); + } } diff --git a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java index fb884a0d50..0d41d53198 100644 --- a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java +++ b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java @@ -7,6 +7,7 @@ import static com.alphawallet.app.entity.EventSync.OKX_BLOCK_SEARCH_INTERVAL; import static com.alphawallet.app.entity.EventSync.POLYGON_BLOCK_SEARCH_INTERVAL; import static com.alphawallet.app.util.Utils.isValidUrl; +import static com.alphawallet.ethereum.EthereumNetworkBase.AMOY_TEST_RPC_URL; import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_GOERLI_TESTNET_FALLBACK_RPC_URL; import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_GOERLI_TEST_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_MAIN_ID; @@ -59,6 +60,7 @@ import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_MAIN_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.PALM_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.PALM_TEST_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_AMOY_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_TEST_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.ROOTSTOCK_MAINNET_ID; @@ -149,6 +151,15 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy : FREE_PALM_RPC_URL; public static final String PALM_TEST_RPC_URL = usesProductionKey ? "https://palm-testnet.infura.io/v3/" + keyProvider.getInfuraKey() : FREE_PALM_TEST_RPC_URL; + + public static final String HOLESKY_BACKUP_RPC_URL = usesProductionKey ? "https://holesky.infura.io/v3/" + keyProvider.getInfuraKey() + : "https://holesky.infura.io/v3/da3717f25f824cc1baa32d812386d93f"; + + public static final String AMOY_RPC = usesProductionKey ? "https://polygon-amoy.infura.io/v3/" + keyProvider.getInfuraKey() + : AMOY_TEST_RPC_URL; + + public static final String AMOY_RPC_FALLBACK = usesProductionKey ? AMOY_TEST_RPC_URL : "https://polygon-amoy-bor-rpc.publicnode.com"; + public static final String USE_KLAYTN_RPC = !TextUtils.isEmpty(keyProvider.getBlockPiCypressKey()) ? "https://klaytn.blockpi.network/v1/rpc/" + keyProvider.getBlockPiCypressKey() : KLAYTN_RPC; public static final String USE_KLAYTN_BAOBAB_RPC = !TextUtils.isEmpty(keyProvider.getBlockPiBaobabKey()) ? "https://klaytn-baobab.blockpi.network/v1/rpc/" + keyProvider.getBlockPiBaobabKey() @@ -192,8 +203,8 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy FANTOM_ID, OPTIMISTIC_MAIN_ID, CRONOS_MAIN_ID, ARBITRUM_MAIN_ID, PALM_ID, KLAYTN_ID, IOTEX_MAINNET_ID, AURORA_MAINNET_ID, MILKOMEDA_C1_ID, OKX_ID)); private static final List testnetList = new ArrayList<>(Arrays.asList( - SEPOLIA_TESTNET_ID, POLYGON_TEST_ID, HOLESKY_ID, GOERLI_ID, BINANCE_TEST_ID, - ROOTSTOCK_TESTNET_ID, CRONOS_TEST_ID, OPTIMISM_GOERLI_TEST_ID, ARBITRUM_GOERLI_TEST_ID, LINEA_TEST_ID, KLAYTN_BAOBAB_ID, + SEPOLIA_TESTNET_ID, POLYGON_AMOY_ID, HOLESKY_ID, GOERLI_ID, BINANCE_TEST_ID, + ROOTSTOCK_TESTNET_ID, CRONOS_TEST_ID, OPTIMISM_GOERLI_TEST_ID, POLYGON_TEST_ID, ARBITRUM_GOERLI_TEST_ID, LINEA_TEST_ID, KLAYTN_BAOBAB_ID, FANTOM_TEST_ID, IOTEX_TESTNET_ID, FUJI_TEST_ID, MILKOMEDA_C1_TEST_ID, AURORA_TESTNET_ID, PALM_TEST_ID)); @@ -208,6 +219,11 @@ public static boolean isInfura(String rpcServerUrl) return rpcServerUrl.contains(INFURA_ENDPOINT); } + public static boolean isOKX(NetworkInfo networkInfo) + { + return networkInfo != null && !TextUtils.isEmpty(networkInfo.etherscanAPI) && networkInfo.etherscanAPI.startsWith("https://www.oklink.com"); + } + // for reset built-in network private static final LongSparseArray builtinNetworkMap = new LongSparseArray() { @@ -262,7 +278,11 @@ public static boolean isInfura(String rpcServerUrl) put(POLYGON_TEST_ID, new NetworkInfo(C.POLYGON_TEST_NETWORK, C.POLYGON_SYMBOL, FREE_MUMBAI_RPC_URL, "https://mumbai.polygonscan.com/tx/", POLYGON_TEST_ID, - MUMBAI_TEST_RPC_URL, " https://api-testnet.polygonscan.com/api?")); + MUMBAI_TEST_RPC_URL, "https://api-testnet.polygonscan.com/api?")); + put(POLYGON_AMOY_ID, new NetworkInfo(C.AMOY_TESTNET_NAME, C.AMOY_TESTNET_SYMBOL, + AMOY_RPC, + "https://amoy.polygonscan.com/tx/", POLYGON_AMOY_ID, AMOY_RPC_FALLBACK, + "https://api-amoy.polygonscan.com/api?")); put(OPTIMISTIC_MAIN_ID, new NetworkInfo(C.OPTIMISTIC_NETWORK, C.ETH_SYMBOL, OPTIMISTIC_MAIN_URL, "https://optimistic.etherscan.io/tx/", OPTIMISTIC_MAIN_ID, OPTIMISTIC_MAIN_FALLBACK_URL, @@ -342,7 +362,6 @@ public static boolean isInfura(String rpcServerUrl) ROOTSTOCK_TESTNET_RPC_URL, "", ROOTSTOCK_TESTNET_ID, "", "")); - put(LINEA_ID, new NetworkInfo(C.LINEA_NAME, C.ETH_SYMBOL, LINEA_RPC, "https://lineascan.build/tx/", LINEA_ID, LINEA_FALLBACK_RPC, @@ -353,7 +372,7 @@ public static boolean isInfura(String rpcServerUrl) "https://api-testnet.lineascan.build/api?")); put(HOLESKY_ID, new NetworkInfo(C.HOLESKY_TESTNET_NAME, C.HOLESKY_TEST_SYMBOL, HOLESKY_RPC_URL, - "https://holesky.etherscan.io/tx/", HOLESKY_ID, HOLESKY_FALLBACK_URL, + "https://holesky.etherscan.io/tx/", HOLESKY_ID, HOLESKY_BACKUP_RPC_URL, "https://api-holesky.etherscan.io/api?")); // Add deprecated networks after this line @@ -379,7 +398,7 @@ public static boolean isInfura(String rpcServerUrl) put(AVALANCHE_ID, R.drawable.ic_icons_tokens_avalanche); put(FUJI_TEST_ID, R.drawable.ic_icons_tokens_avalanche_testnet); put(POLYGON_ID, R.drawable.ic_icons_polygon); - put(POLYGON_TEST_ID, R.drawable.ic_icons_tokens_mumbai); + put(POLYGON_AMOY_ID, R.drawable.ic_icons_tokens_mumbai); put(OPTIMISTIC_MAIN_ID, R.drawable.ic_optimism_logo); put(CRONOS_MAIN_ID, R.drawable.ic_cronos_mainnet); put(CRONOS_TEST_ID, R.drawable.ic_cronos); @@ -403,6 +422,7 @@ public static boolean isInfura(String rpcServerUrl) put(LINEA_ID, R.drawable.ic_icons_linea); put(LINEA_TEST_ID, R.drawable.ic_icons_linea_testnet); put(HOLESKY_ID, R.drawable.ic_icons_holesky); + put(POLYGON_TEST_ID, R.drawable.ic_icons_tokens_mumbai); } }; @@ -421,6 +441,7 @@ public static boolean isInfura(String rpcServerUrl) put(AVALANCHE_ID, R.drawable.ic_icons_network_avalanche); put(FUJI_TEST_ID, R.drawable.ic_icons_tokens_avalanche_testnet); put(POLYGON_ID, R.drawable.ic_icons_network_polygon); + put(POLYGON_AMOY_ID, R.drawable.ic_icons_tokens_mumbai); put(POLYGON_TEST_ID, R.drawable.ic_icons_tokens_mumbai); put(OPTIMISTIC_MAIN_ID, R.drawable.ic_icons_network_optimism); put(CRONOS_MAIN_ID, R.drawable.ic_cronos_mainnet); @@ -464,6 +485,7 @@ public static boolean isInfura(String rpcServerUrl) put(FUJI_TEST_ID, R.color.avalanche_test); put(POLYGON_ID, R.color.polygon_main); put(POLYGON_TEST_ID, R.color.polygon_test); + put(POLYGON_AMOY_ID, R.color.polygon_test); put(OPTIMISTIC_MAIN_ID, R.color.optimistic_main); put(CRONOS_MAIN_ID, R.color.cronos_main); put(CRONOS_TEST_ID, R.color.cronos_test); @@ -495,7 +517,7 @@ public static boolean isInfura(String rpcServerUrl) // + GAS_API //If the gas oracle you're adding doesn't follow this spec then you'll have to change the getGasOracle method private static final List hasGasOracleAPI = Arrays.asList(MAINNET_ID, POLYGON_ID, ARBITRUM_MAIN_ID, AVALANCHE_ID, BINANCE_MAIN_ID, CRONOS_MAIN_ID, GOERLI_ID, - SEPOLIA_TESTNET_ID, FANTOM_ID, LINEA_ID, OPTIMISTIC_MAIN_ID, POLYGON_TEST_ID); + SEPOLIA_TESTNET_ID, FANTOM_ID, LINEA_ID, OPTIMISTIC_MAIN_ID, POLYGON_TEST_ID, POLYGON_AMOY_ID); private static final List hasEtherscanGasOracleAPI = Arrays.asList(MAINNET_ID, HECO_ID, BINANCE_MAIN_ID, POLYGON_ID); private static final List hasBlockNativeGasOracleAPI = Arrays.asList(MAINNET_ID, POLYGON_ID); //These chains don't allow custom gas @@ -1290,7 +1312,7 @@ public static String getChainSymbol(long chainId) public static boolean isEventBlockLimitEnforced(long chainId) { - if (chainId == POLYGON_ID || chainId == POLYGON_TEST_ID) + if (chainId == POLYGON_ID || chainId == POLYGON_TEST_ID || chainId == POLYGON_AMOY_ID) { return true; } diff --git a/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java b/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java index 4e68afafe3..1933f58f61 100644 --- a/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java +++ b/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java @@ -40,6 +40,8 @@ public interface KeyProvider String getOkLinkKey(); + String getOkLBKey(); + String getBlockPiBaobabKey(); String getBlockPiCypressKey(); diff --git a/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java b/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java index 11c10f2ea4..00f9350775 100644 --- a/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java +++ b/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java @@ -45,6 +45,8 @@ public KeyProviderJNIImpl() public native String getOkLinkKey(); + public native String getOkLBKey(); + public native String getBlockPiBaobabKey(); public native String getBlockPiCypressKey(); diff --git a/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java b/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java index af4c1a1041..f10dba66d8 100644 --- a/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java +++ b/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java @@ -405,7 +405,7 @@ public Single update(String contractAddr, long chainId, ContractType { if (chainId == OKX_ID) { - return tokenInfoFromOKLinkService(contractAddr); //don't need type here, we can determine that from the return + return tokenInfoFromOKLinkService(chainId, contractAddr); //don't need type here, we can determine that from the return } switch (type) @@ -424,9 +424,9 @@ public Single update(String contractAddr, long chainId, ContractType } } - private Single tokenInfoFromOKLinkService(String contractAddr) + private Single tokenInfoFromOKLinkService(long chainId, String contractAddr) { - return Single.fromCallable(() -> OkLinkService.get(okClient).getTokenInfo(contractAddr)).observeOn(Schedulers.io()); + return Single.fromCallable(() -> OkLinkService.get(okClient).getTokenInfo(chainId, contractAddr)).observeOn(Schedulers.io()); } @Override diff --git a/app/src/main/java/com/alphawallet/app/service/OkLinkService.java b/app/src/main/java/com/alphawallet/app/service/OkLinkService.java index 2996209fd9..0817200909 100644 --- a/app/src/main/java/com/alphawallet/app/service/OkLinkService.java +++ b/app/src/main/java/com/alphawallet/app/service/OkLinkService.java @@ -1,12 +1,31 @@ package com.alphawallet.app.service; +import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.AVALANCHE_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.BASE_MAINNET_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.BINANCE_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.CLASSIC_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.FANTOM_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.LINEA_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.OKX_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_AMOY_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.SEPOLIA_TESTNET_ID; + import android.net.Uri; import android.text.TextUtils; +import android.util.LongSparseArray; import com.alphawallet.app.entity.EtherscanEvent; +import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.OkxEvent; import com.alphawallet.app.entity.okx.TokenListReponse; -import com.alphawallet.app.entity.okx.TransactionListResponse; +import com.alphawallet.app.entity.okx.OkServiceResponse; +import com.alphawallet.app.entity.okx.OkProtocolType; +import com.alphawallet.app.entity.okx.OkToken; import com.alphawallet.app.entity.tokens.TokenInfo; import com.alphawallet.app.entity.transactionAPI.TransferFetchType; import com.alphawallet.app.repository.KeyProviderFactory; @@ -14,9 +33,12 @@ import com.google.gson.Gson; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import io.reactivex.Single; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; @@ -24,15 +46,40 @@ public class OkLinkService { + private static final String TAG = "OKLINK"; private static final String BASE_URL = "https://www.oklink.com/api"; private static final String LIMIT = "50"; // Max limit; default is 20 - private static final String CHAIN_SHORT_NAME = "OKC"; // Max limit; default is 20 + public static OkLinkService instance; private final OkHttpClient httpClient; + private final boolean hasKey; + + private static final LongSparseArray shortNames = new LongSparseArray<>() + { + { + put(MAINNET_ID, "ETH"); + put(OKX_ID, "OKTC"); + put(POLYGON_AMOY_ID, "AMOY_TESTNET"); + put(ARBITRUM_MAIN_ID, "ARBITRUM"); + put(BINANCE_MAIN_ID, "BSC"); + put(KLAYTN_ID, "KLAYTN"); + put(CLASSIC_ID, "ETC"); + put(POLYGON_ID, "POLYGON"); + put(AVALANCHE_ID, "AVAXC"); + put(FANTOM_ID, "FTM"); + put(OPTIMISTIC_MAIN_ID, "OP"); + put(LINEA_ID, "LINEA"); + put(BASE_MAINNET_ID, "BASE"); + put(SEPOLIA_TESTNET_ID, "SEPOLIA_TESTNET"); + } + }; + + private static final Map> networkChecked = new HashMap<>();// LongSparseArray<>(); public OkLinkService(OkHttpClient httpClient) { this.httpClient = httpClient; + this.hasKey = !KeyProviderFactory.get().getOkLinkKey().isEmpty(); } public static OkLinkService get(OkHttpClient httpClient) @@ -41,23 +88,34 @@ public static OkLinkService get(OkHttpClient httpClient) { instance = new OkLinkService(httpClient); } + return instance; } - private Request buildRequest(String api) + public static boolean supportsChain(long chainId) + { + return !shortNames.get(chainId, "").isEmpty(); + } + + private String getChainShortName(long chainId) + { + return shortNames.get(chainId, ""); + } + + private Request buildRequest(String api, boolean useAlt) { Request.Builder requestB = new Request.Builder() .url(api) .header("User-Agent", "Chrome/74.0.3729.169") .addHeader("Content-Type", "application/json") - .addHeader("Ok-Access-Key", KeyProviderFactory.get().getOkLinkKey()) + .addHeader("Ok-Access-Key", useAlt ? KeyProviderFactory.get().getOkLBKey() : KeyProviderFactory.get().getOkLinkKey()) .get(); return requestB.build(); } - private String executeRequest(String api) + private String executeRequest(String api, boolean useAlt) { - try (okhttp3.Response response = httpClient.newCall(buildRequest(api)).execute()) + try (okhttp3.Response response = httpClient.newCall(buildRequest(api, useAlt)).execute()) { if (response.isSuccessful()) { @@ -81,8 +139,13 @@ private String executeRequest(String api) return JsonUtils.EMPTY_RESULT; } - public EtherscanEvent[] getEtherscanEvents(String address, long lastBlockRead, TransferFetchType tfType) + public EtherscanEvent[] getEtherscanEvents(long chainId, String address, long lastBlockRead, TransferFetchType tfType) { + if (!hasKey || !supportsChain(chainId)) + { + return new EtherscanEvent[0]; + } + String protocolType = getOkxFetchType(tfType); List events = new ArrayList<>(); int page = 1; @@ -91,11 +154,11 @@ public EtherscanEvent[] getEtherscanEvents(String address, long lastBlockRead, T do { - TransactionListResponse response = new Gson().fromJson( - fetchTransactions(address, protocolType, String.valueOf(page++)), - TransactionListResponse.class); + OkServiceResponse response = new Gson().fromJson( + fetchTransactions(chainId, address, protocolType, String.valueOf(page++)), + OkServiceResponse.class); - if (response.data != null && response.data.size() > 0) + if (response.data != null && !response.data.isEmpty()) { String totalPageStr = response.data.get(0).totalPage; if (!TextUtils.isEmpty(totalPageStr)) @@ -129,6 +192,77 @@ public EtherscanEvent[] getEtherscanEvents(String address, long lastBlockRead, T return etherscanEvents.toArray(new EtherscanEvent[0]); } + public Single> getTokensForChain(long chainId, String address, OkProtocolType tokenType) //reserve this call for one per day + { + List txReturn = new ArrayList<>(); + if (!supportsChain(chainId) || !canCheckChain(chainId, address, tokenType)) + { + return Single.fromCallable(() -> txReturn); + } + + return Single.fromCallable(() -> { + int page = 1; + int totalPage = 0; + + LongSparseArray thisChainCheck = getCheckList(address); + + int existingCheck = thisChainCheck.get(chainId, 0); + + int byteEntry = 2^tokenType.ordinal(); + + thisChainCheck.put(chainId, existingCheck | byteEntry); + + do + { + OkServiceResponse response = new Gson().fromJson( + fetchTokens(chainId, address, tokenType, String.valueOf(page++)), + OkServiceResponse.class); + + if (response.data != null && !response.data.isEmpty()) + { + String totalPageStr = response.data.get(0).totalPage; + if (!TextUtils.isEmpty(totalPageStr)) + { + totalPage = Integer.parseInt(totalPageStr); + } + txReturn.addAll(response.data.get(0).tokenList); + } + else + { + break; + } + } + while (page <= totalPage); + + return txReturn; + }); + } + + private LongSparseArray getCheckList(String address) + { + LongSparseArray thisChainCheck = networkChecked.get(address); + if (thisChainCheck == null) + { + thisChainCheck = new LongSparseArray<>(); + } + + return thisChainCheck; + } + + public String fetchTokens(long chainId, String address, + OkProtocolType tokenType, String page) + { + Uri.Builder builder = new Uri.Builder(); + builder.encodedPath(BASE_URL + "/v5/explorer/address/token-balance") + .appendQueryParameter("address", address) + .appendQueryParameter("chainShortName", getChainShortName(chainId)) + .appendQueryParameter("protocolType", tokenType.getValue()) + .appendQueryParameter("limit", LIMIT) + .appendQueryParameter("page", page); + String url = builder.build().toString(); + return executeRequest(url, true); + } + //@SuppressWarnings("ConstantConditions") private boolean compareEventsWithLastRead(List events, long lastBlockRead) { @@ -147,7 +281,7 @@ private boolean compareEventsWithLastRead(List events, long lastBlockR return false; } - public String fetchTransactions(String address, + public String fetchTransactions(long chainId, String address, String protocolType, String page) { @@ -155,33 +289,33 @@ public String fetchTransactions(String address, builder.encodedPath(BASE_URL + "/v5/explorer/address/transaction-list") .appendQueryParameter("address", address) .appendQueryParameter("protocolType", protocolType) - .appendQueryParameter("chainShortName", CHAIN_SHORT_NAME) + .appendQueryParameter("chainShortName", getChainShortName(chainId)) .appendQueryParameter("limit", LIMIT) .appendQueryParameter("page", page); String url = builder.build().toString(); - return executeRequest(url); + return executeRequest(url, false); } - public TokenInfo getTokenInfo(String contractAddress) + public TokenInfo getTokenInfo(long chainId, String contractAddress) { - TokenListReponse.TokenDetails tokenDetails = getTokenDetails(contractAddress); + TokenListReponse.TokenDetails tokenDetails = getTokenDetails(chainId, contractAddress); return new TokenInfo( tokenDetails.tokenContractAddress, tokenDetails.tokenFullName, tokenDetails.token, Integer.parseInt(tokenDetails.precision), true, - 66); + chainId); } - public TokenListReponse.TokenDetails getTokenDetails(String contractAddress) + public TokenListReponse.TokenDetails getTokenDetails(long chainId, String contractAddress) { - TokenListReponse response = new Gson().fromJson(fetchTokenDetails(contractAddress), TokenListReponse.class); - if (response.data.size() > 0) + TokenListReponse response = new Gson().fromJson(fetchTokenDetails(chainId, contractAddress), TokenListReponse.class); + if (!response.data.isEmpty()) { List tokenList = response.data.get(0).tokenList; - if (tokenList.size() > 0) + if (!tokenList.isEmpty()) { return tokenList.get(0); } @@ -189,14 +323,14 @@ public TokenListReponse.TokenDetails getTokenDetails(String contractAddress) return null; } - public String fetchTokenDetails(String contractAddress) + public String fetchTokenDetails(long chainId, String contractAddress) { Uri.Builder builder = new Uri.Builder(); builder.encodedPath(BASE_URL + "/v5/explorer/token/token-list") .appendQueryParameter("tokenContractAddress", contractAddress) - .appendQueryParameter("chainShortName", CHAIN_SHORT_NAME); + .appendQueryParameter("chainShortName", getChainShortName(chainId)); String url = builder.build().toString(); - return executeRequest(url); + return executeRequest(url, false); } private String getOkxFetchType(TransferFetchType tfType) @@ -214,4 +348,10 @@ else if (tfType == TransferFetchType.ERC_1155) return "token_20"; } } + + private boolean canCheckChain(long networkId, String address, OkProtocolType checkType) + { + LongSparseArray thisAddressCheck = networkChecked.get(address); + return (thisAddressCheck == null || (thisAddressCheck.get(networkId, 0) & 2^checkType.ordinal()) == 1); + } } diff --git a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java index 0593b13921..5156caaf4d 100644 --- a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java +++ b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java @@ -18,14 +18,11 @@ import com.alphawallet.app.C; import com.alphawallet.app.entity.ContractType; import com.alphawallet.app.entity.nftassets.NFTAsset; -import com.alphawallet.app.entity.opensea.AssetContract; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.entity.tokens.TokenFactory; import com.alphawallet.app.entity.tokens.TokenInfo; import com.alphawallet.app.repository.KeyProviderFactory; import com.alphawallet.app.util.JsonUtils; -import com.alphawallet.ethereum.EthereumNetworkBase; -import com.google.gson.Gson; import org.json.JSONArray; import org.json.JSONObject; @@ -58,7 +55,7 @@ public class OpenSeaService private final LongSparseArray networkCheckTimes = new LongSparseArray<>(); private final Map pageOffsets = new ConcurrentHashMap<>(); - private final Map API_CHAIN_MAP = Map.of( + private final static Map API_CHAIN_MAP = Map.of( MAINNET_ID, "ethereum", KLAYTN_ID, "klaytn", POLYGON_TEST_ID, "mumbai", @@ -346,7 +343,7 @@ public void resetOffsetRead(List networkFilter) public boolean canCheckChain(long networkId) { long lastCheckTime = networkCheckTimes.get(networkId, 0L); - return System.currentTimeMillis() > (lastCheckTime + DateUtils.MINUTE_IN_MILLIS); + return System.currentTimeMillis() > (lastCheckTime + 10 * DateUtils.MINUTE_IN_MILLIS); } public Single getAsset(Token token, BigInteger tokenId) @@ -407,4 +404,9 @@ public String fetchCollection(long networkId, String slug) String api = C.OPENSEA_COLLECTION_API_MAINNET + slug; return executeRequest(networkId, api); } + + public static boolean hasOpenSeaAPI(long chainId) + { + return API_CHAIN_MAP.containsKey(chainId); + } } diff --git a/app/src/main/java/com/alphawallet/app/service/TickerService.java b/app/src/main/java/com/alphawallet/app/service/TickerService.java index 58a2f0158e..2524498b3e 100644 --- a/app/src/main/java/com/alphawallet/app/service/TickerService.java +++ b/app/src/main/java/com/alphawallet/app/service/TickerService.java @@ -756,7 +756,7 @@ public void addCustomTicker(long chainId, String address, TokenTicker ticker) private void onTickersError(Throwable throwable) { mainTickerUpdate = null; - throwable.printStackTrace(); + Timber.e(throwable); } public static String getFullCurrencyString(double price) @@ -914,4 +914,26 @@ private boolean receivedAllChainPairs() return true; } + + //Store received ticker if required + public void storeTickers(long chainId, Map tickerMap) + { + //if ticker not found or out of date update the price + //ticker up to date? + Map tickerUpdateMap = new HashMap<>(); + for (String key : tickerMap.keySet()) + { + String dbKey = TokensRealmSource.databaseKey(chainId, key); + TokenTicker fromDb = localSource.getCurrentTicker(dbKey); + if (fromDb == null || fromDb.getTickerAgeMillis() > TICKER_STALE_TIMEOUT) + { + tickerUpdateMap.put(key, tickerMap.get(key)); + } + } + + if (!tickerUpdateMap.isEmpty()) + { + localSource.updateERC20Tickers(chainId, tickerUpdateMap); + } + } } diff --git a/app/src/main/java/com/alphawallet/app/service/TokensService.java b/app/src/main/java/com/alphawallet/app/service/TokensService.java index c4e9ac95d5..036f313757 100644 --- a/app/src/main/java/com/alphawallet/app/service/TokensService.java +++ b/app/src/main/java/com/alphawallet/app/service/TokensService.java @@ -3,7 +3,6 @@ import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; -import android.media.Image; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Pair; @@ -21,10 +20,12 @@ import com.alphawallet.app.entity.ServiceSyncCallback; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; +import com.alphawallet.app.entity.okx.OkTokenCheck; import com.alphawallet.app.entity.tokendata.TokenGroup; import com.alphawallet.app.entity.tokendata.TokenTicker; import com.alphawallet.app.entity.tokendata.TokenUpdateType; -import com.alphawallet.app.entity.tokens.Attestation; +import com.alphawallet.app.entity.okx.OkProtocolType; +import com.alphawallet.app.entity.okx.OkToken; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.entity.tokens.TokenCardMeta; import com.alphawallet.app.entity.tokens.TokenFactory; @@ -33,7 +34,6 @@ import com.alphawallet.app.repository.EthereumNetworkRepository; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; import com.alphawallet.app.repository.TokenRepositoryType; -import com.alphawallet.app.ui.widget.entity.IconItem; import com.alphawallet.app.util.Utils; import com.alphawallet.token.entity.ContractAddress; @@ -44,6 +44,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -58,6 +59,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import io.realm.Realm; +import okhttp3.OkHttpClient; import timber.log.Timber; public class TokensService @@ -75,12 +77,14 @@ public class TokensService private final TokenRepositoryType tokenRepository; private final TickerService tickerService; private final OpenSeaService openseaService; + private final OkHttpClient httpClient; private final AnalyticsServiceType analyticsService; private final List networkFilter; private ContractLocator focusToken; private final ConcurrentLinkedDeque unknownTokens; private final ConcurrentLinkedQueue baseTokenCheck; private final ConcurrentLinkedQueue imagesForWrite; + private final ConcurrentLinkedQueue chainCheckList; private long openSeaCheckId; private boolean appHasFocus; private static boolean walletStartup = false; @@ -107,6 +111,8 @@ public class TokensService private Disposable openSeaQueryDisposable; @Nullable private Disposable imageWriter; + @Nullable + private Disposable okDisposable; private static boolean done = false; @@ -114,7 +120,8 @@ public TokensService(EthereumNetworkRepositoryType ethereumNetworkRepository, TokenRepositoryType tokenRepository, TickerService tickerService, OpenSeaService openseaService, - AnalyticsServiceType analyticsService) { + AnalyticsServiceType analyticsService, + OkHttpClient httpClient) { this.ethereumNetworkRepository = ethereumNetworkRepository; this.tokenRepository = tokenRepository; this.tickerService = tickerService; @@ -126,6 +133,8 @@ public TokensService(EthereumNetworkRepositoryType ethereumNetworkRepository, this.unknownTokens = new ConcurrentLinkedDeque<>(); this.baseTokenCheck = new ConcurrentLinkedQueue<>(); this.imagesForWrite = new ConcurrentLinkedQueue<>(); + this.chainCheckList = new ConcurrentLinkedQueue<>(); + this.httpClient = httpClient; setCurrentAddress(ethereumNetworkRepository.getCurrentWalletAddress()); //set current wallet address at service startup appHasFocus = true; transferCheckChain = 0; @@ -139,7 +148,7 @@ private void checkUnknownTokens() ContractAddress t = unknownTokens.pollFirst(); Token cachedToken = t != null ? getToken(t.chainId, t.address) : null; - if (t != null && t.address.length() > 0 && (cachedToken == null || TextUtils.isEmpty(cachedToken.tokenInfo.name))) + if (t != null && !t.address.isEmpty() && (cachedToken == null || TextUtils.isEmpty(cachedToken.tokenInfo.name))) { ContractType type = tokenRepository.determineCommonType(new TokenInfo(t.address, "", "", 18, false, t.chainId)).blockingGet(); @@ -248,7 +257,8 @@ public void startUpdateCycleIfRequired() public void startUpdateCycle() { - if ((lastStartCycleTime + 2000) > System.currentTimeMillis()) + if ((eventTimer != null && !eventTimer.isDisposed() && (lastStartCycleTime + 10000) > System.currentTimeMillis()) + || (lastStartCycleTime + 2000) > System.currentTimeMillis()) { return; // Block this refresh - we need to ensure the cycle restarts but within 1 second no need to restart } @@ -272,6 +282,7 @@ public void startUpdateCycle() startupPass(); checkIssueTokens(); pendingTokenMap.clear(); + checkTokensOnOKx(); return true; }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -328,7 +339,7 @@ private void checkSyncStatus(int unSynced, TokenCardMeta[] tokenList) private boolean syncERC20Tickers(final int chainIndex, final long chainId, final TokenCardMeta[] tokenList) { List erc20OnChain = getERC20OnChain(chainId, tokenList); - if (erc20OnChain.size() > 0) + if (!erc20OnChain.isEmpty()) { tickerService.syncERC20Tickers(chainId, erc20OnChain) .subscribeOn(Schedulers.io()) @@ -344,7 +355,7 @@ private boolean syncERC20Tickers(final int chainIndex, final long chainId, final } } - private List getERC20OnChain( long chainId, TokenCardMeta[] tokenList) + private List getERC20OnChain(long chainId, TokenCardMeta[] tokenList) { List allERC20 = new ArrayList<>(); for (TokenCardMeta tcm : tokenList) @@ -408,12 +419,14 @@ public void stopUpdateCycle() if (checkUnknownTokenCycle != null && !checkUnknownTokenCycle.isDisposed()) { checkUnknownTokenCycle.dispose(); } if (queryUnknownTokensDisposable != null && !queryUnknownTokensDisposable.isDisposed()) { queryUnknownTokensDisposable.dispose(); } if (openSeaQueryDisposable != null && !openSeaQueryDisposable.isDisposed()) { openSeaQueryDisposable.dispose(); } + if (okDisposable != null && !okDisposable.isDisposed()) { okDisposable.dispose(); } pendingChainMap.clear(); tokenStoreList.clear(); baseTokenCheck.clear(); pendingTokenMap.clear(); unknownTokens.clear(); + chainCheckList.clear(); } public String getCurrentAddress() { return currentAddress; } @@ -423,7 +436,7 @@ public void stopUpdateCycle() public void setupFilter(boolean userUpdated) { networkFilter.clear(); - if (CustomViewSettings.getLockedChains().size() > 0) + if (!CustomViewSettings.getLockedChains().isEmpty()) { networkFilter.addAll(CustomViewSettings.getLockedChains()); } @@ -536,6 +549,49 @@ private void writeImages() } } + private void checkTokensOnOKx() + { + if (httpClient == null) + { + return; + } + //refresh check list + //get list of current chains we need to check + chainCheckList.clear(); + for (long chainId : networkFilter) + { + if (OkLinkService.supportsChain(chainId)) + { + chainCheckList.add(new OkTokenCheck(chainId, OkProtocolType.ERC_20)); + chainCheckList.add(new OkTokenCheck(chainId, OkProtocolType.ERC_721)); + chainCheckList.add(new OkTokenCheck(chainId, OkProtocolType.ERC_1155)); + } + } + + if (okDisposable == null || okDisposable.isDisposed()) + { + okDisposable = Observable.interval(2000, 1000, TimeUnit.MILLISECONDS) + .doOnNext(l -> checkChainOnOkx()).subscribe(); + } + } + + private void checkChainOnOkx() + { + if (chainCheckList.isEmpty()) + { + okDisposable.dispose(); + okDisposable = null; + return; + } + + //perform check & update tokens + OkTokenCheck thisCheck = chainCheckList.poll(); + if (thisCheck != null) + { + checkOkTokens(thisCheck.chainId, thisCheck.type); + } + } + private void startupPass() { if (!walletStartup) return; @@ -763,6 +819,68 @@ private void checkOpenSea(long chainId) }, this::openSeaCallError); } + private void checkOkTokens(long chainId, OkProtocolType tokenType) + { + OkLinkService.get(httpClient).getTokensForChain(chainId, currentAddress, tokenType) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(tokenList -> processOkTokenList(tokenList, chainId, tokenType)) + .isDisposed(); + } + + private void processOkTokenList(List tokenList, long chainId, OkProtocolType tokenType) + { + //process the list; it's either an individual NFT or an ERC20 token + Map tickerMap = new HashMap<>(); + for (OkToken okToken : tokenList) + { + //first check if token is known + Token token = getToken(chainId, okToken.tokenContractAddress); + if (token == null) + { + addUnknownTokenToCheck(new ContractAddress(chainId, okToken.tokenContractAddress)); + } + + switch (tokenType) + { + case ERC_20 -> + { + if (!TextUtils.isEmpty(okToken.priceUsd) && !okToken.priceUsd.equals("0")) + { + TokenTicker ticker = new TokenTicker(okToken.priceUsd, "0", okToken.symbol, "", System.currentTimeMillis()); + tickerMap.put(okToken.tokenContractAddress, ticker); + } + } + case ERC_721, ERC_1155 -> + { + //handle each individual token + //check if this tokenId is known + BigInteger tokenId = new BigInteger(okToken.tokenId); + if (token == null) + { + token = tokenFactory.createToken(okToken.createInfo(chainId), OkProtocolType.getStandardType(tokenType), ethereumNetworkRepository.getNetworkByChain(chainId).getShortName()); + } + + NFTAsset asset = token.getAssetForToken(tokenId); + + if (asset == null) + { + //create blank asset with tokenId + asset = new NFTAsset(tokenId); + asset.setBalance(new BigDecimal(okToken.holdingAmount)); + //store the asset + storeAsset(token, tokenId, asset); + } + } + } + } + + if (tokenType == OkProtocolType.ERC_20) + { + tickerService.storeTickers(chainId, tickerMap); + } + } + private void openSeaCallError(Throwable error) { Timber.w(error); @@ -1167,7 +1285,7 @@ public boolean isChainToken(long chainId, String tokenAddress) public boolean hasChainToken(long chainId) { - return EthereumNetworkRepository.getChainOverrideAddress(chainId).length() > 0; + return !EthereumNetworkRepository.getChainOverrideAddress(chainId).isEmpty(); } public Token getServiceToken(long chainId) diff --git a/app/src/main/java/com/alphawallet/app/service/TransactionsNetworkClient.java b/app/src/main/java/com/alphawallet/app/service/TransactionsNetworkClient.java index 1c4a413f7a..509dd81b77 100644 --- a/app/src/main/java/com/alphawallet/app/service/TransactionsNetworkClient.java +++ b/app/src/main/java/com/alphawallet/app/service/TransactionsNetworkClient.java @@ -6,6 +6,7 @@ import static com.alphawallet.ethereum.EthereumNetworkBase.AURORA_TESTNET_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.BINANCE_MAIN_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.OKX_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_AMOY_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_TEST_ID; @@ -29,6 +30,7 @@ import com.alphawallet.app.entity.tokens.TokenInfo; import com.alphawallet.app.entity.transactionAPI.TransferFetchType; import com.alphawallet.app.entity.transactions.TransferEvent; +import com.alphawallet.app.repository.EthereumNetworkBase; import com.alphawallet.app.repository.KeyProvider; import com.alphawallet.app.repository.KeyProviderFactory; import com.alphawallet.app.repository.TransactionsRealmCache; @@ -494,9 +496,9 @@ private EtherscanEvent[] fetchEvents(Realm instance, String walletAddress, Netwo //get oldest record long lastBlockFound = getTokenBlockRead(instance, networkInfo.chainId, tfType); - if (networkInfo.chainId == OKX_ID) + if (EthereumNetworkBase.isOKX(networkInfo)) { - events = OkLinkService.get(httpClient).getEtherscanEvents(walletAddress, lastBlockFound, tfType); + events = OkLinkService.get(httpClient).getEtherscanEvents(networkInfo.chainId, walletAddress, lastBlockFound, tfType); eventList = new ArrayList<>(Arrays.asList(events)); } else @@ -729,7 +731,7 @@ else if (networkInfo.chainId == BINANCE_MAIN_ID) { return BSC_EXPLORER_API_KEY; } - else if (networkInfo.chainId == POLYGON_ID || networkInfo.chainId == POLYGON_TEST_ID) + else if (networkInfo.chainId == POLYGON_ID || networkInfo.chainId == POLYGON_TEST_ID || networkInfo.chainId == POLYGON_AMOY_ID) { return POLYGONSCAN_API_KEY; } @@ -1258,7 +1260,7 @@ private List sortTransactions(Collection txCollection) { List txList = new ArrayList<>(txCollection); - Collections.sort(txList, (t1, t2) -> { + txList.sort((t1, t2) -> { long block1 = Long.parseLong(t1.blockNumber); long block2 = Long.parseLong(t2.blockNumber); diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 5a52b1fbd2..dc45a2c937 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -631,7 +631,6 @@ public void leaveFocus() addressBar.leaveFocus(); if (viewModel != null) viewModel.stopBalanceUpdate(); stopBalanceListener(); - viewModel.getTokenService().startUpdateCycle(); } /** diff --git a/app/src/main/java/com/alphawallet/app/util/DappBrowserUtils.java b/app/src/main/java/com/alphawallet/app/util/DappBrowserUtils.java index bbb003a9b4..43eedd4c89 100644 --- a/app/src/main/java/com/alphawallet/app/util/DappBrowserUtils.java +++ b/app/src/main/java/com/alphawallet/app/util/DappBrowserUtils.java @@ -1,6 +1,7 @@ package com.alphawallet.app.util; import static com.alphawallet.app.util.Utils.isValidUrl; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_AMOY_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID; import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_TEST_ID; @@ -291,7 +292,7 @@ private static void blankPrefEntry(Context context, String key) public static String defaultDapp(long chainId) { - return (chainId == POLYGON_ID || chainId == POLYGON_TEST_ID) ? POLYGON_HOMEPAGE : DEFAULT_HOMEPAGE; + return (chainId == POLYGON_ID || chainId == POLYGON_TEST_ID || chainId == POLYGON_AMOY_ID) ? POLYGON_HOMEPAGE : DEFAULT_HOMEPAGE; } public static boolean isWithinHomePage(String url) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/WalletsViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/WalletsViewModel.java index 5340a75e52..2042bc550c 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/WalletsViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/WalletsViewModel.java @@ -149,7 +149,7 @@ public class WalletsViewModel extends BaseViewModel implements ServiceSyncCallba this.tickerService = tickerService; this.assetService = assetService; this.preferenceRepository = preferenceRepository; - this.tokensService = new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, null, null); + this.tokensService = new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, null, null, null); ensResolver = new AWEnsResolver(TokenRepository.getWeb3jService(MAINNET_ID), context); syncCallback = null; @@ -342,7 +342,7 @@ private void sendUnsyncedValue(Wallet wallet) private Single startWalletSync(Wallet wallet) { return Single.fromCallable(() -> { - TokensService svs = new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, null, null); + TokensService svs = new TokensService(ethereumNetworkRepository, tokenRepository, tickerService, null, null, null); svs.setCurrentAddress(wallet.address.toLowerCase()); svs.startUpdateCycle(); svs.setCompletionCallback(this, 2); diff --git a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java index 50be0eb5fe..0cf0b579ec 100644 --- a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java +++ b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java @@ -120,6 +120,12 @@ public String getOkLinkKey() return FAKE_KEY_FOR_TESTING; } + @Override + public String getOkLBKey() + { + return FAKE_KEY_FOR_TESTING; + } + @Override public String getBlockPiBaobabKey() { diff --git a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java index 866d995530..8108662d07 100644 --- a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java +++ b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java @@ -116,6 +116,12 @@ public String getOkLinkKey() return null; } + @Override + public String getOkLBKey() + { + return null; + } + @Override public String getBlockPiBaobabKey() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2eb89743f5..05312871ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.3.1" +agp = "8.3.2" android_lint_reporter = "2.1.0" androidBom = "1.23.0" bcprovJdk15onVersion = "1.70" @@ -17,7 +17,7 @@ firebaseMessaging = "23.4.1" flexboxLayout = "2.0.1" glide = "4.16.0" googleServices = "4.4.1" -gradle = "8.3.1" +gradle = "8.3.2" gridlayout = "1.0.0" gson = "2.10.1" guava = "30.1.1-android" diff --git a/lib/src/main/java/com/alphawallet/ethereum/EthereumNetworkBase.java b/lib/src/main/java/com/alphawallet/ethereum/EthereumNetworkBase.java index f4936eeec3..94d785a50c 100644 --- a/lib/src/main/java/com/alphawallet/ethereum/EthereumNetworkBase.java +++ b/lib/src/main/java/com/alphawallet/ethereum/EthereumNetworkBase.java @@ -45,6 +45,9 @@ public abstract class EthereumNetworkBase public static final long HOLESKY_ID = 17000; public static final long LINEA_ID = 59144; public static final long LINEA_TEST_ID = 59140; + public static final long POLYGON_AMOY_ID = 80002; + public static final long BASE_MAINNET_ID = 8453; + public static final long BASE_TESTNET_ID = 84532; public static final String MAINNET_RPC_URL = "https://mainnet.infura.io/v3/da3717f25f824cc1baa32d812386d93f"; @@ -60,6 +63,7 @@ public abstract class EthereumNetworkBase public static final String FANTOM_TEST_RPC_URL = "https://rpc.ankr.com/fantom_testnet"; public static final String MATIC_RPC_URL = "https://matic-mainnet.chainstacklabs.com"; public static final String MUMBAI_TEST_RPC_URL = "https://matic-mumbai.chainstacklabs.com"; + public static final String AMOY_TEST_RPC_URL = "https://rpc-amoy.polygon.technology"; public static final String OPTIMISTIC_MAIN_FALLBACK_URL = "https://mainnet.optimism.io"; public static final String CRONOS_MAIN_RPC_URL = "https://evm.cronos.org"; public static final String CRONOS_TEST_URL = "https://evm-t3.cronos.org"; @@ -116,6 +120,8 @@ public abstract class EthereumNetworkBase POLYGON_ID, false)); put(POLYGON_TEST_ID, new NetworkInfo("Mumbai (Test)", "POLY", MUMBAI_TEST_RPC_URL, "https://mumbai.polygonscan.com/tx/", POLYGON_TEST_ID, false)); + put(POLYGON_AMOY_ID, new NetworkInfo("Amoy (Test)", "POLY", AMOY_TEST_RPC_URL, "https://amoy.polygonscan.com/tx/", + POLYGON_AMOY_ID, false)); put(OPTIMISTIC_MAIN_ID, new NetworkInfo("Optimistic", "ETH", OPTIMISTIC_MAIN_FALLBACK_URL, "https://optimistic.etherscan.io/tx/", OPTIMISTIC_MAIN_ID, false));