Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Wallet) Enable Filecoin support flag #18881

Merged
merged 17 commits into from
Jun 23, 2023
Merged
67 changes: 67 additions & 0 deletions android/java/org/chromium/chrome/browser/app/domain/SendModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,30 @@

import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.base.Log;
import org.chromium.brave_wallet.mojom.BlockchainRegistry;
import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.BraveWalletService;
import org.chromium.brave_wallet.mojom.CoinType;
import org.chromium.brave_wallet.mojom.EthTxManagerProxy;
import org.chromium.brave_wallet.mojom.FilTxData;
import org.chromium.brave_wallet.mojom.JsonRpcService;
import org.chromium.brave_wallet.mojom.KeyringService;
import org.chromium.brave_wallet.mojom.SolanaTxManagerProxy;
import org.chromium.brave_wallet.mojom.TxDataUnion;
import org.chromium.brave_wallet.mojom.TxService;
import org.chromium.chrome.browser.crypto_wallet.util.Utils;
import org.chromium.chrome.browser.crypto_wallet.util.WalletUtils;
import org.chromium.mojo.bindings.Callbacks;

import java.text.ParseException;

public class SendModel {
private static final String TAG = "SendModel";

private TxService mTxService;
private KeyringService mKeyringService;
private BlockchainRegistry mBlockchainRegistry;
Expand Down Expand Up @@ -94,4 +105,60 @@ public void sendSolanaToken(String chainId, BlockchainToken token, String fromAd
});
}
}

