Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a746d3d
Merge pull request #53 from gruntsoftware/release/v4.4.1
kcw-grunt Mar 30, 2025
ba8ea45
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
638ce3a
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
5344a96
Add version and code to welcome screen
kcw-grunt Mar 31, 2025
ebc3bb9
reset padding
kcw-grunt Mar 31, 2025
03c68b8
Merge pull request #56 from gruntsoftware/feature/add-version-issue-108
kcw-grunt Apr 1, 2025
c770f01
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
f8c1bd5
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
ff58b57
fix: fix crash when FragmentSignal dismissed
andhikayuana Apr 4, 2025
d64ea93
fix: fix sync after wipe
andhikayuana Apr 7, 2025
277f387
Merge branch 'hotfix/api-manager-fix' of https://github.com/brainwall…
kcw-grunt Apr 7, 2025
9a8a20f
fix: fix wrong lifecycle to trigger callback at FragmentSignal
andhikayuana Apr 7, 2025
832fa36
Update issue templates
kcw-grunt Apr 8, 2025
b294cd6
updated core changes
kcw-grunt Apr 8, 2025
4ed8af3
Merge pull request #57 from gruntsoftware/hotfix/api-manager-fix
kcw-grunt Apr 8, 2025
089995a
Rename .java to .kt
andhikayuana Apr 11, 2025
7e85987
chore: refactor BRApiManager & APIClient
andhikayuana Apr 11, 2025
0aa41e7
chore: remove unused part at APIClient
andhikayuana Apr 14, 2025
0cc29cb
feat: wip new peer discovery
andhikayuana Apr 14, 2025
54d2d9e
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
c6923b1
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
ef4e6d2
feat: filter out peers with NODE_NETWORK, NODE_BLOOM
andhikayuana Apr 15, 2025
2336939
fix: race condition when clear shared prefs values after wipeAll
andhikayuana Apr 16, 2025
51a4d9a
Merge pull request #62 from gruntsoftware/hotfix/prefs-race-condition
kcw-grunt Apr 16, 2025
62afb9d
Merge pull request #61 from gruntsoftware/feat/new-peer-discovery
kcw-grunt Apr 16, 2025
391e319
Updating the core library
kcw-grunt Apr 16, 2025
836b6f0
Chore/revert pre peer discovery (Android) (#69)
andhikayuana Apr 25, 2025
3caa1a7
feat: remove unused activity (ImportActivity) at AndroidManifest.xml …
andhikayuana Apr 29, 2025
a324444
fix: fix crash IllegalStateException: cannot make a new request becau…
andhikayuana May 2, 2025
1f9c5d0
code bump
kcw-grunt May 2, 2025
7ca17d3
Feat/move tx fee (#74)
andhikayuana May 5, 2025
e7fb2b7
fix: fix dismiss allow state loss (#76)
andhikayuana May 7, 2025
3d8ef98
fix: fix typo at strings.xml (#75)
andhikayuana May 7, 2025
25be1a8
feat: new UI for receive and topup flow (moonpay integration) (#72)
andhikayuana May 12, 2025
18eda46
version and code bump
kcw-grunt May 12, 2025
dbb6a5f
Added support url (#81)
kcw-grunt May 19, 2025
c82a5e3
version and code bump
kcw-grunt May 19, 2025
2981ec6
Revert from eda0f532 & cherry pick (#86)
andhikayuana May 25, 2025
c161fed
build bump
kcw-grunt May 25, 2025
a5762de
Updated README
kcw-grunt May 25, 2025
e2c034e
Merge branch 'main' into release/v4.5.4
kcw-grunt May 25, 2025
11826bb
code bump
kcw-grunt May 25, 2025
63d7494
chore: open bread activity first then open moonpay widget (#88)
andhikayuana May 26, 2025
002ad32
Merge branch 'develop' into release/v4.5.4
kcw-grunt May 26, 2025
6c85b3d
code bump
kcw-grunt May 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

## Easy and secure
Brainwallet is a easy and fun way to use your crypto (Litecoin) and memorize your seed words

### Release notes

- v4.5.4
- Added new MP Buy UI
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ android {
applicationId = "ltd.grunt.brainwallet"
minSdk = 29
targetSdk = 34
versionCode = 202505121
versionName = "v4.5.0"
versionCode = 202505261
versionName = "v4.5.4"

multiDexEnabled = true
base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})")
Expand Down
27 changes: 19 additions & 8 deletions app/src/main/java/com/brainwallet/data/model/Fee.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import com.brainwallet.tools.manager.FeeManager.LUXURY
import com.brainwallet.tools.manager.FeeManager.REGULAR
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.math.ceil
import kotlin.math.round

@Serializable
data class Fee(
Expand All @@ -22,23 +20,31 @@ data class Fee(
@JvmField
@SerialName("fee_per_kb_economy")
var economy: Long,
var timestamp: Long
) {
companion object {
//from legacy
// this is the default that matches the mobile-api if the server is unavailable
private const val defaultEconomyFeePerKB: Long =
2500L // From legacy minimum. default min is 1000 as Litecoin Core version v0.17.1
/**
* Default value for economy fee rate per kilobyte.
* Used as a fallback when fee rate cannot be determined dynamically.
*
* Previous value: 2500L (2.5 satoshis per byte). From legacy minimum. default min is 1000 as Litecoin Core version v0.17.1
* Updated economy to 8000L (8 satoshis per byte) on 2023-11-16 (same as iOS)
*/
private const val defaultEconomyFeePerKB: Long = 8000L
private const val defaultRegularFeePerKB: Long = 25000L
private const val defaultLuxuryFeePerKB: Long = 66746L
private const val defaultTimestamp: Long = 1583015199122L

// {"fee_per_kb":5289,"fee_per_kb_economy":2645,"fee_per_kb_luxury":10578}

/**
* currently we are using this static [Default] for our fee
* maybe we need to update core if we need dynamic fee?
*/
@JvmStatic
val Default = Fee(
defaultLuxuryFeePerKB,
defaultRegularFeePerKB,
defaultEconomyFeePerKB,
defaultTimestamp
)
}
}
Expand Down Expand Up @@ -81,3 +87,8 @@ fun FeeOption.getFiatFormatted(currencyEntity: CurrencyEntity): String {
val formatted = String.format("%.3f", fiatValue)
return "${currencyEntity.symbol}$formatted"
}

fun List<FeeOption>.getSelectedIndex(selectedFeeType: String): Int {
return indexOfFirst { it.type == selectedFeeType }.takeIf { it >= 0 }
?: 2 //2 -> index of top, since we have [low,medium,top]
}
18 changes: 7 additions & 11 deletions app/src/main/java/com/brainwallet/data/repository/LtcRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,13 @@ interface LtcRepository {

}

override suspend fun fetchFeePerKb(): Fee {
return sharedPreferences.fetchWithCache(
key = PREF_KEY_NETWORK_FEE_PER_KB,
cachedAtKey = PREF_KEY_NETWORK_FEE_PER_KB_CACHED_AT,
cacheTimeMs = 6 * 60 * 60 * 1000,
fetchData = {
remoteApiSource.getFeePerKb()
},
defaultValue = Fee.Default
)
}
/**
* for now we just using [Fee.Default]
* will move to [RemoteApiSource.getFeePerKb] after fix the calculation when we do send
*
* maybe need updaete core if we need to use dynamic fee?
*/
override suspend fun fetchFeePerKb(): Fee = Fee.Default //using static fee

override suspend fun fetchLimits(baseCurrencyCode: String): MoonpayCurrencyLimit {
return sharedPreferences.fetchWithCache(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.core.content.edit
import com.brainwallet.data.model.AppSetting
import com.brainwallet.data.model.CurrencyEntity
import com.brainwallet.data.model.Language
import com.brainwallet.tools.manager.FeeManager
import com.brainwallet.tools.sqlite.CurrencyDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -36,6 +37,10 @@ interface SettingRepository {

fun toggleDarkMode(isDarkMode: Boolean)

fun putSelectedFeeType(feeType: String)

fun getSelectedFeeType(): String

class Impl(
private val sharedPreferences: SharedPreferences,
private val currencyDataSource: CurrencyDataSource
Expand Down Expand Up @@ -77,6 +82,17 @@ interface SettingRepository {
_state.update { it.copy(isDarkMode = isDarkMode) }
}

override fun putSelectedFeeType(feeType: String) {
sharedPreferences.edit {
putString(KEY_SELECTED_FEE_TYPE, feeType)
}
}

override fun getSelectedFeeType(): String =
sharedPreferences.getString(KEY_SELECTED_FEE_TYPE, FeeManager.REGULAR)
?: FeeManager.REGULAR


private fun load(): AppSetting {
return AppSetting(
isDarkMode = sharedPreferences.getBoolean(KEY_IS_DARK_MODE, true),
Expand All @@ -100,5 +116,6 @@ interface SettingRepository {
const val KEY_IS_DARK_MODE = "is_dark_mode"
const val KEY_LANGUAGE_CODE = "language_code"
const val KEY_FIAT_CURRENCY_CODE = "fiat_currency_code"
const val KEY_SELECTED_FEE_TYPE = "selected_fee_type"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class FragmentSend : Fragment() {

updateText()

//update fee
BRWalletManager.getInstance().setFeePerKb(FeeManager.getInstance().currentFeeValue)

return rootView
}

Expand Down Expand Up @@ -496,7 +499,6 @@ class FragmentSend : Fragment() {

override fun onStop() {
super.onStop()
FeeManager.getInstance().resetFeeType()
}

override fun onResume() {
Expand Down Expand Up @@ -611,9 +613,22 @@ class FragmentSend : Fragment() {
}
Timber.d("timber: updateText: currentAmountInLitoshis %d", currentAmountInLitoshis)

// Service Fee depending on ISOSymbol
var serviceFee = Utils.tieredOpsFee(activity, currentAmountInLitoshis)
val serviceFeeForISOSymbol =
BRExchange.getAmountFromLitoshis(activity, selectedISOSymbol, BigDecimal(serviceFee))
.setScale(scaleValue, RoundingMode.HALF_UP)
val formattedServiceFee = BRCurrency.getFormattedCurrencyString(
activity,
selectedISOSymbol,
serviceFeeForISOSymbol
)

val totalAmountToCalculateFees = currentAmountInLitoshis + serviceFee

// Network Fee depending on ISOSymbol
var networkFee = if (currentAmountInLitoshis > 0) {
BRWalletManager.getInstance().feeForTransactionAmount(currentAmountInLitoshis)
var networkFee = if (totalAmountToCalculateFees > 0) {
BRWalletManager.getInstance().feeForTransactionAmount(totalAmountToCalculateFees)
} else {
0
} //Amount is zero so network fee is also zero
Expand All @@ -626,17 +641,6 @@ class FragmentSend : Fragment() {
networkFeeForISOSymbol
)

// Service Fee depending on ISOSymbol
var serviceFee = Utils.tieredOpsFee(activity, currentAmountInLitoshis)
val serviceFeeForISOSymbol =
BRExchange.getAmountFromLitoshis(activity, selectedISOSymbol, BigDecimal(serviceFee))
.setScale(scaleValue, RoundingMode.HALF_UP)
val formattedServiceFee = BRCurrency.getFormattedCurrencyString(
activity,
selectedISOSymbol,
serviceFeeForISOSymbol
)

// Total Fees depending on ISOSymbol
val totalFees = networkFee + serviceFee
val totalFeeForISOSymbol =
Expand Down
27 changes: 21 additions & 6 deletions app/src/main/java/com/brainwallet/tools/manager/FeeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

import androidx.annotation.StringDef;

import com.brainwallet.data.model.Fee;
import com.brainwallet.data.repository.LtcRepository;
import com.brainwallet.data.repository.SettingRepository;
import com.brainwallet.tools.threads.BRExecutor;
import com.brainwallet.data.model.Fee;

import org.koin.java.KoinJavaComponent;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

//we are still using this, maybe in the future will deprecate?
@Deprecated
public final class FeeManager {


Expand Down Expand Up @@ -56,8 +56,24 @@ public boolean isLuxuryFee() {
public static final String REGULAR = "regular";//medium
public static final String ECONOMY = "economy";//low

public void setFees(long luxuryFee, long regularFee, long economyFee) {
currentFeeOptions = new Fee(luxuryFee, regularFee, economyFee);
public void setFees(Fee fee) {
currentFeeOptions = fee;
}

public long getCurrentFeeValue() {
SettingRepository settingRepository = KoinJavaComponent.get(SettingRepository.class);
String feeType = settingRepository.getSelectedFeeType();

switch (feeType) {
case LUXURY:
return currentFeeOptions.luxury;
case REGULAR:
return currentFeeOptions.regular;
case ECONOMY:
return currentFeeOptions.economy;
default:
return currentFeeOptions.regular; // Default to regular fee
}
}

public static void updateFeePerKb(Context app) {
Expand All @@ -66,8 +82,7 @@ public static void updateFeePerKb(Context app) {
(coroutineScope, continuation) -> ltcRepository.fetchFeePerKb(continuation)
).whenComplete((fee, throwable) -> {

//legacy logic
FeeManager.getInstance().setFees(fee.luxury, fee.regular, fee.economy);
FeeManager.getInstance().setFees(fee);
BRSharedPrefs.putFeeTime(app, System.currentTimeMillis()); //store the time of the last successful fee fetch
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.content.Intent;
import android.view.View;

import com.brainwallet.presenter.activities.settings.SyncBlockchainActivity;
import com.brainwallet.tools.security.BRKeyStore;
import com.brainwallet.tools.threads.BRExecutor;
import com.brainwallet.R;
Expand Down Expand Up @@ -88,6 +89,7 @@ public void run() {
BRSharedPrefs.putStartHeight(app, 0);
BRPeerManager.getInstance().rescan();
BRSharedPrefs.putScanRecommended(app, false);
BRSharedPrefs.putAllowSpend(app, false);
}
});
}
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/com/brainwallet/tools/util/BRConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private BRConstants() {
public static final String TWITTER_LINK = "https://twitter.com/Brainwallet_App";
public static final String INSTAGRAM_LINK = "https://www.instagram.com/brainwalletapp";
public static final String WEB_LINK = "https://brainwallet.co";
public static final String SUPPORT_WEB_LINK = "https://brainwallet.co/support.html";
public static final String TOS_LINK = "https://brainwallet.co/privacy-policy.html";
public static final String MOBILE_MP_LINK = "https://brainwallet.co/mobile-top-up.html";
public static String BITREFILL_AFFILIATE_LINK = "https://www.bitrefill.com/";
Expand Down Expand Up @@ -128,14 +129,14 @@ private BRConstants() {
public static final String _20201118_DTGS = "did_tap_get_support";
public static final String _20200217_DUWP = "did_unlock_with_pin";
public static final String _20200217_DUWB = "did_unlock_with_biometrics";
public static final String _20200301_DUDFPK = "did_use_default_fee_per_kb";
public static final String _20201121_SIL = "started_IFPS_lookup";
public static final String _20201121_DRIA = "did_resolve_IPFS_address";
public static final String _20201121_FRIA = "failed_resolve_IPFS_address";
public static final String _20230113_BAC = "backup_apiserver_called";
public static final String _20230407_DCS = "did_complete_sync";

public static final String _20250303_DSTU = "did_skip_top_up";
public static final String _20250517_WCINFO = "wallet_callback_info";

///Dev: These events not yet used
public static final String _20200207_DTHB = "did_tap_header_balance";
Expand Down Expand Up @@ -169,7 +170,6 @@ private BRConstants() {
_20201118_DTGS,
_20200217_DUWP,
_20200217_DUWB,
_20200301_DUDFPK,
_20201121_SIL,
_20201121_DRIA,
_20201121_FRIA,
Expand All @@ -186,7 +186,8 @@ private BRConstants() {
_20241006_UCR,
_HOME_OPEN,
_20250222_PAC,
_20250303_DSTU
_20250303_DSTU,
_20250517_WCINFO
})
public @interface Event {
}
Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/com/brainwallet/tools/util/BRExchange.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,17 @@ public static BigDecimal getLitoshisFromAmount(Context app, String iso, BigDecim
if (iso.equalsIgnoreCase("LTC")) {
result = BRExchange.getLitoshisForLitecoin(app, amount);
} else {
//multiply by 100 because core function localAmount accepts the smallest amount e.g. cents
CurrencyEntity ent = CurrencyDataSource.getInstance(app).getCurrencyByIso(iso);
if (ent == null) return new BigDecimal(0);
BigDecimal rate = new BigDecimal(ent.rate).multiply(new BigDecimal(100));
result = new BigDecimal(BRWalletManager.getInstance().bitcoinAmount(amount.multiply(new BigDecimal(100)).longValue(), rate.doubleValue()));

// Get the exchange rate
BigDecimal rate = new BigDecimal(ent.rate);

result = amount.divide(rate, 8, BRConstants.ROUNDING_MODE)
.multiply(new BigDecimal("100000000"));

// Round to a whole number of litoshis
result = result.setScale(0, BRConstants.ROUNDING_MODE);
}
return result;
}
Expand Down
41 changes: 16 additions & 25 deletions app/src/main/java/com/brainwallet/tools/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,38 +267,29 @@ else if (name == ServiceItems.CLIENTCODE) {
}
/// Description: 1715876807
public static long tieredOpsFee(Context app, long sendAmount) {

double doubleRate = 83.000;
double sendAmountDouble = new Double(String.valueOf(sendAmount));
String usIso = Currency.getInstance(new Locale("en", "US")).getCurrencyCode();
CurrencyEntity currency = CurrencyDataSource.getInstance(app).getCurrencyByIso(usIso);
if (currency != null) {
doubleRate = currency.rate;
if (sendAmount < 1_398_000) {
return 69900;
}
double usdInLTC = sendAmountDouble * doubleRate / 100_000_000.0;
usdInLTC = Math.floor(usdInLTC * 100) / 100;

if (isBetween(usdInLTC, 0.00, 20.00)) {
double lowRate = usdInLTC * 0.01;
return (long) ((lowRate / doubleRate) * 100_000_000.0);
else if (sendAmount < 6_991_000) {
return 111_910;
}
else if (sendAmount < 27_965_000) {
return 279_700;
}
else if (isBetween(usdInLTC, 20.00, 50.00)) {
return (long) ((0.30 / doubleRate) * 100_000_000.0);
else if (sendAmount < 139_820_000) {
return 699_540;
}
else if (isBetween(usdInLTC, 50.00, 100.00)) {
return (long) ((1.00 / doubleRate) * 100_000_000.0);
}
else if (isBetween(usdInLTC, 100.00, 500.00)) {
return (long) ((2.00 / doubleRate) * 100_000_000.0);
else if (sendAmount < 279_653_600) {
return 1_049_300;
}
else if (isBetween(usdInLTC, 500.00, 1000.00)) {
return (long) ((2.50 / doubleRate) * 100_000_000.0);
else if (sendAmount < 699_220_000) {
return 1_398_800;
}
else if ( usdInLTC > 1000.00) {
return (long) ((3.00 / doubleRate) * 100_000_000.0);
else if (sendAmount < 1_398_440_000) {
return 2_797_600;
}
else {
return (long) ((3.00 / doubleRate) * 100_000_000.0);
return 2_797_600;
}
}
private static boolean isBetween(double x, double lower, double upper) {
Expand Down
Loading