+{
+ void call(T t);
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/HomeCommsInterface.java b/app/src/main/java/com/alphawallet/app/entity/HomeCommsInterface.java
index 2de9a623d9..af9233bf60 100644
--- a/app/src/main/java/com/alphawallet/app/entity/HomeCommsInterface.java
+++ b/app/src/main/java/com/alphawallet/app/entity/HomeCommsInterface.java
@@ -2,7 +2,6 @@
public interface HomeCommsInterface
{
- void downloadReady(String ready);
void requestNotificationPermission();
void backupSuccess(String keyAddress);
void resetTokens();
diff --git a/app/src/main/java/com/alphawallet/app/entity/HomeReceiver.java b/app/src/main/java/com/alphawallet/app/entity/HomeReceiver.java
index 2fedbaa754..fa06a12fbf 100644
--- a/app/src/main/java/com/alphawallet/app/entity/HomeReceiver.java
+++ b/app/src/main/java/com/alphawallet/app/entity/HomeReceiver.java
@@ -27,10 +27,6 @@ public void onReceive(Context context, Intent intent)
Bundle bundle = intent.getExtras();
switch (intent.getAction())
{
- case C.DOWNLOAD_READY:
- String message = bundle.getString("Version");
- homeCommsInterface.downloadReady(message);
- break;
case C.REQUEST_NOTIFICATION_ACCESS:
homeCommsInterface.requestNotificationPermission();
break;
@@ -49,7 +45,6 @@ public void onReceive(Context context, Intent intent)
public void register()
{
IntentFilter filter = new IntentFilter();
- filter.addAction(C.DOWNLOAD_READY);
filter.addAction(C.REQUEST_NOTIFICATION_ACCESS);
filter.addAction(C.BACKUP_WALLET_SUCCESS);
filter.addAction(C.WALLET_CONNECT_REQUEST);
diff --git a/app/src/main/java/com/alphawallet/app/entity/QRResult.java b/app/src/main/java/com/alphawallet/app/entity/QRResult.java
index 59b8a94039..50c3f6ed38 100644
--- a/app/src/main/java/com/alphawallet/app/entity/QRResult.java
+++ b/app/src/main/java/com/alphawallet/app/entity/QRResult.java
@@ -2,7 +2,9 @@
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.Nullable;
+
+import com.alphawallet.app.util.Utils;
+
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
@@ -161,6 +163,11 @@ else if (params.size() == 2)
{
type = EIP681Type.OTHER_PROTOCOL;
}
+ else if (isEIP681() && Utils.isAddressValid(address)) //Metamask addresses
+ {
+ type = EIP681Type.ADDRESS;
+ return;
+ }
else
{
type = EIP681Type.OTHER;
diff --git a/app/src/main/java/com/alphawallet/app/entity/QueryResponse.java b/app/src/main/java/com/alphawallet/app/entity/QueryResponse.java
new file mode 100644
index 0000000000..a232a2f2db
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/QueryResponse.java
@@ -0,0 +1,23 @@
+package com.alphawallet.app.entity;
+
+/**
+ * Created by JB on 5/11/2022.
+ */
+public class QueryResponse
+{
+ public final int code;
+ public final String body;
+
+ public QueryResponse(int code, String body)
+ {
+ this.code = code;
+ this.body = body;
+ }
+
+ public boolean isSuccessful()
+ {
+ return code >= 200 && code <= 299;
+ }
+}
+
+
diff --git a/app/src/main/java/com/alphawallet/app/entity/SignAuthenticationCallback.java b/app/src/main/java/com/alphawallet/app/entity/SignAuthenticationCallback.java
index cc8ae9e2f8..440998c77e 100644
--- a/app/src/main/java/com/alphawallet/app/entity/SignAuthenticationCallback.java
+++ b/app/src/main/java/com/alphawallet/app/entity/SignAuthenticationCallback.java
@@ -1,7 +1,5 @@
package com.alphawallet.app.entity;
-import com.alphawallet.token.entity.Signable;
-
/**
* Created by James on 21/07/2019.
* Stormbird in Sydney
@@ -9,8 +7,7 @@
public interface SignAuthenticationCallback
{
void gotAuthorisation(boolean gotAuth);
- default void gotAuthorisationForSigning(boolean gotAuth, Signable messageToSign) { } //if you implement message signing
default void createdKey(String keyAddress) { }
void cancelAuthentication();
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/Transaction.java b/app/src/main/java/com/alphawallet/app/entity/Transaction.java
index 2108cf9119..18037a924c 100644
--- a/app/src/main/java/com/alphawallet/app/entity/Transaction.java
+++ b/app/src/main/java/com/alphawallet/app/entity/Transaction.java
@@ -28,14 +28,13 @@
import java.util.Map;
/**
- *
* This is supposed to be a generic transaction class which can
* contain all of 3 stages of a transaction:
- *
+ *
* 1. being compiled, in progress, or ready to be signed;
* 2. compiled and signed, or ready to be broadcasted;
- * 2. already broadcasted, obtained in its raw format from a node,
- * including the signatures in it;
+ * 2. already broadcasted, obtained in its raw format from a node,
+ * including the signatures in it;
* 4. already included in a blockchain.
*/
public class Transaction implements Parcelable
@@ -61,654 +60,666 @@ public class Transaction implements Parcelable
public TransactionInput transactionInput = null;
public static final String CONSTRUCTOR = "Constructor";
- public static final TransactionDecoder decoder = new TransactionDecoder();
- public static ParseMagicLink parser = null;
-
- //placeholder for error
- public Transaction()
- {
- //blank transaction
- hash = "";
- blockNumber = "";
- timeStamp = 0;
- nonce = 0;
- from = "";
- to = "";
- value = "";
- gas = "";
- gasPrice = "";
- gasUsed = "";
- input = "";
- error = "";
- chainId = 0;
- maxFeePerGas = "";
- maxPriorityFee = "";
- }
-
- public boolean isPending()
- {
- return TextUtils.isEmpty(blockNumber) || blockNumber.equals("0") || blockNumber.equals("-2");
- }
-
- public boolean hasError()
- {
- return !TextUtils.isEmpty(error) && error.equals("1");
- }
-
- public boolean hasData()
- {
- return !TextUtils.isEmpty(input) && input.length() > 2;
- }
+ public static final TransactionDecoder decoder = new TransactionDecoder();
+ public static ParseMagicLink parser = null;
+
+ //placeholder for error
+ public Transaction()
+ {
+ //blank transaction
+ hash = "";
+ blockNumber = "";
+ timeStamp = 0;
+ nonce = 0;
+ from = "";
+ to = "";
+ value = "";
+ gas = "";
+ gasPrice = "";
+ gasUsed = "";
+ input = "";
+ error = "";
+ chainId = 0;
+ maxFeePerGas = "";
+ maxPriorityFee = "";
+ }
+
+ public boolean isPending()
+ {
+ return TextUtils.isEmpty(blockNumber) || blockNumber.equals("0") || blockNumber.equals("-2");
+ }
+
+ public boolean hasError()
+ {
+ return !TextUtils.isEmpty(error) && error.equals("1");
+ }
+
+ public boolean hasData()
+ {
+ return !TextUtils.isEmpty(input) && input.length() > 2;
+ }
public Transaction(
String hash,
String error,
String blockNumber,
long timeStamp,
- int nonce,
- String from,
- String to,
- String value,
- String gas,
- String gasPrice,
- String input,
- String gasUsed,
+ int nonce,
+ String from,
+ String to,
+ String value,
+ String gas,
+ String gasPrice,
+ String input,
+ String gasUsed,
long chainId,
- boolean isConstructor) {
+ boolean isConstructor)
+ {
this.hash = hash;
this.error = error;
this.blockNumber = blockNumber;
this.timeStamp = timeStamp;
- this.nonce = nonce;
- this.from = from;
- this.to = to;
- this.value = value;
- this.gas = gas;
- this.gasPrice = gasPrice;
- this.input = input;
- this.gasUsed = gasUsed;
- this.chainId = chainId;
- this.isConstructor = isConstructor;
- this.maxFeePerGas = "";
- this.maxPriorityFee = "";
- }
-
- public Transaction(Web3Transaction tx, long chainId, String wallet)
- {
- this.hash = null;
- this.error = null;
- this.blockNumber = null;
- this.timeStamp = System.currentTimeMillis()/1000;
- this.nonce = -1;
- this.from = wallet;
- this.to = tx.recipient.toString();
- this.value = tx.value.toString();
- this.gas = tx.gasLimit.toString();
- this.gasPrice = tx.gasPrice.toString();
- this.input = tx.payload;
- this.gasUsed = tx.gasLimit.toString();
- this.chainId = chainId;
- this.isConstructor = tx.isConstructor();
- this.maxFeePerGas = tx.maxFeePerGas.toString();
- this.maxPriorityFee = tx.maxPriorityFeePerGas.toString();
- }
-
- public Transaction(CovalentTransaction cTx, long chainId, long transactionTime)
- {
- if (cTx.to_address == null || cTx.to_address.equals("null"))
- {
- isConstructor = true;
- input = CONSTRUCTOR;
- //determine creation address from events
- to = cTx.determineContractAddress();
- }
- else
- {
- to = cTx.to_address;
- input = "";
- }
-
- this.hash = cTx.tx_hash;
- this.blockNumber = cTx.block_height;
- this.timeStamp = transactionTime;
- this.error = cTx.successful ? "0" : "1";
- this.nonce = 0; //don't know this
- this.from = cTx.from_address;
- this.value = cTx.value;
- this.gas = String.valueOf(cTx.gas_offered);
- this.gasPrice = cTx.gas_price;
- this.gasUsed = cTx.gas_spent;
- this.chainId = chainId;
- this.maxFeePerGas = "";
- this.maxPriorityFee = "";
- }
-
- public Transaction(org.web3j.protocol.core.methods.response.Transaction ethTx, long chainId, boolean isSuccess, long timeStamp)
- {
- // Get contract address if constructor
- String contractAddress = ethTx.getCreates() != null ? ethTx.getCreates() : "";
- int nonce = ethTx.getNonceRaw() != null ? Numeric.toBigInt(ethTx.getNonceRaw()).intValue() : 0;
-
- if (!TextUtils.isEmpty(contractAddress)) //must be a constructor
- {
- to = contractAddress;
- isConstructor = true;
- input = CONSTRUCTOR;
- }
- else if (ethTx.getTo() == null && ethTx.getInput() != null && ethTx.getInput().startsWith("0x60"))
- {
- // some clients don't populate the 'creates' data for constructors. Note: Ethereum constructor always starts with a 'PUSH' 0x60 instruction
- input = CONSTRUCTOR;
- isConstructor = true;
- to = calculateContractAddress(ethTx.getFrom(), nonce);
- }
- else
- {
- this.to = ethTx.getTo() != null ? ethTx.getTo() : "";
- this.input = ethTx.getInput();
- }
-
- this.hash = ethTx.getHash();
- this.blockNumber = ethTx.getBlockNumber().toString();
- this.timeStamp = timeStamp;
- this.error = isSuccess ? "0" : "1";
- this.nonce = nonce;
- this.from = ethTx.getFrom();
- this.value = ethTx.getValue().toString();
- this.gas = ethTx.getGas().toString();
- this.gasPrice = ethTx.getGasPrice().toString();
- this.gasUsed = ethTx.getGas().toString();
- this.chainId = chainId;
- this.maxFeePerGas = ethTx.getMaxFeePerGas();
- this.maxPriorityFee = ethTx.getMaxPriorityFeePerGas();
- }
-
- public Transaction(String hash, String isError, String blockNumber, long timeStamp, int nonce, String from, String to,
- String value, String gas, String gasPrice, String input, String gasUsed, long chainId, String contractAddress)
- {
- //Is it a constructor?
- if (!TextUtils.isEmpty(contractAddress))
- {
- String testContractDeploymentAddress = Utils.calculateContractAddress(from, nonce);
- if (testContractDeploymentAddress.equalsIgnoreCase(contractAddress))
- {
- to = contractAddress;
- isConstructor = true;
- input = CONSTRUCTOR;
- }
- }
-
- this.to = to;
- this.hash = hash;
- this.error = isError;
- this.blockNumber = blockNumber;
- this.timeStamp = timeStamp;
- this.nonce = nonce;
- this.from = from;
- this.value = value;
- this.gas = gas;
- this.gasPrice = gasPrice;
- this.input = input;
- this.gasUsed = gasUsed;
- this.chainId = chainId;
- this.maxFeePerGas = "";
- this.maxPriorityFee = "";
- }
-
- public Transaction(String hash, String isError, String blockNumber, long timeStamp, int nonce, String from, String to,
- String value, String gas, String gasPrice, String maxFeePerGas, String maxPriorityFee, String input, String gasUsed, long chainId, String contractAddress)
- {
- if (!TextUtils.isEmpty(contractAddress))
- {
- String testContractDeploymentAddress = Utils.calculateContractAddress(from, nonce);
- if (testContractDeploymentAddress.equalsIgnoreCase(contractAddress))
- {
- to = contractAddress;
- isConstructor = true;
- input = CONSTRUCTOR;
- }
- }
-
- this.to = to;
- this.hash = hash;
- this.error = isError;
- this.blockNumber = blockNumber;
- this.timeStamp = timeStamp;
- this.nonce = nonce;
- this.from = from;
- this.value = value;
- this.gas = gas;
- this.maxFeePerGas = maxFeePerGas;
- this.maxPriorityFee = maxPriorityFee;
- this.gasPrice = gasPrice;
- this.input = input;
- this.gasUsed = gasUsed;
- this.chainId = chainId;
- }
-
- protected Transaction(Parcel in)
- {
- hash = in.readString();
- error = in.readString();
- blockNumber = in.readString();
- timeStamp = in.readLong();
- nonce = in.readInt();
- from = in.readString();
- to = in.readString();
- value = in.readString();
- gas = in.readString();
- gasPrice = in.readString();
- input = in.readString();
- gasUsed = in.readString();
- chainId = in.readLong();
- maxFeePerGas = in.readString();
- maxPriorityFee = in.readString();
- }
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public Transaction createFromParcel(Parcel in) {
- return new Transaction(in);
- }
-
- @Override
- public Transaction[] newArray(int size) {
- return new Transaction[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
+ this.nonce = nonce;
+ this.from = from;
+ this.to = to;
+ this.value = value;
+ this.gas = gas;
+ this.gasPrice = gasPrice;
+ this.input = input;
+ this.gasUsed = gasUsed;
+ this.chainId = chainId;
+ this.isConstructor = isConstructor;
+ this.maxFeePerGas = "";
+ this.maxPriorityFee = "";
+ }
+
+ public Transaction(Web3Transaction tx, long chainId, String wallet)
+ {
+ this.hash = null;
+ this.error = null;
+ this.blockNumber = null;
+ this.timeStamp = System.currentTimeMillis() / 1000;
+ this.nonce = -1;
+ this.from = wallet;
+ this.to = tx.recipient.toString();
+ this.value = tx.value.toString();
+ this.gas = tx.gasLimit.toString();
+ this.gasPrice = tx.gasPrice.toString();
+ this.input = tx.payload;
+ this.gasUsed = tx.gasLimit.toString();
+ this.chainId = chainId;
+ this.isConstructor = tx.isConstructor();
+ this.maxFeePerGas = tx.maxFeePerGas.toString();
+ this.maxPriorityFee = tx.maxPriorityFeePerGas.toString();
+ }
+
+ public Transaction(CovalentTransaction cTx, long chainId, long transactionTime)
+ {
+ if (cTx.to_address == null || cTx.to_address.equals("null"))
+ {
+ isConstructor = true;
+ input = CONSTRUCTOR;
+ //determine creation address from events
+ to = cTx.determineContractAddress();
+ }
+ else
+ {
+ to = cTx.to_address;
+ input = "0x";
+ }
+
+ this.hash = cTx.tx_hash;
+ this.blockNumber = cTx.block_height;
+ this.timeStamp = transactionTime;
+ this.error = cTx.successful ? "0" : "1";
+ this.nonce = 0; //don't know this
+ this.from = cTx.from_address;
+ this.value = cTx.value;
+ this.gas = String.valueOf(cTx.gas_offered);
+ this.gasPrice = cTx.gas_price;
+ this.gasUsed = cTx.gas_spent;
+ this.chainId = chainId;
+ this.maxFeePerGas = "";
+ this.maxPriorityFee = "";
+ }
+
+ public Transaction(org.web3j.protocol.core.methods.response.Transaction ethTx, long chainId, boolean isSuccess, long timeStamp)
+ {
+ // Get contract address if constructor
+ String contractAddress = ethTx.getCreates() != null ? ethTx.getCreates() : "";
+ int nonce = ethTx.getNonceRaw() != null ? Numeric.toBigInt(ethTx.getNonceRaw()).intValue() : 0;
+
+ if (!TextUtils.isEmpty(contractAddress)) //must be a constructor
+ {
+ to = contractAddress;
+ isConstructor = true;
+ input = CONSTRUCTOR;
+ }
+ else if (ethTx.getTo() == null && ethTx.getInput() != null && ethTx.getInput().startsWith("0x60"))
+ {
+ // some clients don't populate the 'creates' data for constructors. Note: Ethereum constructor always starts with a 'PUSH' 0x60 instruction
+ input = CONSTRUCTOR;
+ isConstructor = true;
+ to = calculateContractAddress(ethTx.getFrom(), nonce);
+ }
+ else
+ {
+ this.to = ethTx.getTo() != null ? ethTx.getTo() : "";
+ this.input = ethTx.getInput();
+ }
+
+ this.hash = ethTx.getHash();
+ this.blockNumber = ethTx.getBlockNumber().toString();
+ this.timeStamp = timeStamp;
+ this.error = isSuccess ? "0" : "1";
+ this.nonce = nonce;
+ this.from = ethTx.getFrom();
+ this.value = ethTx.getValue().toString();
+ this.gas = ethTx.getGas().toString();
+ this.gasPrice = ethTx.getGasPrice().toString();
+ this.gasUsed = ethTx.getGas().toString();
+ this.chainId = chainId;
+ this.maxFeePerGas = ethTx.getMaxFeePerGas();
+ this.maxPriorityFee = ethTx.getMaxPriorityFeePerGas();
+ }
+
+ public Transaction(String hash, String isError, String blockNumber, long timeStamp, int nonce, String from, String to,
+ String value, String gas, String gasPrice, String input, String gasUsed, long chainId, String contractAddress)
+ {
+ //Is it a constructor?
+ if (!TextUtils.isEmpty(contractAddress))
+ {
+ String testContractDeploymentAddress = Utils.calculateContractAddress(from, nonce);
+ if (testContractDeploymentAddress.equalsIgnoreCase(contractAddress))
+ {
+ to = contractAddress;
+ isConstructor = true;
+ input = CONSTRUCTOR;
+ }
+ }
+
+ this.to = to;
+ this.hash = hash;
+ this.error = isError;
+ this.blockNumber = blockNumber;
+ this.timeStamp = timeStamp;
+ this.nonce = nonce;
+ this.from = from;
+ this.value = value;
+ this.gas = gas;
+ this.gasPrice = gasPrice;
+ this.input = input;
+ this.gasUsed = gasUsed;
+ this.chainId = chainId;
+ this.maxFeePerGas = "";
+ this.maxPriorityFee = "";
+ }
+
+ public Transaction(String hash, String isError, String blockNumber, long timeStamp, int nonce, String from, String to,
+ String value, String gas, String gasPrice, String maxFeePerGas, String maxPriorityFee, String input, String gasUsed, long chainId, String contractAddress)
+ {
+ if (!TextUtils.isEmpty(contractAddress))
+ {
+ String testContractDeploymentAddress = Utils.calculateContractAddress(from, nonce);
+ if (testContractDeploymentAddress.equalsIgnoreCase(contractAddress))
+ {
+ to = contractAddress;
+ isConstructor = true;
+ input = CONSTRUCTOR;
+ }
+ }
+
+ this.to = to;
+ this.hash = hash;
+ this.error = isError;
+ this.blockNumber = blockNumber;
+ this.timeStamp = timeStamp;
+ this.nonce = nonce;
+ this.from = from;
+ this.value = value;
+ this.gas = gas;
+ this.maxFeePerGas = maxFeePerGas;
+ this.maxPriorityFee = maxPriorityFee;
+ this.gasPrice = gasPrice;
+ this.input = input;
+ this.gasUsed = gasUsed;
+ this.chainId = chainId;
+ }
+
+ protected Transaction(Parcel in)
+ {
+ hash = in.readString();
+ error = in.readString();
+ blockNumber = in.readString();
+ timeStamp = in.readLong();
+ nonce = in.readInt();
+ from = in.readString();
+ to = in.readString();
+ value = in.readString();
+ gas = in.readString();
+ gasPrice = in.readString();
+ input = in.readString();
+ gasUsed = in.readString();
+ chainId = in.readLong();
+ maxFeePerGas = in.readString();
+ maxPriorityFee = in.readString();
+ }
+
+ public static final Creator CREATOR = new Creator()
+ {
+ @Override
+ public Transaction createFromParcel(Parcel in)
+ {
+ return new Transaction(in);
+ }
+
+ @Override
+ public Transaction[] newArray(int size)
+ {
+ return new Transaction[size];
+ }
+ };
+
+ @Override
+ public int describeContents()
+ {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags)
+ {
dest.writeString(hash);
dest.writeString(error);
dest.writeString(blockNumber);
dest.writeLong(timeStamp);
- dest.writeInt(nonce);
- dest.writeString(from);
- dest.writeString(to);
- dest.writeString(value);
- dest.writeString(gas);
- dest.writeString(gasPrice);
- dest.writeString(input);
- dest.writeString(gasUsed);
- dest.writeLong(chainId);
- dest.writeString(maxFeePerGas);
- dest.writeString(maxPriorityFee);
- }
-
- public boolean isRelated(String contractAddress, String walletAddress)
- {
- if (contractAddress.equals("eth"))
- {
- return (input.equals("0x") || from.equalsIgnoreCase(walletAddress));
- }
- else if (walletAddress.equalsIgnoreCase(contractAddress)) //transactions sent from or sent to the main currency account
- {
- return from.equalsIgnoreCase(walletAddress) || to.equalsIgnoreCase(walletAddress);
- }
- else if (to.equalsIgnoreCase(contractAddress))
- {
- return true;
- }
- else
- {
- return getWalletInvolvedInTransaction(walletAddress);
- }
- }
-
- /**
- * Fetch result of transaction operation.
- * This is very much a WIP
- * @param token
- * @return
- */
- public String getOperationResult(Token token, int precision)
- {
- //get amount here. will be amount + symbol if appropriate
- if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- String value = transactionInput.getOperationValue(token, this);
- boolean isSendOrReceive = !from.equalsIgnoreCase(to) && transactionInput.isSendOrReceive(this);
- String prefix = (value.length() == 0 || (value.startsWith("#") || !isSendOrReceive)) ? "" :
- (token.getIsSent(this) ? "- " : "+ ");
- return prefix + value;
- }
- else
- {
- return token.getTransactionValue(this, precision);
- }
- }
-
- /**
- * Can the contract call be valid if the operation token is Ethereum?
- * @param token
- * @return
- */
- public boolean shouldShowSymbol(Token token)
- {
- if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- return transactionInput.shouldShowSymbol(token);
- }
- else
- {
- return true;
- }
- }
-
- public String getOperationTokenAddress()
- {
- if (hasInput())
- {
- return to;
- }
- else
- {
- return "";
- }
- }
-
- public boolean isLegacyTransaction()
- {
- try
- {
- return !TextUtils.isEmpty(gasPrice) && new BigInteger(gasPrice).compareTo(BigInteger.ZERO) > 0;
- }
- catch (Exception e)
- {
- return true;
- }
- }
-
- public String getOperationName(Context ctx, Token token, String walletAddress)
- {
- String txName = null;
- if (isPending())
- {
- txName = ctx.getString(R.string.status_pending);
- }
- else if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- if (token.isEthereum() && shouldShowSymbol(token))
- {
- transactionInput.type = TransactionType.CONTRACT_CALL;
- }
-
- return transactionInput.getOperationTitle(ctx);
- }
-
- return txName;
- }
-
- public boolean hasInput()
- {
- return input != null && input.length() >= 10;
- }
-
- public int getOperationToFrom(String walletAddress)
- {
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.getOperationToFrom();
- }
- else
- {
- return 0;
- }
- }
-
- public StatusType getOperationImage(Token token)
- {
- if (hasError())
- {
- return StatusType.FAILED;
- }
- else if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- return transactionInput.getOperationImage(this, token.getWallet());
- }
- else
- {
- return from.equalsIgnoreCase(token.getWallet()) ? StatusType.SENT : StatusType.RECEIVE;
- }
- }
-
- public TransactionType getTransactionType(String wallet)
- {
- if (hasError())
- {
- return TransactionType.UNKNOWN;
- }
- else if (hasInput())
- {
- decodeTransactionInput(wallet);
- return transactionInput.type;
- }
- else
- {
- return TransactionType.SEND_ETH;
- }
- }
-
- /**
- * Supplimental info in this case is the intrinsic root value attached to a contract call
- * EG: Calling cryptokitties ERC721 'breedWithAuto' function requires you to call the function and also attach a small amount of ETH
- * for the 'breeding fee'. That fee is later released to the caller of the 'birth' function.
- * Supplemental info for these transaction would appear as -0.031 for the 'breedWithAuto' and +0.031 on the 'birth' call
- * However it's not that simple - the 'breeding fee' will be in the value attached to the transaction, however the 'midwife' reward appears
- * as an internal transaction, so won't be in the 'value' property.
- *
- * @return
- */
- public String getSupplementalInfo(String walletAddress, String networkName)
- {
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.getSupplimentalInfo(this, walletAddress, networkName);
- }
- else
- {
- return "";
- }
- }
-
- public String getPrefix(Token token)
- {
- if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- if (!transactionInput.isSendOrReceive(this) || token.isEthereum())
- {
- return "";
- }
- else if (token.isERC721())
- {
- return "";
- }
- }
-
- boolean isSent = token.getIsSent(this);
- boolean isSelf = from.equalsIgnoreCase(to);
- if (isSelf) return "";
- else if (isSent) return "- ";
- else return "+ ";
- }
+ dest.writeInt(nonce);
+ dest.writeString(from);
+ dest.writeString(to);
+ dest.writeString(value);
+ dest.writeString(gas);
+ dest.writeString(gasPrice);
+ dest.writeString(input);
+ dest.writeString(gasUsed);
+ dest.writeLong(chainId);
+ dest.writeString(maxFeePerGas);
+ dest.writeString(maxPriorityFee);
+ }
+
+ public boolean isRelated(String contractAddress, String walletAddress)
+ {
+ if (contractAddress.equals("eth"))
+ {
+ return (input.equals("0x") || from.equalsIgnoreCase(walletAddress));
+ }
+ else if (walletAddress.equalsIgnoreCase(contractAddress)) //transactions sent from or sent to the main currency account
+ {
+ return from.equalsIgnoreCase(walletAddress) || to.equalsIgnoreCase(walletAddress);
+ }
+ else if (to.equalsIgnoreCase(contractAddress))
+ {
+ return true;
+ }
+ else
+ {
+ return getWalletInvolvedInTransaction(walletAddress);
+ }
+ }
+
+ /**
+ * Fetch result of transaction operation.
+ * This is very much a WIP
+ *
+ * @param token
+ * @return
+ */
+ public String getOperationResult(Token token, int precision)
+ {
+ //get amount here. will be amount + symbol if appropriate
+ if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ String value = transactionInput.getOperationValue(token, this);
+ boolean isSendOrReceive = !from.equalsIgnoreCase(to) && transactionInput.isSendOrReceive(this);
+ String prefix = (value.length() == 0 || (value.startsWith("#") || !isSendOrReceive)) ? "" :
+ (token.getIsSent(this) ? "- " : "+ ");
+ return prefix + value;
+ }
+ else
+ {
+ return token.getTransactionValue(this, precision);
+ }
+ }
+
+ /**
+ * Can the contract call be valid if the operation token is Ethereum?
+ *
+ * @param token
+ * @return
+ */
+ public boolean shouldShowSymbol(Token token)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ return transactionInput.shouldShowSymbol(token);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ public String getOperationTokenAddress()
+ {
+ if (hasInput())
+ {
+ return to;
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+ public boolean isLegacyTransaction()
+ {
+ try
+ {
+ return !TextUtils.isEmpty(gasPrice) && new BigInteger(gasPrice).compareTo(BigInteger.ZERO) > 0;
+ }
+ catch (Exception e)
+ {
+ return true;
+ }
+ }
+
+ public String getOperationName(Context ctx, Token token, String walletAddress)
+ {
+ String txName = null;
+ if (isPending())
+ {
+ txName = ctx.getString(R.string.status_pending);
+ }
+ else if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ if (token.isEthereum() && shouldShowSymbol(token))
+ {
+ transactionInput.type = TransactionType.CONTRACT_CALL;
+ }
+
+ return transactionInput.getOperationTitle(ctx);
+ }
+
+ return txName;
+ }
+
+ public boolean hasInput()
+ {
+ return input != null && input.length() >= 10;
+ }
+
+ public int getOperationToFrom(String walletAddress)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.getOperationToFrom();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ public StatusType getOperationImage(Token token)
+ {
+ if (hasError())
+ {
+ return StatusType.FAILED;
+ }
+ else if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ return transactionInput.getOperationImage(this, token.getWallet());
+ }
+ else
+ {
+ return from.equalsIgnoreCase(token.getWallet()) ? StatusType.SENT : StatusType.RECEIVE;
+ }
+ }
+
+ public TransactionType getTransactionType(String wallet)
+ {
+ if (hasError())
+ {
+ return TransactionType.UNKNOWN;
+ }
+ else if (hasInput())
+ {
+ decodeTransactionInput(wallet);
+ return transactionInput.type;
+ }
+ else
+ {
+ return TransactionType.SEND_ETH;
+ }
+ }
+
+ /**
+ * Supplimental info in this case is the intrinsic root value attached to a contract call
+ * EG: Calling cryptokitties ERC721 'breedWithAuto' function requires you to call the function and also attach a small amount of ETH
+ * for the 'breeding fee'. That fee is later released to the caller of the 'birth' function.
+ * Supplemental info for these transaction would appear as -0.031 for the 'breedWithAuto' and +0.031 on the 'birth' call
+ * However it's not that simple - the 'breeding fee' will be in the value attached to the transaction, however the 'midwife' reward appears
+ * as an internal transaction, so won't be in the 'value' property.
+ *
+ * @return
+ */
+ public String getSupplementalInfo(String walletAddress, String networkName)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.getSupplimentalInfo(this, walletAddress, networkName);
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+ public String getPrefix(Token token)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ if (!transactionInput.isSendOrReceive(this) || token.isEthereum())
+ {
+ return "";
+ }
+ else if (token.isERC721())
+ {
+ return "";
+ }
+ }
+
+ boolean isSent = token.getIsSent(this);
+ boolean isSelf = from.equalsIgnoreCase(to);
+ if (isSelf) return "";
+ else if (isSent) return "- ";
+ else return "+ ";
+ }
public BigDecimal getRawValue(String walletAddress) throws Exception
{
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.getRawValue();
- }
- else
- {
- return new BigDecimal(value);
- }
- }
-
- public StatusType getTransactionStatus()
- {
- if (hasError())
- {
- return StatusType.FAILED;
- }
- else if (blockNumber.equals("-1"))
- {
- return StatusType.REJECTED;
- }
- else if (isPending())
- {
- return StatusType.PENDING;
- }
- else
- {
- return null;
- }
- }
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.getRawValue();
+ }
+ else
+ {
+ return new BigDecimal(value);
+ }
+ }
+
+ public StatusType getTransactionStatus()
+ {
+ if (hasError())
+ {
+ return StatusType.FAILED;
+ }
+ else if (blockNumber.equals("-1"))
+ {
+ return StatusType.REJECTED;
+ }
+ else if (isPending())
+ {
+ return StatusType.PENDING;
+ }
+ else
+ {
+ return null;
+ }
+ }
public void addTransactionElements(Map resultMap)
{
- resultMap.put("__hash", new EventResult("", hash));
- resultMap.put("__to", new EventResult("", to));
- resultMap.put("__from", new EventResult("", from));
- resultMap.put("__value", new EventResult("", value));
- resultMap.put("__chainId", new EventResult("", String.valueOf(chainId)));
- }
-
- public String getEventName(String walletAddress)
- {
- String eventName = "";
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- eventName = transactionInput.getOperationEvent(walletAddress);
- }
-
- return eventName;
- }
-
- public int getSupplementalColour(String supplementalTxt)
- {
- if (!TextUtils.isEmpty(supplementalTxt))
- {
- switch (supplementalTxt.charAt(1))
- {
- case '-':
- return R.color.negative;
- case '+':
- return R.color.positive;
- default:
- break;
- }
- }
-
- return R.color.text_primary;
- }
-
- public String getDestination(Token token)
- {
- if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- return transactionInput.getOperationAddress(this, token);
- }
- else
- {
- return token.getAddress();
- }
- }
-
- public String getOperationDetail(Context ctx, Token token, TokensService tService)
- {
- if (hasInput())
- {
- decodeTransactionInput(token.getWallet());
- return transactionInput.getOperationDescription (ctx, this, token, tService);
- }
- else
- {
- return ctx.getString(R.string.operation_definition, ctx.getString(R.string.to), ENSHandler.matchENSOrFormat(ctx, to));
- }
- }
-
- private void decodeTransactionInput(String walletAddress)
- {
- if (transactionInput == null && hasInput() && Utils.isAddressValid(walletAddress))
- {
- transactionInput = decoder.decodeInput(this, walletAddress);
- }
- }
-
- public boolean getWalletInvolvedInTransaction(String walletAddr)
- {
- decodeTransactionInput(walletAddr);
- if ((transactionInput != null && transactionInput.functionData != null) && transactionInput.containsAddress(walletAddr)) return true;
- else if (from.equalsIgnoreCase(walletAddr)) return true;
- else if (to.equalsIgnoreCase(walletAddr)) return true;
- else return input != null && input.length() > 40 && input.contains(Numeric.cleanHexPrefix(walletAddr.toLowerCase()));
- }
-
- public boolean isNFTSent(String walletAddress)
- {
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.isSent();
- }
- else
- {
- return true;
- }
- }
-
- public boolean getIsSent(String walletAddress)
- {
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.isSent();
- }
- else
- {
- return from.equalsIgnoreCase(walletAddress);
- }
- }
-
- public boolean isValueChange(String walletAddress)
- {
- if (hasInput())
- {
- decodeTransactionInput(walletAddress);
- return transactionInput.isSendOrReceive(this);
- }
- else
- {
- return true;
- }
- }
-
- private String calculateContractAddress(String account, long nonce){
- byte[] addressAsBytes = Numeric.hexStringToByteArray(account);
- byte[] calculatedAddressAsBytes =
- Hash.sha3(RlpEncoder.encode(
- new RlpList(
- RlpString.create(addressAsBytes),
- RlpString.create((nonce)))));
-
- calculatedAddressAsBytes = Arrays.copyOfRange(calculatedAddressAsBytes,
- 12, calculatedAddressAsBytes.length);
- return Numeric.toHexString(calculatedAddressAsBytes);
- }
+ resultMap.put("__hash", new EventResult("", hash));
+ resultMap.put("__to", new EventResult("", to));
+ resultMap.put("__from", new EventResult("", from));
+ resultMap.put("__value", new EventResult("", value));
+ resultMap.put("__chainId", new EventResult("", String.valueOf(chainId)));
+ }
+
+ public String getEventName(String walletAddress)
+ {
+ String eventName = "";
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ eventName = transactionInput.getOperationEvent(walletAddress);
+ }
+
+ return eventName;
+ }
+
+ public int getSupplementalColour(String supplementalTxt)
+ {
+ if (!TextUtils.isEmpty(supplementalTxt))
+ {
+ switch (supplementalTxt.charAt(1))
+ {
+ case '-':
+ return R.color.negative;
+ case '+':
+ return R.color.positive;
+ default:
+ break;
+ }
+ }
+
+ return R.color.text_primary;
+ }
+
+ public String getDestination(Token token)
+ {
+ if (token == null) return "";
+ if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ return transactionInput.getOperationAddress(this, token);
+ }
+ else
+ {
+ return token.getAddress();
+ }
+ }
+
+ public String getOperationDetail(Context ctx, Token token, TokensService tService)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(token.getWallet());
+ return transactionInput.getOperationDescription(ctx, this, token, tService);
+ }
+ else
+ {
+ return ctx.getString(R.string.operation_definition, ctx.getString(R.string.to), ENSHandler.matchENSOrFormat(ctx, to));
+ }
+ }
+
+ private void decodeTransactionInput(String walletAddress)
+ {
+ if (transactionInput == null && hasInput() && Utils.isAddressValid(walletAddress))
+ {
+ transactionInput = decoder.decodeInput(this, walletAddress);
+ }
+ }
+
+ public boolean getWalletInvolvedInTransaction(String walletAddr)
+ {
+ decodeTransactionInput(walletAddr);
+ if ((transactionInput != null && transactionInput.functionData != null) && transactionInput.containsAddress(walletAddr))
+ return true;
+ else if (from.equalsIgnoreCase(walletAddr)) return true;
+ else if (to.equalsIgnoreCase(walletAddr)) return true;
+ else
+ return input != null && input.length() > 40 && input.contains(Numeric.cleanHexPrefix(walletAddr.toLowerCase()));
+ }
+
+ public boolean isNFTSent(String walletAddress)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.isSent();
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ public boolean getIsSent(String walletAddress)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.isSent();
+ }
+ else
+ {
+ return from.equalsIgnoreCase(walletAddress);
+ }
+ }
+
+ public boolean isValueChange(String walletAddress)
+ {
+ if (hasInput())
+ {
+ decodeTransactionInput(walletAddress);
+ return transactionInput.isSendOrReceive(this);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ private String calculateContractAddress(String account, long nonce)
+ {
+ byte[] addressAsBytes = Numeric.hexStringToByteArray(account);
+ byte[] calculatedAddressAsBytes =
+ Hash.sha3(RlpEncoder.encode(
+ new RlpList(
+ RlpString.create(addressAsBytes),
+ RlpString.create((nonce)))));
+
+ calculatedAddressAsBytes = Arrays.copyOfRange(calculatedAddressAsBytes,
+ 12, calculatedAddressAsBytes.length);
+ return Numeric.toHexString(calculatedAddressAsBytes);
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/TransactionDecoder.java b/app/src/main/java/com/alphawallet/app/entity/TransactionDecoder.java
index 53bae51983..2a22edb640 100644
--- a/app/src/main/java/com/alphawallet/app/entity/TransactionDecoder.java
+++ b/app/src/main/java/com/alphawallet/app/entity/TransactionDecoder.java
@@ -1,6 +1,8 @@
package com.alphawallet.app.entity;
-import com.alphawallet.app.BuildConfig;
+import static com.alphawallet.app.entity.TransactionDecoder.ReadState.ARGS;
+import static org.web3j.crypto.Keys.ADDRESS_LENGTH_IN_HEX;
+
import com.alphawallet.app.web3.entity.Web3Transaction;
import org.web3j.crypto.Hash;
@@ -13,9 +15,6 @@
import java.util.List;
import java.util.Map;
-import static com.alphawallet.app.entity.TransactionDecoder.ReadState.ARGS;
-import static org.web3j.crypto.Keys.ADDRESS_LENGTH_IN_HEX;
-
import timber.log.Timber;
/**
@@ -29,7 +28,6 @@
* input.
*/
-// TODO: Should be a factory class that emits an object containing transaction interpretation
public class TransactionDecoder
{
public static final int FUNCTION_LENGTH = 10;
@@ -43,7 +41,7 @@ public class TransactionDecoder
private FunctionData getUnknownFunction()
{
- return new FunctionData("N/A", ContractType.OTHER);
+ return new FunctionData("Contract Call", ContractType.OTHER);
}
public TransactionDecoder()
@@ -63,9 +61,12 @@ public TransactionInput decodeInput(String input)
return thisData;
}
- try {
- while (parseIndex < input.length() && !(parseState == ParseStage.FINISH)) {
- switch (parseState) {
+ try
+ {
+ while (parseIndex < input.length() && !(parseState == ParseStage.FINISH))
+ {
+ switch (parseState)
+ {
case PARSE_FUNCTION: //get function
parseState = setFunction(thisData, readBytes(input, FUNCTION_LENGTH), input.length());
break;
@@ -107,7 +108,8 @@ public TransactionInput decodeInput(Web3Transaction web3Tx, long chainId, String
return thisData;
}
- private ParseStage setFunction(TransactionInput thisData, String input, int length) {
+ private ParseStage setFunction(TransactionInput thisData, String input, int length)
+ {
//first get expected arg list:
FunctionData data = functionList.get(input);
@@ -136,7 +138,8 @@ enum ReadState
SIGNATURE
}
- private ParseStage getParams(TransactionInput thisData, String input) {
+ private ParseStage getParams(TransactionInput thisData, String input)
+ {
state = ARGS;
BigInteger count;
StringBuilder sb = new StringBuilder();
@@ -152,26 +155,33 @@ private ParseStage getParams(TransactionInput thisData, String input) {
sb.setLength(0);
argData = read256bits(input);
BigInteger dataCount = Numeric.toBigInt(argData);
- String stuff = readBytes(input, dataCount.intValue());
- thisData.miscData.add(stuff);
+ String hexBytes = readBytes(input, dataCount.intValue());
+ thisData.miscData.add(hexBytes);
+ thisData.hexArgs.add("0x" + hexBytes);
break;
case "string":
count = new BigInteger(argData, 16);
sb.setLength(0);
argData = read256bits(input);
- if (count.intValue() > argData.length()) count = BigInteger.valueOf(argData.length());
- for (int index = 0; index < (count.intValue()*2); index += 2)
+ if (count.intValue() > argData.length())
+ count = BigInteger.valueOf(argData.length());
+ for (int index = 0; index < (count.intValue() * 2); index += 2)
{
- int v = Integer.parseInt(argData.substring(index, index+2), 16);
- char c = (char)v;
+ int v = Integer.parseInt(argData.substring(index, index + 2), 16);
+ char c = (char) v;
sb.append(c);
}
thisData.miscData.add(Numeric.cleanHexPrefix(sb.toString()));
+
+ //Should be ASCII, try to convert
+ thisData.hexArgs.add(new String(Numeric.hexStringToByteArray(sb.toString())));
break;
case "address":
if (argData.length() >= 64 - ADDRESS_LENGTH_IN_HEX)
{
- thisData.addresses.add("0x" + argData.substring(64 - ADDRESS_LENGTH_IN_HEX));
+ String addr = "0x" + argData.substring(64 - ADDRESS_LENGTH_IN_HEX);
+ thisData.addresses.add(addr);
+ thisData.hexArgs.add(addr);
}
break;
case "bytes32":
@@ -181,17 +191,21 @@ private ParseStage getParams(TransactionInput thisData, String input) {
case "uint16[]":
case "uint256[]":
count = new BigInteger(argData, 16);
- for (int i = 0; i < count.intValue(); i++) {
+ for (int i = 0; i < count.intValue(); i++)
+ {
String inputData = read256bits(input);
thisData.arrayValues.add(new BigInteger(inputData, 16));
+ thisData.hexArgs.add(inputData);
if (inputData.equals("0")) break;
}
break;
case "uint256":
+ case "uint":
addArg(thisData, argData);
break;
case "uint8": //In our standards, we will put uint8 as the signature marker
- if (thisData.functionData.hasSig) {
+ if (thisData.functionData.hasSig)
+ {
state = ReadState.SIGNATURE;
sigCount = 0;
}
@@ -200,6 +214,11 @@ private ParseStage getParams(TransactionInput thisData, String input) {
case "nodata":
//no need to store this data - eg placeholder to indicate presence of a vararg
break;
+ case "bool":
+ //zero or one?
+ BigInteger val = new BigInteger(argData, 16);
+ thisData.hexArgs.add(val.longValue() == 0 ? "false" : "true");
+ break;
default:
break;
}
@@ -225,13 +244,14 @@ private void addArg(TransactionInput thisData, String input)
if (++sigCount == 3) state = ARGS;
break;
}
+ thisData.hexArgs.add(input);
}
private String readBytes(String input, int bytes)
{
if ((parseIndex + bytes) <= input.length())
{
- String value = input.substring(parseIndex, parseIndex+bytes);
+ String value = input.substring(parseIndex, parseIndex + bytes);
parseIndex += bytes;
return value;
}
@@ -245,7 +265,7 @@ private String read256bits(String input)
{
if ((parseIndex + 64) <= input.length())
{
- String value = input.substring(parseIndex, parseIndex+64);
+ String value = input.substring(parseIndex, parseIndex + 64);
parseIndex += 64;
return value;
}
@@ -452,7 +472,7 @@ public int[] getIndices(TransactionInput data)
if (data != null && data.arrayValues != null)
{
indices = new int[data.arrayValues.size()];
- for (int i = 0; i < data.arrayValues.size() ; i++)
+ for (int i = 0; i < data.arrayValues.size(); i++)
{
indices[i] = data.arrayValues.get(i).intValue();
}
@@ -461,7 +481,8 @@ public int[] getIndices(TransactionInput data)
return indices;
}
- public static String buildMethodId(String methodSignature) {
+ public static String buildMethodId(String methodSignature)
+ {
byte[] input = methodSignature.getBytes();
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(0, 10);
diff --git a/app/src/main/java/com/alphawallet/app/entity/TransactionInput.java b/app/src/main/java/com/alphawallet/app/entity/TransactionInput.java
index 69eee3cdf3..03ecb925e3 100644
--- a/app/src/main/java/com/alphawallet/app/entity/TransactionInput.java
+++ b/app/src/main/java/com/alphawallet/app/entity/TransactionInput.java
@@ -1,5 +1,9 @@
package com.alphawallet.app.entity;
+import static com.alphawallet.app.C.BURN_ADDRESS;
+import static com.alphawallet.app.C.ETHER_DECIMALS;
+import static com.alphawallet.app.ui.widget.holder.TransactionHolder.TRANSACTION_BALANCE_PRECISION;
+
import android.content.Context;
import android.text.TextUtils;
@@ -23,11 +27,6 @@
import java.util.ArrayList;
import java.util.List;
-import static com.alphawallet.app.C.BURN_ADDRESS;
-import static com.alphawallet.app.C.ETHER_DECIMALS;
-import static com.alphawallet.app.entity.tokenscript.TokenscriptFunction.ZERO_ADDRESS;
-import static com.alphawallet.app.ui.widget.holder.TransactionHolder.TRANSACTION_BALANCE_PRECISION;
-
/**
* Created by James on 4/03/2018.
*
@@ -47,6 +46,7 @@ public class TransactionInput
public List arrayValues;
public List sigData;
public List miscData;
+ public List hexArgs;
public String tradeAddress;
public TransactionType type;
@@ -59,6 +59,7 @@ public TransactionInput()
addresses = new ArrayList<>();
sigData = new ArrayList<>();
miscData = new ArrayList<>();
+ hexArgs = new ArrayList<>();
}
//Addresses are in 256bit format
@@ -815,4 +816,45 @@ public boolean isSendOrReceive(Transaction tx)
return !tx.value.equals("0");
}
}
+
+ public String buildFunctionCallText()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(functionData.functionName);
+ sb.append("(");
+ boolean firstArg = true;
+ for (String arg : hexArgs)
+ {
+ if (!firstArg) sb.append(", ");
+ if (arg.startsWith("0"))
+ {
+ sb.append(truncateValue(arg));
+ }
+ else
+ {
+ sb.append(arg);
+ }
+ firstArg = false;
+ }
+
+ sb.append(")");
+
+ return sb.toString();
+ }
+
+ private String truncateValue(String arg)
+ {
+ String retVal = arg;
+ try
+ {
+ BigInteger argVal = new BigInteger(arg, 16);
+ retVal = argVal.toString(16);
+ }
+ catch (Exception e)
+ {
+ //
+ }
+
+ return retVal;
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/Version.java b/app/src/main/java/com/alphawallet/app/entity/Version.java
new file mode 100644
index 0000000000..b2f957d80f
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/Version.java
@@ -0,0 +1,54 @@
+package com.alphawallet.app.entity;
+
+public class Version implements Comparable
+{
+ private final String version;
+
+ public Version(String version)
+ {
+ if (version == null)
+ throw new IllegalArgumentException("Version cannot be null");
+ if (!version.matches("[0-9]+(\\.[0-9]+)*"))
+ throw new IllegalArgumentException("Invalid version format");
+ this.version = version;
+ }
+
+ public final String get()
+ {
+ return this.version;
+ }
+
+ @Override
+ public int compareTo(Version that)
+ {
+ if (that == null)
+ return 1;
+ String[] thisParts = this.get().split("\\.");
+ String[] thatParts = that.get().split("\\.");
+ int length = Math.max(thisParts.length, thatParts.length);
+ for (int i = 0; i < length; i++)
+ {
+ int thisPart = i < thisParts.length ?
+ Integer.parseInt(thisParts[i]) : 0;
+ int thatPart = i < thatParts.length ?
+ Integer.parseInt(thatParts[i]) : 0;
+ if (thisPart < thatPart)
+ return -1;
+ if (thisPart > thatPart)
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object that)
+ {
+ if (this == that)
+ return true;
+ if (that == null)
+ return false;
+ if (this.getClass() != that.getClass())
+ return false;
+ return this.compareTo((Version) that) == 0;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/Wallet.java b/app/src/main/java/com/alphawallet/app/entity/Wallet.java
index 799c3f499c..8d4a5d4d1f 100644
--- a/app/src/main/java/com/alphawallet/app/entity/Wallet.java
+++ b/app/src/main/java/com/alphawallet/app/entity/Wallet.java
@@ -10,7 +10,8 @@
import java.math.BigDecimal;
-public class Wallet implements Parcelable {
+public class Wallet implements Parcelable
+{
public final String address;
public String balance;
public String ENSname;
@@ -22,103 +23,115 @@ public class Wallet implements Parcelable {
public String balanceSymbol;
public String ENSAvatar;
public boolean isSynced;
+ public Token[] tokens;
- public Wallet(String address) {
- this.address = address;
- this.balance = "-";
- this.ENSname = "";
- this.name = "";
- this.type = WalletType.NOT_DEFINED;
- this.lastBackupTime = 0;
- this.authLevel = KeyService.AuthenticationLevel.NOT_SET;
- this.walletCreationTime = 0;
- this.balanceSymbol = "";
- this.ENSAvatar = "";
- }
-
- private Wallet(Parcel in)
- {
- address = in.readString();
- balance = in.readString();
- ENSname = in.readString();
- name = in.readString();
- int t = in.readInt();
- type = WalletType.values()[t];
- lastBackupTime = in.readLong();
- t = in.readInt();
- authLevel = KeyService.AuthenticationLevel.values()[t];
- walletCreationTime = in.readLong();
- balanceSymbol = in.readString();
- ENSAvatar = in.readString();
- }
-
- public void setWalletType(WalletType wType)
- {
- type = wType;
- }
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public Wallet createFromParcel(Parcel in) {
- return new Wallet(in);
- }
-
- @Override
- public Wallet[] newArray(int size) {
- return new Wallet[size];
- }
- };
-
- public boolean sameAddress(String address) {
- return this.address.equalsIgnoreCase(address);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int i)
- {
- parcel.writeString(address);
- parcel.writeString(balance);
- parcel.writeString(ENSname);
- parcel.writeString(name);
- parcel.writeInt(type.ordinal());
- parcel.writeLong(lastBackupTime);
- parcel.writeInt(authLevel.ordinal());
- parcel.writeLong(walletCreationTime);
- parcel.writeString(balanceSymbol);
- parcel.writeString(ENSAvatar);
- }
-
- public boolean setWalletBalance(Token token)
- {
- balanceSymbol = token.tokenInfo != null ? token.tokenInfo.symbol : "ETH";
- String newBalance = token.getFixedFormattedBalance();
- if (newBalance.equals(balance))
- {
- return false;
- }
- else
- {
- balance = newBalance;
- return true;
- }
- }
-
- public void zeroWalletBalance(NetworkInfo networkInfo)
- {
- if (balance.equals("-"))
- {
- balanceSymbol = networkInfo.symbol;
- balance = BalanceUtils.getScaledValueFixed(BigDecimal.ZERO, 0, Token.TOKEN_BALANCE_PRECISION);
- }
- }
+ public Wallet(String address)
+ {
+ this.address = address;
+ this.balance = "-";
+ this.ENSname = "";
+ this.name = "";
+ this.type = WalletType.NOT_DEFINED;
+ this.lastBackupTime = 0;
+ this.authLevel = KeyService.AuthenticationLevel.NOT_SET;
+ this.walletCreationTime = 0;
+ this.balanceSymbol = "";
+ this.ENSAvatar = "";
+ }
+
+ private Wallet(Parcel in)
+ {
+ address = in.readString();
+ balance = in.readString();
+ ENSname = in.readString();
+ name = in.readString();
+ int t = in.readInt();
+ type = WalletType.values()[t];
+ lastBackupTime = in.readLong();
+ t = in.readInt();
+ authLevel = KeyService.AuthenticationLevel.values()[t];
+ walletCreationTime = in.readLong();
+ balanceSymbol = in.readString();
+ ENSAvatar = in.readString();
+ }
+
+ public void setWalletType(WalletType wType)
+ {
+ type = wType;
+ }
+
+ public static final Creator CREATOR = new Creator()
+ {
+ @Override
+ public Wallet createFromParcel(Parcel in)
+ {
+ return new Wallet(in);
+ }
+
+ @Override
+ public Wallet[] newArray(int size)
+ {
+ return new Wallet[size];
+ }
+ };
+
+ public boolean sameAddress(String address)
+ {
+ return this.address.equalsIgnoreCase(address);
+ }
+
+ @Override
+ public int describeContents()
+ {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i)
+ {
+ parcel.writeString(address);
+ parcel.writeString(balance);
+ parcel.writeString(ENSname);
+ parcel.writeString(name);
+ parcel.writeInt(type.ordinal());
+ parcel.writeLong(lastBackupTime);
+ parcel.writeInt(authLevel.ordinal());
+ parcel.writeLong(walletCreationTime);
+ parcel.writeString(balanceSymbol);
+ parcel.writeString(ENSAvatar);
+ }
+
+ public boolean setWalletBalance(Token token)
+ {
+ balanceSymbol = token.tokenInfo != null ? token.tokenInfo.symbol : "ETH";
+ String newBalance = token.getFixedFormattedBalance();
+ if (newBalance.equals(balance))
+ {
+ return false;
+ }
+ else
+ {
+ balance = newBalance;
+ return true;
+ }
+ }
+
+ public void zeroWalletBalance(NetworkInfo networkInfo)
+ {
+ if (balance.equals("-"))
+ {
+ balanceSymbol = networkInfo.symbol;
+ balance = BalanceUtils.getScaledValueFixed(BigDecimal.ZERO, 0, Token.TOKEN_BALANCE_PRECISION);
+ }
+ }
public boolean canSign()
{
- return BuildConfig.DEBUG || type != WalletType.WATCH;
+ return BuildConfig.DEBUG || !watchOnly();
+ }
+
+ public boolean watchOnly()
+ {
+ return type == WalletType.WATCH;
}
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetMode.java b/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetMode.java
new file mode 100644
index 0000000000..41376a1e60
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetMode.java
@@ -0,0 +1,30 @@
+package com.alphawallet.app.entity.analytics;
+
+/**
+ * Created by JB on 12/01/2021.
+ */
+public enum ActionSheetMode
+{
+ SEND_TRANSACTION("Send Transaction"),
+ SEND_TRANSACTION_DAPP("Send Transaction DApp"),
+ SEND_TRANSACTION_WC("Send Transaction WalletConnect"),
+ SIGN_MESSAGE("Sign Message"),
+ SIGN_TRANSACTION("Sign Transaction"),
+ SPEEDUP_TRANSACTION("Speed Up Transaction"),
+ CANCEL_TRANSACTION("Cancel Transaction"),
+ MESSAGE("Message"),
+ WALLET_CONNECT_REQUEST("WalletConnect Request"),
+ NODE_STATUS_INFO("Node Status Info");
+
+ private final String mode;
+
+ ActionSheetMode(String mode)
+ {
+ this.mode = mode;
+ }
+
+ public String getValue()
+ {
+ return mode;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetSource.java b/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetSource.java
new file mode 100644
index 0000000000..05a9b9e92b
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/analytics/ActionSheetSource.java
@@ -0,0 +1,26 @@
+package com.alphawallet.app.entity.analytics;
+
+public enum ActionSheetSource
+{
+ WALLET_CONNECT("WalletConnect"),
+ SWAP("Swap"),
+ SEND_FUNGIBLE("Send Fungible"),
+ SEND_NFT("Send NFT"),
+ TOKENSCRIPT("TokenScript"),
+ BROWSER("Browser"),
+ CLAIM_PAID_MAGIC_LINK("Claim Paid MagicLink"),
+ SPEEDUP_TRANSACTION("Speed Up Transaction"),
+ CANCEL_TRANSACTION("Cancel Transaction");
+
+ private final String source;
+
+ ActionSheetSource(String source)
+ {
+ this.source = source;
+ }
+
+ public String getValue()
+ {
+ return source;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/analytics/FirstWalletAction.java b/app/src/main/java/com/alphawallet/app/entity/analytics/FirstWalletAction.java
new file mode 100644
index 0000000000..21ad0d7c3a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/analytics/FirstWalletAction.java
@@ -0,0 +1,21 @@
+package com.alphawallet.app.entity.analytics;
+
+public enum FirstWalletAction
+{
+ CREATE_WALLET("Create Wallet"),
+ IMPORT_WALLET("Import Wallet");
+
+ public static final String KEY = "action";
+
+ private final String action;
+
+ FirstWalletAction(String action)
+ {
+ this.action = action;
+ }
+
+ public String getValue()
+ {
+ return action;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/analytics/ImportWalletType.java b/app/src/main/java/com/alphawallet/app/entity/analytics/ImportWalletType.java
new file mode 100644
index 0000000000..707ba22ec2
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/analytics/ImportWalletType.java
@@ -0,0 +1,21 @@
+package com.alphawallet.app.entity.analytics;
+
+public enum ImportWalletType
+{
+ SEED_PHRASE("Seed Phrase"),
+ KEYSTORE("Keystore"),
+ PRIVATE_KEY("Private Key"),
+ WATCH("Watch");
+
+ private final String type;
+
+ ImportWalletType(String type)
+ {
+ this.type = type;
+ }
+
+ public String getValue()
+ {
+ return type;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/analytics/QrScanSource.java b/app/src/main/java/com/alphawallet/app/entity/analytics/QrScanSource.java
new file mode 100644
index 0000000000..10cd9301c5
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/analytics/QrScanSource.java
@@ -0,0 +1,27 @@
+package com.alphawallet.app.entity.analytics;
+
+public enum QrScanSource
+{
+ WALLET_CONNECT("Wallet Connect"),
+ ADDRESS_TEXT_FIELD("Address Text Field"),
+ BROWSER_SCREEN("Browser Screen"),
+ IMPORT_WALLET_SCREEN("Import Wallet Screen"),
+ ADD_CUSTOM_TOKEN_SCREEN("Add Custom Token Screen"),
+ WALLET_SCREEN("Wallet Screen"),
+ SEND_FUNGIBLE_SCREEN("Send Screen"),
+ QUICK_ACTION("Quick Action");
+
+ public static final String KEY = "qr_scan_source";
+
+ private final String type;
+
+ QrScanSource(String type)
+ {
+ this.type = type;
+ }
+
+ public String getValue()
+ {
+ return type;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/coinbasepay/DestinationWallet.java b/app/src/main/java/com/alphawallet/app/entity/coinbasepay/DestinationWallet.java
new file mode 100644
index 0000000000..44ef08699c
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/coinbasepay/DestinationWallet.java
@@ -0,0 +1,31 @@
+package com.alphawallet.app.entity.coinbasepay;
+
+import java.util.List;
+
+public class DestinationWallet
+{
+ final transient Type type;
+ String address;
+ List blockchains;
+ List assets;
+
+ public DestinationWallet(Type type, String address, List list)
+ {
+ this.type = type;
+ this.address = address;
+ if (type.equals(Type.ASSETS))
+ {
+ this.assets = list;
+ }
+ else
+ {
+ this.blockchains = list;
+ }
+ }
+
+ public enum Type
+ {
+ ASSETS,
+ BLOCKCHAINS
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java
new file mode 100644
index 0000000000..110be10c1a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java
@@ -0,0 +1,50 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public class Action
+{
+ @SerializedName("fromChainId")
+ @Expose
+ public long fromChainId;
+
+ @SerializedName("toChainId")
+ @Expose
+ public long toChainId;
+
+ @SerializedName("fromToken")
+ @Expose
+ public Token fromToken;
+
+ @SerializedName("toToken")
+ @Expose
+ public Token toToken;
+
+ @SerializedName("fromAmount")
+ @Expose
+ public String fromAmount;
+
+ @SerializedName("slippage")
+ @Expose
+ public double slippage;
+
+ @SerializedName("fromAddress")
+ @Expose
+ public String fromAddress;
+
+ @SerializedName("toAddress")
+ @Expose
+ public String toAddress;
+
+ public String getCurrentPrice()
+ {
+ return new BigDecimal(fromToken.priceUSD)
+ .divide(new BigDecimal(toToken.priceUSD), 4, RoundingMode.DOWN)
+ .stripTrailingZeros()
+ .toPlainString();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java
index cc1f084bcd..01fe4a324c 100644
--- a/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java
@@ -4,7 +4,6 @@
import com.google.gson.annotations.SerializedName;
import java.util.List;
-import java.util.Objects;
public class Connection
{
@@ -18,61 +17,9 @@ public class Connection
@SerializedName("fromTokens")
@Expose
- public List fromTokens;
+ public List fromTokens;
@SerializedName("toTokens")
@Expose
- public List toTokens;
-
- public static class LToken
- {
- @SerializedName("address")
- @Expose
- public String address;
-
- @SerializedName("symbol")
- @Expose
- public String symbol;
-
- @SerializedName("decimals")
- @Expose
- public long decimals;
-
- @SerializedName("chainId")
- @Expose
- public long chainId;
-
- @SerializedName("name")
- @Expose
- public String name;
-
- @SerializedName("coinKey")
- @Expose
- public String coinKey;
-
- @SerializedName("priceUSD")
- @Expose
- public String priceUSD;
-
- @SerializedName("logoURI")
- @Expose
- public String logoURI;
-
- public String balance;
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- LToken lToken = (LToken) o;
- return address.equals(lToken.address) && symbol.equals(lToken.symbol);
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hash(address, symbol);
- }
- }
+ public List toTokens;
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Estimate.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Estimate.java
new file mode 100644
index 0000000000..ab63b81844
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Estimate.java
@@ -0,0 +1,100 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+
+public class Estimate
+{
+ @SerializedName("fromAmount")
+ @Expose
+ public String fromAmount;
+
+ @SerializedName("toAmount")
+ @Expose
+ public String toAmount;
+
+ @SerializedName("toAmountMin")
+ @Expose
+ public String toAmountMin;
+
+ @SerializedName("approvalAddress")
+ @Expose
+ public String approvalAddress;
+
+ @SerializedName("executionDuration")
+ @Expose
+ public long executionDuration;
+
+ @SerializedName("feeCosts")
+ @Expose
+ public ArrayList feeCosts;
+
+ @SerializedName("gasCosts")
+ @Expose
+ public ArrayList gasCosts;
+
+ @SerializedName("data")
+ @Expose
+ public Data data;
+
+ @SerializedName("fromAmountUSD")
+ @Expose
+ public String fromAmountUSD;
+
+ @SerializedName("toAmountUSD")
+ @Expose
+ public String toAmountUSD;
+
+ public static class Data
+ {
+ @SerializedName("blockNumber")
+ @Expose
+ public long blockNumber;
+
+ @SerializedName("network")
+ @Expose
+ public long network;
+
+ @SerializedName("srcToken")
+ @Expose
+ public String srcToken;
+
+ @SerializedName("srcDecimals")
+ @Expose
+ public long srcDecimals;
+
+ @SerializedName("srcAmount")
+ @Expose
+ public String srcAmount;
+
+ @SerializedName("destToken")
+ @Expose
+ public String destToken;
+
+ @SerializedName("destDecimals")
+ @Expose
+ public long destDecimals;
+
+ @SerializedName("destAmount")
+ @Expose
+ public String destAmount;
+
+ @SerializedName("gasCostUSD")
+ @Expose
+ public String gasCostUSD;
+
+ @SerializedName("gasCost")
+ @Expose
+ public String gasCost;
+
+ @SerializedName("buyAmount")
+ @Expose
+ public String buyAmount;
+
+ @SerializedName("sellAmount")
+ @Expose
+ public String sellAmount;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java b/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java
new file mode 100644
index 0000000000..bbbd5cd645
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java
@@ -0,0 +1,25 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+
+public class FeeCost
+{
+ @SerializedName("name")
+ @Expose
+ public String name;
+
+ @SerializedName("percentage")
+ @Expose
+ public String percentage;
+
+ @SerializedName("token")
+ @Expose
+ public Token token;
+
+ @SerializedName("amount")
+ @Expose
+ public String amount;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java b/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java
new file mode 100644
index 0000000000..d2ccd5b39b
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java
@@ -0,0 +1,19 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class GasCost
+{
+ @SerializedName("amount")
+ @Expose
+ public String amount;
+
+ @SerializedName("amountUSD")
+ @Expose
+ public String amountUSD;
+
+ @SerializedName("token")
+ @Expose
+ public Token token;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Quote.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Quote.java
index 7d7dd6bbc5..fc2d80e4f1 100644
--- a/app/src/main/java/com/alphawallet/app/entity/lifi/Quote.java
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Quote.java
@@ -3,9 +3,6 @@
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
public class Quote
{
@SerializedName("id")
@@ -20,143 +17,18 @@ public class Quote
@Expose
public String tool;
+ @SerializedName("toolDetails")
+ @Expose
+ public SwapProvider swapProvider;
+
@SerializedName("action")
@Expose
public Action action;
- public static class Action
- {
- @SerializedName("fromChainId")
- @Expose
- public long fromChainId;
-
- @SerializedName("toChainId")
- @Expose
- public long toChainId;
-
- @SerializedName("fromToken")
- @Expose
- public Connection.LToken fromToken;
-
- @SerializedName("toToken")
- @Expose
- public Connection.LToken toToken;
-
- @SerializedName("fromAmount")
- @Expose
- public String fromAmount;
-
- @SerializedName("slippage")
- @Expose
- public double slippage;
-
- @SerializedName("fromAddress")
- @Expose
- public String fromAddress;
-
- @SerializedName("toAddress")
- @Expose
- public String toAddress;
- }
-
@SerializedName("estimate")
@Expose
public Estimate estimate;
- public static class Estimate
- {
- @SerializedName("fromAmount")
- @Expose
- public String fromAmount;
-
- @SerializedName("toAmount")
- @Expose
- public String toAmount;
-
- @SerializedName("toAmountMin")
- @Expose
- public String toAmountMin;
-
- @SerializedName("approvalAddress")
- @Expose
- public String approvalAddress;
-
- @SerializedName("executionDuration")
- @Expose
- public long executionDuration;
-
-// @SerializedName("feeCosts")
-// @Expose
-// public JSONArray feeCosts;
-//
-// @SerializedName("gasCosts")
-// @Expose
-// public JSONArray gasCosts;
-
- @SerializedName("data")
- @Expose
- public Data data;
-
- public static class Data
- {
- @SerializedName("blockNumber")
- @Expose
- public long blockNumber;
-
- @SerializedName("network")
- @Expose
- public long network;
-
- @SerializedName("srcToken")
- @Expose
- public String srcToken;
-
- @SerializedName("srcDecimals")
- @Expose
- public long srcDecimals;
-
- @SerializedName("srcAmount")
- @Expose
- public String srcAmount;
-
- @SerializedName("destToken")
- @Expose
- public String destToken;
-
- @SerializedName("destDecimals")
- @Expose
- public long destDecimals;
-
- @SerializedName("destAmount")
- @Expose
- public String destAmount;
-
- @SerializedName("gasCostUSD")
- @Expose
- public String gasCostUSD;
-
- @SerializedName("gasCost")
- @Expose
- public String gasCost;
-
- @SerializedName("buyAmount")
- @Expose
- public String buyAmount;
-
- @SerializedName("sellAmount")
- @Expose
- public String sellAmount;
- }
-
- @SerializedName("fromAmountUSD")
- @Expose
- public String fromAmountUSD;
-
- @SerializedName("toAmountUSD")
- @Expose
- public String toAmountUSD;
- }
-
@SerializedName("transactionRequest")
@Expose
public TransactionRequest transactionRequest;
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Route.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Route.java
new file mode 100644
index 0000000000..97719ea00c
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Route.java
@@ -0,0 +1,36 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+public class Route
+{
+ @SerializedName("gasCostUSD")
+ @Expose
+ public String gasCostUSD;
+
+ @SerializedName("steps")
+ @Expose
+ public List steps;
+
+ @SerializedName("tags")
+ @Expose
+ public List tags;
+
+ public static class Step
+ {
+ @SerializedName("toolDetails")
+ @Expose
+ public SwapProvider swapProvider;
+
+ @SerializedName("action")
+ @Expose
+ public Action action;
+
+ @SerializedName("estimate")
+ @Expose
+ public Estimate estimate;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/RouteError.java b/app/src/main/java/com/alphawallet/app/entity/lifi/RouteError.java
new file mode 100644
index 0000000000..aa6135d733
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/RouteError.java
@@ -0,0 +1,23 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class RouteError
+{
+ @SerializedName("tool")
+ @Expose
+ public String tool;
+
+ @SerializedName("message")
+ @Expose
+ public String message;
+
+ @SerializedName("errorType")
+ @Expose
+ public String errorType;
+
+ @SerializedName("code")
+ @Expose
+ public String code;
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/RouteOptions.java b/app/src/main/java/com/alphawallet/app/entity/lifi/RouteOptions.java
new file mode 100644
index 0000000000..99882f661a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/RouteOptions.java
@@ -0,0 +1,33 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.Gson;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RouteOptions
+{
+ public String integrator;
+ public String slippage;
+ public Exchanges exchanges;
+ public String order;
+
+ public RouteOptions()
+ {
+ this.exchanges = new Exchanges();
+ }
+
+ public static class Exchanges
+ {
+ public List allow = new ArrayList<>();
+ }
+
+ public JSONObject getJson() throws JSONException
+ {
+ String json = new Gson().toJson(this);
+ return new JSONObject(json);
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/SwapProvider.java b/app/src/main/java/com/alphawallet/app/entity/lifi/SwapProvider.java
new file mode 100644
index 0000000000..6b01e6a292
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/SwapProvider.java
@@ -0,0 +1,25 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class SwapProvider
+{
+ @SerializedName("key")
+ @Expose
+ public String key;
+
+ @SerializedName("name")
+ @Expose
+ public String name;
+
+ @SerializedName("logoURI")
+ @Expose
+ public String logoURI;
+
+ @SerializedName("url")
+ @Expose
+ public String url;
+
+ public boolean isChecked;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Token.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Token.java
new file mode 100644
index 0000000000..83ec1225a6
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Token.java
@@ -0,0 +1,91 @@
+package com.alphawallet.app.entity.lifi;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+public class Token
+{
+ @SerializedName("address")
+ @Expose
+ public String address;
+
+ @SerializedName("symbol")
+ @Expose
+ public String symbol;
+
+ @SerializedName("decimals")
+ @Expose
+ public long decimals;
+
+ @SerializedName("chainId")
+ @Expose
+ public long chainId;
+
+ @SerializedName("name")
+ @Expose
+ public String name;
+
+ @SerializedName("coinKey")
+ @Expose
+ public String coinKey;
+
+ @SerializedName("priceUSD")
+ @Expose
+ public String priceUSD;
+
+ @SerializedName("logoURI")
+ @Expose
+ public String logoURI;
+
+ public String balance;
+ public double fiatEquivalent;
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Token lToken = (Token) o;
+ return address.equals(lToken.address) && symbol.equals(lToken.symbol);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(address, symbol);
+ }
+
+ // Note: In the LIFI API, the native token has either of these two addresses.
+ public boolean isNativeToken()
+ {
+ return address.equalsIgnoreCase("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") ||
+ address.equalsIgnoreCase("0x0000000000000000000000000000000000000000");
+ }
+
+ public double getFiatValue()
+ {
+ try
+ {
+ double value = Double.parseDouble(balance);
+ double priceUSD = Double.parseDouble(this.priceUSD);
+ return value * priceUSD;
+ }
+ catch (NumberFormatException | NullPointerException e)
+ {
+ return 0.0;
+ }
+ }
+
+ public boolean isSimilarTo(com.alphawallet.app.entity.tokens.Token aToken, String walletAddress)
+ {
+ if (this.chainId == aToken.tokenInfo.chainId
+ && this.address.equalsIgnoreCase(aToken.getAddress()))
+ {
+ return true;
+ }
+
+ return aToken.getAddress().equalsIgnoreCase(walletAddress) && isNativeToken();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java b/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java
index b461cb184a..1a9b46ee76 100644
--- a/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java
+++ b/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java
@@ -55,8 +55,8 @@ public NFTAsset[] newArray(int size)
private static final String DESCRIPTION = "description";
private static final String IMAGE_ORIGINAL_URL = "image_original_url";
private static final String IMAGE_ANIMATION = "animation_url";
- private static final String[] IMAGE_DESIGNATORS = {IMAGE, IMAGE_URL, IMAGE_ANIMATION, IMAGE_ORIGINAL_URL, IMAGE_PREVIEW};
- private static final String[] SVG_OVERRIDE = {IMAGE_ORIGINAL_URL, IMAGE_ANIMATION, IMAGE, IMAGE_URL};
+ private static final String[] IMAGE_DESIGNATORS = {IMAGE, IMAGE_URL, IMAGE_ORIGINAL_URL, IMAGE_PREVIEW, IMAGE_ANIMATION};
+ private static final String[] SVG_OVERRIDE = {IMAGE_ORIGINAL_URL, IMAGE, IMAGE_URL, IMAGE_ANIMATION};
private static final String[] IMAGE_THUMBNAIL_DESIGNATORS = {IMAGE_PREVIEW, IMAGE, IMAGE_URL, IMAGE_ORIGINAL_URL, IMAGE_ANIMATION};
private static final String BACKGROUND_COLOUR = "background_color";
private static final String EXTERNAL_LINK = "external_link";
@@ -81,7 +81,8 @@ public NFTAsset(String metaData)
public NFTAsset(RealmNFTAsset realmAsset)
{
- loadFromMetaData(realmAsset.getMetaData());
+ String metaData = realmAsset.getMetaData() != null ? realmAsset.getMetaData() : new NFTAsset(new BigInteger(realmAsset.getTokenId())).jsonMetaData();
+ loadFromMetaData(metaData);
balance = realmAsset.getBalance();
}
@@ -99,6 +100,7 @@ public NFTAsset(BigInteger tokenId)
attributeMap.clear();
balance = BigDecimal.ONE;
assetMap.put(NAME, "ID #" + tokenId.toString());
+ assetMap.put(LOADING_TOKEN, ".");
}
public NFTAsset(NFTAsset asset)
@@ -169,10 +171,9 @@ public String getName()
return assetMap.get(NAME);
}
- public boolean isAnimation()
+ public String getAnimation()
{
- String anim = assetMap.get(IMAGE_ANIMATION);
- return anim != null;
+ return assetMap.get(IMAGE_ANIMATION);
}
public String getImage()
@@ -382,6 +383,11 @@ public boolean needsLoading()
return (assetMap.size() == 0 || assetMap.containsKey(LOADING_TOKEN));
}
+ public boolean hasImageAsset()
+ {
+ return !TextUtils.isEmpty(getThumbnail());
+ }
+
public boolean requiresReplacement()
{
return (needsLoading() || !assetMap.containsKey(NAME) || TextUtils.isEmpty(getImage()));
@@ -563,4 +569,4 @@ public String getValue()
return this.category;
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/opensea/OpenSeaAsset.java b/app/src/main/java/com/alphawallet/app/entity/opensea/OpenSeaAsset.java
index 9280fb2109..635de75260 100644
--- a/app/src/main/java/com/alphawallet/app/entity/opensea/OpenSeaAsset.java
+++ b/app/src/main/java/com/alphawallet/app/entity/opensea/OpenSeaAsset.java
@@ -40,6 +40,10 @@ public class OpenSeaAsset
@Expose
public String animationUrl;
+ @SerializedName("animation_url")
+ @Expose
+ public String animation_url;
+
@SerializedName("name")
@Expose
public String name;
@@ -84,6 +88,10 @@ public class OpenSeaAsset
@Expose
public LastSale lastSale;
+ @SerializedName("rarity_data")
+ @Expose
+ public Rarity rarity;
+
public static class Collection
{
@SerializedName("stats")
@@ -323,6 +331,22 @@ else if (totalPrice.length() <= decimals)
return result;
}
+ public String getAnimationUrl()
+ {
+ if (animationUrl != null)
+ {
+ return animationUrl;
+ }
+ else if (animation_url != null)
+ {
+ return animation_url;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
public String getImageUrl()
{
if (image != null)
@@ -345,6 +369,10 @@ else if (imagePreviewUrl != null)
{
return imagePreviewUrl;
}
+ else if (animation_url != null)
+ {
+ return animation_url;
+ }
else
{
return "";
diff --git a/app/src/main/java/com/alphawallet/app/entity/opensea/Rarity.java b/app/src/main/java/com/alphawallet/app/entity/opensea/Rarity.java
new file mode 100644
index 0000000000..5160fcaec5
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/opensea/Rarity.java
@@ -0,0 +1,31 @@
+package com.alphawallet.app.entity.opensea;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class Rarity
+{
+ @SerializedName("strategy_id")
+ @Expose
+ public String strategyId;
+
+ @SerializedName("strategy_version")
+ @Expose
+ public String strategyVersion;
+
+ @SerializedName("rank")
+ @Expose
+ public long rank;
+
+ @SerializedName("score")
+ @Expose
+ public double score;
+
+ @SerializedName("max_rank")
+ @Expose
+ public long maxRank;
+
+ @SerializedName("tokens_scored")
+ @Expose
+ public long tokensScored;
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenUpdateType.java b/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenUpdateType.java
new file mode 100644
index 0000000000..4f75f70e96
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/tokendata/TokenUpdateType.java
@@ -0,0 +1,9 @@
+package com.alphawallet.app.entity.tokendata;
+
+/**
+ * Created by JB on 22/08/2022.
+ */
+public enum TokenUpdateType
+{
+ ACTIVE_SYNC, STORED
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java
index f879983a38..84f8ecc789 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java
@@ -276,27 +276,39 @@ public Function getTransferFunction(String to, List tokenIds) throws
}
@Override
- public List getChangeList(Map assetMap)
+ public Map getAssetChange(Map oldAssetList)
{
- //detect asset removal
- List oldAssetIdList = new ArrayList<>(assetMap.keySet());
- oldAssetIdList.removeAll(assets.keySet());
+ //first see if there's no change; if this is the case we can skip
+ if (assetsUnchanged(oldAssetList)) return assets;
- List changeList = new ArrayList<>(oldAssetIdList);
+ //add all known tokens in
+ Map sum = new HashMap<>(oldAssetList);
+ sum.putAll(assets);
+ Set tokenIds = sum.keySet();
+ Function balanceOfBatch = balanceOfBatch(getWallet(), tokenIds);
+ List balances = callSmartContractFunctionArray(tokenInfo.chainId, balanceOfBatch, getAddress(), getWallet());
+ Map updatedAssetMap;
- //Now detect differences or new tokens
- for (BigInteger tokenId : assets.keySet())
+ if (balances != null && balances.size() > 0)
{
- NFTAsset newAsset = assets.get(tokenId);
- NFTAsset oldAsset = assetMap.get(tokenId);
-
- if (oldAsset == null || newAsset.hashCode() != oldAsset.hashCode())
+ updatedAssetMap = new HashMap<>();
+ int index = 0;
+ for (BigInteger tokenId : tokenIds)
{
- changeList.add(tokenId);
+ NFTAsset thisAsset = new NFTAsset(sum.get(tokenId));
+ BigInteger balance = balances.get(index).getValue();
+ thisAsset.setBalance(new BigDecimal(balance));
+ updatedAssetMap.put(tokenId, thisAsset);
+
+ index++;
}
}
+ else
+ {
+ updatedAssetMap = assets;
+ }
- return changeList;
+ return updatedAssetMap;
}
private List fetchBalances(Set tokenIds)
@@ -308,16 +320,10 @@ private List fetchBalances(Set tokenIds)
@Override
public Map queryAssets(Map assetMap)
{
- //first see if there's no change; if this is the case we can skip
- if (assetsUnchanged(assetMap)) return assets;
-
- //add all known tokens in
- Map sum = new HashMap<>(assetMap);
- sum.putAll(assets);
- Set tokenIds = sum.keySet();
+ Set tokenIds = assetMap.keySet();
Function balanceOfBatch = balanceOfBatch(getWallet(), tokenIds);
List balances = callSmartContractFunctionArray(tokenInfo.chainId, balanceOfBatch, getAddress(), getWallet());
- Map updatedAssetMap;
+ Map updatedAssetMap = new HashMap<>();
if (balances != null && balances.size() > 0)
{
@@ -325,7 +331,7 @@ public Map queryAssets(Map assetMap)
int index = 0;
for (BigInteger tokenId : tokenIds)
{
- NFTAsset thisAsset = new NFTAsset(sum.get(tokenId));
+ NFTAsset thisAsset = new NFTAsset(assetMap.get(tokenId));
BigInteger balance = balances.get(index).getValue();
thisAsset.setBalance(new BigDecimal(balance));
updatedAssetMap.put(tokenId, thisAsset);
@@ -333,10 +339,6 @@ public Map queryAssets(Map assetMap)
index++;
}
}
- else
- {
- updatedAssetMap = assets;
- }
return updatedAssetMap;
}
@@ -643,7 +645,7 @@ public BigDecimal updateBalance(Realm realm)
try
{
- final Web3j web3j = TokenRepository.getWeb3jService(tokenInfo.chainId);
+ final Web3j web3j = TokenRepository.getWeb3jServiceForEvents(tokenInfo.chainId);
Pair, HashSet>> evRead = eventSync.processTransferEvents(web3j,
getBalanceUpdateEvents(), startBlock, endBlock, realm);
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Ticket.java b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Ticket.java
index 813eeda7fa..49a6e8c5ca 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Ticket.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Ticket.java
@@ -171,7 +171,7 @@ public boolean hasArrayBalance()
public List getNonZeroArrayBalance()
{
List nonZeroValues = new ArrayList<>();
- for (BigInteger value : balanceArray) if (value.compareTo(BigInteger.ZERO) != 0 && !nonZeroValues.contains(value)) nonZeroValues.add(value);
+ for (BigInteger value : balanceArray) if (value.compareTo(BigInteger.ZERO) != 0) nonZeroValues.add(value);
return nonZeroValues;
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java
index 6a3ad167cc..0de075182e 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java
@@ -1,10 +1,13 @@
package com.alphawallet.app.entity.tokens;
+import static com.alphawallet.app.repository.TokenRepository.callSmartContractFunction;
+import static com.alphawallet.app.repository.TokensRealmSource.databaseKey;
import static com.alphawallet.app.util.Utils.parseTokenId;
import static org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction;
import static org.web3j.tx.Contract.staticExtractEventParameters;
import android.app.Activity;
+import android.text.TextUtils;
import android.util.Pair;
import com.alphawallet.app.R;
@@ -15,6 +18,7 @@
import com.alphawallet.app.entity.TransactionInput;
import com.alphawallet.app.entity.nftassets.NFTAsset;
import com.alphawallet.app.entity.tokendata.TokenGroup;
+import com.alphawallet.app.repository.EthereumNetworkBase;
import com.alphawallet.app.repository.EventResult;
import com.alphawallet.app.repository.TokenRepository;
import com.alphawallet.app.repository.entity.RealmNFTAsset;
@@ -28,32 +32,41 @@
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeEncoder;
import org.web3j.abi.TypeReference;
+import org.web3j.abi.Utils;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
+import org.web3j.protocol.core.BatchRequest;
+import org.web3j.protocol.core.BatchResponse;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
+import org.web3j.protocol.core.Request;
+import org.web3j.protocol.core.Response;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.core.methods.response.Log;
import org.web3j.utils.Numeric;
+import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import io.realm.Case;
import io.realm.Realm;
+import io.realm.RealmResults;
import timber.log.Timber;
/**
@@ -63,6 +76,8 @@
public class ERC721Token extends Token
{
private final Map tokenBalanceAssets;
+ private static final Map balanceChecks = new ConcurrentHashMap<>();
+ private boolean batchProcessingError;
public ERC721Token(TokenInfo tokenInfo, Map balanceList, BigDecimal balance, long blancaTime, String networkName, ContractType type)
{
@@ -77,6 +92,7 @@ public ERC721Token(TokenInfo tokenInfo, Map balanceList, B
}
setInterfaceSpec(type);
group = TokenGroup.NFT;
+ batchProcessingError = false;
}
@Override
@@ -259,7 +275,9 @@ public boolean checkRealmBalanceChange(RealmToken realmToken)
public boolean checkBalanceChange(Token oldToken)
{
if (super.checkBalanceChange(oldToken)) return true;
- if (getTokenAssets().size() != oldToken.getTokenAssets().size()) return true;
+ if ((getTokenAssets() != null && oldToken.getTokenAssets() != null)
+ && getTokenAssets().size() != oldToken.getTokenAssets().size()) return true;
+
for (BigInteger tokenId : tokenBalanceAssets.keySet())
{
NFTAsset newAsset = tokenBalanceAssets.get(tokenId);
@@ -326,6 +344,11 @@ private Event getTransferEvents()
@Override
public BigDecimal updateBalance(Realm realm)
{
+ if (balanceChecks.containsKey(tokenInfo.address))
+ {
+ return balance;
+ }
+
//first get current block
SyncDef sync = eventSync.getSyncDef(realm);
if (sync == null) return balance;
@@ -339,11 +362,20 @@ public BigDecimal updateBalance(Realm realm)
try
{
- final Web3j web3j = TokenRepository.getWeb3jService(tokenInfo.chainId);
+ balanceChecks.put(tokenInfo.address, true); //set checking
+ final Web3j web3j = TokenRepository.getWeb3jServiceForEvents(tokenInfo.chainId);
+ if (contractType == ContractType.ERC721_ENUMERABLE)
+ {
+ updateEnumerableBalance(web3j, realm);
+ }
+
Pair, HashSet>> evRead = eventSync.processTransferEvents(web3j,
getTransferEvents(), startBlock, endBlock, realm);
- eventSync.updateEventReads(realm, sync, currentBlock, evRead.first); //means our event read was fine
+ eventSync.updateEventReads(realm, sync, currentBlock, evRead.first); //means our event read was fine
+
+ //No need to go any further if this is enumerable
+ if (contractType == ContractType.ERC721_ENUMERABLE) return balance;
HashSet allMovingTokens = new HashSet<>(evRead.second.first);
allMovingTokens.addAll(evRead.second.second);
@@ -362,6 +394,7 @@ public BigDecimal updateBalance(Realm realm)
if (eventSync.handleEthLogError(e.error, startBlock, endBlock, sync, realm))
{
//recurse until we find a good value
+ balanceChecks.remove(tokenInfo.address);
updateBalance(realm);
}
}
@@ -369,10 +402,107 @@ public BigDecimal updateBalance(Realm realm)
{
Timber.w(e);
}
+ finally
+ {
+ balanceChecks.remove(tokenInfo.address);
+ }
+
+ //check for possible issues
+ if (endBlock == DefaultBlockParameterName.LATEST && balance.compareTo(BigDecimal.valueOf(tokenBalanceAssets.size())) != 0)
+ {
+ //possible mismatch, scan from beginning again
+ eventSync.resetEventReads(realm);
+ }
return balance;
}
+ /***********
+ * For ERC721Enumerable interface
+ **********/
+ private void updateEnumerableBalance(Web3j web3j, Realm realm) throws IOException
+ {
+ HashSet tokenIdsHeld = new HashSet<>();
+ //get enumerable balance
+ //find tokenIds held
+ long currentBalance = balance != null ? balance.longValue() : 0;
+
+ if (EthereumNetworkBase.getBatchProcessingLimit(tokenInfo.chainId) > 0 && !batchProcessingError && currentBalance > 1) //no need to do batch query for 1
+ {
+ updateEnumerableBatchBalance(web3j, currentBalance, tokenIdsHeld, realm);
+ }
+ else
+ {
+ for (long tokenIndex = 0; tokenIndex < currentBalance; tokenIndex++)
+ {
+ // find tokenId from index
+ String tokenId = callSmartContractFunction(tokenInfo.chainId, tokenOfOwnerByIndex(BigInteger.valueOf(tokenIndex)), getAddress(), getWallet());
+ if (tokenId == null) continue;
+ tokenIdsHeld.add(new BigInteger(tokenId));
+ }
+ }
+
+ updateRealmForEnumerable(realm, tokenIdsHeld);
+ }
+
+ private void updateEnumerableBatchBalance(Web3j web3j, long currentBalance, HashSet tokenIdsHeld, Realm realm) throws IOException
+ {
+ BatchRequest requests = web3j.newBatch();
+
+ for (long tokenIndex = 0; tokenIndex < currentBalance; tokenIndex++)
+ {
+ requests.add(getContractCall(web3j, tokenOfOwnerByIndex(BigInteger.valueOf(tokenIndex)), getAddress()));
+ if (requests.getRequests().size() >= EthereumNetworkBase.getBatchProcessingLimit(tokenInfo.chainId))
+ {
+ //do this send
+ handleEnumerableRequests(requests, tokenIdsHeld);
+ requests = web3j.newBatch();
+ }
+ }
+
+ if (requests.getRequests().size() > 0)
+ {
+ //do final call
+ handleEnumerableRequests(requests, tokenIdsHeld);
+ }
+
+ if (batchProcessingError)
+ {
+ updateEnumerableBalance(web3j, realm);
+ }
+ }
+
+ private void handleEnumerableRequests(BatchRequest requests, HashSet tokenIdsHeld) throws IOException
+ {
+ BatchResponse responses = requests.send();
+ if (responses.getResponses().size() != requests.getRequests().size())
+ {
+ batchProcessingError = true;
+ return;
+ }
+
+ //process responses
+ for (Response> rsp : responses.getResponses())
+ {
+ BigInteger tokenId = getTokenId(rsp);
+ if (tokenId == null) continue;
+ tokenIdsHeld.add(tokenId);
+ }
+ }
+
+ private BigInteger getTokenId(Response> rsp)
+ {
+ List> outputParams = Utils.convert(Collections.singletonList(new TypeReference() {}));
+ List responseValues = FunctionReturnDecoder.decode(((EthCall)rsp).getValue(), outputParams);
+ if (!responseValues.isEmpty())
+ {
+ String tokenIdStr = responseValues.get(0).getValue().toString();
+ if (!TextUtils.isEmpty(tokenIdStr)) return new BigInteger(tokenIdStr);
+ }
+
+ return null;
+ }
+
private void updateRealmBalance(Realm realm, Set tokenIds, Set allMovingTokens)
{
boolean updated = false;
@@ -419,6 +549,40 @@ private void removeRealmBalance(Realm realm, HashSet removedTokens)
});
}
+ private void updateRealmForEnumerable(Realm realm, HashSet currentTokens)
+ {
+ HashSet storedBalance = new HashSet<>();
+ RealmResults results = realm.where(RealmNFTAsset.class)
+ .like("tokenIdAddr", databaseKey(this) + "-*", Case.INSENSITIVE)
+ .findAll();
+
+ for (RealmNFTAsset t : results)
+ {
+ storedBalance.add(new BigInteger(t.getTokenId()));
+ }
+
+ if (!currentTokens.equals(storedBalance))
+ {
+ realm.executeTransaction(r -> {
+ results.deleteAllFromRealm();
+ for (BigInteger tokenId : currentTokens)
+ {
+ String key = RealmNFTAsset.databaseKey(this, tokenId);
+ RealmNFTAsset realmAsset = realm.where(RealmNFTAsset.class)
+ .equalTo("tokenIdAddr", key)
+ .findFirst();
+
+ if (realmAsset == null)
+ {
+ realmAsset = r.createObject(RealmNFTAsset.class, key); //create asset in realm
+ realmAsset.setMetaData(new NFTAsset(tokenId).jsonMetaData());
+ r.insertOrUpdate(realmAsset);
+ }
+ }
+ });
+ }
+ }
+
private void updateRealmBalances(Realm realm, Set tokenIds)
{
if (realm == null) return;
@@ -463,13 +627,15 @@ public HashSet processLogsAndStoreTransferEvents(EthLog receiveLogs,
return tokenIds;
}
- private HashSet checkBalances(Web3j web3j, HashSet eventIds)
+ private HashSet checkBalances(Web3j web3j, HashSet eventIds) throws IOException
{
HashSet heldTokens = new HashSet<>();
+ if (EthereumNetworkBase.getBatchProcessingLimit(tokenInfo.chainId) > 0 && !batchProcessingError && eventIds.size() > 1) return checkBatchBalances(web3j, eventIds);
+
for (BigInteger tokenId : eventIds)
{
- String owner = callSmartContractFunction(web3j, ownerOf(tokenId), getAddress(), getWallet());
- if (owner == null || owner.toLowerCase().equals(getWallet()))
+ String owner = callSmartContractFunction(tokenInfo.chainId, ownerOf(tokenId), getAddress(), getWallet());
+ if (owner == null || owner.equalsIgnoreCase(getWallet()))
{
heldTokens.add(tokenId);
}
@@ -478,6 +644,80 @@ private HashSet checkBalances(Web3j web3j, HashSet event
return heldTokens;
}
+ private HashSet checkBatchBalances(Web3j web3j, HashSet eventIds) throws IOException
+ {
+ HashSet heldTokens = new HashSet<>();
+ List balanceIds = new ArrayList<>();
+ BatchRequest requests = web3j.newBatch();
+ for (BigInteger tokenId : eventIds)
+ {
+ requests.add(getContractCall(web3j, ownerOf(tokenId), getAddress()));
+ balanceIds.add(tokenId);
+ if (requests.getRequests().size() >= EthereumNetworkBase.getBatchProcessingLimit(tokenInfo.chainId))
+ {
+ //do this send
+ handleRequests(requests, balanceIds, heldTokens);
+ requests = web3j.newBatch();
+ }
+ }
+
+ if (requests.getRequests().size() > 0)
+ {
+ //do final call
+ handleRequests(requests, balanceIds, heldTokens);
+ }
+
+ if (batchProcessingError)
+ {
+ return checkBalances(web3j, eventIds);
+ }
+ else
+ {
+ return heldTokens;
+ }
+ }
+
+ private void handleRequests(BatchRequest requests, List balanceIds, HashSet heldTokens) throws IOException
+ {
+ int index = 0;
+ BatchResponse responses = requests.send();
+ if (responses.getResponses().size() != requests.getRequests().size())
+ {
+ batchProcessingError = true;
+ return;
+ }
+
+ //process responses
+ for (Response> rsp : responses.getResponses())
+ {
+ BigInteger tokenId = balanceIds.get(index);
+ if (isOwner(rsp, tokenId))
+ {
+ heldTokens.add(tokenId);
+ }
+
+ index++;
+ }
+
+ balanceIds.clear();
+ }
+
+ private boolean isOwner(Response> rsp, BigInteger tokenId)
+ {
+ EthCall response = (EthCall) rsp;
+ Function function = ownerOf(tokenId);
+ List responseValues = FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());
+ if (!responseValues.isEmpty())
+ {
+ String owner = responseValues.get(0).getValue().toString();
+ return (!owner.isEmpty() && owner.equalsIgnoreCase(getWallet()));
+ }
+ else
+ {
+ return false;
+ }
+ }
+
@Override
public EthFilter getReceiveBalanceFilter(Event event, DefaultBlockParameter startBlock, DefaultBlockParameter endBlock)
{
@@ -510,24 +750,6 @@ public EthFilter getSendBalanceFilter(Event event, DefaultBlockParameter startBl
return filter;
}
- /**
- * Returns false if the Asset balance appears to be entries with only TokenId - indicating an ERC721Ticket
- *
- * @return
- */
- @Override
- public boolean checkBalanceType()
- {
- boolean onlyHasTokenId = true;
- //if elements contain asset with only assetId then most likely this is a ticket.
- for (NFTAsset a : tokenBalanceAssets.values())
- {
- if (!a.isBlank()) onlyHasTokenId = false;
- }
-
- return tokenBalanceAssets.size() == 0 || !onlyHasTokenId;
- }
-
public String getTransferID(Transaction tx)
{
if (tx.transactionInput != null && tx.transactionInput.miscData.size() > 0)
@@ -601,30 +823,32 @@ public BigDecimal getBalanceRaw()
* If there is a token that previously was there, but now isn't, it could be because
* the opensea call was split or that the owner transferred the token
*
- * @param assetMap Loaded Assets from Realm
- * @return map of currently known live assets
+ * @param assetMap Loaded Assets which are new assets (don't add assets from opensea unless we double check here first)
+ * @return map of checked assets
*/
@Override
public Map queryAssets(Map assetMap)
{
final Web3j web3j = TokenRepository.getWeb3jService(tokenInfo.chainId);
- //check all tokens in this contract
- assetMap.putAll(tokenBalanceAssets);
+ HashSet currentAssets = new HashSet<>(assetMap.keySet());
+
+ try
+ {
+ currentAssets = checkBalances(web3j, currentAssets);
+ }
+ catch (Exception e)
+ {
+ //
+ }
- //now check balance for all tokenIds (note that ERC1155 has a batch balance check, ERC721 does not)
for (Map.Entry entry : assetMap.entrySet())
{
BigInteger checkId = entry.getKey();
NFTAsset checkAsset = entry.getValue();
//check balance
- String owner = callSmartContractFunction(web3j, ownerOf(checkId), getAddress(), getWallet());
- if (owner == null) //play it safe. If there's no 'ownerOf' for an ERC721, it's something custom like ENS
- {
- checkAsset.setBalance(BigDecimal.ONE);
- }
- else if (owner.toLowerCase().equals(getWallet()))
+ if (currentAssets.contains(checkId))
{
checkAsset.setBalance(BigDecimal.ONE);
}
@@ -633,61 +857,82 @@ else if (owner.toLowerCase().equals(getWallet()))
checkAsset.setBalance(BigDecimal.ZERO);
}
- //add back into asset map
- tokenBalanceAssets.put(checkId, checkAsset);
+ assetMap.put(checkId, checkAsset);
}
- return tokenBalanceAssets;
+ return assetMap;
}
-
+
+ // Check for new/missing tokenBalanceAssets
@Override
- public List getChangeList(Map assetMap)
+ public Map getAssetChange(Map oldAssetList)
{
- //detect asset removal
- List oldAssetIdList = new ArrayList<>(assetMap.keySet());
- oldAssetIdList.removeAll(tokenBalanceAssets.keySet());
+ Map updatedAssets = new HashMap<>();
+ // detect asset removal, first find new assets
+ HashSet changedAssetList = new HashSet<>(tokenBalanceAssets.keySet());
+ changedAssetList.removeAll(oldAssetList.keySet());
+
+ HashSet unchangedAssets = new HashSet<>(tokenBalanceAssets.keySet());
+ unchangedAssets.removeAll(changedAssetList);
- List changeList = new ArrayList<>(oldAssetIdList);
+ // removed assets
+ HashSet removedAssets = new HashSet<>(oldAssetList.keySet());
+ removedAssets.removeAll(tokenBalanceAssets.keySet());
+ changedAssetList.addAll(removedAssets);
+ HashSet balanceAssets = new HashSet<>();
+
+ final Web3j web3j = TokenRepository.getWeb3jService(tokenInfo.chainId);
+
+ try
+ {
+ balanceAssets = checkBalances(web3j, changedAssetList);
+ }
+ catch (Exception e)
+ {
+ //
+ }
//Now detect differences or new tokens
- for (BigInteger tokenId : tokenBalanceAssets.keySet())
+ for (BigInteger tokenId : changedAssetList)
{
- NFTAsset newAsset = tokenBalanceAssets.get(tokenId);
- NFTAsset oldAsset = assetMap.get(tokenId);
+ NFTAsset asset = tokenBalanceAssets.get(tokenId);
+ if (asset == null) asset = oldAssetList.get(tokenId);
- if (oldAsset == null || newAsset.hashCode() != oldAsset.hashCode())
+ if (asset == null)
{
- changeList.add(tokenId);
+ continue;
}
- }
- return changeList;
- }
+ if (balanceAssets.contains(tokenId))
+ {
+ asset.setBalance(BigDecimal.ZERO);
+ }
+ else
+ {
+ asset.setBalance(BigDecimal.ONE);
+ }
- private String callSmartContractFunction(Web3j web3j,
- Function function, String contractAddress, String walletAddr)
- {
- String encodedFunction = FunctionEncoder.encode(function);
+ updatedAssets.put(tokenId, asset);
+ }
- try
+ for (BigInteger tokenId : unchangedAssets)
{
- org.web3j.protocol.core.methods.request.Transaction transaction
- = createEthCallTransaction(walletAddr, contractAddress, encodedFunction);
- EthCall response = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
-
- List responseValues = FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());
-
- if (!responseValues.isEmpty())
+ NFTAsset asset = tokenBalanceAssets.get(tokenId);
+ if (asset != null)
{
- return responseValues.get(0).getValue().toString();
+ updatedAssets.put(tokenId, asset);
}
}
- catch (Exception e)
- {
- //
- }
- return null;
+ return updatedAssets;
+ }
+
+ private Request, EthCall> getContractCall(Web3j web3j, Function function, String contractAddress)
+ {
+ String encodedFunction = FunctionEncoder.encode(function);
+ org.web3j.protocol.core.methods.request.Transaction transaction
+ = createEthCallTransaction(getWallet(), contractAddress, encodedFunction);
+ return web3j.ethCall(transaction, DefaultBlockParameterName.LATEST);
}
private static Function ownerOf(BigInteger token)
@@ -700,6 +945,13 @@ private static Function ownerOf(BigInteger token)
}));
}
+ private Function tokenOfOwnerByIndex(BigInteger index)
+ {
+ return new Function("tokenOfOwnerByIndex",
+ Arrays.asList(new Address(getWallet()), new Uint256(index)),
+ Collections.singletonList(new TypeReference() {}));
+ }
+
@Override
public List getStandardFunctions()
{
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/Ticket.java b/app/src/main/java/com/alphawallet/app/entity/tokens/Ticket.java
index e909b34178..44e5b85115 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/Ticket.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/Ticket.java
@@ -10,7 +10,6 @@
import com.alphawallet.app.entity.tokendata.TokenGroup;
import com.alphawallet.app.repository.EventResult;
import com.alphawallet.app.repository.entity.RealmToken;
-import com.alphawallet.app.service.AssetDefinitionService;
import com.alphawallet.app.util.Utils;
import com.alphawallet.app.viewmodel.BaseViewModel;
import com.alphawallet.token.entity.TicketRange;
@@ -27,31 +26,34 @@
import java.util.List;
/**
- * Created by James on 27/01/2018. It might seem counter intuitive
- * but here Ticket refers to a container of an asset class here, not
- * the right to seat somewhere in the venue. Therefore, there
- * shouldn't be List To understand this, imagine that one says
- * "I have two cryptocurrencies: Ether and Bitcoin, each amounts to a
- * hundred", and he pauses and said, "I also have two indices: FIFA
- * and Formuler-one, which, too, amounts to a hundred each".
+ * Created by James on 27/01/2018.
*/
public class Ticket extends Token
{
private final List balanceArray;
- private boolean isMatchedInXML = false;
- public Ticket(TokenInfo tokenInfo, List balances, long blancaTime, String networkName, ContractType type) {
+ public Ticket(TokenInfo tokenInfo, List balances, long blancaTime, String networkName, ContractType type)
+ {
super(tokenInfo, BigDecimal.ZERO, blancaTime, networkName, type);
this.balanceArray = balances;
- balance = balanceArray != null ? BigDecimal.valueOf(balanceArray.size()) : BigDecimal.ZERO;
+ balance = balanceArray != null ? BigDecimal.valueOf(getNonZeroArrayBalance().size()) : BigDecimal.ZERO;
group = TokenGroup.NFT;
}
- public Ticket(TokenInfo tokenInfo, String balances, long blancaTime, String networkName, ContractType type) {
+ public Ticket(TokenInfo tokenInfo, String balances, long blancaTime, String networkName, ContractType type)
+ {
super(tokenInfo, BigDecimal.ZERO, blancaTime, networkName, type);
this.balanceArray = stringHexToBigIntegerList(balances);
- balance = BigDecimal.valueOf(balanceArray.size());
+ balance = BigDecimal.valueOf(getNonZeroArrayBalance().size());
+ group = TokenGroup.NFT;
+ }
+
+ public Ticket(Token oldTicket, List balances)
+ {
+ super(oldTicket.tokenInfo, BigDecimal.ZERO, oldTicket.updateBlancaTime, oldTicket.getNetworkName(), oldTicket.contractType);
+ this.balanceArray = balances;
+ balance = BigDecimal.valueOf(getNonZeroArrayBalance().size());
group = TokenGroup.NFT;
}
@@ -235,11 +237,6 @@ private List tokenIdsToTokenIndices(List tokenIds)
return indexList;
}
- public void checkIsMatchedInXML(AssetDefinitionService assetService)
- {
- isMatchedInXML = assetService.hasDefinition(tokenInfo.chainId, tokenInfo.address);
- }
-
@Override
public Function getTransferFunction(String to, List tokenIndices) throws NumberFormatException
{
@@ -305,7 +302,10 @@ public boolean hasArrayBalance()
public List getNonZeroArrayBalance()
{
List nonZeroValues = new ArrayList<>();
- for (BigInteger value : balanceArray) if (value.compareTo(BigInteger.ZERO) != 0 && !nonZeroValues.contains(value)) nonZeroValues.add(value);
+ for (BigInteger value : balanceArray)
+ {
+ if (value.compareTo(BigInteger.ZERO) != 0) nonZeroValues.add(value);
+ }
return nonZeroValues;
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java
index ba8c2e4acd..0108c12332 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java
@@ -49,6 +49,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -202,6 +203,18 @@ public String getFullName(AssetDefinitionService assetDefinition, int count)
}
}
+ public String getTokenSymbol(Token token){
+
+ if (!TextUtils.isEmpty(token.tokenInfo.symbol) && token.tokenInfo.symbol.length() > 1)
+ {
+ return Utils.getIconisedText(token.tokenInfo.symbol);
+ }
+ else
+ {
+ return Utils.getIconisedText(token.getName());
+ }
+ }
+
public String getTSName(AssetDefinitionService assetDefinition, int count) {
String name = assetDefinition != null ? assetDefinition.getTokenName(tokenInfo.chainId, tokenInfo.address, count) : null;
if (name != null) {
@@ -370,12 +383,12 @@ public void setIsEthereum()
public boolean isBad()
{
- return tokenInfo == null || (tokenInfo.symbol == null && tokenInfo.name == null);
+ return tokenInfo == null || tokenInfo.chainId == 0 || (tokenInfo.symbol == null && tokenInfo.name == null);
}
public void setTokenWallet(String address)
{
- this.tokenWallet = address;
+ this.tokenWallet = address.toLowerCase(Locale.ROOT);
}
public void setupRealmToken(RealmToken realmToken)
@@ -698,11 +711,6 @@ public int hashCode()
return hash;
}
- public boolean checkBalanceType()
- {
- return true;
- }
-
public String getTransactionDetail(Context ctx, Transaction tx, TokensService tService)
{
if (isEthereum())
@@ -835,6 +843,7 @@ public boolean needsTransactionCheck()
case OTHER:
case NOT_SET:
case ERC721:
+ case ERC721_ENUMERABLE:
case ERC721_LEGACY:
case ERC721_UNDETERMINED:
case CREATION:
@@ -908,11 +917,6 @@ public boolean mayRequireRefresh()
|| (!TextUtils.isEmpty(tokenInfo.symbol) && tokenInfo.symbol.contains("?"));
}
- public List getChangeList(Map assetMap)
- {
- return new ArrayList<>();
- }
-
public void setAssetContract(AssetContract contract) { }
public AssetContract getAssetContract() { return null; }
@@ -931,6 +935,11 @@ public Map queryAssets(Map assetMap)
return assetMap;
}
+ public Map getAssetChange(Map oldAssetList)
+ {
+ return oldAssetList;
+ }
+
/**
* Token Metadata handling
*/
@@ -1005,4 +1014,4 @@ public HashSet processLogsAndStoreTransferEvents(EthLog receiveLogs,
{
return null;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenCardMeta.java b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenCardMeta.java
index da4baba593..099aeaa42a 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenCardMeta.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenCardMeta.java
@@ -8,7 +8,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.format.DateUtils;
-import android.util.Pair;
import androidx.annotation.NonNull;
@@ -18,11 +17,6 @@
import com.alphawallet.app.repository.EthereumNetworkRepository;
import com.alphawallet.app.repository.TokensRealmSource;
import com.alphawallet.app.service.AssetDefinitionService;
-import com.alphawallet.app.service.TokensService;
-import com.alphawallet.app.ui.widget.holder.TokenHolder;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
/**
* Created by JB on 12/07/2020.
@@ -261,10 +255,11 @@ public float calculateBalanceUpdateWeight()
{
float updateWeight = 0;
//calculate balance update time
+ long timeDiff = (System.currentTimeMillis() - lastUpdate) / DateUtils.SECOND_IN_MILLIS;
+
if (isEthereum())
{
- long currentTime = System.currentTimeMillis();
- if (lastUpdate < currentTime - 30 * DateUtils.SECOND_IN_MILLIS)
+ if (timeDiff > 30)
{
updateWeight = 2.0f;
}
@@ -278,15 +273,15 @@ else if (hasValidName())
if (isNFT())
{
//ERC721 types which usually get their balance from opensea. Still need to check the balance for stale tokens to spot a positive -> zero balance transition
- updateWeight = 0.25f;
+ updateWeight = (timeDiff > 30) ? 0.25f : 0.01f;
}
else if (isEnabled)
{
- updateWeight = hasPositiveBalance() ? 1.0f : 0.5f; //30 seconds
+ updateWeight = hasPositiveBalance() ? 1.0f : 0.1f; //30 seconds
}
else
{
- updateWeight = 0.25f; //1 minute
+ updateWeight = 0.1f; //1 minute
}
}
return updateWeight;
@@ -307,4 +302,12 @@ public boolean equals(TokenCardMeta other)
{
return (tokenId.equalsIgnoreCase(other.tokenId));
}
+
+ //30 seconds for enabled with balance, 120 seconds for enabled zero balance, 300 for not enabled
+ //Note that if we pick up a balance change for non-enabled token that hasn't been locked out, it'll appear with the transfer sweep
+ public long calculateUpdateFrequency()
+ {
+ long timeInSeconds = isEnabled ? (hasPositiveBalance() ? 30 : 120) : 300;
+ return timeInSeconds * DateUtils.SECOND_IN_MILLIS;
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java
index b367ac04f5..c5b8843e8f 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java
@@ -34,6 +34,7 @@ public Token createToken(TokenInfo tokenInfo, BigDecimal balance, List STL Office Token" +
+ " STL Office Tokens Boleto de admisión" +
+ " Boleto de admisiónes 入場券" +
+ " 入場券 " +
+ " 0xC3eeCa3Feb9Dbc06c6e749702AcB8d56A07BFb05 " +
+ " Unlock 开锁" +
+ " Abrir " +
+ " " +
+ " Lock 关锁" +
+ " Cerrar " +
+ " Mint Ape 1 " +
+ " " +
+ " Mint Ape 2 " +
+ " " +
+ " Mint STL Token" +
+ " " +
+ " " +
+ " ";
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenScriptFile.java b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenScriptFile.java
index 0dac3b5df9..a7a9689839 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenScriptFile.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenScriptFile.java
@@ -125,6 +125,10 @@ public String calcMD5()
String rand = String.valueOf(new Random(System.currentTimeMillis()).nextInt());
sb.append(rand); //never matches
}
+ catch (Exception e)
+ {
+ Timber.w(e);
+ }
//return complete hash
return sb.toString();
diff --git a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java
index 6c2f43a6fc..9d1d42e18f 100644
--- a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java
+++ b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java
@@ -1,8 +1,10 @@
package com.alphawallet.app.entity.tokenscript;
+import static com.alphawallet.app.repository.TokenRepository.getWeb3jService;
+import static org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction;
+
import android.text.TextUtils;
-import com.alphawallet.app.BuildConfig;
import com.alphawallet.app.entity.tokens.Token;
import com.alphawallet.app.repository.TokenRepository;
import com.alphawallet.app.util.BalanceUtils;
@@ -11,12 +13,12 @@
import com.alphawallet.token.entity.Attribute;
import com.alphawallet.token.entity.AttributeInterface;
import com.alphawallet.token.entity.ContractAddress;
-import com.alphawallet.token.entity.EventDefinition;
import com.alphawallet.token.entity.FunctionDefinition;
import com.alphawallet.token.entity.MethodArg;
import com.alphawallet.token.entity.TokenScriptResult;
import com.alphawallet.token.entity.TokenscriptElement;
import com.alphawallet.token.entity.TransactionResult;
+import com.alphawallet.token.entity.ViewType;
import com.alphawallet.token.tools.TokenDefinition;
import org.web3j.abi.FunctionEncoder;
@@ -29,7 +31,102 @@
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Uint;
import org.web3j.abi.datatypes.Utf8String;
-import org.web3j.abi.datatypes.generated.*;
+import org.web3j.abi.datatypes.generated.Bytes1;
+import org.web3j.abi.datatypes.generated.Bytes10;
+import org.web3j.abi.datatypes.generated.Bytes11;
+import org.web3j.abi.datatypes.generated.Bytes12;
+import org.web3j.abi.datatypes.generated.Bytes13;
+import org.web3j.abi.datatypes.generated.Bytes14;
+import org.web3j.abi.datatypes.generated.Bytes15;
+import org.web3j.abi.datatypes.generated.Bytes16;
+import org.web3j.abi.datatypes.generated.Bytes17;
+import org.web3j.abi.datatypes.generated.Bytes18;
+import org.web3j.abi.datatypes.generated.Bytes19;
+import org.web3j.abi.datatypes.generated.Bytes2;
+import org.web3j.abi.datatypes.generated.Bytes20;
+import org.web3j.abi.datatypes.generated.Bytes21;
+import org.web3j.abi.datatypes.generated.Bytes22;
+import org.web3j.abi.datatypes.generated.Bytes23;
+import org.web3j.abi.datatypes.generated.Bytes24;
+import org.web3j.abi.datatypes.generated.Bytes25;
+import org.web3j.abi.datatypes.generated.Bytes26;
+import org.web3j.abi.datatypes.generated.Bytes27;
+import org.web3j.abi.datatypes.generated.Bytes28;
+import org.web3j.abi.datatypes.generated.Bytes29;
+import org.web3j.abi.datatypes.generated.Bytes3;
+import org.web3j.abi.datatypes.generated.Bytes30;
+import org.web3j.abi.datatypes.generated.Bytes31;
+import org.web3j.abi.datatypes.generated.Bytes32;
+import org.web3j.abi.datatypes.generated.Bytes4;
+import org.web3j.abi.datatypes.generated.Bytes5;
+import org.web3j.abi.datatypes.generated.Bytes6;
+import org.web3j.abi.datatypes.generated.Bytes7;
+import org.web3j.abi.datatypes.generated.Bytes8;
+import org.web3j.abi.datatypes.generated.Bytes9;
+import org.web3j.abi.datatypes.generated.Int104;
+import org.web3j.abi.datatypes.generated.Int112;
+import org.web3j.abi.datatypes.generated.Int120;
+import org.web3j.abi.datatypes.generated.Int128;
+import org.web3j.abi.datatypes.generated.Int136;
+import org.web3j.abi.datatypes.generated.Int144;
+import org.web3j.abi.datatypes.generated.Int152;
+import org.web3j.abi.datatypes.generated.Int16;
+import org.web3j.abi.datatypes.generated.Int160;
+import org.web3j.abi.datatypes.generated.Int168;
+import org.web3j.abi.datatypes.generated.Int176;
+import org.web3j.abi.datatypes.generated.Int184;
+import org.web3j.abi.datatypes.generated.Int192;
+import org.web3j.abi.datatypes.generated.Int200;
+import org.web3j.abi.datatypes.generated.Int208;
+import org.web3j.abi.datatypes.generated.Int216;
+import org.web3j.abi.datatypes.generated.Int224;
+import org.web3j.abi.datatypes.generated.Int232;
+import org.web3j.abi.datatypes.generated.Int24;
+import org.web3j.abi.datatypes.generated.Int240;
+import org.web3j.abi.datatypes.generated.Int248;
+import org.web3j.abi.datatypes.generated.Int256;
+import org.web3j.abi.datatypes.generated.Int32;
+import org.web3j.abi.datatypes.generated.Int40;
+import org.web3j.abi.datatypes.generated.Int48;
+import org.web3j.abi.datatypes.generated.Int56;
+import org.web3j.abi.datatypes.generated.Int64;
+import org.web3j.abi.datatypes.generated.Int72;
+import org.web3j.abi.datatypes.generated.Int8;
+import org.web3j.abi.datatypes.generated.Int80;
+import org.web3j.abi.datatypes.generated.Int88;
+import org.web3j.abi.datatypes.generated.Int96;
+import org.web3j.abi.datatypes.generated.Uint104;
+import org.web3j.abi.datatypes.generated.Uint112;
+import org.web3j.abi.datatypes.generated.Uint120;
+import org.web3j.abi.datatypes.generated.Uint128;
+import org.web3j.abi.datatypes.generated.Uint136;
+import org.web3j.abi.datatypes.generated.Uint144;
+import org.web3j.abi.datatypes.generated.Uint152;
+import org.web3j.abi.datatypes.generated.Uint16;
+import org.web3j.abi.datatypes.generated.Uint160;
+import org.web3j.abi.datatypes.generated.Uint168;
+import org.web3j.abi.datatypes.generated.Uint176;
+import org.web3j.abi.datatypes.generated.Uint184;
+import org.web3j.abi.datatypes.generated.Uint192;
+import org.web3j.abi.datatypes.generated.Uint200;
+import org.web3j.abi.datatypes.generated.Uint208;
+import org.web3j.abi.datatypes.generated.Uint216;
+import org.web3j.abi.datatypes.generated.Uint224;
+import org.web3j.abi.datatypes.generated.Uint232;
+import org.web3j.abi.datatypes.generated.Uint24;
+import org.web3j.abi.datatypes.generated.Uint240;
+import org.web3j.abi.datatypes.generated.Uint248;
+import org.web3j.abi.datatypes.generated.Uint256;
+import org.web3j.abi.datatypes.generated.Uint32;
+import org.web3j.abi.datatypes.generated.Uint40;
+import org.web3j.abi.datatypes.generated.Uint48;
+import org.web3j.abi.datatypes.generated.Uint56;
+import org.web3j.abi.datatypes.generated.Uint64;
+import org.web3j.abi.datatypes.generated.Uint72;
+import org.web3j.abi.datatypes.generated.Uint8;
+import org.web3j.abi.datatypes.generated.Uint80;
+import org.web3j.abi.datatypes.generated.Uint88;
+import org.web3j.abi.datatypes.generated.Uint96;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.EthFilter;
@@ -50,12 +147,9 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import io.reactivex.Observable;
+import io.reactivex.Single;
import timber.log.Timber;
-import static com.alphawallet.app.repository.TokenRepository.getWeb3jService;
-import static org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction;
-
/**
* Created by James on 13/06/2019.
* Stormbird in Sydney
@@ -73,14 +167,14 @@ public Function generateTransactionFunction(Token token, BigInteger tokenId, Tok
//pre-parse tokenId.
if (tokenId.bitCount() > 256) tokenId = tokenId.or(BigInteger.ONE.shiftLeft(256).subtract(BigInteger.ONE)); //truncate tokenId too large
- List params = new ArrayList();
- List> returnTypes = new ArrayList>();
+ List params = new ArrayList<>();
+ List> returnTypes = new ArrayList<>();
for (MethodArg arg : function.parameters)
{
String value = resolveReference(token, arg.element, tokenId, definition, attrIf);
//get arg.element.value in the form of BigInteger if appropriate
- byte[] argValueBytes = null;
- BigInteger argValueBI = null;
+ byte[] argValueBytes = {0};
+ BigInteger argValueBI = BigInteger.ZERO;
if (valueNotFound)
{
@@ -432,7 +526,7 @@ public Function generateTransactionFunction(Token token, BigInteger tokenId, Tok
}
break;
default:
- Timber.d("NOT IMPLEMENTED: " + arg.parameterType);
+ Timber.d("NOT IMPLEMENTED: %s", arg.parameterType);
break;
}
}
@@ -528,7 +622,7 @@ private String handleTransactionResult(TransactionResult result, Function functi
{
case Boolean:
value = Numeric.toBigInt(hexBytes);
- transResult = value.equals(BigDecimal.ZERO) ? "FALSE" : "TRUE";
+ transResult = value.equals(BigInteger.ZERO) ? "FALSE" : "TRUE";
break;
case Integer:
value = Numeric.toBigInt(hexBytes);
@@ -590,7 +684,7 @@ else if (val.getTypeAsString().equals("address"))
return transResult;
}
- private String checkBytesString(String responseValue) throws Exception
+ private String checkBytesString(String responseValue)
{
String name = "";
if (responseValue.length() > 0)
@@ -618,7 +712,7 @@ private String checkBytesString(String responseValue) throws Exception
public TokenScriptResult.Attribute parseFunctionResult(TransactionResult transactionResult, Attribute attr)
{
String res = attr.getSyntaxVal(transactionResult.result);
- BigInteger val = transactionResult.tokenId; //?
+ BigInteger val = transactionResult.tokenId;
if (attr.syntax == TokenDefinition.Syntax.Boolean)
{
@@ -644,7 +738,7 @@ else if (transactionResult.result.startsWith("0x"))
val = BigInteger.ZERO;
}
}
- return new TokenScriptResult.Attribute(attr.name, attr.label, val, res);
+ return new TokenScriptResult.Attribute(attr, val, res);
}
public static final String ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
@@ -656,10 +750,10 @@ else if (transactionResult.result.startsWith("0x"))
* @param definition
* @return
*/
- public Observable fetchResultFromEthereum(Token token, ContractAddress contractAddress, Attribute attr,
+ public Single fetchResultFromEthereum(Token token, ContractAddress contractAddress, Attribute attr,
BigInteger tokenId, TokenDefinition definition, AttributeInterface attrIf)
{
- return Observable.fromCallable(() -> {
+ return Single.fromCallable(() -> {
TransactionResult transactionResult = new TransactionResult(contractAddress.chainId, contractAddress.address, tokenId, attr);
Function transaction = generateTransactionFunction(token, tokenId, definition, attr.function, attrIf);
@@ -672,7 +766,7 @@ public Observable fetchResultFromEthereum(Token token, Contra
else
{
//now push the transaction
- result = callSmartContractFunction(TokenRepository.getWeb3jService(contractAddress.chainId), transaction, contractAddress.address, ZERO_ADDRESS);
+ result = callSmartContractFunction(TokenRepository.getWeb3jService(contractAddress.chainId), transaction, contractAddress.address, token.getWallet());
}
transactionResult.result = handleTransactionResult(transactionResult, transaction, result, attr, System.currentTimeMillis());
@@ -753,7 +847,7 @@ else if (!TextUtils.isEmpty(element.value))
}
else
{
- return fetchAttrResult(token, attr, tokenId, definition, attrIf, false).blockingSingle().text;
+ return fetchAttrResult(token, attr, tokenId, definition, attrIf, ViewType.VIEW).blockingGet().text;
}
return null;
@@ -781,28 +875,29 @@ else if (!TextUtils.isEmpty(element.value))
* @return
*/
- public Observable fetchAttrResult(Token token, Attribute attr, BigInteger tokenId,
- TokenDefinition td, AttributeInterface attrIf, boolean itemView)
+ public Single fetchAttrResult(Token token, Attribute attr, BigInteger tokenId,
+ TokenDefinition td, AttributeInterface attrIf, ViewType itemView)
{
+ final BigInteger useTokenId = (attr == null || !attr.usesTokenId()) ? BigInteger.ZERO : tokenId;
if (attr == null)
{
- return Observable.fromCallable(() -> new TokenScriptResult.Attribute("bd", "bd", BigInteger.ZERO, ""));
+ return Single.fromCallable(() -> new TokenScriptResult.Attribute("bd", "bd", BigInteger.ZERO, ""));
}
- else if (token.getAttributeResult(attr.name, tokenId) != null)
+ else if (token.getAttributeResult(attr.name, useTokenId) != null)
{
- return Observable.fromCallable(() -> token.getAttributeResult(attr.name, tokenId));
+ return Single.fromCallable(() -> token.getAttributeResult(attr.name, useTokenId));
}
else if (attr.event != null)
{
//retrieve events from DB
ContractAddress useAddress = new ContractAddress(attr.event.contract.addresses.keySet().iterator().next(),
attr.event.contract.addresses.values().iterator().next().get(0));
- TransactionResult cachedResult = attrIf.getFunctionResult(useAddress, attr, tokenId); //Needs to allow for multiple tokenIds
+ TransactionResult cachedResult = attrIf.getFunctionResult(useAddress, attr, useTokenId); //Needs to allow for multiple tokenIds
//if the latest event result is not yet found, then find it here
if (TextUtils.isEmpty(cachedResult.result))
{
//try to fetch latest event result - this can happen at startup
- return getEventResult(cachedResult, attr, tokenId, attrIf);
+ return getEventResult(cachedResult, attr, useTokenId, attrIf);
}
else
{
@@ -811,22 +906,22 @@ else if (attr.event != null)
}
else if (attr.function == null) // static attribute from tokenId (eg city mapping from tokenId)
{
- return staticAttribute(attr, tokenId);
+ return staticAttribute(attr, useTokenId);
}
else
{
ContractAddress useAddress = new ContractAddress(attr.function); //always use the function attribute's address
long lastTxUpdate = attrIf.getLastTokenUpdate(useAddress.chainId, useAddress.address);
- TransactionResult cachedResult = attrIf.getFunctionResult(useAddress, attr, tokenId); //Needs to allow for multiple tokenIds
- if ((itemView || (!attr.isVolatile() && ((attrIf.resolveOptimisedAttr(useAddress, attr, cachedResult) || !cachedResult.needsUpdating(lastTxUpdate)))))) //can we use wallet's known data or cached value?
+ TransactionResult cachedResult = attrIf.getFunctionResult(useAddress, attr, useTokenId); //Needs to allow for multiple tokenIds
+ if ((itemView == ViewType.ITEM_VIEW || (!attr.isVolatile() && ((attrIf.resolveOptimisedAttr(useAddress, attr, cachedResult) || !cachedResult.needsUpdating(lastTxUpdate)))))) //can we use wallet's known data or cached value?
{
return resultFromDatabase(cachedResult, attr);
}
else //if cached value is invalid or if value is dynamic
{
final String walletAddress = attrIf.getWalletAddr();
- return fetchResultFromEthereum(token, useAddress, attr, tokenId, td, attrIf) // Fetch function result from blockchain
- .map(transactionResult -> addParseResultIfValid(token, tokenId, attr, transactionResult))// only cache live transaction result
+ return fetchResultFromEthereum(token, useAddress, attr, useTokenId, td, attrIf) // Fetch function result from blockchain
+ .map(transactionResult -> addParseResultIfValid(token, useTokenId, attr, transactionResult))// only cache live transaction result
.map(result -> restoreFromDBIfRequired(result, cachedResult)) // If network unavailable restore value from cache
.map(txResult -> attrIf.storeAuxData(walletAddress, txResult)) // store new data
.map(result -> parseFunctionResult(result, attr)); // write returned data into attribute
@@ -834,10 +929,10 @@ else if (attr.function == null) // static attribute from tokenId (eg city mappi
}
}
- private Observable getEventResult(TransactionResult txResult, Attribute attr, BigInteger tokenId, AttributeInterface attrIf)
+ private Single getEventResult(TransactionResult txResult, Attribute attr, BigInteger tokenId, AttributeInterface attrIf)
{
//fetch the function
- return Observable.fromCallable(() -> {
+ return Single.fromCallable(() -> {
String walletAddress = attrIf.getWalletAddr();
Web3j web3j = getWeb3jService(attr.event.getEventChainId());
List tokenIds = new ArrayList<>(Collections.singletonList(tokenId));
@@ -846,7 +941,7 @@ private Observable getEventResult(TransactionResult
//use last received log
if (ethLogs.getLogs().size() > 0)
{
- EthLog.LogResult ethLog = ethLogs.getLogs().get(ethLogs.getLogs().size() - 1);
+ EthLog.LogResult> ethLog = ethLogs.getLogs().get(ethLogs.getLogs().size() - 1);
String selectVal = EventUtils.getSelectVal(attr.event, ethLog);
txResult.result = attr.getSyntaxVal(selectVal);
txResult.resultTime = ((Log)ethLog.get()).getBlockNumber().longValue();
@@ -857,9 +952,9 @@ private Observable getEventResult(TransactionResult
});
}
- private Observable staticAttribute(Attribute attr, BigInteger tokenId)
+ private Single staticAttribute(Attribute attr, BigInteger tokenId)
{
- return Observable.fromCallable(() -> {
+ return Single.fromCallable(() -> {
try
{
if (attr.userInput)
@@ -870,19 +965,19 @@ private Observable staticAttribute(Attribute attr,
{
BigInteger val = tokenId.and(attr.bitmask).shiftRight(attr.bitshift);
Timber.d("ATTR: " + attr.label + " : " + attr.name + " : " + attr.getSyntaxVal(attr.toString(val)));
- return new TokenScriptResult.Attribute(attr.name, attr.label, val, attr.getSyntaxVal(attr.toString(val)));
+ return new TokenScriptResult.Attribute(attr, val, attr.getSyntaxVal(attr.toString(val)));
}
}
catch (Exception e)
{
- return new TokenScriptResult.Attribute(attr.name, attr.label, tokenId, "unsupported encoding");
+ return new TokenScriptResult.Attribute(attr, tokenId, "unsupported encoding");
}
});
}
- private Observable resultFromDatabase(TransactionResult transactionResult, Attribute attr)
+ private Single resultFromDatabase(TransactionResult transactionResult, Attribute attr)
{
- return Observable.fromCallable(() -> parseFunctionResult(transactionResult, attr));
+ return Single.fromCallable(() -> parseFunctionResult(transactionResult, attr));
}
/**
diff --git a/app/src/main/java/com/alphawallet/app/entity/unstoppable/GetRecordsResult.java b/app/src/main/java/com/alphawallet/app/entity/unstoppable/GetRecordsResult.java
new file mode 100644
index 0000000000..ebc5ccb485
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/unstoppable/GetRecordsResult.java
@@ -0,0 +1,17 @@
+package com.alphawallet.app.entity.unstoppable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.HashMap;
+
+public class GetRecordsResult
+{
+ @SerializedName("meta")
+ @Expose
+ public Meta meta;
+
+ @SerializedName("records")
+ @Expose
+ public HashMap records;
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/unstoppable/Meta.java b/app/src/main/java/com/alphawallet/app/entity/unstoppable/Meta.java
new file mode 100644
index 0000000000..29104c96ea
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/unstoppable/Meta.java
@@ -0,0 +1,35 @@
+package com.alphawallet.app.entity.unstoppable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class Meta
+{
+ @SerializedName("resolver")
+ @Expose
+ public String resolver;
+
+ @SerializedName("blockchain")
+ @Expose
+ public String blockchain;
+
+ @SerializedName("networkId")
+ @Expose
+ public long networkId;
+
+ @SerializedName("registry")
+ @Expose
+ public String registry;
+
+ @SerializedName("domain")
+ @Expose
+ public String domain;
+
+ @SerializedName("owner")
+ @Expose
+ public String owner;
+
+ @SerializedName("reverse")
+ @Expose
+ public boolean reverse;
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/walletconnect/NamespaceParser.java b/app/src/main/java/com/alphawallet/app/entity/walletconnect/NamespaceParser.java
new file mode 100644
index 0000000000..f80c6f2b4a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/walletconnect/NamespaceParser.java
@@ -0,0 +1,72 @@
+package com.alphawallet.app.entity.walletconnect;
+
+import com.walletconnect.sign.client.Sign;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class NamespaceParser
+{
+ private final List chains = new ArrayList<>();
+ private final List methods = new ArrayList<>();
+ private final List events = new ArrayList<>();
+ private final List wallets = new ArrayList<>();
+
+ public void parseProposal(Map requiredNamespaces)
+ {
+ for (Map.Entry entry : requiredNamespaces.entrySet())
+ {
+ chains.addAll(entry.getValue().getChains());
+ methods.addAll(entry.getValue().getMethods());
+ events.addAll(entry.getValue().getEvents());
+ }
+ }
+
+ public void parseSession(Map namespaces)
+ {
+ for (Map.Entry entry : namespaces.entrySet())
+ {
+ chains.addAll(parseChains(entry.getValue().getAccounts()));
+ methods.addAll(entry.getValue().getMethods());
+ events.addAll(entry.getValue().getEvents());
+ wallets.addAll(parseWallets(entry.getValue().getAccounts()));
+ }
+ }
+
+ private List parseWallets(List accounts)
+ {
+ return accounts.stream()
+ .map((account) -> account.substring(account.lastIndexOf(":") + 1))
+ .collect(Collectors.toList());
+ }
+
+ private List parseChains(List accounts)
+ {
+ return accounts.stream()
+ .map((account) -> account.substring(0, account.lastIndexOf(":")))
+ .collect(Collectors.toList());
+ }
+
+ public List getChains()
+ {
+ return chains;
+ }
+
+ public List getMethods()
+ {
+ return methods;
+ }
+
+ public List getEvents()
+ {
+ return events;
+ }
+
+ public List getWallets()
+ {
+ return new ArrayList<>(new HashSet<>(wallets));
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectSessionItem.java b/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectSessionItem.java
index b5df80e809..8e0e48c195 100644
--- a/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectSessionItem.java
+++ b/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectSessionItem.java
@@ -7,20 +7,28 @@
*/
public class WalletConnectSessionItem
{
- public final String name;
- public final String url;
- public final String icon;
- public final String sessionId;
- public final String localSessionId;
- public final long chainId;
+ public String name = "";
+ public String url = "";
+ public String icon = "";
+ public String sessionId;
+ public String localSessionId;
+ public long chainId;
public WalletConnectSessionItem(RealmWCSession s)
{
- name = s.getRemotePeerData().getName();
- url = s.getRemotePeerData().getUrl();
- icon = s.getRemotePeerData().getIcons().size() > 0 ? s.getRemotePeerData().getIcons().get(0) : null;
+ if (s.getRemotePeerData() != null)
+ {
+ name = s.getRemotePeerData().getName();
+ url = s.getRemotePeerData().getUrl();
+ icon = s.getRemotePeerData().getIcons().size() > 0 ? s.getRemotePeerData().getIcons().get(0) : null;
+ }
sessionId = s.getSession().getTopic();
localSessionId = s.getSessionId();
chainId = s.getChainId() == 0 ? 1 : s.getChainId(); //older sessions without chainId set must be mainnet
}
-}
\ No newline at end of file
+
+ public WalletConnectSessionItem()
+ {
+
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectV2SessionItem.java b/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectV2SessionItem.java
new file mode 100644
index 0000000000..f02562247a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/entity/walletconnect/WalletConnectV2SessionItem.java
@@ -0,0 +1,100 @@
+package com.alphawallet.app.entity.walletconnect;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.walletconnect.sign.client.Sign;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class WalletConnectV2SessionItem extends WalletConnectSessionItem implements Parcelable
+{
+ public boolean settled;
+ public List chains = new ArrayList<>();
+ public List wallets = new ArrayList<>();
+ public List methods = new ArrayList<>();
+
+ public WalletConnectV2SessionItem(Sign.Model.Session s)
+ {
+ super();
+ name = Objects.requireNonNull(s.getMetaData()).getName();
+ url = Objects.requireNonNull(s.getMetaData()).getUrl();
+ icon = s.getMetaData().getIcons().isEmpty() ? null : s.getMetaData().getIcons().get(0);
+ sessionId = s.getTopic();
+ localSessionId = s.getTopic();
+ settled = true;
+ NamespaceParser namespaceParser = new NamespaceParser();
+ namespaceParser.parseSession(s.getNamespaces());
+ chains = namespaceParser.getChains();
+ methods = namespaceParser.getMethods();
+ wallets = namespaceParser.getWallets();
+ }
+
+ public WalletConnectV2SessionItem(Parcel in)
+ {
+ name = in.readString();
+ url = in.readString();
+ icon = in.readString();
+ sessionId = in.readString();
+ localSessionId = in.readString();
+ settled = in.readInt() == 1;
+ in.readStringList(chains);
+ in.readStringList(wallets);
+ in.readStringList(methods);
+ }
+
+ public WalletConnectV2SessionItem()
+ {
+ }
+
+ public static WalletConnectV2SessionItem from(Sign.Model.SessionProposal sessionProposal)
+ {
+ WalletConnectV2SessionItem item = new WalletConnectV2SessionItem();
+ item.name = sessionProposal.getName();
+ item.url = sessionProposal.getUrl();
+ item.icon = sessionProposal.getIcons().isEmpty() ? null : sessionProposal.getIcons().get(0).toString();
+ item.sessionId = sessionProposal.getProposerPublicKey();
+ item.settled = false;
+ NamespaceParser namespaceParser = new NamespaceParser();
+ namespaceParser.parseProposal(sessionProposal.getRequiredNamespaces());
+ item.chains.addAll(namespaceParser.getChains());
+ item.methods.addAll(namespaceParser.getMethods());
+ return item;
+ }
+
+ @Override
+ public int describeContents()
+ {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags)
+ {
+ dest.writeString(name);
+ dest.writeString(url);
+ dest.writeString(icon);
+ dest.writeString(sessionId);
+ dest.writeString(localSessionId);
+ dest.writeInt(settled ? 1 : 0);
+ dest.writeStringList(chains);
+ dest.writeStringList(wallets);
+ dest.writeStringList(methods);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>()
+ {
+ public WalletConnectV2SessionItem createFromParcel(Parcel in)
+ {
+ return new WalletConnectV2SessionItem(in);
+ }
+
+ @Override
+ public WalletConnectV2SessionItem[] newArray(int size)
+ {
+ return new WalletConnectV2SessionItem[0];
+ }
+ };
+}
diff --git a/app/src/main/java/com/alphawallet/app/interact/CreateTransactionInteract.java b/app/src/main/java/com/alphawallet/app/interact/CreateTransactionInteract.java
index 99d94e91d8..c24bd876f7 100644
--- a/app/src/main/java/com/alphawallet/app/interact/CreateTransactionInteract.java
+++ b/app/src/main/java/com/alphawallet/app/interact/CreateTransactionInteract.java
@@ -28,15 +28,15 @@ public CreateTransactionInteract(TransactionRepositoryType transactionRepository
this.transactionRepository = transactionRepository;
}
- public Single sign(Wallet wallet, MessagePair messagePair, long chainId)
+ public Single sign(Wallet wallet, MessagePair messagePair)
{
- return transactionRepository.getSignature(wallet, messagePair, chainId)
+ return transactionRepository.getSignature(wallet, messagePair)
.map(sig -> new SignaturePair(messagePair.selection, sig.signature, messagePair.message));
}
- public Single sign(Wallet wallet, Signable message, long chainId)
+ public Single sign(Wallet wallet, Signable message)
{
- return transactionRepository.getSignature(wallet, message, chainId);
+ return transactionRepository.getSignature(wallet, message);
}
public Single create(Wallet from, String to, BigInteger subunitAmount, BigInteger gasPrice, BigInteger gasLimit, byte[] data, long chainId)
@@ -117,4 +117,4 @@ public Single signTransaction(Wallet from, Web3Transaction web3
web3Tx.gasPrice, web3Tx.gasLimit, web3Tx.nonce,
!TextUtils.isEmpty(web3Tx.payload) ? Numeric.hexStringToByteArray(web3Tx.payload) : new byte[0], chainId);
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/alphawallet/app/interact/GenericWalletInteract.java b/app/src/main/java/com/alphawallet/app/interact/GenericWalletInteract.java
index 2be594ab6f..4e40c949bc 100644
--- a/app/src/main/java/com/alphawallet/app/interact/GenericWalletInteract.java
+++ b/app/src/main/java/com/alphawallet/app/interact/GenericWalletInteract.java
@@ -3,9 +3,6 @@
import static com.alphawallet.app.C.ETHER_DECIMALS;
import static com.alphawallet.app.entity.tokens.Token.TOKEN_BALANCE_PRECISION;
-import android.util.Log;
-
-import com.alphawallet.app.BuildConfig;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.repository.WalletItem;
import com.alphawallet.app.repository.WalletRepositoryType;
@@ -33,6 +30,13 @@ public Single find() {
.observeOn(AndroidSchedulers.mainThread());
}
+ public Single findWallet(String account)
+ {
+ return walletRepository.findWallet(account)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread());
+ }
+
/**
* Called when wallet marked as backed up.
* Update the wallet date
diff --git a/app/src/main/java/com/alphawallet/app/interact/ImportWalletInteract.java b/app/src/main/java/com/alphawallet/app/interact/ImportWalletInteract.java
index da4cfc7d58..4fb65a751a 100644
--- a/app/src/main/java/com/alphawallet/app/interact/ImportWalletInteract.java
+++ b/app/src/main/java/com/alphawallet/app/interact/ImportWalletInteract.java
@@ -1,7 +1,7 @@
package com.alphawallet.app.interact;
import com.alphawallet.app.repository.WalletRepositoryType;
-import com.alphawallet.app.util.AWEnsResolver;
+import com.alphawallet.app.util.ens.AWEnsResolver;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
diff --git a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java
new file mode 100644
index 0000000000..01fc497ac1
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java
@@ -0,0 +1,131 @@
+package com.alphawallet.app.interact;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.alphawallet.app.entity.WalletConnectActions;
+import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem;
+import com.alphawallet.app.entity.walletconnect.WalletConnectV2SessionItem;
+import com.alphawallet.app.repository.entity.RealmWCSession;
+import com.alphawallet.app.service.RealmManager;
+import com.alphawallet.app.service.WalletConnectService;
+import com.alphawallet.app.viewmodel.WalletConnectViewModel;
+import com.alphawallet.app.walletconnect.WCClient;
+import com.alphawallet.app.walletconnect.entity.WCUtils;
+import com.walletconnect.sign.client.Sign;
+import com.walletconnect.sign.client.SignClient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import io.realm.Realm;
+import io.realm.RealmResults;
+import io.realm.Sort;
+import timber.log.Timber;
+
+public class WalletConnectInteract
+{
+ private final RealmManager realmManager;
+
+ @Inject
+ public WalletConnectInteract(RealmManager realmManager)
+ {
+ this.realmManager = realmManager;
+ }
+
+ public int getSessionsCount()
+ {
+ return getSessions().size();
+ }
+
+ public List getSessions()
+ {
+ List result = new ArrayList<>();
+ result.addAll(getWalletConnectV1SessionItems());
+ result.addAll(getWalletConnectV2SessionItems());
+ return result;
+ }
+
+ public void fetchSessions(Context context, SessionFetchCallback sessionFetchCallback)
+ {
+ ServiceConnection connection = new ServiceConnection()
+ {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService();
+ fetch(walletConnectService, sessionFetchCallback);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ }
+ };
+
+ WCUtils.startServiceLocal(context, connection, WalletConnectActions.CONNECT);
+ }
+
+ private void fetch(WalletConnectService walletConnectService, SessionFetchCallback sessionFetchCallback)
+ {
+ List result = new ArrayList<>();
+ List sessionItems = getWalletConnectV1SessionItems();
+ for (WalletConnectSessionItem item : sessionItems)
+ {
+ WCClient wcClient = walletConnectService.getClient(item.sessionId);
+ if (wcClient != null && wcClient.isConnected())
+ {
+ result.add(item);
+ }
+ }
+
+ result.addAll(getWalletConnectV2SessionItems());
+ sessionFetchCallback.onFetched(result);
+ }
+
+ private List getWalletConnectV1SessionItems()
+ {
+ List sessions = new ArrayList<>();
+ try (Realm realm = realmManager.getRealmInstance(WalletConnectViewModel.WC_SESSION_DB))
+ {
+ RealmResults items = realm.where(RealmWCSession.class)
+ .sort("lastUsageTime", Sort.DESCENDING)
+ .findAll();
+
+ for (RealmWCSession r : items)
+ {
+ sessions.add(new WalletConnectSessionItem(r));
+ }
+ }
+
+ return sessions;
+ }
+
+ private List getWalletConnectV2SessionItems()
+ {
+ List result = new ArrayList<>();
+ try
+ {
+ List listOfSettledSessions = SignClient.INSTANCE.getListOfSettledSessions();
+ for (Sign.Model.Session session : listOfSettledSessions)
+ {
+ result.add(new WalletConnectV2SessionItem(session));
+ }
+ }
+ catch (IllegalStateException e)
+ {
+ Timber.e(e);
+ }
+ return result;
+ }
+
+ public interface SessionFetchCallback
+ {
+ void onFetched(List sessions);
+ }
+}
+
diff --git a/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepository.java b/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepository.java
new file mode 100644
index 0000000000..a1bf99cf23
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepository.java
@@ -0,0 +1,56 @@
+package com.alphawallet.app.repository;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.alphawallet.app.entity.coinbasepay.DestinationWallet;
+import com.alphawallet.app.util.CoinbasePayUtils;
+
+import java.util.List;
+
+public class CoinbasePayRepository implements CoinbasePayRepositoryType
+{
+ private static final String SCHEME = "https";
+ private static final String AUTHORITY = "pay.coinbase.com";
+ private static final String BUY_PATH = "buy";
+ private static final String SELECT_ASSET_PATH = "select-asset";
+ private final KeyProvider keyProvider = KeyProviderFactory.get();
+
+ @Override
+ public String getUri(DestinationWallet.Type type, String address, List list)
+ {
+ String appId = keyProvider.getCoinbasePayAppId();
+ if (TextUtils.isEmpty(appId))
+ {
+ return "";
+ }
+ else
+ {
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(SCHEME)
+ .authority(AUTHORITY)
+ .appendPath(BUY_PATH)
+ .appendPath(SELECT_ASSET_PATH)
+ .appendQueryParameter(RequestParams.APP_ID, keyProvider.getCoinbasePayAppId())
+ .appendQueryParameter(RequestParams.DESTINATION_WALLETS, CoinbasePayUtils.getDestWalletJson(type, address, list));
+
+ return builder.build().toString();
+ }
+ }
+
+ public static class Blockchains
+ {
+ public static final String ETHEREUM = "ethereum";
+ public static final String SOLANA = "solana";
+ public static final String AVALANCHE_C_CHAIN = "avalanche-c-chain";
+ }
+
+ private static class RequestParams
+ {
+ public static final String APP_ID = "appId";
+ public static final String ADDRESS = "address";
+ public static final String DESTINATION_WALLETS = "destinationWallets";
+ public static final String ASSETS = "assets";
+ public static final String BLOCKCHAINS = "blockchains";
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepositoryType.java b/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepositoryType.java
new file mode 100644
index 0000000000..f0b8136e13
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/repository/CoinbasePayRepositoryType.java
@@ -0,0 +1,9 @@
+package com.alphawallet.app.repository;
+
+import com.alphawallet.app.entity.coinbasepay.DestinationWallet;
+
+import java.util.List;
+
+public interface CoinbasePayRepositoryType {
+ String getUri(DestinationWallet.Type type, String json, List list);
+}
diff --git a/app/src/main/java/com/alphawallet/app/repository/CurrencyRepository.java b/app/src/main/java/com/alphawallet/app/repository/CurrencyRepository.java
index 80e5ca7c6f..57abe9c760 100644
--- a/app/src/main/java/com/alphawallet/app/repository/CurrencyRepository.java
+++ b/app/src/main/java/com/alphawallet/app/repository/CurrencyRepository.java
@@ -7,6 +7,10 @@
import java.util.Arrays;
public class CurrencyRepository implements CurrencyRepositoryType {
+ /**
+ * Find currency symbol here: https://coinyep.com/en/currencies
+ * Find drawables here - https://github.com/Shusshu/android-flags/tree/master/flags/src/main/res/drawable
+ */
public static final CurrencyItem[] CURRENCIES = {
new CurrencyItem("USD", "American Dollar", "$", R.drawable.ic_flags_usa),
new CurrencyItem("EUR", "Euro", "€", R.drawable.ic_flags_euro),
@@ -19,8 +23,9 @@ public class CurrencyRepository implements CurrencyRepositoryType {
new CurrencyItem("KRW", "Korean Won","₩", R.drawable.ic_flags_korea),
new CurrencyItem("RUB", "Russian Ruble","₽", R.drawable.ic_flags_russia),
new CurrencyItem("VND", "Vietnamese đồng", "₫", R.drawable.ic_flags_vietnam),
- new CurrencyItem("PKR", "Pakistani rupee", "Rs", R.drawable.ic_flags_pakistan),
- new CurrencyItem("MMK", "Myanmar Kyat", "Ks", R.drawable.ic_flags_myanmar)
+ new CurrencyItem("PKR", "Pakistani Rupee", "Rs", R.drawable.ic_flags_pakistan),
+ new CurrencyItem("MMK", "Myanmar Kyat", "Ks", R.drawable.ic_flags_myanmar),
+ new CurrencyItem("IDR", "Indonesian Rupiah", "Rp", R.drawable.ic_flags_indonesia)
};
private final PreferenceRepositoryType preferences;
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 7e3028677e..3a8a658566 100644
--- a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java
+++ b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java
@@ -3,12 +3,18 @@
/* Please don't add import android at this point. Later this file will be shared
* between projects including non-Android projects */
+import static com.alphawallet.app.entity.EventSync.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.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;
-import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_TEST_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.ARTIS_SIGMA1_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.ARTIS_TAU1_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.AURORA_MAINNET_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.AURORA_MAINNET_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.AURORA_TESTNET_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.AURORA_TESTNET_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.AVALANCHE_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.AVALANCHE_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.BINANCE_MAIN_ID;
@@ -22,33 +28,37 @@
import static com.alphawallet.ethereum.EthereumNetworkBase.FANTOM_TEST_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.FUJI_TEST_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.FUJI_TEST_RPC_URL;
+import static com.alphawallet.ethereum.EthereumNetworkBase.GNOSIS_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.GOERLI_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.HECO_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.HECO_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.HECO_TEST_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.HECO_TEST_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.IOTEX_MAINNET_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.IOTEX_MAINNET_RPC_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.IOTEX_TESTNET_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.IOTEX_TESTNET_RPC_URL;
+import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_BAOBAB_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_BAOBAB_RPC;
-import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_BOABAB_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_RPC;
-import static com.alphawallet.ethereum.EthereumNetworkBase.KOVAN_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.MATIC_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.MATIC_TEST_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.MILKOMEDA_C1_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.MILKOMEDA_C1_RPC;
import static com.alphawallet.ethereum.EthereumNetworkBase.MILKOMEDA_C1_TEST_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.MILKOMEDA_C1_TEST_RPC;
+import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISM_GOERLI_TESTNET_FALLBACK_RPC_URL;
+import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISM_GOERLI_TEST_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_MAIN_FALLBACK_URL;
import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_MAIN_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_TEST_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.PALM_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.PALM_TEST_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.PHI_NETWORK_MAIN_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.POA_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.RINKEBY_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.ROPSTEN_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.SOKOL_ID;
-import static com.alphawallet.ethereum.EthereumNetworkBase.XDAI_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_TEST_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.SEPOLIA_TESTNET_ID;
+import static com.alphawallet.ethereum.EthereumNetworkBase.SEPOLIA_TESTNET_RPC_URL;
+import static com.alphawallet.ethereum.EthereumNetworkBase.XDAI_RPC_URL;
import android.text.TextUtils;
import android.util.LongSparseArray;
@@ -70,7 +80,6 @@
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
-import org.web3j.protocol.http.HttpService;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -88,13 +97,9 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy
{
public static final String COVALENT = "[COVALENT]";
- private static final String DEFAULT_HOMEPAGE = "https://alphawallet.com/browser/";
-
- private static final String POLYGON_HOMEPAGE = "https://alphawallet.com/browser-item-category/polygon/";
-
private static final String GAS_API = "module=gastracker&action=gasoracle";
- private static final String DEFAULT_INFURA_KEY = "da3717f25f824cc1baa32d812386d93f";
+ public static final String DEFAULT_INFURA_KEY = "da3717f25f824cc1baa32d812386d93f";
/* constructing URLs from BuildConfig. In the below area you will see hardcoded key like da3717...
These hardcoded keys are fallbacks used by AlphaWallet forks.
@@ -103,107 +108,120 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy
If you wish your node to be the fallback, tried in case the primary times out then add/replace in ..._FALLBACK_RPC_URL list
*/
- static {
- System.loadLibrary("keys");
- }
+ private static final KeyProvider keyProvider = KeyProviderFactory.get();
+ public static final boolean usesProductionKey = !keyProvider.getInfuraKey().equals(DEFAULT_INFURA_KEY);
- public static native String getAmberDataKey();
- public static native String getInfuraKey();
- public static native String getSecondaryInfuraKey();
- private static final boolean usesProductionKey = !getInfuraKey().equals(DEFAULT_INFURA_KEY);
-
- public static final String FREE_MAINNET_RPC_URL = "https://main-rpc.linkpool.io";
+ public static final String FREE_MAINNET_RPC_URL = "https://rpc.ankr.com/eth";
public static final String FREE_POLYGON_RPC_URL = "https://polygon-rpc.com";
public static final String FREE_ARBITRUM_RPC_URL = "https://arbitrum.public-rpc.com";
- public static final String FREE_RINKEBY_RPC_URL = "https://rinkeby-light.eth.linkpool.io";
- public static final String FREE_GOERLI_RPC_URL = "https://goerli-light.eth.linkpool.io";
+ public static final String FREE_GOERLI_RPC_URL = "https://rpc.ankr.com/eth_goerli";
public static final String FREE_MUMBAI_RPC_URL = "https://rpc-mumbai.maticvigil.com";
- public static final String FREE_OPTIMISM_RPC_URL = "https://mainnet.optimism.io";
- public static final String FREE_ARBITRUM_TEST_RPC_URL = "https://rinkeby.arbitrum.io/rpc";
- public static final String FREE_KOVAN_RPC_URL = "https://kovan.poa.network";
- public static final String FREE_OPTIMISM_TESTRPC_URL = "https://kovan.optimism.io";
public static final String FREE_PALM_RPC_URL = "https://palm-mainnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b";
public static final String FREE_PALM_TEST_RPC_URL = "https://palm-testnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b";
public static final String FREE_CRONOS_MAIN_BETA_RPC_URL = "https://evm.cronos.org";
- public static final String MAINNET_RPC_URL = usesProductionKey ? "https://mainnet.infura.io/v3/" + getInfuraKey()
+ public static final String MAINNET_RPC_URL = usesProductionKey ? "https://mainnet.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_MAINNET_RPC_URL;
- public static final String RINKEBY_RPC_URL = usesProductionKey ? "https://rinkeby.infura.io/v3/" + getInfuraKey()
- : FREE_RINKEBY_RPC_URL;
- public static final String KOVAN_RPC_URL = usesProductionKey ? "https://kovan.infura.io/v3/" + getInfuraKey()
- : FREE_KOVAN_RPC_URL;
- public static final String GOERLI_RPC_URL = usesProductionKey ? "https://goerli.infura.io/v3/" + getInfuraKey()
+ public static final String GOERLI_RPC_URL = usesProductionKey ? "https://goerli.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_GOERLI_RPC_URL;
- public static final String MATIC_RPC_URL = usesProductionKey ? "https://polygon-mainnet.infura.io/v3/" + getInfuraKey()
+ public static final String POLYGON_RPC_URL = usesProductionKey ? "https://polygon-mainnet.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_POLYGON_RPC_URL;
- public static final String ARBITRUM_MAINNET_RPC = usesProductionKey ? "https://arbitrum-mainnet.infura.io/v3/" + getInfuraKey()
+ public static final String ARBITRUM_MAINNET_RPC = usesProductionKey ? "https://arbitrum-mainnet.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_ARBITRUM_RPC_URL;
- public static final String MUMBAI_TEST_RPC_URL = usesProductionKey ? "https://polygon-mumbai.infura.io/v3/" + getInfuraKey()
+ public static final String MUMBAI_TEST_RPC_URL = usesProductionKey ? "https://polygon-mumbai.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_MUMBAI_RPC_URL;
- public static final String OPTIMISTIC_MAIN_URL = usesProductionKey ? "https://optimism-mainnet.infura.io/v3/" + getInfuraKey()
- : FREE_OPTIMISM_RPC_URL;
- public static final String OPTIMISTIC_TEST_URL = usesProductionKey ? "https://optimism-kovan.infura.io/v3/" + getInfuraKey()
- : FREE_OPTIMISM_TESTRPC_URL;
- public static final String ARBITRUM_TESTNET_RPC = usesProductionKey ? "https://arbitrum-rinkeby.infura.io/v3/" + getInfuraKey()
- : FREE_ARBITRUM_TEST_RPC_URL;
- public static final String PALM_RPC_URL = usesProductionKey ? "https://palm-mainnet.infura.io/v3/" + getInfuraKey()
+ public static final String OPTIMISTIC_MAIN_URL = usesProductionKey ? "https://optimism-mainnet.infura.io/v3/" + keyProvider.getInfuraKey()
+ : OPTIMISTIC_MAIN_FALLBACK_URL;
+ public static final String PALM_RPC_URL = usesProductionKey ? "https://palm-mainnet.infura.io/v3/" + keyProvider.getInfuraKey()
: FREE_PALM_RPC_URL;
- public static final String PALM_TEST_RPC_URL = usesProductionKey ? "https://palm-testnet.infura.io/v3/" + getInfuraKey()
+ 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 USE_KLAYTN_RPC = usesProductionKey ? "https://node-api.klaytnapi.com/v1/klaytn"
+ : KLAYTN_RPC;
+ public static final String USE_KLAYTN_BAOBAB_RPC = usesProductionKey ? "https://node-api.klaytnapi.com/v1/klaytn"
+ : KLAYTN_BAOBAB_RPC;
public static final String CRONOS_MAIN_RPC_URL = "https://evm.cronos.org";
// Use the "Free" routes as backup in order to diversify node usage; to avoid single point of failure
- public static final String MAINNET_FALLBACK_RPC_URL = usesProductionKey ? FREE_MAINNET_RPC_URL : "https://mainnet.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String RINKEBY_FALLBACK_RPC_URL = usesProductionKey ? FREE_RINKEBY_RPC_URL : "https://rinkeby.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String KOVAN_FALLBACK_RPC_URL = usesProductionKey ? FREE_KOVAN_RPC_URL : "https://kovan.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String GOERLI_FALLBACK_RPC_URL = usesProductionKey ? FREE_GOERLI_RPC_URL : "https://goerli.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String ARBITRUM_FALLBACK_MAINNET_RPC = usesProductionKey ? FREE_ARBITRUM_RPC_URL : "https://arbitrum-mainnet.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String PALM_RPC_FALLBACK_URL = usesProductionKey ? FREE_PALM_RPC_URL : "https://palm-mainnet.infura.io/v3/" + getSecondaryInfuraKey();
- public static final String PALM_TEST_RPC_FALLBACK_URL = usesProductionKey ? FREE_PALM_RPC_URL : "https://palm-testnet.infura.io/v3/" + getSecondaryInfuraKey();
+ public static final String MAINNET_FALLBACK_RPC_URL = usesProductionKey ? FREE_MAINNET_RPC_URL : "https://mainnet.infura.io/v3/" + keyProvider.getSecondaryInfuraKey();
+ public static final String GOERLI_FALLBACK_RPC_URL = usesProductionKey ? FREE_GOERLI_RPC_URL : "https://goerli.infura.io/v3/" + keyProvider.getSecondaryInfuraKey();
+ public static final String ARBITRUM_FALLBACK_MAINNET_RPC = usesProductionKey ? FREE_ARBITRUM_RPC_URL : "https://arbitrum-mainnet.infura.io/v3/" + keyProvider.getSecondaryInfuraKey();
+ public static final String PALM_RPC_FALLBACK_URL = usesProductionKey ? FREE_PALM_RPC_URL : "https://palm-mainnet.infura.io/v3/" + keyProvider.getSecondaryInfuraKey();
+ public static final String PALM_TEST_RPC_FALLBACK_URL = usesProductionKey ? FREE_PALM_RPC_URL : "https://palm-testnet.infura.io/v3/" + keyProvider.getSecondaryInfuraKey();
//Note that AlphaWallet now uses a double node configuration. See class AWHttpService comment 'try primary node'.
//If you supply a main RPC and secondary it will try the secondary if the primary node times out after 10 seconds.
//See the declaration of NetworkInfo - it has a member backupNodeUrl. Put your secondary node here.
- public static final String ROPSTEN_FALLBACK_RPC_URL = "https://ropsten.infura.io/v3/" + getSecondaryInfuraKey();
public static final String CLASSIC_RPC_URL = "https://www.ethercluster.com/etc";
- public static final String XDAI_RPC_URL = com.alphawallet.ethereum.EthereumNetworkBase.XDAI_RPC_URL;
public static final String POA_RPC_URL = "https://core.poa.network/";
- public static final String ROPSTEN_RPC_URL = "https://ropsten.infura.io/v3/" + getInfuraKey();
- public static final String SOKOL_RPC_URL = "https://sokol.poa.network";
public static final String ARTIS_SIGMA1_RPC_URL = "https://rpc.sigma1.artis.network";
public static final String ARTIS_TAU1_RPC_URL = "https://rpc.tau1.artis.network";
public static final String BINANCE_TEST_RPC_URL = "https://data-seed-prebsc-1-s3.binance.org:8545";
public static final String BINANCE_TEST_FALLBACK_RPC_URL = "https://data-seed-prebsc-2-s1.binance.org:8545";
public static final String BINANCE_MAIN_RPC_URL = "https://bsc-dataseed.binance.org";
public static final String BINANCE_MAIN_FALLBACK_RPC_URL = "https://bsc-dataseed2.ninicoin.io:443";
- public static final String HECO_RPC_URL = "https://http-mainnet.hecochain.com";
- public static final String HECO_TEST_RPC_URL = "https://http-testnet.hecochain.com";
- public static final String MATIC_FALLBACK_RPC_URL = "https://matic-mainnet.chainstacklabs.com";
+ public static final String POLYGON_FALLBACK_RPC_URL = "https://matic-mainnet.chainstacklabs.com";
public static final String MUMBAI_FALLBACK_RPC_URL = "https://matic-mumbai.chainstacklabs.com";
- public static final String OPTIMISTIC_MAIN_FALLBACK_URL = "https://mainnet.optimism.io";
- public static final String OPTIMISTIC_TEST_FALLBACK_URL = "https://kovan.optimism.io";
public static final String CRONOS_TEST_URL = "https://evm-t3.cronos.org";
public static final String ARBITRUM_FALLBACK_TESTNET_RPC = "https://rinkeby.arbitrum.io/rpc";
- public static final String IOTEX_MAINNET_RPC_URL = "https://babel-api.mainnet.iotex.io";
public static final String IOTEX_MAINNET_RPC_FALLBACK_URL = "https://rpc.ankr.com/iotex";
- public static final String IOTEX_TESTNET_RPC_URL = "https://babel-api.testnet.iotex.io";
- public static final String AURORA_MAINNET_RPC_URL = "https://mainnet.aurora.dev";
- public static final String AURORA_TESTNET_RPC_URL = "https://testnet.aurora.dev";
- public static final String PHI_NETWORK_RPC = "https://rpc1.phi.network";
+ public static final String OPTIMISM_GOERLI_TESTNET_RPC_URL = "https://optimism-goerli.infura.io/v3/" + keyProvider.getInfuraKey();
+ public static final String ARBITRUM_GOERLI_TESTNET_RPC_URL = "https://arbitrum-goerli.infura.io/v3/" + keyProvider.getInfuraKey();
//All chains that have fiat/real value (not testnet) must be put here
//Note: This list also determines the order of display for main net chains in the wallet.
//If your wallet prioritises xDai for example, you may want to move the XDAI_ID to the front of this list,
//Then xDai would appear as the first token at the top of the wallet
private static final List hasValue = new ArrayList<>(Arrays.asList(
- MAINNET_ID, CLASSIC_ID, XDAI_ID, POA_ID, ARTIS_SIGMA1_ID, BINANCE_MAIN_ID, HECO_ID, AVALANCHE_ID,
- FANTOM_ID, MATIC_ID, OPTIMISTIC_MAIN_ID, CRONOS_MAIN_ID, ARBITRUM_MAIN_ID, PALM_ID, KLAYTN_ID, IOTEX_MAINNET_ID, AURORA_MAINNET_ID, MILKOMEDA_C1_ID,
- PHI_NETWORK_MAIN_ID));
+ MAINNET_ID, GNOSIS_ID, POLYGON_ID, CLASSIC_ID, POA_ID, ARTIS_SIGMA1_ID, BINANCE_MAIN_ID, HECO_ID, AVALANCHE_ID,
+ FANTOM_ID, OPTIMISTIC_MAIN_ID, CRONOS_MAIN_ID, ARBITRUM_MAIN_ID, PALM_ID, KLAYTN_ID, IOTEX_MAINNET_ID, AURORA_MAINNET_ID, MILKOMEDA_C1_ID));
+
+ private static final List testnetList = new ArrayList<>(Arrays.asList(
+ GOERLI_ID, BINANCE_TEST_ID, HECO_TEST_ID, CRONOS_TEST_ID, OPTIMISM_GOERLI_TEST_ID, ARBITRUM_GOERLI_TEST_ID, KLAYTN_BAOBAB_ID,
+ FANTOM_TEST_ID, IOTEX_TESTNET_ID, FUJI_TEST_ID, POLYGON_TEST_ID, MILKOMEDA_C1_TEST_ID, ARTIS_TAU1_ID,
+ SEPOLIA_TESTNET_ID, AURORA_TESTNET_ID, PALM_TEST_ID));
+
+ private static final List deprecatedNetworkList = new ArrayList<>(Arrays.asList(
+ // Add deprecated testnet IDs here
+ ));
+
+ private static final String INFURA_ENDPOINT = ".infura.io/v3/";
+
+ @Override
+ public String getDappBrowserRPC(long chainId)
+ {
+ NetworkInfo info = getNetworkByChain(chainId);
+
+ if (info == null)
+ {
+ return "";
+ }
+
+ int index = info.rpcServerUrl.indexOf(INFURA_ENDPOINT);
+ if (index > 0)
+ {
+ return info.rpcServerUrl.substring(0, index + INFURA_ENDPOINT.length()) + keyProvider.getTertiaryInfuraKey();
+ }
+ else if (info.backupNodeUrl != null)
+ {
+ return info.backupNodeUrl;
+ }
+ else
+ {
+ return info.rpcServerUrl;
+ }
+ }
+
+ public static boolean isInfura(String rpcServerUrl)
+ {
+ return rpcServerUrl.contains(INFURA_ENDPOINT);
+ }
// for reset built-in network
- private static final LongSparseArray