/**
* Sends a given amount of Filecoins to a destination address and creates an unapproved
* transaction or notifies the callback with an error message. Coin type of blockchain token
* must be {@code CoinType.FIL}.
* @param token Filecoin blockchain token containing extra data required to create a
* transaction.
* @param fromAddress Source address that sends Filecoins.
* @param toAddress Destinations address that receives Filecoins.
* @param amount Amount of Filecoins to send.
* @param callback Callback used to notify if the transaction has been created correctly.
*/
public void sendFilecoinToken(@Nullable final BlockchainToken token,
@Nullable final String fromAddress, @NonNull final String toAddress,
@Nullable final String amount,
@NonNull final Callbacks.Callback3<Boolean, String, String> callback) {
if (token == null) {
Log.e(TAG, "Token cannot be null.");
return;
}
if (TextUtils.isEmpty(fromAddress)) {
Log.e(TAG, "Contract address cannot be null or empty.");
return;
}
if (TextUtils.isEmpty(amount)) {
Log.e(TAG, "Amount to send cannot be null or empty.");
return;
}

if (token.coin != CoinType.FIL) {
throw new IllegalStateException("Coin type must be `CoinType.FIL`.");
}

final FilTxData filTxData = new FilTxData();
filTxData.to = toAddress;
filTxData.from = fromAddress;
try {
filTxData.value = Utils.multiplyByDecimals(amount, token.decimals).toString();
} catch (ParseException parseException) {
Log.e(TAG, "Error while parsing Filecoin amount to send.", parseException);
return;
}
filTxData.nonce = "";
filTxData.gasPremium = "";
filTxData.gasFeeCap = "";
filTxData.gasLimit = "";
filTxData.maxFee = "0";

final TxDataUnion txDataUnion = new TxDataUnion();
txDataUnion.setFilTxData(filTxData);

mTxService.addUnapprovedTransaction(
txDataUnion, fromAddress, null, null, (success, txMetaId, errorMessage) -> {
callback.call(success, txMetaId, errorMessage);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ public void onTransactionStatusChanged(TransactionInfo txInfo) {
private void updateTx(
TransactionInfo txInfo, WeakReference<BraveWalletBaseActivity> mActivityRef) {
List<WalletListItemModel> items = _mParsedTransactions.getValue();
if (txInfo.txStatus == TransactionStatus.REJECTED
if (items != null && txInfo.txStatus == TransactionStatus.REJECTED
&& items.stream().anyMatch(
walletItem -> walletItem.getTransactionInfo().id.equals(txInfo.id))) {
// Remove rejected transaction
Expand All @@ -253,7 +253,7 @@ private void updateTx(
-> !walletItem.getTransactionInfo().id.equals(txInfo.id))
.collect(Collectors.toList());
_mParsedTransactions.postValue(items);
} else if (txInfo.txStatus != TransactionStatus.UNAPPROVED) {
} else if (items != null && txInfo.txStatus != TransactionStatus.UNAPPROVED) {
// Update status
WalletListItemModel walletItemTx =
items.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,14 @@ public void onAssetClick(WalletListItemModel asset) {
setSendToFromValueValidationResult(errorMessage, false, true);
});
} else if (mSelectedAccount.accountId.coin == CoinType.FIL) {
// TODO: implement Filecoin send action.
// https://github.com/brave/brave-browser/issues/30402
mSendModel.sendFilecoinToken(mCurrentBlockchainToken, mSelectedAccount.address,
to, amount, (success, txMetaId, errorMessage) -> {
// Do nothing here when success as we will receive an
// unapproved transaction in TxServiceObserver.
// When we have error, let the user know,
// error_message is localized, do not disable send button
setSendToFromValueValidationResult(errorMessage, false, true);
});
}
} else if (mActivityType == ActivityType.BUY) {
Intent selectPurchaseMethodIntent = SelectPurchaseMethodActivity.getIntent(this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public static ParsedTransaction parseTransaction(TransactionInfo txInfo,
final String value = isSPLTransaction
? solTxData != null ? String.valueOf(solTxData.amount) : ""
: isSolTransaction ? solTxData != null ? String.valueOf(solTxData.lamports) : ""
: isFilTransaction ? filTxData.value != null ? filTxData.value : ""
: isFilTransaction ? filTxData.value != null ? Utils.toHex(filTxData.value) : ""
: txData != null ? txData.baseData.value
: "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@

import static org.chromium.chrome.browser.crypto_wallet.util.WalletConstants.SOLANA_TRANSACTION_TYPES;

import androidx.annotation.NonNull;

import org.chromium.brave_wallet.mojom.FilTxData;
import org.chromium.brave_wallet.mojom.NetworkInfo;
import org.chromium.brave_wallet.mojom.TransactionInfo;
import org.chromium.brave_wallet.mojom.TxData1559;
import org.chromium.brave_wallet.mojom.TxDataUnion;

import java.math.BigInteger;

/*
* Transaction fees parser. Java version of
* components/brave_wallet_ui/common/hooks/transaction-parser.ts.
* Transaction fees parser. Java version adapted from
* components/brave_wallet_ui/utils/tx-utils.ts.
*/
public class ParsedTransactionFees {
// Strings are initialized to empty string instead of null, as the default value from mojo
Expand Down Expand Up @@ -104,11 +108,27 @@ public static ParsedTransactionFees parseTransactionFees(TransactionInfo txInfo,
final int networkDecimals = selectedNetwork.decimals;
final boolean isSolTransaction = SOLANA_TRANSACTION_TYPES.contains(txInfo.txType);
final boolean isFilTransaction = filTxData != null;
final String gasLimit = isFilTransaction ? filTxData.gasLimit
: (txData != null ? txData.baseData.gasLimit : "");
final String gasPrice = txData != null ? txData.baseData.gasPrice : "";
final String maxFeePerGas = txData != null ? txData.maxFeePerGas : "";
final String maxPriorityFeePerGas = txData != null ? txData.maxPriorityFeePerGas : "";

final String gasLimit;
final String gasPrice;
final String maxFeePerGas;
final String maxPriorityFeePerGas;
if (isFilTransaction) {
final String maxFeePerGasDecimal =
filTxData.gasFeeCap != null ? filTxData.gasFeeCap : "";
final String maxPriorityFeePerGasDecimal =
filTxData.gasPremium != null ? filTxData.gasPremium : "";

gasLimit = filTxData.gasLimit != null ? Utils.toHex(filTxData.gasLimit) : "";
maxFeePerGas = Utils.toHex(maxFeePerGasDecimal);
maxPriorityFeePerGas = Utils.toHex(maxPriorityFeePerGasDecimal);
gasPrice = calculateFilGasPrice(maxFeePerGasDecimal, maxPriorityFeePerGasDecimal);
} else {
gasLimit = txData != null ? txData.baseData.gasLimit : "";
gasPrice = txData != null ? txData.baseData.gasPrice : "";
maxFeePerGas = txData != null ? txData.maxFeePerGas : "";
maxPriorityFeePerGas = txData != null ? txData.maxPriorityFeePerGas : "";
}
final boolean isEIP1559Transaction =
!maxPriorityFeePerGas.isEmpty() && !maxFeePerGas.isEmpty();
final double[] gasFeeArr =
Expand All @@ -130,20 +150,43 @@ public static ParsedTransactionFees parseTransactionFees(TransactionInfo txInfo,
return parsedTransactionFees;
}

@NonNull
private static String calculateFilGasPrice(@NonNull final String maxFeePerGasDecimal,
@NonNull final String maxPriorityFeePerGasDecimal) {
if (maxFeePerGasDecimal.isEmpty() || maxPriorityFeePerGasDecimal.isEmpty()) {
return "";
} else {
String result;
try {
BigInteger minued = new BigInteger(maxFeePerGasDecimal);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minuend

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

BigInteger subtrahend = new BigInteger(maxPriorityFeePerGasDecimal);
BigInteger gasPrice = minued.subtract(subtrahend);
result = "0x" + gasPrice.toString(16);
} catch (NumberFormatException nfe) {
result = "";
}
return result;
}
}

// Desktop doesn't seem to have slow/avg/fast MaxFeePerGas options,
// so extracting this part as a separate function
public static double[] calcGasFee(NetworkInfo selectedNetwork, double networkSpotPrice,
boolean isEIP1559Transaction, String gasLimit, String gasPrice, String maxFeePerGas,
boolean isSolTransaction, long solFeeEstimatesFee) {
final int networkDecimals = selectedNetwork.decimals;
final double gasFee = isSolTransaction
? solFeeEstimatesFee != 0
? Utils.fromWei(Long.toString(solFeeEstimatesFee), networkDecimals)
: 0.0d
: Utils.fromHexWei(isEIP1559Transaction
? Utils.multiplyHexBN(maxFeePerGas, gasLimit)
: Utils.multiplyHexBN(gasPrice, gasLimit),
networkDecimals);
double gasFee;
if (isSolTransaction) {
gasFee = solFeeEstimatesFee != 0
? Utils.fromWei(Long.toString(solFeeEstimatesFee), networkDecimals)
: 0.0d;
} else {
// Gas fee calculations for Eth and Fil use the same logic.
gasFee = Utils.fromHexWei(isEIP1559Transaction
? Utils.multiplyHexBN(maxFeePerGas, gasLimit)
: Utils.multiplyHexBN(gasPrice, gasLimit),
networkDecimals);
}

final double gasFeeFiat = gasFee * networkSpotPrice;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,20 @@ public static double fromHexGWeiToGWEI(String number) {
return 0;
}

// Supposedly toWei shall always end up with an integer
private static BigInteger toWeiInternal(String number, int decimals) throws ParseException {
/**
* Converts a given string to a big integer and multiplies the value of the object by
* ten raised to the power of decimals.
*
* @param number Number to be multiplied, represented as a string.
* @param decimals Number of decimals to multiply by.
* @return The result of multiplying the number by ten raised to the power of decimals,
* expressed as a {@code BigInteger}.
* @throws ParseException If the input string cannot be parsed as a {@code BigDecimal}.
*
* <b>Note:</b>: Supposedly, when converting to Wei the result shall always end up with an
* integer.
*/
public static BigInteger multiplyByDecimals(String number, int decimals) throws ParseException {
NumberFormat nf = NumberFormat.getInstance(Locale.getDefault());
ParsePosition parsePosition = new ParsePosition(0);
BigDecimal parsed = null;
Expand Down Expand Up @@ -466,7 +478,7 @@ public static String toHexWei(String number, int decimals) {
}

try {
return "0x" + toWeiInternal(number, decimals).toString(16);
return "0x" + multiplyByDecimals(number, decimals).toString(16);
} catch (ParseException ex) {
return "0x0";
}
Expand Down Expand Up @@ -1063,6 +1075,8 @@ public static void openTransaction(TransactionInfo txInfo, AppCompatActivity act
BraveWalletConstants.FILECOIN_ETHEREUM_TESTNET_CHAIN_ID);
if (isFileCoinEvmNet) {
openAddress("/" + txInfo.txHash, activity, coinType, networkInfo);
} else if (coinType == CoinType.FIL) {
openAddress("?cid=" + txInfo.txHash, activity, coinType, networkInfo);
} else {
openAddress("/tx/" + txInfo.txHash, activity, coinType, networkInfo);
}
Expand All @@ -1075,7 +1089,7 @@ public static void openAddress(
if (blockExplorerUrl.length() > 2) {
blockExplorerUrl = blockExplorerUrl.substring(1, blockExplorerUrl.length() - 1);
}
if (coinType == CoinType.ETH) {
if (coinType == CoinType.ETH || coinType == CoinType.FIL) {
blockExplorerUrl += toAppend;
} else if (coinType == CoinType.SOL) {
int iPos = blockExplorerUrl.indexOf("?cluster=");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

import android.content.res.Resources;

import androidx.annotation.NonNull;

import org.chromium.base.ContextUtils;
import org.chromium.brave_wallet.mojom.BlockchainRegistry;
import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.BraveWalletConstants;
import org.chromium.brave_wallet.mojom.BraveWalletService;
import org.chromium.brave_wallet.mojom.CoinType;
import org.chromium.brave_wallet.mojom.KeyringService;
Expand All @@ -32,11 +35,9 @@ public void validate(NetworkInfo selectedNetwork, KeyringService keyringService,
BlockchainRegistry blockchainRegistry, BraveWalletService braveWalletService,
String senderAccountAddress, String receiverAccountAddress,
Callbacks.Callback2<String, Boolean> callback) {
String senderAccountAddressLower =
senderAccountAddress.toLowerCase(Locale.getDefault());
String senderAccountAddressLower = senderAccountAddress.toLowerCase(Locale.ENGLISH);

String receiverAccountAddressLower =
receiverAccountAddress.toLowerCase(Locale.getDefault());
String receiverAccountAddressLower = receiverAccountAddress.toLowerCase(Locale.ENGLISH);

Resources resources = ContextUtils.getApplicationContext().getResources();

Expand All @@ -55,6 +56,15 @@ public void validate(NetworkInfo selectedNetwork, KeyringService keyringService,
}
callback.call(responseMsg, success);
});
} else if (coinType == CoinType.FIL) {
if (receiverAccountAddress.isEmpty()) {
return;
}
final boolean showErrorMessage = !isValidFilAddress(receiverAccountAddress);
final String errorMessage = showErrorMessage
? resources.getString(R.string.invalid_fil_address)
: "";
callback.call(errorMessage, showErrorMessage);
} else {
// Steps to validate:
// 1. Valid hex string
Expand All @@ -71,10 +81,8 @@ public void validate(NetworkInfo selectedNetwork, KeyringService keyringService,
if (!receiverAccountAddress.isEmpty()
&& bytesReceiverAccountAddress.length
!= VALID_ACCOUNT_ADDRESS_BYTE_LENGTH) {
int invalidAddressId = coinType == CoinType.FIL
? R.string.invalid_fil_address
: R.string.wallet_not_valid_eth_address;
callback.call(resources.getString(invalidAddressId), true);
callback.call(
resources.getString(R.string.wallet_not_valid_eth_address), true);
return;
}

Expand Down Expand Up @@ -120,6 +128,20 @@ public void validate(NetworkInfo selectedNetwork, KeyringService keyringService,
});
}

/**
* Checks if the Filecoin contract address provided is valid.
* @param address Lowercase Filecoin contract address.
* @return {@code true} if the Filecoin contract address is valid, {@code false} otherwise.
*/
private boolean isValidFilAddress(@NonNull final String address) {
if (!address.startsWith(BraveWalletConstants.FILECOIN_MAINNET)
&& !address.startsWith(BraveWalletConstants.FILECOIN_TESTNET)) {
return false;
}
// Secp256k have 41 address length and BLS keys have 86.
return (address.length() == 41 || address.length() == 86);
}

private void checkForKnowContracts(String receiverAccountAddressLower,
Callbacks.Callback2<String, Boolean> callback, Resources resources) {
assert mKnownContractAddresses != null;
Expand Down
Loading