From 71114605537f43dafe281da14fbd605f5ec18213 Mon Sep 17 00:00:00 2001 From: acrespo Date: Fri, 2 Aug 2024 19:13:09 -0300 Subject: [PATCH] Apollo: Release source code for 52.1 --- android/CHANGELOG.md | 21 ++ android/Dockerfile | 2 +- android/apollo/build.gradle | 15 +- .../data/analytics/AnalyticsProvider.kt | 2 +- .../java/io/muun/apollo/data/external/Gen.kt | 17 -- .../os/BackgroundExecutionMetricsProvider.kt | 12 +- .../apollo/data/os/DateTimeZoneProvider.kt | 37 +++ .../SecureStoragePreferences.java | 11 +- .../secure_storage/SecureStorageProvider.java | 4 + .../data/preferences/UserRepository.java | 75 +++-- .../SafeCurrencyUnitDeserializer.kt | 33 +++ .../serialization/SerializationUtils.java | 52 ++-- .../java/io/muun/apollo/domain/Flags.java | 2 - .../apollo/domain/action/LogoutActions.java | 9 +- .../operation/ResolveBitcoinUriAction.java | 24 +- .../operation/ResolveLnInvoiceAction.kt | 22 +- .../operation/ResolveMuunUriAction.java | 29 +- .../apollo/domain/libwallet/BitcoinUri.kt | 14 + .../domain/model/NextTransactionSize.java | 2 +- .../apollo/domain/model/PaymentRequest.kt | 25 -- .../muun/apollo/domain/model/user/User.java | 3 + .../domain/selector/LogoutOptionsSelector.kt | 18 +- .../domain/utils/DeprecatedCurrencyUnit.kt | 44 +++ .../io/muun/apollo/domain/utils/Extensions.kt | 8 + .../domain/action/OperationActionsTest.java | 34 +-- android/apolloui/build.gradle | 16 +- .../presentation/LoginAndSignUpTests.kt | 51 +++- .../muun/apollo/presentation/ReceiveTests.kt | 27 ++ .../muun/apollo/presentation/SettingsTests.kt | 37 ++- .../java/io/muun/apollo/utils/AutoFlows.kt | 136 ++++++++- .../utils/screens/ChangePasswordScreen.kt | 7 +- .../apollo/utils/screens/ReceiveScreen.kt | 176 +++++++++++- .../apollo/utils/screens/SettingsScreen.kt | 16 +- .../muun/apollo/utils/screens/SignInScreen.kt | 29 +- .../apollo/presentation/app/Navigator.java | 2 +- .../presentation/ui/base/BaseActivity.java | 2 +- .../presentation/ui/base/BaseFragment.java | 4 +- .../ek_save/EmergencyKitSaveFragment.kt | 2 +- .../ui/fragments/home/HomeFragment.kt | 4 +- .../fragments/manual_fee/ManualFeeFragment.kt | 31 -- .../ui/fragments/settings/SettingsFragment.kt | 4 +- .../fragments/settings/SettingsPresenter.kt | 4 +- .../ui/fragments/settings/SettingsView.java | 2 +- .../ui/launcher/LauncherPresenter.java | 4 + .../edit_password/ChangePasswordFragment.kt | 64 ++++- .../edit_password/ChangePasswordPresenter.kt | 7 +- .../edit_password/ChangePasswordView.java | 2 + .../presentation/ui/view/FeeManualInput.java | 18 +- .../res/layout/change_password_fragment.xml | 15 +- .../layout/manual_fee_selection_fragment.xml | 16 -- .../src/main/res/values-es/strings.xml | 2 +- .../apolloui/src/main/res/values/strings.xml | 2 +- build.gradle | 2 +- .../java/io/muun/common/api/SessionJson.java | 2 +- .../io/muun/common/api/error/ErrorCode.java | 4 +- .../io/muun/common/utils/Preconditions.java | 17 +- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 266 +++++++++++------- gradlew.bat | 37 +-- libwallet/go.mod | 11 +- libwallet/go.sum | 228 +++++---------- libwallet/newop/state.go | 15 +- libwallet/operation/payment_analyzer.go | 27 -- libwallet/operation/payment_analyzer_test.go | 167 ----------- tools/bootstrap-gomobile.sh | 2 +- tools/libwallet-android.sh | 15 +- 67 files changed, 1117 insertions(+), 873 deletions(-) create mode 100644 android/apollo/src/main/java/io/muun/apollo/data/os/DateTimeZoneProvider.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/data/serialization/SafeCurrencyUnitDeserializer.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/utils/DeprecatedCurrencyUnit.kt create mode 100644 android/apolloui/src/androidTest/java/io/muun/apollo/presentation/ReceiveTests.kt diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 228e10d7..775975ca 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,6 +6,27 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] +## [52.1] - 2024-08-02 + +### ADDED + +- Background notification processing reliability improvements +- Wallet delete checks client-side (e.g prevent it wallet not fully empty) + +### FIXED + +- Handling of missing or deprecated currencies +- Tiny text copy when updating emergency kit + +### CHANGED + +- Upgraded compiledSdkVersion and targetSdkVersion to 34 +- Upgraded go version to 1.21.11 +- Enhanced password input for change password flow (consistency with rest of the app) +- Enhanced error metadata for strange secure storage errors +- Removed never used "max fee" button and calculations +- Revamped to UI test suite. Enhancing reliability and coverage. + ## [52] - 2024-06-14 ### ADDED diff --git a/android/Dockerfile b/android/Dockerfile index 97a2826d..0781f849 100644 --- a/android/Dockerfile +++ b/android/Dockerfile @@ -3,7 +3,7 @@ FROM --platform=linux/amd64 openjdk:17-jdk-buster@sha256:9217da81dcff19e60861791 ENV NDK_VERSION 22.0.7026061 ENV ANDROID_PLATFORM_VERSION 28 ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3 -ENV GO_VERSION 1.18.1 +ENV GO_VERSION 1.21.11 RUN apt-get update \ && apt-get install --yes --no-install-recommends \ diff --git a/android/apollo/build.gradle b/android/apollo/build.gradle index 1340d203..a95528c2 100644 --- a/android/apollo/build.gradle +++ b/android/apollo/build.gradle @@ -21,20 +21,17 @@ apply from: "${project.rootDir}/linters/pmd/check-android.gradle" //apply from: "${project.rootDir}/linters/findbugs/check-android.gradle" android { - compileSdkVersion 31 + compileSdk 34 defaultConfig { - minSdkVersion 19 - targetSdkVersion 31 - versionCode 1 - versionName "1.0" + minSdk 19 + targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { minified { - debuggable true minifyEnabled true } } @@ -48,12 +45,12 @@ android { jvmTarget = JavaVersion.VERSION_1_8.toString() } - lintOptions { + lint { abortOnError true htmlReport true textReport true - lintConfig file("${project.rootDir}/linters/android-lint/config.xml") - baseline file("lint-baseline.xml") + lintConfig file("$rootDir/linters/android-lint/config.xml") + baseline file('lint-baseline.xml') } testOptions { diff --git a/android/apollo/src/main/java/io/muun/apollo/data/analytics/AnalyticsProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/analytics/AnalyticsProvider.kt index b2c5f5df..e453d3c5 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/analytics/AnalyticsProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/analytics/AnalyticsProvider.kt @@ -55,7 +55,7 @@ class AnalyticsProvider @Inject constructor(context: Context) { // Avoid recursion (Timber.i reports a breadcrumb). TODO proper design and fix this if (event !is AnalyticsEvent.E_BREADCRUMB) { - Timber.i("AnalyticsProvider", event.toString()) + Timber.i("AnalyticsProvider: $event") } } catch (t: Throwable) { diff --git a/android/apollo/src/main/java/io/muun/apollo/data/external/Gen.kt b/android/apollo/src/main/java/io/muun/apollo/data/external/Gen.kt index 75f22cf5..010cc851 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/external/Gen.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/external/Gen.kt @@ -292,23 +292,6 @@ object Gen { private fun muunAddress() = MuunAddress(1, "m/1/2/3", address()) - /** - * Get a PaymentRequest - */ - fun payReq( - amount: MonetaryAmount = Money.of(0, "USD"), - feeRate: Double = 10.0, - takeFeeFromAmount: Boolean = false, - - ) = PaymentRequest( - type = PaymentRequest.Type.TO_ADDRESS, - amount = amount, - description = "foo", - address = address(), - feeInSatoshisPerByte = feeRate, - takeFeeFromAmount = takeFeeFromAmount - ) - fun submarineSwap( outputAmountInSatoshis: Long, sweepFeeInSatoshis: Long = 0, diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt index 6241e0f1..16b8e008 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt @@ -11,7 +11,6 @@ import io.muun.apollo.data.net.ConnectivityInfoProvider import io.muun.apollo.data.net.NetworkInfoProvider import kotlinx.serialization.Serializable import java.util.Locale -import java.util.TimeZone import javax.inject.Inject private const val UNSUPPORTED = -1 @@ -27,6 +26,7 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val activityManagerInfoProvider: ActivityManagerInfoProvider, private val resourcesInfoProvider: ResourcesInfoProvider, private val systemCapabilitiesProvider: SystemCapabilitiesProvider, + private val dateTimeZoneProvider: DateTimeZoneProvider, ) { private val powerManager: PowerManager by lazy { @@ -54,7 +54,7 @@ class BackgroundExecutionMetricsProvider @Inject constructor( SystemClock.elapsedRealtime(), hardwareCapabilitiesProvider.bootCount, Locale.getDefault().toString(), - TimeZone.getDefault().rawOffset / 1000L, + dateTimeZoneProvider.timeZoneOffsetSeconds, telephonyInfoProvider.region.orElse(""), telephonyInfoProvider.simRegion, appInfoProvider.appDatadir, @@ -68,7 +68,10 @@ class BackgroundExecutionMetricsProvider @Inject constructor( systemCapabilitiesProvider.developerEnabled, connectivityInfoProvider.proxyHttp, connectivityInfoProvider.proxyHttps, - connectivityInfoProvider.proxySocks + connectivityInfoProvider.proxySocks, + dateTimeZoneProvider.autoDateTime, + dateTimeZoneProvider.autoTimeZone, + dateTimeZoneProvider.timeZoneId ) @Suppress("ArrayInDataClass") @@ -108,6 +111,9 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val proxyHttp: String, private val proxyHttps: String, private val proxySocks: String, + private val autoDateTime: Int, + private val autoTimeZone: Int, + private val timeZoneId: String, ) /** diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/DateTimeZoneProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/DateTimeZoneProvider.kt new file mode 100644 index 00000000..281b3696 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/DateTimeZoneProvider.kt @@ -0,0 +1,37 @@ +package io.muun.apollo.data.os + +import android.content.Context +import android.provider.Settings +import java.util.TimeZone +import javax.inject.Inject + +class DateTimeZoneProvider @Inject constructor(private val context: Context) { + + val autoTimeZone: Int + get() { + return Settings.Global.getInt( + context.contentResolver, + Settings.Global.AUTO_TIME_ZONE, + -1 + ) + } + + val autoDateTime: Int + get() { + return Settings.Global.getInt( + context.contentResolver, + Settings.Global.AUTO_TIME, + -1 + ) + } + + val timeZoneId: String + get() { + return TimeZone.getDefault().id.take(100) + } + + val timeZoneOffsetSeconds: Long + get() { + return TimeZone.getDefault().rawOffset / 1000L + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStoragePreferences.java b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStoragePreferences.java index 103d5fe8..6f9bf591 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStoragePreferences.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStoragePreferences.java @@ -77,6 +77,7 @@ public byte[] getAesIv(String key) { */ public synchronized byte[] getPersistentSecureRandomBytes(String key, int size) { if (sharedPreferences.contains(key)) { + Timber.i("getPersistentSecureRandomBytes for " + key + ". Cached."); final byte[] iv = getBytes(key); // We've had a few InvalidKeyExceptions that might come from invalid IVs @@ -90,6 +91,7 @@ public synchronized byte[] getPersistentSecureRandomBytes(String key, int size) return iv; } else { + Timber.i("getPersistentSecureRandomBytes for " + key + ". Generate new"); final byte[] bytes = RandomGenerator.getBytes(size); saveBytes(bytes, key); return bytes; @@ -102,7 +104,14 @@ public synchronized byte[] getPersistentSecureRandomBytes(String key, int size) public void saveBytes(byte[] bytes, String key) { initSecureStorage(); - sharedPreferences.edit().putString(key, SerializationUtils.serializeBytes(bytes)).commit(); + final boolean writeSuccess = sharedPreferences.edit() + .putString(key, SerializationUtils.serializeBytes(bytes)) + .commit(); + + Timber.i("SaveBytes for " + key + " success:" + writeSuccess ); + if (!writeSuccess) { + Timber.e("Error while committing write to secure storage preferences"); + } } /** diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java index 3d0ce9cc..65fee134 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java @@ -123,6 +123,9 @@ public boolean has(String key) { // our error report infra offers more metadata/insights on this issue if (hasKeyInPreferences != hasKeyInKeystore) { final SecureStorageError error = new SecureStorageError(debugSnapshot()); + error.addMetadata("key", key); + error.addMetadata("hasKeyInPreferences", hasKeyInPreferences); + error.addMetadata("hasKeyInKeystore", hasKeyInKeystore); Timber.e(error); throw error; } @@ -196,6 +199,7 @@ private void storeEncrypted(String key, byte[] input) { preferences.saveBytes(keyStore.encryptData(input, key, preferences.getAesIv(key)), key); } catch (Throwable e) { Timber.i("SecureStorageError on WRITE for key: " + key); + Timber.e(e); final SecureStorageError ssError = new SecureStorageError(e, debugSnapshot()); enhanceError(ssError, key); throw ssError; diff --git a/android/apollo/src/main/java/io/muun/apollo/data/preferences/UserRepository.java b/android/apollo/src/main/java/io/muun/apollo/data/preferences/UserRepository.java index e47e3907..14807b8c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/preferences/UserRepository.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/preferences/UserRepository.java @@ -12,6 +12,7 @@ import io.muun.apollo.domain.model.user.UserPhoneNumber; import io.muun.apollo.domain.model.user.UserProfile; import io.muun.common.Optional; +import io.muun.common.model.Currency; import io.muun.common.model.PhoneNumber; import io.muun.common.utils.Preconditions; @@ -32,6 +33,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; +import javax.money.CurrencyUnit; @Singleton public class UserRepository extends BaseRepository { @@ -487,26 +489,37 @@ public static class StoredUserJson { // and migrate the preference to a non-minified JSON this class is APPEND-ONLY. public long hid; + public String email; + public String createdAt; public String phoneNumber; + public boolean isPhoneNumberVerified; public String firstName; + public String lastName; + public String profilePictureUrl; public boolean isEmailVerified; + public boolean hasRecoveryCode; + public boolean hasPassword; + public boolean hasP2PEnabled; + public boolean hasExportedKeys; public String currency; public String emergencyKitLastExportedAt; + public Integer emergencyKitVersion; + public String emergencyKitExportMethod; @NonNull // Not backed by Houston, cached locally @@ -551,25 +564,27 @@ public StoredUserJson() { /** * Manual constructor. */ - public StoredUserJson(long hid, - String email, - String createdAt, - String phoneNumber, - boolean isPhoneNumberVerified, - String firstName, - String lastName, - String profilePictureUrl, - boolean isEmailVerified, - boolean hasRecoveryCode, - boolean hasPassword, - boolean hasP2PEnabled, - boolean hasExportedKeys, - String currency, - String emergencyKitLastExportedAt, - Integer emergencyKitVersion, - EmergencyKitExport.Method emergencyKitExportMethod, - @NonNull StoredEkVerificationCodes ekVerificationCodes, - @NonNull List ekVersions) { + public StoredUserJson( + long hid, + String email, + String createdAt, + String phoneNumber, + boolean isPhoneNumberVerified, + String firstName, + String lastName, + String profilePictureUrl, + boolean isEmailVerified, + boolean hasRecoveryCode, + boolean hasPassword, + boolean hasP2PEnabled, + boolean hasExportedKeys, + String currency, + String emergencyKitLastExportedAt, + Integer emergencyKitVersion, + EmergencyKitExport.Method emergencyKitExportMethod, + @NonNull StoredEkVerificationCodes ekVerificationCodes, + @NonNull List ekVersions + ) { this.hid = hid; this.email = email; @@ -608,7 +623,7 @@ User toUser() { ? Optional.of(new UserProfile(firstName, lastName, profilePictureUrl)) : Optional.empty(), - SerializationUtils.deserializeCurrencyUnit(currency != null ? currency : "USD"), + loadCurrencyFromStorage(), hasRecoveryCode, hasPassword, @@ -626,6 +641,18 @@ User toUser() { ); } + private CurrencyUnit loadCurrencyFromStorage() { + try { + final String currencyCode = currency != null ? currency : "USD"; + return SerializationUtils.deserializeCurrencyUnit(currencyCode); + + } catch (Exception e) { + // This can happen for example if user primary currency is no longer supported + // after an app or OS update. + return Currency.getUnit(Currency.DEFAULT.getCode()).get(); + } + } + void initEmergencyKitVersion() { if (emergencyKitLastExportedAt != null) { emergencyKitVersion = (int) Libwallet.EKVersionDescriptors; @@ -683,9 +710,11 @@ public StoredUserJson get(@NonNull String key, @NonNull SharedPreferences prefer } @Override - public void set(@NonNull String key, - @NonNull StoredUserJson value, - @NonNull SharedPreferences.Editor editor) { + public void set( + @NonNull String key, + @NonNull StoredUserJson value, + @NonNull SharedPreferences.Editor editor + ) { super.set(key, value, editor); } } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/serialization/SafeCurrencyUnitDeserializer.kt b/android/apollo/src/main/java/io/muun/apollo/data/serialization/SafeCurrencyUnitDeserializer.kt new file mode 100644 index 00000000..058f84a1 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/serialization/SafeCurrencyUnitDeserializer.kt @@ -0,0 +1,33 @@ +package io.muun.apollo.data.serialization + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import io.muun.apollo.domain.errors.MissingCurrencyError +import io.muun.apollo.domain.utils.DeprecatedCurrencyUnit +import timber.log.Timber +import java.io.IOException +import javax.money.CurrencyUnit +import javax.money.Monetary +import javax.money.UnknownCurrencyException + +class SafeCurrencyUnitDeserializer : JsonDeserializer() { + + @Throws(IOException::class) + override fun deserialize(parser: JsonParser, context: DeserializationContext): CurrencyUnit { + val currencyCode = parser.valueAsString + + if (Monetary.isCurrencyAvailable(currencyCode)) { + return Monetary.getCurrency(currencyCode) + + } else { + // In practice, only this type of error should arise. + Timber.e(MissingCurrencyError(UnknownCurrencyException(currencyCode))) + + // This can happen for example if user primary currency is no longer supported + // after an app or OS update, or if user has changed to a device that no longer supports + // their primary currency. + return DeprecatedCurrencyUnit(currencyCode) + } + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/serialization/SerializationUtils.java b/android/apollo/src/main/java/io/muun/apollo/data/serialization/SerializationUtils.java index 11e72515..2c1fc596 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/serialization/SerializationUtils.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/serialization/SerializationUtils.java @@ -8,6 +8,7 @@ import io.muun.apollo.domain.errors.data.MuunDeserializationError; import io.muun.apollo.domain.model.BitcoinAmount; import io.muun.apollo.domain.utils.DateUtils; +import io.muun.apollo.domain.utils.DeprecatedCurrencyUnit; import io.muun.common.dates.MuunZonedDateTime; import io.muun.common.model.PhoneNumber; @@ -63,11 +64,13 @@ public final class SerializationUtils { .addDeserializer(PhoneNumber.class, new PhoneNumberDeserializer()) .addSerializer(BitcoinAmount.class, new BitcoinAmountSerializer()) - .addDeserializer(BitcoinAmount.class, new BitcoinAmountDeserializer()); + .addDeserializer(BitcoinAmount.class, new BitcoinAmountDeserializer()) + + .addDeserializer(CurrencyUnit.class, new SafeCurrencyUnitDeserializer()); JSON_MAPPER = new ObjectMapper() - .registerModule(simpleModule) - .registerModule(new MoneyModule()); + .registerModule(new MoneyModule()) + .registerModule(simpleModule); // Last so our custom deserializers take precedence // Allows unknown Enum values to be ignored and a predefined value specified through // @JsonEnumDefaultValue annotation. If enabled, but no predefined default Enum value is @@ -90,8 +93,10 @@ public static > String serializeEnum(@NotNull T enumValue) { * Deserialize an enum. */ @NotNull - public static > T deserializeEnum(@NotNull Class enumClass, - @NotNull String enumString) { + public static > T deserializeEnum( + @NotNull Class enumClass, + @NotNull String enumString + ) { return Enum.valueOf(enumClass, enumString); } @@ -131,8 +136,10 @@ public static String serializeJson(@NotNull Class jsonType, @NotNull T js * Serialize a class to JSON. */ @NotNull - public static String serializeJson(@NotNull TypeReference jsonType, - @NotNull T jsonValue) { + public static String serializeJson( + @NotNull TypeReference jsonType, + @NotNull T jsonValue + ) { try { return JSON_MAPPER.writerFor(jsonType).writeValueAsString(jsonValue); @@ -158,8 +165,10 @@ public static T deserializeJson(@NotNull Class jsonType, @NotNull String * Deserialize a class from JSON. */ @NotNull - public static T deserializeJson(@NotNull TypeReference jsonType, - @NotNull String jsonString) { + public static T deserializeJson( + @NotNull TypeReference jsonType, + @NotNull String jsonString + ) { try { return JSON_MAPPER.readValue(jsonString, jsonType); @@ -214,7 +223,6 @@ public static String serializeCurrencyUnit(@NotNull CurrencyUnit currencyValue) */ @NotNull public static CurrencyUnit deserializeCurrencyUnit(@NotNull String currencyString) { - try { return Monetary.getCurrency(currencyString); @@ -253,11 +261,19 @@ public static MonetaryAmount deserializeMonetaryAmount(@NotNull String moneyStri } final BigDecimal number = deserializeBigDecimal(parts[0]); - final CurrencyUnit currency = deserializeCurrencyUnit(parts[1]); - + final CurrencyUnit currency = safeDeserializeCurrencyUnit(parts[1]); return Money.of(number, currency); } + private static CurrencyUnit safeDeserializeCurrencyUnit(@NotNull String currencyString) { + if (!Monetary.isCurrencyAvailable(currencyString)) { + return new DeprecatedCurrencyUnit(currencyString); + + } else { + return deserializeCurrencyUnit(currencyString); + } + } + /** * Serialize a BitcoinAmount. */ @@ -282,8 +298,8 @@ public static BitcoinAmount deserializeBitcoinAmount(@NotNull String string) { } final Long inSatoshis = Long.valueOf(parts[0]); - final MonetaryAmount inInputCurrency = deserializeMonetaryAmount(parts[1]); - final MonetaryAmount inPrimaryCurrency = deserializeMonetaryAmount(parts[2]); + final MonetaryAmount inInputCurrency = deserializeMonetaryAmount(parts[1]); + final MonetaryAmount inPrimaryCurrency = deserializeMonetaryAmount(parts[2]); return new BitcoinAmount(inSatoshis, inInputCurrency, inPrimaryCurrency); } @@ -335,9 +351,11 @@ public static String serializeMap(Class keyType, Class valueType, M /** * Deserialize a list of objects from a JSON array. */ - public static Map deserializeMap(Class keyType, - Class valueType, - String json) { + public static Map deserializeMap( + Class keyType, + Class valueType, + String json + ) { try { return JSON_MAPPER .readerFor(TYPE_FACTORY.constructMapType(Map.class, keyType, valueType)) diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/Flags.java b/android/apollo/src/main/java/io/muun/apollo/domain/Flags.java index 760918e3..4e116603 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/Flags.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/Flags.java @@ -5,6 +5,4 @@ */ public class Flags { - public static final boolean USE_MAXIMUM_FEE_ENABLED = false; - } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/LogoutActions.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/LogoutActions.java index 1baa678e..5b8d6517 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/LogoutActions.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/LogoutActions.java @@ -100,11 +100,14 @@ public void destroyRecoverableWallet() { } /** - * Wipe all user associated data from the app (unrecoverable only). + * Wipe all user associated data from the app. + * Note: if user is unrecoverable or recoverable user has performed "delete wallet" this + * action is irreversible (and its intended to be). We're naming this "dangerously" because + * callers should be careful when calling this. */ - public void dangerouslyDestroyUnrecoverableWallet() { + public void dangerouslyDestroyWallet() { final LogoutOptions logoutOptions = logoutOptionsSel.get(); - Preconditions.checkState(!logoutOptions.isBlocked()); // just checking + Preconditions.checkState(logoutOptions.canDeleteWallet()); // just checking destroyWallet(); } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveBitcoinUriAction.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveBitcoinUriAction.java index fa18bf3b..223bb2b5 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveBitcoinUriAction.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveBitcoinUriAction.java @@ -1,34 +1,27 @@ package io.muun.apollo.domain.action.operation; -import io.muun.apollo.data.preferences.FeeWindowRepository; import io.muun.apollo.domain.action.base.BaseAsyncAction1; import io.muun.apollo.domain.errors.newop.InvalidPaymentRequestError; import io.muun.apollo.domain.libwallet.LibwalletBridge; import io.muun.apollo.domain.model.BitcoinUriContent; -import io.muun.apollo.domain.model.FeeWindow; import io.muun.apollo.domain.model.OperationUri; import io.muun.apollo.domain.model.PaymentRequest; -import io.muun.apollo.domain.utils.StringUtils; -import io.muun.common.utils.BitcoinUtils; import rx.Observable; import javax.inject.Inject; import javax.inject.Singleton; -import javax.money.MonetaryAmount; @Singleton public class ResolveBitcoinUriAction extends BaseAsyncAction1 { - private final FeeWindowRepository feeWindowRepository; /** * Resolves a Bitcoin URI, using BIP-72 or BIP-21 as appropriate. */ @Inject - public ResolveBitcoinUriAction(FeeWindowRepository feeWindowRepository) { + public ResolveBitcoinUriAction() { - this.feeWindowRepository = feeWindowRepository; } @Override @@ -44,22 +37,9 @@ public Observable action(OperationUri operationUri) { } private PaymentRequest resolveBitcoinUri(OperationUri uri) { - final FeeWindow feeWindow = feeWindowRepository.fetchOne(); - final BitcoinUriContent uriContent = LibwalletBridge.getBitcoinUriContent(uri); - final MonetaryAmount amount = (uriContent.amountInStatoshis != null) - ? BitcoinUtils.satoshisToBitcoins(uriContent.amountInStatoshis) - : null; - - final String description = StringUtils.joinText(": ", new String[]{ - uriContent.merchant, - uriContent.memo - }); - - final double feeRate = feeWindow.getFastestFeeInSatoshisPerByte(); - - return PaymentRequest.toAddress(uriContent.address, amount, description, feeRate); + return PaymentRequest.toAddress(uriContent.address); } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt index 07b84ad6..fe0d008c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt @@ -3,7 +3,6 @@ package io.muun.apollo.domain.action.operation import androidx.annotation.VisibleForTesting import io.muun.apollo.data.net.HoustonClient import io.muun.apollo.data.preferences.BackgroundTimesRepository -import io.muun.apollo.data.preferences.FeeWindowRepository import io.muun.apollo.data.preferences.KeysRepository import io.muun.apollo.domain.action.base.BaseAsyncAction2 import io.muun.apollo.domain.analytics.NewOperationOrigin @@ -12,14 +11,12 @@ import io.muun.apollo.domain.errors.newop.InvoiceExpiredException import io.muun.apollo.domain.libwallet.DecodedInvoice import io.muun.apollo.domain.libwallet.Invoice.decodeInvoice import io.muun.apollo.domain.model.PaymentRequest -import io.muun.apollo.domain.model.PaymentRequest.Companion.toLnInvoice import io.muun.apollo.domain.model.SubmarineSwap import io.muun.apollo.domain.model.SubmarineSwapRequest import io.muun.apollo.domain.utils.DateUtils import io.muun.common.api.SubmarineSwapJson import io.muun.common.crypto.hd.PublicKey import io.muun.common.crypto.hd.PublicKeyPair -import io.muun.common.utils.BitcoinUtils import io.muun.common.utils.Encodings import io.muun.common.utils.Hashes import io.muun.common.utils.LnInvoice @@ -45,14 +42,12 @@ import org.bitcoinj.script.ScriptOpCodes.OP_SWAP import rx.Observable import javax.inject.Inject import javax.inject.Singleton -import javax.money.MonetaryAmount @Singleton class ResolveLnInvoiceAction @Inject internal constructor( private val network: NetworkParameters, private val houstonClient: HoustonClient, private val keysRepository: KeysRepository, - private val feeWindowRepository: FeeWindowRepository, private val backgroundTimesRepository: BackgroundTimesRepository ) : BaseAsyncAction2() { @@ -99,9 +94,6 @@ class ResolveLnInvoiceAction @Inject internal constructor( } private fun buildPaymentRequest(invoice: DecodedInvoice, swap: SubmarineSwap): PaymentRequest { - val feeWindow = feeWindowRepository.fetchOne() - val amount = getInvoiceAmount(invoice) - if (!swap.isLend) { validateNonLendSwap(invoice, swap) } @@ -110,14 +102,9 @@ class ResolveLnInvoiceAction @Inject internal constructor( throw InvalidSwapException(swap.houstonUuid) } - // For AmountLess Invoices, fee rate is initially unknown - val feeRate = if (invoice.amountInSat != null) feeWindow.getFeeRate(swap) else null - return toLnInvoice( + return PaymentRequest.toLnInvoice( invoice, - amount, - invoice.description, swap, - feeRate ) } @@ -143,13 +130,6 @@ class ResolveLnInvoiceAction @Inject internal constructor( } } - private fun getInvoiceAmount(invoice: DecodedInvoice): MonetaryAmount? { - return if (invoice.amountInSat != null) { - BitcoinUtils.satoshisToBitcoins(invoice.amountInSat) - } else - null - } - /** * Create a new Submarine Swap. */ diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveMuunUriAction.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveMuunUriAction.java index 7119ceb1..b823a004 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveMuunUriAction.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveMuunUriAction.java @@ -1,45 +1,38 @@ package io.muun.apollo.domain.action.operation; import io.muun.apollo.data.db.contact.ContactDao; -import io.muun.apollo.data.preferences.FeeWindowRepository; import io.muun.apollo.data.preferences.UserRepository; import io.muun.apollo.domain.action.base.BaseAsyncAction1; import io.muun.apollo.domain.model.Contact; import io.muun.apollo.domain.model.ExchangeRateWindow; -import io.muun.apollo.domain.model.FeeWindow; import io.muun.apollo.domain.model.OperationUri; import io.muun.apollo.domain.model.PaymentRequest; import io.muun.apollo.domain.model.user.User; import io.muun.apollo.domain.selector.ExchangeRateSelector; -import org.javamoney.moneta.Money; import rx.Observable; -import java.math.BigDecimal; import javax.inject.Inject; import javax.inject.Singleton; -import javax.money.MonetaryAmount; @Singleton public class ResolveMuunUriAction extends BaseAsyncAction1 { private final UserRepository userRepository; private final ContactDao contactDao; - private final FeeWindowRepository feeWindowRepository; private final ExchangeRateSelector rateSelector; /** * Resolves a Muun URI, fetching User and/or Contact as needed. */ @Inject - public ResolveMuunUriAction(UserRepository userRepository, - ContactDao contactDao, - FeeWindowRepository feeWindowRepository, - ExchangeRateSelector rateSelector) { - + public ResolveMuunUriAction( + UserRepository userRepository, + ContactDao contactDao, + ExchangeRateSelector rateSelector + ) { this.userRepository = userRepository; this.contactDao = contactDao; - this.feeWindowRepository = feeWindowRepository; this.rateSelector = rateSelector; } @@ -50,7 +43,6 @@ public Observable action(OperationUri operationUri) { private PaymentRequest resolveMuunUri(OperationUri uri) { final User user = userRepository.fetchOne(); - final FeeWindow feeWindow = feeWindowRepository.fetchOne(); // TODO: this could cause unexpected behaviour since it may not be same rate window as // the one used in paymentContext. We've seen rates for some currencies suddenly being // dropped which may cause trouble if the primary currency is one of them. @@ -66,13 +58,6 @@ private PaymentRequest resolveMuunUri(OperationUri uri) { final String descriptionParam = uri.getParam(OperationUri.MUUN_DESCRIPTION) .orElse(""); - final MonetaryAmount amount = Money.of( - new BigDecimal(amountParam), - currencyParam.toUpperCase() - ); - - final double feeRate = feeWindow.getFastestFeeInSatoshisPerByte(); - switch (uri.getHost()) { case OperationUri.MUUN_HOST_CONTACT: final Contact contact = contactDao @@ -80,11 +65,11 @@ private PaymentRequest resolveMuunUri(OperationUri uri) { .toBlocking() .first(); - return PaymentRequest.toContact(contact, amount, descriptionParam, feeRate); + return PaymentRequest.toContact(contact); case OperationUri.MUUN_HOST_EXTERNAL: final String externalAddress = uri.getExternalAddress(); - return PaymentRequest.toAddress(externalAddress, amount, descriptionParam, feeRate); + return PaymentRequest.toAddress(externalAddress); default: throw new IllegalArgumentException("Invalid host: " + uri.getHost()); diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/BitcoinUri.kt b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/BitcoinUri.kt index 23d465a3..4fbee3d9 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/BitcoinUri.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/BitcoinUri.kt @@ -1,6 +1,7 @@ package io.muun.apollo.domain.libwallet import androidx.annotation.VisibleForTesting +import io.muun.apollo.data.external.Globals import io.muun.apollo.domain.model.BitcoinAmount import io.muun.common.utils.BitcoinUtils import libwallet.Libwallet @@ -31,4 +32,17 @@ object BitcoinUri { .number .numberValue(BigDecimal::class.java) .toPlainString() // Avoid scientific notation for golang to parse/compare smoothly + + /** + * This is a utility method meant EXCLUSIVELY for testing. It's required since UiTests can't + * directly call Libwallet's go code yet. + * Note: DO NOT use in main code. + */ + @VisibleForTesting + fun parse(rawBip21Uri: String): Triple { + val bitcoinUri = io.muun.common.bitcoinj.BitcoinUri(Globals.INSTANCE.network, rawBip21Uri) + val invoice = bitcoinUri.getParameterByName("lightning") as String + return Triple(bitcoinUri.address!!, bitcoinUri.amount?.value, invoice) + } + } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java b/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java index d2037020..2ad67a31 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java @@ -58,7 +58,7 @@ public long getUtxoBalance() { * Get the spendable balance (considering debt). */ public long getUserBalance() { - return Preconditions.checkNonNegative(getUtxoBalance() - getExpectedDebtInSat()); + return Preconditions.checkNotNegative(getUtxoBalance() - getExpectedDebtInSat()); } /** diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/PaymentRequest.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/PaymentRequest.kt index 75461263..b3edf05b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/PaymentRequest.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/PaymentRequest.kt @@ -2,18 +2,12 @@ package io.muun.apollo.domain.model import io.muun.apollo.domain.libwallet.DecodedInvoice import io.muun.common.utils.Preconditions -import javax.money.MonetaryAmount data class PaymentRequest( val type: Type, - val amount: MonetaryAmount? = null, - val description: String? = null, val contact: Contact? = null, val address: String? = null, - val invoice: DecodedInvoice? = null, val swap: SubmarineSwap? = null, - val feeInSatoshisPerByte: Double?, //initially null for AmountLess Invoice - val takeFeeFromAmount: Boolean = false, ) { enum class Type { @@ -28,19 +22,13 @@ data class PaymentRequest( @JvmStatic fun toContact( contact: Contact, - amount: MonetaryAmount, - description: String, - feeInSatoshisPerByte: Double, ): PaymentRequest { Preconditions.checkNotNull(contact) return PaymentRequest( Type.TO_CONTACT, - amount = amount, - description = description, contact = contact, - feeInSatoshisPerByte = feeInSatoshisPerByte ) } @@ -48,19 +36,13 @@ data class PaymentRequest( @JvmStatic fun toAddress( address: String, - amount: MonetaryAmount?, - description: String?, - feeInSatoshisPerByte: Double, ): PaymentRequest { Preconditions.checkNotNull(address) return PaymentRequest( Type.TO_ADDRESS, - amount = amount, - description = description, address = address, - feeInSatoshisPerByte = feeInSatoshisPerByte ) } @@ -68,21 +50,14 @@ data class PaymentRequest( @JvmStatic fun toLnInvoice( invoice: DecodedInvoice, - amount: MonetaryAmount?, - description: String, submarineSwap: SubmarineSwap, - feeInSatoshisPerByte: Double?, ): PaymentRequest { Preconditions.checkNotNull(invoice) return PaymentRequest( Type.TO_LN_INVOICE, - amount = amount, - description = description, - invoice = invoice, swap = submarineSwap, - feeInSatoshisPerByte = feeInSatoshisPerByte ) } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/user/User.java b/android/apollo/src/main/java/io/muun/apollo/domain/model/user/User.java index ff999cea..11ea5d43 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/user/User.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/user/User.java @@ -169,7 +169,10 @@ public CurrencyUnit getPrimaryCurrency(ExchangeRateProvider rateProvider) { // flawed. It will (strangely) return true when there's no rate for certain currencies. if (rateProvider.getCurrencies().contains(targetCurrency)) { return targetCurrency; + } else { + // TODO this "defaulting" to btc is contrary to our defaulting to usd elsewhere + // We should probably unify behavior. return Currency.getUnit("BTC").get(); } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/selector/LogoutOptionsSelector.kt b/android/apollo/src/main/java/io/muun/apollo/domain/selector/LogoutOptionsSelector.kt index 2588463d..d111415c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/selector/LogoutOptionsSelector.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/selector/LogoutOptionsSelector.kt @@ -1,28 +1,30 @@ package io.muun.apollo.domain.selector import io.muun.common.model.OperationStatus +import io.muun.common.utils.Preconditions import rx.Observable import javax.inject.Inject class LogoutOptionsSelector @Inject constructor( private val userSel: UserSelector, private val paymentContextSel: PaymentContextSelector, - private val operationSel: OperationSelector + private val operationSel: OperationSelector, ) { class LogoutOptions( private val isRecoverable: Boolean, private val hasBalance: Boolean, private val hasUnsettledOps: Boolean, - private val hasPendingIncomingSwaps: Boolean + private val hasPendingIncomingSwaps: Boolean, ) { - fun isBlocked(): Boolean { - return if (isRecoverable) { - hasPendingIncomingSwaps - } else { - hasBalance || hasUnsettledOps - } + fun isLogoutBlocked(): Boolean { + Preconditions.checkArgument(isRecoverable) + return hasPendingIncomingSwaps + } + + fun canDeleteWallet(): Boolean { + return !hasBalance && !hasUnsettledOps } fun isRecoverable(): Boolean { diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/utils/DeprecatedCurrencyUnit.kt b/android/apollo/src/main/java/io/muun/apollo/domain/utils/DeprecatedCurrencyUnit.kt new file mode 100644 index 00000000..562c7f63 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/utils/DeprecatedCurrencyUnit.kt @@ -0,0 +1,44 @@ +package io.muun.apollo.domain.utils + +import javax.money.CurrencyContext +import javax.money.CurrencyContextBuilder +import javax.money.CurrencyUnit + +/** + * This is our "placeholder" ad-hoc CurrencyUnit implementation. It helps us in the edge case where + * a user's primary currency is no longer supported (by the Moneta lib) after an app or OS update, + * or if user has changed to a device that no longer supports their primary currency. Notice this + * situation also affects operation in user's operation history (e.g an operation amount is recorded + * in btc and the user's primary currency at the time of the operation). + * + * Note: main goal for this class is to be able to distinguish a primary currency when its + * deprecated (via instance of) and to still be able to display amounts in + * {@link #wrappedCurrencyCode} in the user's operation history. + */ +class DeprecatedCurrencyUnit(private val wrappedCurrencyCode: String) : CurrencyUnit { + + override fun compareTo(other: CurrencyUnit?): Int { + if (other == null) { + return -1 + } + + // wrappedCurrencyCode is deprecated so we shouldn't find it "in the wild" + return wrappedCurrencyCode.compareTo(other.currencyCode) + } + + override fun getCurrencyCode(): String = + wrappedCurrencyCode + + override fun getNumericCode(): Int = + 0 + + /** + * Using 0 default fraction digits as we expect currencies to be deprecated to be high + * inflationary or otherwise very devalued. + */ + override fun getDefaultFractionDigits(): Int = + 0 + + override fun getContext(): CurrencyContext = + CurrencyContextBuilder.of("FAKE_PROVIDER").build() +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt b/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt index 8e02bbce..fff4230f 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt @@ -24,6 +24,7 @@ import java.util.concurrent.TimeoutException import javax.crypto.BadPaddingException import javax.money.Monetary import javax.money.MonetaryException +import javax.money.UnknownCurrencyException fun Observable.toVoid(): Observable = map(RxHelper::toVoid) @@ -54,6 +55,13 @@ fun T.applyArgs(f: Bundle.() -> Unit) = arguments = (arguments ?: Bundle()).apply(f) } + +/** + * Needed as inline reified functions can't be called from Java. + */ +fun Throwable.isInstanceOrIsCausedByUnknownCurrencyException() = + isInstanceOrIsCausedByError() + /** * Needed as inline reified functions can't be called from Java. */ diff --git a/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java b/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java index 52f27161..bea2283b 100644 --- a/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java +++ b/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java @@ -9,7 +9,6 @@ import io.muun.apollo.domain.action.operation.OperationMetadataMapper; import io.muun.apollo.domain.action.operation.SubmitPaymentAction; import io.muun.apollo.domain.model.Contact; -import io.muun.apollo.domain.model.ExchangeRateWindow; import io.muun.apollo.domain.model.Operation; import io.muun.apollo.domain.model.PaymentRequest; import io.muun.apollo.domain.model.PreparedPayment; @@ -18,9 +17,7 @@ import io.muun.common.crypto.hd.MuunAddress; import io.muun.common.crypto.hd.PrivateKey; import io.muun.common.crypto.hd.PublicKey; -import io.muun.common.model.ExchangeRateProvider; import io.muun.common.model.OperationDirection; -import io.muun.common.utils.BitcoinUtils; import androidx.core.util.Pair; import br.com.six2six.fixturefactory.Fixture; @@ -33,7 +30,6 @@ import rx.Observable; import java.util.List; -import javax.money.MonetaryAmount; import static io.muun.apollo.TestUtils.fetchItemFromObservable; import static org.assertj.core.api.Assertions.assertThat; @@ -106,8 +102,6 @@ public void fetchReplaceOperations() { @Test public void buildPaymentToContact() { - final ExchangeRateWindow rates = Fixture.from(ExchangeRateWindow.class).gimme("valid"); - final Contact contact = Fixture.from(Contact.class).gimme("valid"); final PublicKey publicKey = contact.publicKey; final MuunAddress contactAddress = new MuunAddress( @@ -118,10 +112,7 @@ public void buildPaymentToContact() { final long someFee = 123456; final PaymentRequest payReq = PaymentRequest.toContact( - contact, - TemplateHelpers.money().generateValue(), - "some description", - 10.0 + contact ); doReturn(contact.publicProfile) @@ -156,14 +147,6 @@ public void buildPaymentToContact() { contactAddress.getDerivationPath() ); - // check amount - final ExchangeRateProvider provider = new ExchangeRateProvider(rates.toJson()); - final MonetaryAmount inBtc = payReq.getAmount().with(provider.getCurrencyConversion("BTC")); - final long inSatoshis = BitcoinUtils.bitcoinsToSatoshis(inBtc); - - assertThat(operation.amount.inInputCurrency).isEqualTo(payReq.getAmount()); - assertThat(operation.amount.inSatoshis).isEqualTo(inSatoshis); - // check fee assertThat(operation.fee.inSatoshis).isEqualTo(someFee); } @@ -171,13 +154,8 @@ public void buildPaymentToContact() { @Test public void buildPaymentToAddress() { - final ExchangeRateWindow rates = Fixture.from(ExchangeRateWindow.class).gimme("valid"); - final PaymentRequest payReq = PaymentRequest.toAddress( - TemplateHelpers.address().generateValue(), - TemplateHelpers.money().generateValue(), - "some description", - 10.0 + TemplateHelpers.address().generateValue() ); final long someFee = 123456; @@ -205,14 +183,6 @@ public void buildPaymentToAddress() { assertThat(operation.receiverAddress).isEqualTo(payReq.getAddress()); assertThat(operation.receiverAddressDerivationPath).isEqualTo(null); - // check amount - final ExchangeRateProvider provider = new ExchangeRateProvider(rates.toJson()); - final MonetaryAmount inBtc = payReq.getAmount().with(provider.getCurrencyConversion("BTC")); - final long inSatoshis = BitcoinUtils.bitcoinsToSatoshis(inBtc); - - assertThat(operation.amount.inInputCurrency).isEqualTo(payReq.getAmount()); - assertThat(operation.amount.inSatoshis).isEqualTo(inSatoshis); - // check fee assertThat(operation.fee.inSatoshis).isEqualTo(someFee); } diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 727f11dc..502e5b68 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -84,7 +84,7 @@ static def configExternalLinks(productFlavor, String host) { android { - compileSdkVersion 33 + compileSdk 34 buildFeatures { viewBinding true @@ -92,10 +92,10 @@ android { defaultConfig { applicationId "io.muun.apollo" - minSdkVersion 19 - targetSdkVersion 33 - versionCode 1200 - versionName "52" + minSdk 19 + targetSdk 34 + versionCode 1201 + versionName "52.1" // Needed to make sure these classes are available in the main DEX file for API 19 // See: https://spin.atomicobject.com/2018/07/16/support-kitkat-multidex/ @@ -282,12 +282,12 @@ android { jvmTarget = JavaVersion.VERSION_1_8.toString() } - lintOptions { + lint { abortOnError true htmlReport true textReport true - lintConfig file("${project.rootDir}/linters/android-lint/config.xml") - baseline file("lint-baseline.xml") + lintConfig file("$rootDir/linters/android-lint/config.xml") + baseline file('lint-baseline.xml') } packagingOptions { diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/LoginAndSignUpTests.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/LoginAndSignUpTests.kt index a65ec396..aac228e7 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/LoginAndSignUpTests.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/LoginAndSignUpTests.kt @@ -110,7 +110,7 @@ open class LoginAndSignUpTests : BaseInstrumentationTest() { signInScreen.awaitEmailVerification(user.email) signInScreen.back() - signInScreen.abortDialogCancel() + signInScreen.abortDialogCancelWithSafeguard() signInScreen.back() signInScreen.abortDialogAbort() @@ -130,7 +130,7 @@ open class LoginAndSignUpTests : BaseInstrumentationTest() { signInScreen.back() signInScreen.back() - signInScreen.abortDialogCancel() + signInScreen.abortDialogCancelWithSafeguard() signInScreen.back() signInScreen.abortDialogAbort() } @@ -143,31 +143,64 @@ open class LoginAndSignUpTests : BaseInstrumentationTest() { autoFlows.signUp() autoFlows.receiveMoneyFromNetwork(Money.of(0.02, "BTC")) - // Case 1) U.u user with balance > 0 but unconfirmed receiving tx + testDeleteWalletCases(isRecoverableUser = false) + } + + @Test + fun test_06_a_recoverable_user_can_delete_wallet() { + val user = RandomUser() + + autoFlows.createRecoverableUser(user.pin, user.email, user.password) + val recoveryCodeParts = autoFlows.setUpRecoveryCode() + + autoFlows.receiveMoneyFromNetwork(Money.of(0.02, "BTC")) + + testDeleteWalletCases(isRecoverableUser = true) + + // Let's check user credentials don't work anymore + signInScreen.startLogin() + + // Reject invalid email: + signInScreen.checkEmailConfirmEnabled(false) + signInScreen.enterEmail(user.email) + signInScreen.checkEmailConfirmEnabled(true) + signInScreen.confirmEmail() + signInScreen.checkEmailError() + + signInScreen.recoverWithRecoveryCode() + signInScreen.enterRecoveryCode(recoveryCodeParts) + signInScreen.confirmRecoveryCodeOnlyLogin() + + label(R.string.error_incorrect_recovery_code) + } + + private fun testDeleteWalletCases(isRecoverableUser: Boolean) { + + // Case 1) user with balance > 0 but unconfirmed receiving tx autoFlows.checkCannotDeleteWallet() - // Case 2) U.u user with balance > 0 with confirmed receiving tx but not settled + // Case 2) user with balance > 0 with confirmed receiving tx but not settled generateBlocksAndWaitForUpdate(1) autoFlows.checkCannotDeleteWallet() - // Case 3) U.u user with balance > 0 with settled receiving tx + // Case 3) user with balance > 0 with settled receiving tx generateBlocksAndWaitForUpdate(5) // Transaction Settled autoFlows.checkCannotDeleteWallet() - // Case 4) U.u user with balance = 0 but unconfirmed spending tx + // Case 4) user with balance = 0 but unconfirmed spending tx autoFlows.spendAllFunds("some description") autoFlows.checkCannotDeleteWallet() - // Case 5) U.u user with balance = 0 with confirmed spending tx but not settled + // Case 5) user with balance = 0 with confirmed spending tx but not settled generateBlocksAndWaitForUpdate(1) autoFlows.checkCannotDeleteWallet() - // Case 6) U.u user with balance = 0 with settled spending tx + // Case 6) user with balance = 0 with settled spending tx generateBlocksAndWaitForUpdate(5) // Transaction Settled - autoFlows.deleteWallet() + autoFlows.deleteWallet(isRecoverableUser = isRecoverableUser) // TODO make more convoluted scenarios? Failed txs, failed swaps, etc...? } diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/ReceiveTests.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/ReceiveTests.kt new file mode 100644 index 00000000..be63cdde --- /dev/null +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/ReceiveTests.kt @@ -0,0 +1,27 @@ +package io.muun.apollo.presentation + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.muun.apollo.domain.model.AddressType +import org.javamoney.moneta.Money +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +open class ReceiveTests : BaseInstrumentationTest() { + + @Test + fun test_01_a_user_can_receive_money_onchain_with_fixed_amount_aka_bitcoin_uri() { + autoFlows.signUp() + + // Try with SEGWIT, default + val amount = Money.of(0.000102, "BTC") + autoFlows.receiveMoneyFromNetworkViaBitcoinUri(amount) + + // Try with LEGACY + autoFlows.receiveMoneyFromNetworkViaBitcoinUri(amount, AddressType.LEGACY) + + // Try with Taproot + autoFlows.receiveMoneyFromNetworkViaBitcoinUri(amount, AddressType.TAPROOT) + + } +} \ No newline at end of file diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/SettingsTests.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/SettingsTests.kt index 9eda3806..19105df0 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/SettingsTests.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/presentation/SettingsTests.kt @@ -1,7 +1,8 @@ package io.muun.apollo.presentation import io.muun.apollo.utils.RandomUser -import org.junit.Ignore +import io.muun.apollo.utils.screens.ReceiveScreen +import io.muun.common.model.ReceiveFormatPreference import org.junit.Test open class SettingsTests : BaseInstrumentationTest() { @@ -41,18 +42,40 @@ open class SettingsTests : BaseInstrumentationTest() { } @Test - @Ignore("feature is not yet turned on") - fun test_03_a_user_can_change_lightning_default() { + fun test_03_a_user_can_change_receive_preference_and_receive_funds() { val user = RandomUser() autoFlows.signUp(user.pin) - autoFlows.checkOnReceiveIfQRIs(false) + // Check Default receive preference is bitcoin (no actual receive, other tests exercise that) + autoFlows.checkReceivePreferenceIs(ReceiveFormatPreference.ONCHAIN) + // Change to Lightning + receive autoFlows.turnOnReceiveLightningByDefault() - autoFlows.checkOnReceiveIfQRIs(true) + autoFlows.checkReceivePreferenceIs(ReceiveFormatPreference.LIGHTNING) - autoFlows.turnOnReceiveBitcoinByDefault() - autoFlows.checkOnReceiveIfQRIs(false) + autoFlows.receiveMoneyFromLNWithAmountLessInvoice(100_000) + + // Change to Unified QR + receive + autoFlows.turnOnUnifiedQr() + autoFlows.checkReceivePreferenceIs(ReceiveFormatPreference.UNIFIED) + + autoFlows.receiveMoneyFromUnifiedQrViaLightning( + 100_000, + ReceiveScreen.UnifiedQrDraft.OffChain(100_000) + ) + + // Test Unified QR changes in config + + homeScreen.goToReceive() + receiveScreen.checkUnifiedQrConfig() + + // Test receive unified QR with amountless invoice + + autoFlows.receiveMoneyFromUnifiedQrViaLightning( + 200_000, + ReceiveScreen.UnifiedQrDraft.OffChain() + ) } + } diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/AutoFlows.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/AutoFlows.kt index ef2a472b..37b1dd0c 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/AutoFlows.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/AutoFlows.kt @@ -6,6 +6,9 @@ import androidx.test.uiautomator.UiDevice import io.muun.apollo.R import io.muun.apollo.data.debug.LappClient import io.muun.apollo.data.external.Gen +import io.muun.apollo.data.external.Globals +import io.muun.apollo.domain.libwallet.BitcoinUri +import io.muun.apollo.domain.model.AddressType import io.muun.apollo.domain.model.user.UserPhoneNumber import io.muun.apollo.presentation.ui.helper.isBtc import io.muun.apollo.presentation.ui.utils.OS @@ -13,8 +16,10 @@ import io.muun.apollo.presentation.ui.utils.UiUtils import io.muun.apollo.utils.WithMuunInstrumentationHelpers.Companion.balanceNotEqualsErrorMessage import io.muun.apollo.utils.screens.ReceiveScreen import io.muun.common.model.DebtType +import io.muun.common.model.ReceiveFormatPreference import io.muun.common.utils.BitcoinUtils import io.muun.common.utils.LnInvoice +import io.muun.common.utils.Preconditions import org.javamoney.moneta.Money import javax.money.MonetaryAmount @@ -149,12 +154,19 @@ class AutoFlows( } /** - * For Unrecoverable Users (some extra logic may apply, e.g u.u with positive balance may not + * Delete Wallet (some extra logic may apply, e.g u.u with positive balance may not * delete their wallets). */ - fun deleteWallet() { + fun deleteWallet(isRecoverableUser: Boolean = false) { goToSettingsAndClickDeleteWallet() + if (isRecoverableUser) { + label(R.string.settings_delete_wallet_alert_body_recoverable_user).await() + + } else { + label(R.string.settings_delete_wallet_alert_body_unrecoverable_user).await() + } + // Confirm on pop-up message. normalizedLabel(R.string.settings_delete_wallet_alert_yes).click() @@ -163,7 +175,7 @@ class AutoFlows( } /** - * For Unrecoverable Users, in some cases, we won't let them delete wallet if they can lose + * In some cases, we won't let them delete wallet if they can lose * money. This AutoFlow is what happens in those scenarios. */ fun checkCannotDeleteWallet() { @@ -175,6 +187,54 @@ class AutoFlows( backToHome() } + /** + * Receive funds using Unified QR feature, using the LN invoice embedded. + * Receive funds via Lightning Network using an AmountLess Invoice. TurboChannels flag hints + * whether we should check for operation status pending or not. + * + * NOTE: BEWARE if amount is low (< debt limit) and turboChannels is disabled, this method will + * fail as our Receive LN feature confirms the payment instantly (full debt mechanism). + */ + fun receiveMoneyFromUnifiedQrViaLightning( + amountInSat: Long, + unifiedQrDraft: ReceiveScreen.UnifiedQrDraft, + ) { + val prevBalance = homeScreen.balanceInBtc + + val unifiedQr = unifiedQrDraft as ReceiveScreen.UnifiedQrDraft.OffChain + Preconditions.checkArgument( + unifiedQr.amountInSat == null || unifiedQr.amountInSat == amountInSat + ) + + // For fixed amount invoices, amount MUST NOT be specified, otherwise an error occurs. + val amountToReceive = unifiedQr.amountInSat + + val invoice = getOwnInvoiceFromUnifiedQr(amountToReceive) + + LappClient().receiveBtcViaLN(invoice, amountInSat, unifiedQr.turboChannel) + + // Wait for balance to be updated: + val amount = BitcoinUtils.satoshisToBitcoins(amountInSat) + homeScreen.waitUntilBalanceEquals(prevBalance.add(amount)) + + checkOperationDetails(amount, statusPending = !unifiedQr.turboChannel) { + homeScreen.goToOperationDetail(0) + } + } + + private fun getOwnInvoiceFromUnifiedQr(amountInSat: Long? = null): String { + homeScreen.goToReceive() + + if (amountInSat != null) { + receiveScreen.addUnifiedQrAmount(amountInSat) + } + + val unifiedQr = receiveScreen.unifiedQr + + device.pressBack() // Back to Home + return BitcoinUri.parse(unifiedQr).third + } + /** * Receive funds via Lightning Network using an AmountLess Invoice. TurboChannels flag hints * whether we should check for operation status pending or not. @@ -232,7 +292,55 @@ class AutoFlows( return invoice } - fun receiveMoneyFromNetwork(amount: Money) = try { + fun receiveMoneyFromNetworkViaBitcoinUri( + amount: MonetaryAmount, + addressType: AddressType = AddressType.SEGWIT, + ) = try { + tryReceiveMoneyFromNetworkViaBitcoinUri(amount, addressType) + } catch (e: AssertionError) { + if (e.message != null && e.message!!.contains(balanceNotEqualsErrorMessage)) { + + LappClient().generateBlocks(30) // we don't want to need this again soon + Thread.sleep(2000) + tryReceiveMoneyFromNetwork(amount) + } else { + throw e + } + } + + private fun tryReceiveMoneyFromNetworkViaBitcoinUri( + amount: MonetaryAmount, + addressType: AddressType = AddressType.SEGWIT, + ) { + val expectedBalance = homeScreen.balanceInBtc + val balanceAfter = expectedBalance.add(amount) + + // Generate a Bitcoin Uri with amount: + val rawClipboard = getOwnBitcoinUri(amount, addressType) + val bitcoinUri = io.muun.common.bitcoinj.BitcoinUri(Globals.INSTANCE.network, rawClipboard) + + val amountInBtc = BitcoinUtils.satoshisToBitcoins(bitcoinUri.amount.value) + assertMoneyEqualsWithRoundingHack(amountInBtc, amount) + + // Hit RegTest to receive money from the network: + LappClient().receiveBtc(amountInBtc.number.toDouble(), bitcoinUri.address!!) + + // Wait for balance to be updated: + homeScreen.waitUntilBalanceEquals(balanceAfter) + } + + private fun getOwnBitcoinUri(amount: MonetaryAmount, addressType: AddressType): String { + homeScreen.goToReceive() + + receiveScreen.addBitcoinUriAmount(amount) + receiveScreen.selectAddressType(addressType) + + val bitcoinUri = receiveScreen.bitcoinUri + device.pressBack() // Back to Home + return bitcoinUri + } + + fun receiveMoneyFromNetwork(amount: MonetaryAmount) = try { tryReceiveMoneyFromNetwork(amount) } catch (e: AssertionError) { if (e.message != null && e.message!!.contains(balanceNotEqualsErrorMessage)) { @@ -245,7 +353,7 @@ class AutoFlows( } } - private fun tryReceiveMoneyFromNetwork(amount: Money) { + private fun tryReceiveMoneyFromNetwork(amount: MonetaryAmount) { val expectedBalance = homeScreen.balanceInBtc val balanceAfter = expectedBalance.add(amount) @@ -582,16 +690,16 @@ class AutoFlows( backToHome() } - fun checkOnReceiveIfQRIs(lightning: Boolean) { - homeScreen.goToReceive() - if (lightning) { - receiveScreen.id(R.id.address_settings).assertDoesntExist() - receiveScreen.id(R.id.invoice_settings).exists() - } else { - receiveScreen.id(R.id.address_settings).exists() - receiveScreen.id(R.id.invoice_settings).assertDoesntExist() - } + fun turnOnUnifiedQr() { + homeScreen.goToSettings() + settingsScreen.turnOnUnifiedQr() + backToHome() + } + + fun checkReceivePreferenceIs(receiveFormatPreference: ReceiveFormatPreference) { + homeScreen.goToReceive() + receiveScreen.checkReceivePreferenceIs(receiveFormatPreference) device.pressBack() } diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ChangePasswordScreen.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ChangePasswordScreen.kt index dd450439..bca0ecd9 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ChangePasswordScreen.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ChangePasswordScreen.kt @@ -31,6 +31,7 @@ class ChangePasswordScreen( checkEmailVerificationScreenDisplayed(user.email) editNewPassword(newPassword) + reEnterNewPassword(newPassword) acceptConditions() confirmNewPassword() @@ -67,7 +68,11 @@ class ChangePasswordScreen( } private fun editNewPassword(newPassword: String) { - input(R.id.change_password).text = newPassword + input(R.id.change_password_input).text = newPassword + } + + private fun reEnterNewPassword(newPassword: String) { + input(R.id.change_password_confirm_input).text = newPassword } private fun confirmNewPassword() { diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ReceiveScreen.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ReceiveScreen.kt index 5e88562c..be92f3e4 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ReceiveScreen.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/ReceiveScreen.kt @@ -3,10 +3,17 @@ package io.muun.apollo.utils.screens import android.content.Context import androidx.test.uiautomator.UiDevice import io.muun.apollo.R +import io.muun.apollo.data.external.Globals +import io.muun.apollo.domain.libwallet.BitcoinUri +import io.muun.apollo.domain.model.AddressType import io.muun.apollo.presentation.ui.show_qr.ShowQrPage import io.muun.apollo.utils.Clipboard import io.muun.apollo.utils.WithMuunInstrumentationHelpers +import io.muun.common.bitcoinj.ValidationHelpers +import io.muun.common.model.ReceiveFormatPreference +import io.muun.common.utils.Bech32SegwitAddress import io.muun.common.utils.BitcoinUtils +import org.assertj.core.api.Assertions.assertThat import javax.money.MonetaryAmount class ReceiveScreen( @@ -19,6 +26,18 @@ class ReceiveScreen( private set } + sealed class UnifiedQrDraft { + data class OnChain( + val amount: MonetaryAmount? = null, + val addressType: AddressType = AddressType.SEGWIT, + ) : UnifiedQrDraft() + + data class OffChain( + val amountInSat: Long? = null, + val turboChannel: Boolean = true, + ) : UnifiedQrDraft() + } + val address: String get() { id(R.id.show_qr_copy).click() @@ -26,6 +45,9 @@ class ReceiveScreen( return lastCopiedFromClipboard } + val bitcoinUri: String + get() = address // Yes, its obtained the same way, but callers should set up an amount first + val invoice: String get() { normalizedLabel(ShowQrPage.LN.titleRes).click() @@ -34,25 +56,173 @@ class ReceiveScreen( return lastCopiedFromClipboard } + val unifiedQr: String + get() { + id(R.id.show_qr_copy).click() + lastCopiedFromClipboard = Clipboard.read() + return lastCopiedFromClipboard + } + fun goToScanLnUrl() { desc(R.string.scan_lnurl).click() } + fun selectAddressType(addressType: AddressType) { + + if (!id(R.id.edit_address_type).exists()) { + id(R.id.address_settings).click() + } + + id(R.id.edit_address_type).click() + + when (addressType) { + AddressType.SEGWIT -> label(R.string.address_picker_segwit_title).click() + AddressType.LEGACY -> label(R.string.address_picker_legacy_title).click() + AddressType.TAPROOT -> label(R.string.address_picker_taproot_title).click() + } + } + + private fun selectAddressTypeForUnifiedQr(addressType: AddressType) { + + if (!id(R.id.edit_address_type).exists()) { + id(R.id.unified_qr_settings).click() + } + + id(R.id.edit_address_type).click() + + when (addressType) { + AddressType.SEGWIT -> label(R.string.address_picker_segwit_title).click() + AddressType.LEGACY -> label(R.string.address_picker_legacy_title).click() + AddressType.TAPROOT -> label(R.string.address_picker_taproot_title).click() + } + } + + fun addUnifiedQrAmount(amountInSat: Long) { + id(R.id.unified_qr_settings).click() + + editAmount(amountInSat) + } + fun addInvoiceAmount(amountInSat: Long) { normalizedLabel(ShowQrPage.LN.titleRes).click() id(R.id.invoice_settings).click() - id(R.id.add_amount).click() + editAmount(amountInSat) + } - editAmount(BitcoinUtils.satoshisToBitcoins(amountInSat)) + fun addBitcoinUriAmount(amount: MonetaryAmount) { + normalizedLabel(ShowQrPage.BITCOIN.titleRes).click() + id(R.id.address_settings).click() - pressMuunButton(R.id.confirm_amount_button) + editAmount(amount) + } + + private fun editAmount(amountInSat: Long) { + editAmount(BitcoinUtils.satoshisToBitcoins(amountInSat)) } private fun editAmount(amount: MonetaryAmount) { + if (id(R.id.add_amount).exists()) { + id(R.id.add_amount).click() + + } else { + id(R.id.amount_label).click() + } + id(R.id.currency_code).click() labelWith(amount.currency.currencyCode).click() id(R.id.muun_amount).text = amount.number.toString() + + pressMuunButton(R.id.confirm_amount_button) + } + + fun checkReceivePreferenceIs(receiveFormatPreference: ReceiveFormatPreference) { + when (receiveFormatPreference) { + ReceiveFormatPreference.ONCHAIN -> { + receiveScreen.id(R.id.address_settings).exists() + receiveScreen.id(R.id.invoice_settings).assertDoesntExist() + receiveScreen.id(R.id.unified_qr_settings).assertDoesntExist() + } + ReceiveFormatPreference.LIGHTNING -> { + receiveScreen.id(R.id.address_settings).assertDoesntExist() + receiveScreen.id(R.id.invoice_settings).exists() + receiveScreen.id(R.id.unified_qr_settings).assertDoesntExist() + } + ReceiveFormatPreference.UNIFIED -> { + receiveScreen.id(R.id.address_settings).assertDoesntExist() + receiveScreen.id(R.id.invoice_settings).assertDoesntExist() + receiveScreen.id(R.id.unified_qr_settings).exists() + } + } + } + + fun checkUnifiedQrConfig() { + var amountInSat: Long = 100 + var addressType = AddressType.SEGWIT + + addUnifiedQrAmount(amountInSat) + selectAddressTypeForUnifiedQr(addressType) + checkUnifiedQrConfig(amountInSat, addressType) + + amountInSat = 200 + addressType = AddressType.LEGACY + + changeAndCheck(amountInSat, addressType) + + amountInSat = 300 + addressType = AddressType.TAPROOT + + changeAndCheck(amountInSat, addressType) + + device.pressBack() + } + + private fun changeAndCheck(amountInSat: Long, addressType: AddressType) { + editAmount(amountInSat) + selectAddressTypeForUnifiedQr(addressType) + + } + + private fun checkUnifiedQrConfig( + amountInSat: Long, + addressType: AddressType, + ) { + checkAmountIs(amountInSat) + checkAddressTypeIs(addressType) + id(R.id.expiration_time_item).exists() + } + + private fun checkAmountIs(amountInSat: Long) { + checkAmountIs(BitcoinUtils.satoshisToBitcoins(amountInSat)) + } + + private fun checkAmountIs(expectedAmount: MonetaryAmount) { + val selectedAmount = id(R.id.selected_amount).text.toMoney() + assertMoneyEqualsWithRoundingHack(selectedAmount, expectedAmount) + } + + private fun checkAddressTypeIs(expectedAddressType: AddressType) { + val addressType = id(R.id.edit_address_type).text + + val (address, _, _) = BitcoinUri.parse(unifiedQr) + val params = Globals.INSTANCE.network + + when (expectedAddressType) { + AddressType.SEGWIT -> { + assert(Bech32SegwitAddress.decode(params, address).fst.toInt() == 0) + assertThat(addressType).isEqualTo(context.getString(R.string.segwit)) + } + + AddressType.LEGACY -> { + assert(ValidationHelpers.isValidBase58Address(params, address)) + assertThat(addressType).isEqualTo(context.getString(R.string.legacy)) + } + + AddressType.TAPROOT -> { + assert(Bech32SegwitAddress.decode(params, address).fst.toInt() == 1) + assertThat(addressType).isEqualTo(context.getString(R.string.taproot)) + } + } } } \ No newline at end of file diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SettingsScreen.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SettingsScreen.kt index 4c53c814..a4d8c25b 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SettingsScreen.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SettingsScreen.kt @@ -19,7 +19,7 @@ class SettingsScreen( } fun deleteWallet() { - id(R.id.log_out_text_view).click() + id(R.id.delete_wallet_text_view).click() } fun setBitcoinUnitToSat() { @@ -43,7 +43,7 @@ class SettingsScreen( id(R.id.receive_preference_value).click() - label(R.string.tab_ln_invoice).click() + label(R.string.receive_preference_lightning_title).click() device.pressBack() } @@ -53,7 +53,17 @@ class SettingsScreen( id(R.id.receive_preference_value).click() - label(R.string.tab_bitcoin_address).click() + label(R.string.receive_preference_bitcoin_title).click() + + device.pressBack() + } + + fun turnOnUnifiedQr() { + id(R.id.settings_lightning).click() + + id(R.id.receive_preference_value).click() + + label(R.string.receive_preference_unified).click() device.pressBack() } diff --git a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SignInScreen.kt b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SignInScreen.kt index 25095b01..18da7e76 100644 --- a/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SignInScreen.kt +++ b/android/apolloui/src/androidTest/java/io/muun/apollo/utils/screens/SignInScreen.kt @@ -47,16 +47,24 @@ class SignInScreen( startLogin() - pressMuunButton(R.id.enter_email_use_rc_only) + recoverWithRecoveryCode() enterRecoveryCode(recoveryCodeParts) - pressMuunButton(R.id.rc_only_login_continue) + confirmRecoveryCodeOnlyLogin() if (email != null) { checkRcLoginEmailAuthScreenDisplayed(email) } } + fun recoverWithRecoveryCode() { + pressMuunButton(R.id.enter_email_use_rc_only) + } + + fun confirmRecoveryCodeOnlyLogin() { + pressMuunButton(R.id.rc_only_login_continue) + } + fun startSignup() { normalizedLabel(R.string.signup_start).click() } @@ -141,6 +149,19 @@ class SignInScreen( checkInputError(R.id.signup_unlock_edit_password, R.string.error_incorrect_password) } + fun abortDialogCancelWithSafeguard() { + try { + signInScreen.abortDialogCancel() + } catch (_: Exception) { + // I'm DONE with this annoying issue regarding Google Password Manager. + // Not gonna keep suffering flakiness from it. ENOUGH! + // TODO: find reliable way to disable Google Password Manager + signInScreen.back() // Add extra back to dismiss Google Password Manager popup + signInScreen.abortDialogCancel() + } + } + + fun abortDialogCancel() { normalizedLabel(R.string.cancel).click() } @@ -149,11 +170,11 @@ class SignInScreen( normalizedLabel(R.string.abort).click() } - private fun enterRecoveryCode(recoveryCodeParts: List) { + fun enterRecoveryCode(recoveryCodeParts: List) { recoveryCodeScreen.enterRecoveryCode(recoveryCodeParts) } - private fun confirmRecoveryCode() { + fun confirmRecoveryCode() { pressMuunButton(R.id.signup_forgot_password_continue) } } \ No newline at end of file diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/app/Navigator.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/app/Navigator.java index 1e68b0bd..f2944d04 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/app/Navigator.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/app/Navigator.java @@ -495,7 +495,7 @@ public void navigateToLauncher(@NotNull Context context) { public void navigateToDeleteWallet(@NotNull Context context) { final Optional maybeSupportId = userSel.getOptional().flatMap(User::getSupportId); - logoutActions.dangerouslyDestroyUnrecoverableWallet(); + logoutActions.dangerouslyDestroyWallet(); final Intent intent = SuccessDeleteWalletActivity .getStartActivityIntent(context, maybeSupportId); diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java index ad72d37c..d66f57e6 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java @@ -160,7 +160,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { try { super.onCreate(savedInstanceState); - Timber.d("Lifecycle: " + getClass().getSimpleName() + "#onCreate"); + Timber.i("Lifecycle: " + getClass().getSimpleName() + "#onCreate"); if (savedInstanceState != null) { Timber.i("Lifecycle: " + getClass().getSimpleName() + " is being recreated"); diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java index d42d0762..121320c5 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java @@ -160,7 +160,7 @@ public View onCreateView( ViewGroup container, Bundle savedInstanceState ) { - Timber.d("Lifecycle: " + getClass().getSimpleName() + "#onCreateView"); + Timber.i("Lifecycle: " + getClass().getSimpleName() + "#onCreateView"); if (savedInstanceState != null) { Timber.i("Lifecycle: " + getClass().getSimpleName() + " is being recreated"); @@ -200,7 +200,7 @@ private View inflateLayout(@NonNull LayoutInflater inflater, ViewGroup container */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - Timber.d("Lifecycle: " + getClass().getSimpleName() + "#onViewCreated"); + Timber.i("Lifecycle: " + getClass().getSimpleName() + "#onViewCreated"); initializeUi(view); presenter.onViewCreated(savedInstanceState); } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/ek_save/EmergencyKitSaveFragment.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/ek_save/EmergencyKitSaveFragment.kt index d9f599f3..8140fdae 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/ek_save/EmergencyKitSaveFragment.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/ek_save/EmergencyKitSaveFragment.kt @@ -107,7 +107,7 @@ class EmergencyKitSaveFragment : SingleFragment(), override fun setDriveUploading(isUploading: Boolean) { if (isUploading) { val message = if (argumentsBundle.getBoolean(ARG_UPDATE_KIT)) { - R.string.ek_uploading_body + R.string.ek_updating_body } else { R.string.ek_uploading_body } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/home/HomeFragment.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/home/HomeFragment.kt index ed30d771..0f74d4ca 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/home/HomeFragment.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/home/HomeFragment.kt @@ -166,7 +166,7 @@ class HomeFragment : SingleFragment(), HomeFragmentView { private val minTravelDistance = ViewConfiguration.get(context).scaledTouchSlop override fun onFling( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float, @@ -174,7 +174,7 @@ class HomeFragment : SingleFragment(), HomeFragmentView { // Future reader: for MotionEvent we want rawX/Y, other coordinates suck (BIG TIME) // See: https://stackoverflow.com/q/1410885/901465 - if (abs(velocityY) > minVelocity && e1.rawY - e2.rawY > minTravelDistance) { + if (abs(velocityY) > minVelocity && e1 != null && e1.rawY - e2.rawY > minTravelDistance) { chevron.performClick() return true } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/manual_fee/ManualFeeFragment.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/manual_fee/ManualFeeFragment.kt index d68aee59..d1b72ab5 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/manual_fee/ManualFeeFragment.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/manual_fee/ManualFeeFragment.kt @@ -2,13 +2,11 @@ package io.muun.apollo.presentation.ui.fragments.manual_fee import android.text.TextUtils import android.view.View -import android.widget.TextView import butterknife.BindString import butterknife.BindView import butterknife.OnClick import icepick.State import io.muun.apollo.R -import io.muun.apollo.domain.Flags import io.muun.apollo.domain.model.BitcoinAmount import io.muun.apollo.domain.model.BitcoinUnit import io.muun.apollo.presentation.ui.base.SingleFragment @@ -36,9 +34,6 @@ class ManualFeeFragment : SingleFragment(), ManualFeeView { @BindView(R.id.status_message) lateinit var statusMessage: StatusMessage - @BindView(R.id.use_maximum_fee) - lateinit var useMaximumFee: TextView - @BindView(R.id.confirm_fee) lateinit var confirmButton: MuunButton @@ -51,8 +46,6 @@ class ManualFeeFragment : SingleFragment(), ManualFeeView { @State lateinit var mBitcoinUnit: BitcoinUnit - private var shouldShowMaxFeeButton = false - override fun inject() { component.inject(this) } @@ -89,7 +82,6 @@ class ManualFeeFragment : SingleFragment(), ManualFeeView { confirmButton.isEnabled = false statusMessage.visibility = View.GONE feeInput.resetVisibility() - useMaximumFee.visibility = if (shouldShowMaxFeeButton) View.VISIBLE else View.GONE // 1.5 If feeRate is null, we're back at initial state (empty input), nothing else to do if (feeRateInSatsPerVByte == null) { @@ -130,16 +122,6 @@ class ManualFeeFragment : SingleFragment(), ManualFeeView { } } } - - shouldShowMaxFeeButton = showMaximumFeeButton(state) - - if (shouldShowMaxFeeButton) { - useMaximumFee.visibility = View.VISIBLE - useMaximumFee.setOnClickListener { - feeInput.setFeeRate(state.maxFeeRateInSatsPerVByte) - useMaximumFee.visibility = View.GONE - } - } } @OnClick(R.id.confirm_fee) @@ -189,19 +171,6 @@ class ManualFeeFragment : SingleFragment(), ManualFeeView { confirmButton.isEnabled = true // just a warning } - private fun showMaximumFeeButton(state: EditFeeState): Boolean { - - // TODO disabled until properly tested and QA vetted - if (!Flags.USE_MAXIMUM_FEE_ENABLED) { - return false - } - - // Replicating Falcon logic here. - // The use max fee button is only displayed in case the user is not taking fee from amount - // (aka using all funds) and if the user doesn't have a selected fee - return !state.amountInfo.takeFeeFromAmount && state.validated.feeNeedsChange - } - private fun onHowThisWorksClick() { val dialog = TitleAndDescriptionDrawer() dialog.setTitle(R.string.manual_fee_how_this_works_explanation_title) diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsFragment.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsFragment.kt index cc82daa4..7e8f47a6 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsFragment.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsFragment.kt @@ -368,8 +368,8 @@ open class SettingsFragment : SingleFragment(), SettingsView showDialog(muunDialog) } - override fun handleDeleteWallet(isActionBlocked: Boolean, isRecoverableUser: Boolean) { - if (isActionBlocked) { + override fun handleDeleteWallet(canDeleteWallet: Boolean, isRecoverableUser: Boolean) { + if (!canDeleteWallet) { showCantDeleteNonEmptyWalletDialog() } else { diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsPresenter.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsPresenter.kt index c8c0406f..a4d6cdec 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsPresenter.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsPresenter.kt @@ -203,7 +203,7 @@ class SettingsPresenter @Inject constructor( val options = logoutOptionsSel.watch() .toBlocking() .first() - val shouldBlockAndExplain = options.isBlocked() + val shouldBlockAndExplain = options.isLogoutBlocked() Preconditions.checkArgument(options.isRecoverable()) view.handleLogout(shouldBlockAndExplain) @@ -218,7 +218,7 @@ class SettingsPresenter @Inject constructor( .toBlocking() .first() - view.handleDeleteWallet(options.isBlocked(), options.isRecoverable()) + view.handleDeleteWallet(options.canDeleteWallet(), options.isRecoverable()) } override fun getEntryEvent(): AnalyticsEvent { diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsView.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsView.java index b079c19c..dc6b6060 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsView.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsView.java @@ -42,7 +42,7 @@ public interface SettingsView extends BaseView { /** * Handle the delete wallet action. */ - void handleDeleteWallet(boolean isActionBlocked, boolean isRecoverableUser); + void handleDeleteWallet(boolean canDeleteWallet, boolean isRecoverableUser); /** * Show a simple, standard muun error dialog to communicate that non empty wallet can't be diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java index 4679eb37..28de2805 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java @@ -12,6 +12,7 @@ import android.net.Uri; import androidx.annotation.Nullable; +import timber.log.Timber; import javax.inject.Inject; @@ -50,6 +51,9 @@ public void handleLaunch(@Nullable Uri uri, boolean isTaskRoot) { if (Globals.INSTANCE.getVersionCode() >= minClientVersion) { + final String maybeUri = uri != null ? uri.toString() : "null"; + Timber.i("HandleLaunch(isTaskRoot: %s, uri: %s)", isTaskRoot, maybeUri); + // If we caught an Intent with an URI (from an external Muun link), handle it: if (uri != null) { useMuunLinkAction.run(uri.toString()); diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordFragment.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordFragment.kt index 255a7812..26268521 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordFragment.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordFragment.kt @@ -1,5 +1,6 @@ package io.muun.apollo.presentation.ui.settings.edit_password +import android.text.TextUtils import android.view.View import android.widget.CheckBox import butterknife.BindView @@ -10,11 +11,15 @@ import io.muun.apollo.presentation.ui.base.BaseActivity import io.muun.apollo.presentation.ui.base.SingleFragment import io.muun.apollo.presentation.ui.view.MuunButton import io.muun.apollo.presentation.ui.view.MuunTextInput +import io.muun.common.Rules class ChangePasswordFragment : SingleFragment(), ChangePasswordView { - @BindView(R.id.change_password) - lateinit var password: MuunTextInput + @BindView(R.id.change_password_input) + lateinit var passwordInput: MuunTextInput + + @BindView(R.id.change_password_confirm_input) + lateinit var passwordConfirmInput: MuunTextInput @BindView(R.id.change_password_condition) lateinit var condition: CheckBox @@ -31,10 +36,28 @@ class ChangePasswordFragment : SingleFragment(), Change } override fun initializeUi(view: View) { - password.setPasswordRevealEnabled(true) continueButton.isEnabled = false - condition.setOnCheckedChangeListener { _, _ -> onConditionCheckedChanged() } + passwordInput.setPasswordRevealEnabled(true) + passwordInput.setOnChangeListener(this) { + // Ugly check needed for some convoluted scenario where we receive input and fragment + // is being re-created or something + if (::passwordInput.isInitialized) { + validateInputs() + } + } + passwordConfirmInput.setPasswordRevealEnabled(true) + passwordConfirmInput.setOnChangeListener(this) { + // Ugly check needed for some convoluted scenario where we receive input and fragment + // is being re-created or something + if (::passwordConfirmInput.isInitialized) { + validateInputs() + } + } + + condition.setOnCheckedChangeListener { _, _ -> + validateInputs() + } continueButton.setOnClickListener { onContinueButtonClick() } } @@ -51,7 +74,7 @@ class ChangePasswordFragment : SingleFragment(), Change } private fun abort() { - safeGetParentActivity().ifPresent(BaseActivity<*>::finish) + safeGetParentActivity().ifPresent(BaseActivity<*>::finishActivity) } override fun setLoading(loading: Boolean) { @@ -60,27 +83,44 @@ class ChangePasswordFragment : SingleFragment(), Change } override fun setPasswordError(error: UserFacingError?) { - password.clearError() + passwordInput.clearError() + + if (error != null) { + passwordInput.setError(error) + passwordInput.requestFocusInput() + } + } + + override fun setConfirmPasswordError(error: UserFacingError?) { + passwordConfirmInput.clearError() + if (error != null) { - password.setError(error) - password.requestFocusInput() + passwordConfirmInput.setError(error) + passwordConfirmInput.requestFocusInput() } } override fun onResume() { super.onResume() - password.requestFocusInput() + passwordInput.requestFocusInput() } override fun blockScreenshots(): Boolean { return true } - private fun onConditionCheckedChanged() { - continueButton.isEnabled = condition.isChecked + private fun validateInputs() { + val validPassword = isValidPassword(passwordInput.text.toString()) + val validPasswordConfirm = isValidPassword(passwordConfirmInput.text.toString()) + + continueButton.isEnabled = validPassword && validPasswordConfirm && condition.isChecked } private fun onContinueButtonClick() { - presenter.submitPassword(password.text.toString()) + presenter.submitPassword(passwordInput.text.toString(), passwordConfirmInput.text.toString()) } + + private fun isValidPassword(password: String) = + !TextUtils.isEmpty(password) && password.length >= Rules.PASSWORD_MIN_LENGTH + } \ No newline at end of file diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordPresenter.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordPresenter.kt index 2ea18c46..89adf99e 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordPresenter.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordPresenter.kt @@ -8,6 +8,7 @@ import io.muun.apollo.domain.analytics.AnalyticsEvent.E_PASSWORD_CHANGED import io.muun.apollo.domain.analytics.AnalyticsEvent.S_PASSWORD_CHANGE_END import io.muun.apollo.domain.errors.EmptyFieldError import io.muun.apollo.domain.errors.passwd.PasswordTooShortError +import io.muun.apollo.domain.errors.passwd.PasswordsDontMatchError import io.muun.apollo.presentation.ui.base.di.PerFragment import io.muun.common.Rules import javax.inject.Inject @@ -46,7 +47,7 @@ class ChangePasswordPresenter @Inject constructor( /** * Submit the new password, checking for errors. */ - fun submitPassword(password: String) { + fun submitPassword(password: String, confirmPassword: String) { view.setPasswordError(null) if (password == "") { @@ -55,8 +56,12 @@ class ChangePasswordPresenter @Inject constructor( } else if (password.length < Rules.PASSWORD_MIN_LENGTH) { view.setPasswordError(PasswordTooShortError()) + } else if (password != confirmPassword) { + view.setConfirmPasswordError(PasswordsDontMatchError()) + } else { view.setPasswordError(null) + view.setConfirmPasswordError(null) view.setLoading(true) finishPasswordChange.run(parentPresenter.currentUuid, password) } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordView.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordView.java index 2a24c90b..38111c56 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordView.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/ChangePasswordView.java @@ -7,4 +7,6 @@ public interface ChangePasswordView extends SingleFragmentView { void setPasswordError(UserFacingError error); + void setConfirmPasswordError(UserFacingError error); + } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/view/FeeManualInput.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/view/FeeManualInput.java index de893d76..bf55aaa3 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/view/FeeManualInput.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/view/FeeManualInput.java @@ -24,7 +24,6 @@ import icepick.State; import timber.log.Timber; -import java.math.RoundingMode; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.ParseException; @@ -78,8 +77,6 @@ public interface OnChangeListener { private OnChangeListener onChangeListener; - private boolean isSkippingListeners; - public FeeManualInput(Context context) { super(context); } @@ -115,9 +112,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { - if (!isSkippingListeners) { - updateFeeRate(parseNumber(s.toString())); - } + updateFeeRate(parseNumber(s.toString())); } }); } @@ -133,17 +128,6 @@ public void requestFocusInput() { } } - /** - * Set input's fee rate, in satoshis per virtual byte. - */ - public void setFeeRate(double feeRateInSatsPerVbyte) { - isSkippingListeners = true; - feeRateInput.setText(UiUtils.formatFeeRate(feeRateInSatsPerVbyte, RoundingMode.FLOOR)); - isSkippingListeners = false; - - updateFeeRate(feeRateInSatsPerVbyte); - } - private void updateFeeRate(Double feeRateInSatsPerVbyte) { this.feeRateInSatsPerVbyte = feeRateInSatsPerVbyte; notifyChange(); diff --git a/android/apolloui/src/main/res/layout/change_password_fragment.xml b/android/apolloui/src/main/res/layout/change_password_fragment.xml index e8f60153..8a888a6c 100644 --- a/android/apolloui/src/main/res/layout/change_password_fragment.xml +++ b/android/apolloui/src/main/res/layout/change_password_fragment.xml @@ -26,7 +26,7 @@ android:text="@string/change_password_title" /> + + - - Ingresa tu nueva contraseña Nueva contraseña @string/create_password_input_helper + @string/create_password_confirm_input_hint Entiendo que mi contraseña anterior ya no será válida. Confirma tu nueva contraseña @@ -1114,7 +1115,6 @@ No tienes fondos suficientes para pagar la comisión óptima. Elige una comisión para continuar. - Usar máxima comisión disponible Total (Monto + Comisión Mínima) Tu balance diff --git a/android/apolloui/src/main/res/values/strings.xml b/android/apolloui/src/main/res/values/strings.xml index b37e33e4..275a2e59 100644 --- a/android/apolloui/src/main/res/values/strings.xml +++ b/android/apolloui/src/main/res/values/strings.xml @@ -875,6 +875,7 @@ Enter your new password New password @string/create_password_input_helper + @string/create_password_confirm_input_hint I understand that my previous password is no longer valid. @@ -1074,7 +1075,6 @@ Not enough funds to pay for the optimal network fee. Select a network fee to continue. - Use maximum fee Total (Amount + Minimum Fee) Your balance diff --git a/build.gradle b/build.gradle index 6c5f2510..db6dc2a3 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { // Add third party gradle plugins - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.4.2' // We're using a patched version of SQLDelight, for reproducible builds. Thanks JitPack! classpath 'com.github.muun.sqldelight:gradle-plugin:1.5.3-reproducible-build' } diff --git a/common/src/main/java/io/muun/common/api/SessionJson.java b/common/src/main/java/io/muun/common/api/SessionJson.java index 5ea7803e..b9c39223 100644 --- a/common/src/main/java/io/muun/common/api/SessionJson.java +++ b/common/src/main/java/io/muun/common/api/SessionJson.java @@ -18,7 +18,7 @@ public class SessionJson { public String buildType; @Nonnegative - public int version; + public int version; // this is the clientVersion, not a session version. @NotEmpty public String gcmRegistrationToken; diff --git a/common/src/main/java/io/muun/common/api/error/ErrorCode.java b/common/src/main/java/io/muun/common/api/error/ErrorCode.java index c8ff5944..26435839 100644 --- a/common/src/main/java/io/muun/common/api/error/ErrorCode.java +++ b/common/src/main/java/io/muun/common/api/error/ErrorCode.java @@ -229,7 +229,9 @@ public enum ErrorCode { 2086, StatusCode.CLIENT_FAILURE, "Cannot delete wallet with funds" ), UNSETTLED_OPERATIONS( - 2087, StatusCode.CLIENT_FAILURE, "Cannot delete wallet with unsettled operations" + 2087, + StatusCode.CLIENT_FAILURE, + "Cannot delete wallet with unsettled operations" ), // error responses diff --git a/common/src/main/java/io/muun/common/utils/Preconditions.java b/common/src/main/java/io/muun/common/utils/Preconditions.java index 240248e2..79655c11 100644 --- a/common/src/main/java/io/muun/common/utils/Preconditions.java +++ b/common/src/main/java/io/muun/common/utils/Preconditions.java @@ -187,7 +187,7 @@ public static T checkNotNull(T reference, @Nullable Object errorMessage) { /** * If a condition is true, ensures that an object reference is not null. If it's false, ensure - * that the the reference is null. + * that the reference is null. * * @return the null reference that was validated * @throws IllegalArgumentException if {@code reference} is not null @@ -201,7 +201,6 @@ public static T checkNotNullOnlyIf(@Nullable T reference, boolean condition) } } - /** * Ensures that an object reference passed as a parameter to the calling method is null. * @@ -349,20 +348,6 @@ public static long checkPositive(long number, @Nullable Object errorMessage) { return number; } - /** - * Ensures that {@code number} is positive. - * - * @param number a number - * @return the value of {@code number} - * @throws IllegalArgumentException if {@code number} is negative - */ - public static long checkNonNegative(long number) { - if (number < 0) { - throw new IllegalArgumentException("Number " + number + " expected to be non-negative"); - } - return number; - } - /** * Ensures that {@code index} specifies a valid element in an array, list or string of * size {@code size}. An element index may range from zero, inclusive, to {@code size}, diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 27732 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 G>;C}_dM#G~ delta 22714 zcmV)LK)Juv+5^C{1F$Or4XW8@*aHOs0O|<<04pTw zK!b{dHxe$1wboX!v`W1o0WAS+MB5I@A&gFD(#gb2?-zUh2fp^DPhG2h3AC=-)z|)u z{);|o_nFB+5`wEN)|oT=?A!P4efH${GOj3? z!d0q7E&2j*mCWsE53!n}+H1!u7+?OJcbt zmfIb8SHXLDUxwa+WwFgGID~=>&Ja0gScW^n5K1H$8Kg*^Ve#2&X_-6o`m#xq zXvWU#=A!Nx;=L}E+*PB(kj&UlFZMzkhUS@Q|%DTGaa%Y?& zToGG_V~M5A9sQo3Hg&6*&bp3a6~}#vVW%${CLj0m(VZei*xN>#LGeRgu}hT8?q*|# zPXF|(?ojr5+j98>chb}=m5i+yI0@svg~i?U!d#}|NEnwWYfr?mry;f{5~0QU40l)U z0z+Seg2R7TOrCes{uycZHWT--9FP}lb$f1Tg7kM0SNXd$df8Kxu|mNvKFIU3YuHvr zMvqELh zn@0`Fb}h9wO4zj*=B9|+M6&UEOpP}ed#bLP*`k>t&7PKW1?>_mayR?1;__1SMGQQ& zT6njZyVrGxTQoD0!ORFEZDS<{M?-7OuR4ERCkl4utB!CKyvLft`cjd6g}ak&#zkM^ z1>rhP+SljB@x<0)wFO`uT2P)h+t@5^u}QvY&_oRDo_&|P`fQ^w|6Vlts*93aMO4(h zxG@YzTLwxSL>_8tTyZX@WFonxnPfsZtBdK}%=N|u?{1ZmO-Xm@ijc>ic0ArBpeuj2 zLrN_`+mO}<=tktW&KK#EJV4)O@fQLUBaqf(^p>V4qi1+%4eVFi?7(qaBc58Gn^iT~8B16g{&oY+bfkS8YY)LqOc3c8g!p)R>SO5|e63P!hw_&`#Pl%T6<$ zTKbPn&_rK^XyOm>M;Y&wf(}! z#H+|EkG8l9&AA^;>PFb2+=h~S-LZ|s zC4bYkwO3@AI|s<%Y|6H(iuKU+o<2?$j1L!SOp?Yqo){(@S~m+#)9>4sP&V$lC?KFq z{F^xDGQcE@HdbAl?gLZgp^Zljh5x%uhU|&4Q;C_8O*3SA#E8fBV6)rOVwYjU%tc8; z>Mp~wUm1XC6~^^a{%nZh$q?W_QZuJxWPd`-a)YZir8t>L`uXiLLvBck?XsEh^oTYw zPp`&>`4X_qAVDvHP64en1B|U4!1_uX3mN*4_ktgB31&V$06bi!HHj>8eex@cnq2M& z67Rg^A;!FM&pt%z8!jBc`EnxX5e?WU*-v!-OcMJ8(zzpP0;Vz5mwrmC)%V4Q`#Z`W z0s6ko?mDJ>v>QGA46gJ_Thw|Um*L==fn`#E08mQ<1PTBE2nYZG06_pXwmh4_4gdh0 z9RL6!0F$BUBY&M)34C0~S^t01?!Kqh)8X~GzOqj2Sk`GfjvdR16HC5i%aW{>Og^VQ=sK2P|i^97HC@c zKeLB+rL9QoUo>y#n{Vd3|1-1s+_zr(41k^T)*#m7*MEZO!Dp-3yP=xbRP*bq`3*Ju zITODb#BbrZ4SYU`Abux^-^K3*@eF=nHGiOZ{!lf46vPhvv4Jlb_>&-(<4=PK;m?A2 z9$!?`UsBDVoA`?$-jBaj&0iV#vikmY5YORng7{l}MKym{MQML;;va(eNBon4e>U-Y z5S#HYet)Qc^?-@52Jx@>w`%-5{v(KI@t-FCOHuw??esN8`I>>RD}DY)HUF!c|5MF3 zRP%;vzNwmTDV=VbLbLisOsO!X(hy$|`|(**{QOFQEMlmhNjFJV5M2^9r8(4Sz*dbwd*QFRvWU$lz*@(YYkav%K9LNWP>RiO}Rzw8=)q$ z$&_17xy_W#rff0gc2l;R(x5ijW=NwUO#-Vck#OA3xSdKlDM6^uOgclpN zy`(Fs8|*pW(|fAt+|Z%^Xjg9!*}Bi$7wxvVotS8gdTuN+u}@IbnM|ZSJK+u4@w8(~ zvwxtrufO+5|DkB~T<1}4B9N|1k?!)Hp}6|s@SWc|qmWJ%U3CC+2?C;7+i6z?($KsBbVAx}0N;RfSDJ!#N%t?8%M1M|J zZRuJjz@f{d&a|53;`+SIu7u~f2|G^Z(r$dguH9T#n@&0Ife9yXS%M^*c)U8rn{8dEHK8!kc6)^EuX;Oe-PG=VRm|f8NawSNXV#+O8=P%i2gdqe@jO zP?=17f>i}`deh!?+N0AQdy3N)F@G*!S(xog%ugE9Vo0kYZH8<&WQQR;sc*_2cLv>< zVB-Q`Z(Hb3IwWSu9h6O_pU>U zQC$JIHqD zMP1SE>~#;>Zak*ARu*v;5-HEPr-(72Bg@X}q7@o4yY&R4vP-axMLn91+nzI6@Wid< z15#?X_FO_EnmNr)FC|@C6DrwGXRBaEL)jgIiZ+bALOZ*sW?G3zzYzY96!TB4xqDc?)UPT*M^{-;c-WzDduU zZk<+Op)@z-PD^&McMXn&tua!3waazu{u zTw1~aOSJ!j02eoLYsZKOR)64E(Mwq}E)#+)rw;V@boCsG45pZ9k)s0xeG!csky6-*BriH{ z#7+}KE3@w7(z;tnxGElb5%QIT{(V6>H4 zsKh1R*uy!HTD`CaI?}PYuFybMJ>{KA3qOyKSQ1lKo|g-j#D9hPP_NduQf$1nL&KCM z31O)Q=fQ_Szo5=7nU;rgxm3J%b74ho7JLShS%q?PiXq4l*OHX5iv5EFhg+D}X}MU! zmtVTP3@!;Gih8;A@>INaB$gO!J!pHjH#6-Jl<9qa8ZDdV23}UR4j!BC3=wpGvwj|0 zu_>o$&-?SarhmL$CNQbUrI zt+|p>wZ#Ldl-QTUJr%gimdvOs)IipHF$Zf_Zj^(oy6kXjo?;|ZtyvTevR6X3HL$JQ zl4{$MiZpCV?bH8lE9zSEuslL6DO2HH2Mc$Z4ND=|4u4kW_I%yt@P>$8?u@7T<&V{3 zOh^>57Z-DPNrDVay2(q~OV_&MijU4DJo|D{s8^Ofx=QnJhId0st7a(>k7=DZqtCD< z&DK|ed3zST9fB#xJLrtt={OUAUD0t^!l(DBQDSYE#H{P;^M~F1g)cC5VJzmR^tJOe z3&~4re}5D>=kIvS8WvwP!wuzcTxzbC<|c{*v39|pB^S65t?G9kO7nI)@2IV1?m6Y} z)U~vL&tIA#Sl+VB8bAN@=RKo;CZZBTkIcd95<=e!lQ*vQC zpt)79Q=6vm;;lpN@+T1ADPB575s3+9>+%Knvt!4U&i+eo%wqx?^lev{qEzx}O8RI# znNrs##w0pA=_sr**gu*~dD&hcK`_^|Rj5q%PNp4&!>XtbyY|GCzQ|3#sa#o1>H6N5 zDSsOR^r;Z*LiG-9TslVz$e(?tH*_u)D}R-J^UspNc+#D+y#*$e@l+WTa$MMi_2_}X zfq^h$IM4nC02PR1EvAqtjFTKs7mXi6NWC7|52*)){-$$ZJ|p-IlJ_!Z*gnkBApG$0 zTh-Vk%`LN#7xgxeXvPY*n_%Kryp_GiEF-=TZ__{PRcS$@drDM=?kQoFTEwF{5r6OG zjQSyPW8*9;8k?@6va#hFe2rn>HTWBwuOYw*V_xn?8Vziwv@P84cGO}kR-l27*oF<% zLg}(AyJJC@jRjpcYBMWz&(pf7p3=%kDj!ift$MYVz>1K09aT3_JBQ$~%%b`_ETv1O z)}>ajZ=nxbu^ijAY%9AiC;RQ1SAUk1_yc$cMP$8NsTbBYXHb(t?WxA5=2_Iu+nC+= z4(_{){X6x(k?fL!O?9|}AJnqy@I%^*Kg_<5lqmb}$_uO9gn<6puZ9n!c7M}#)Mv1) zJurvR@En#8Q}Bvetn3K~GFa8_YitVpX0e(sU(;@ceW7sJ$Y5>Qm%+Mr|9`Rue9CM$ z)o8=+VAY=Lsy#Jff7r}mQ&V%uyv~HJRtB%Es`(_E0%!@F zl`E?;Viz42k;e~qkk5s@paPPz*!MI zAby;aD%fISM5n-yQkz=5f_LLb7}TA37LPM=4NAVkxhI$|53<&OjDJ;>`ZoMHdqK*4 z1V4eFL^XB1jQ6ltLu+2cPtjJNN{oC;v6o9J_OdBeA=P*i??a`iw9c;Zi;1UblV2zI ztEjrkjcW|7H?YINGk*r2HSm4|&lz~$K)-<#5Fh2-WQ{JxC7SCS=(tHu1B)36Q8#H# z$qW=-41C}w3$A|AO#UAk*xaN&W%n&A98>+ck*m_<^r9-SO>@{W+?c^mVRHxncJc2{ z{_W=9o>|;=wNSSKz2HI-4s62D(xvkvK1j|F;lnu*znFv9{eO*3v)J3y@)~?EhT5Ab zD}#Mc!_rB7H(0v&kXe1PO@S8y=t8*TYve=lU9@BKyLv&;ctBG?F zqTW5ky?LD8U%-+51svIr7Z_kV33uV=G&a6nV|K+GsAh~-6pYwRCcf{pP$_W=4RGdh zy5nF5hfXwy4u5BGBy_a$)9A|JSWk24xEgo+I3BJHorsdy?rRaw_hiu9?hpIIeopob zS2l(EGB`Vy~N9tEUSJN)FeykQ5Mr(bQ=12u|M`)Ob)QQk(wmr=^a0`)cT`pgE#2lz^&)`f; zHeaqXyMNAJEo96peNw)c0eq6WzmH|~6k+a}qDiU{z01XxZ04kHv$QG0Emh#`26359uyQkBY1?kK8p}CzmZ~j;3y%`>P{NXDAR*3}) zo}pqho~c5`v*hq>Ioz$_IVvh7xdMOBRq#9o&-dd6e!Nf;&Z~G4UhKz9{CKH?dsNg) z##&sEi!W30a=b#pD^<*N$*)rJYTPSf?~}u8)mpAj~w3X$3;Ibsi?&Joni#Rwi!@i0aed`Q7%73*-Aog0HIBj;{&K>e$=T7HL1Sucv+Qkand@a$tXhz|>YVnX>hyy=HBBnLaG+^odaii&sr4J1mR#S&FJ>brE>aiq^@+J5yFf)IAsMKUfx^4{+ zug+sxK+Pg=*DZl+mm_92ZHvVXu2v6OCKv6Pk&+Yv#WGF1Ek@poA>B$D{8*T;XEYvb zYBduHJ=rgf-RR=ONOO)1SAG z?9)<3uym1YU)qkvo5p|X(oPp^AC>Ji6ST7;ZVn7cW#@WDA>u4@UrmISRhc;OPDR#cmL|DO?zk!kLV%>Pm8zUQoo|P#n(MtU~!RriDw` z{BdTb)Ge10NyBd1x3`OHf^y~;5PQxgZT)P{9`c0p5^a~+8rOeQsW#*IHZz^_9fR!D(N0}qHc^Gq^s z>irt_qDR9Mk=9@%rQs;(*U&_#Lc=%lE&9LsP%?)mbMP4ovQBf^mZu50e2`h7;oJC* zg70eh9==bWQv!eGr_wRQ7Fhj%96Jp^zz;S22tQWv6Ah2zr_7@9L)59K1{p*RKf})z z{6fPo@hc56JVnD0PH8xSrz-fhhTq_~0&_gf>XPL+9_Qh97tT=SmC~WVEA-$P|8CtL zY)Pez1hdAHe7~2&9|Y<>n76W4rEE^5znE~YL4U-b1hjwrJfcbN&K2Y3PQw0K!Cy4| z6@Sz4cNyG&;F^Yi;u_1toy-eskw8_!mVe>ja^GVL{-fb>{8ynMA;c6#lxe~z$`w(e z2}SssPd7M0^wfAx>`f=_Si;CH5?@qmLX}$hj#GI}R0&OBbE}?Ans%t)ppglNFcg*N zZixO09XEfiP}V0Q*7?wJ%S?o#Y)QxTlo6_5oT^_@kcZvJP8d;J6H`UC6j!5&X_}}N z0Y%hlVmi{SG&errnwWuWikPX1SzZVA%QXV)j_OC~cxjw#N%> zokUg6LtzEk&`>lNnC>M;-aFEEi>w%da4`gZuyEq;OB;9~4itAnw-Br;X`z#9=Y<9r z^_YLsV0hotYKvu)cKC+CUIlfmD|xOKlut*1VmMaN^g~{0<0-!$76vmy@*p@J7?!R zt9Q^c&&YeD;~g2kp6H``LzugJj^*Pv{KX?H=Ag9doP{nz7-dy8+ciy~)^F4XTfiy`(hpSMayIaTrrBIV}4)Ryf2E_H)!+ z1{}Z>_*;oS93&Svi+iY46=_#TP!?G_0^d#!<-0~v(KigG*%$O>;6D)bm0w0>Us<54 zr_UGAdNP=rLG?JfMv|vV@_|v*a%Z5qJXkLG)&+fy8BA}k7{QEYC8&^EF^YegWSN!0 z>_`^@}6Ha3mc$0w%xt_Q*y#N~|f; zGCFb;;l9Q|!!R00u#`T`l9uL?#vM{k#ei+7LkE`7d}-877YY`Yfb)NK)GX^KV6(YGf4a`<4#l_Nx@p9N3V-#``c^5(Arjm$A98 ztg&YVTZVDRRXV8UA#8sYxERT*)i#Xwiw<%PlV=?^(OVr1f0?7(5(WqSs$t3ms9_yX z(LA1@FvGct+I3LRP6k=(-pIh!aNlX1;jE53&*B`l@1#{{@g#oDW2(8UPv&1Oe0vqF zdkmEd?h17&6spQcXV&08Cl7!Hje)8Rw%v>AZsa-X$e>fIJ%Tze>Bh3WQ1?@;fjP{z zad$3oa?ll81~G?X_@d2?n@T$vn8~ESz8}ORlH?50itN_ziGC z;q_w&000RP001JB(8v~(aOw|#omcsP9AzDT-s~~c&4i|b2A8%FrO?fmWP8xorUxxa zE0IlFk|v>{D3jgkW}3;&Iy>6~4?OTz6fX#;Cq+dm*lbg(fTE~~3d*nk9ezFn>hsR* zZZ?~3Z2e*1^Lsq+`+dIOclNb^zwjb}O?Yb=8$&inWs{T5q-;#t9E)LpGo}<+F{qeU zV8;-{l$t8NYuwE5H!VZpiu5u4xSledl$`Gn zXqGZrv*7iauE3R*nQKlIJ=3vAT&t>N8(wN;sGmwJq*%tWkD9rnt4le7&a~s^QaM-8 zT1M)GtLO8En;HxH-da)W$Lrk;PwJbtY#lC63$z8!EZxqfhACiw=co|%Ce6ay4Fanh zdFi(co^DxEZl`IR-fn^W6H5s$yo5%x%2_Y@P>WG_jtYc(oh)yCdD^s%!Q%9U;U3Z_ zWE|SkGAyIIYsz_fF)U9hp!M4}&0Bh*U~qcv5+_-^k%tb`5~I|b=&TQJ%$v?vLm$e@ z2n&u@R^|E+>^qr%G4kcG#HMw3%5Vem7TYkgg+AwmZ8>_DisPh5K|gBr)zd@soP63i zZdjxc@wNumxo@S?l$8iw$MFQ(7asalzH;!6I+dLZ53-vkT82G6Gc};+eH8_dDtJu6 z;{;tjK|#S20#{Y8!H>m(iDO1aLRdCz=4{<7x^%a9u|N%f0P;FhbX~*tQoX!b>8=5u z*sxP{Ge&S%S~(ouQjFo=XJ;&@KoSfX-lUUN@T7+C;(Ho);}#8DuvNo0*}NayHS}PI zhMm}@VGq7fTeYq<`~dv|SJ#v4)}m=;4Ohbt@gogS;WWeB?M!KSTF&?2#~OZupK5qU zHb0YVKgY9w3eISF4$lijy7Rj0Y50ZQK8sm;tpu9JJEX;uf^!tKoOj!mGHO+~_kb!;|Xd8L!lFPtnlt@dpKe)bJYqq~Ue^S;JrO zR|+>EAeqza6s>HB?RXtU`(88OLFROL4m;5;pbi;-dcm=KX!Q+wqBrq34S$#ZZwrHu$Ju0f66D$c_ zpJ-HnMeEC9vsvh?ki&<|ni4e&0nX*bxp$)ASVhk`sC$#V7(J6Q3atAZSozbJUC|pH zWB#sIH83_`>=aq(MqHECH?iicKqMpRlX@6Xn?l#Tur`gyX;GU zltnJ6nS;g&xr7dW`EG{5&4{+P$_Ex;zpdQ!ne}_Vw0_UW*6;Zm^DjY&zdhJX?OQn- zM-vqGHzwE5LL_gRMN_hC7NKN3Jd1EL9+^cX8IR5)nmh;PIZ8I6mu-~#zy|cOZRZ*- zU>`mJ_#^3wub6zPxJN%tZzCf8h_^0(Ks}s1i>4BmT|n&cIWz||t%R1gE7+8<{2V{3 zxEd;9#SGT0FQIJ)?OYIYG1$0r4p*HCG}|755`h9mS(_SUYz5KQEOnM@qP%#Q4x3f81PG-e?Nk zCQjp?o=BbPBuxegrSZ1)!8BB!-n77BjarvPM2Fo~fq0~XBgD-cdFz3%cw`PKn%yuK zkDP%Jy<$j=@b6jJXNQed{-4i(1X;SBEKQS}N65=8IXOvIK1fbJL{^?9Q(h%k-XKT* zLCts2C)Qw}Scm;0iQ7akZWlv1NXuz4h5_*a2F0^DAfCsNcm>1absQ3JV$^5h5iFzK zTkt_Z=1PTBE2nYZG06_o>RH!U}EffF%bSsnLrzU@$SqXep)zv>Iv%Jah*a9I8>xcxh zhaCxsgd|8b2}oFs6yas&B^j9|&b%QBwQ4O^YqhOgEn3&AXr)z95+I6e)mq%Dt=dhi zw$`ejw*40Uil+bb-ppi@3^Np2d; z(`5e13Qu=&zMCH484AyI(*!PX(;hCAo+4?A6)thpRL*sCDVMpalFQ|DmNc`anKO(I z@?3@Ixp=<93uMMZH_hZzq<@i%E9ALY*j;}jW2d?)kC(dmGeIHy)bsVG%Ka46$)nvg)?1TCq4BFHz>Ty#j9O>mUOIf(=u+9X04lE<8=zJS9pWG zp6#YuZgSH~K1bn=ZmJjREBR|K-XtIAN;5~{&2DPsEedOHZf2h}emAX?9^Fk%oa=w$ z7J0TxGsn$s9B}b@F5W6^2eUcEVG%Ck;&yqSFFZvRj=8B-6xzzhF#4F|(ri<>!%ac1 zm8MfBb}77F;jg>te3{$M7s!Hdh`blN=@Y(4J};8Di^Vh-Df~?)wKg2qqg6pI7Sm%) zp6Z$vmFw!(ZmzCvT)U=r`MR~Ws~UecudZCXk}0R|JZ+m+9@N6E<&8!(5N=(}G`uPj zju~3mSg!@+x{EJiat0%(TN$a}X2t7Xg5K#1#$$nP`iekMuk`u!Sge0u3u`8C z<(Vkd9CZQ6IhO>&0b?oCxdmS$*OyCjY_<#6Guf*mew}G#T_CJC#6!(`bghO#u|UM9 z1=nlQfP5!9?M7PwmYbAuXR%E%2=3j!sID1$bs%OiEy^gt2I~ofwgg(^QOyWM!ix(n zqX#18q7yNNFMXV;@VH4qB0qn&j|Q6K^1Ut^WEx?S59>zxx;3?!lAAuIu}zyZe?enB z#56i6qF1L4D*P>U*A4Dwns-bsPam=hJ1eqtbs(Bzs$XW+-29wCyL>~Jz=_^2%VG-e zfLSo;iwB|JG=`@Y45U(+$$M;VdM6VH@K*~Mo3^!I2od*)p~z46o|zH!f>L&)F4il zz-xS}-$I4%U!!Y&D;mZOe!T_X3Ta{Zmx>jU zXu_)${tomTh;4regprgWxUHb@9LN}nHE6Op+ph<837*jm%ECk?7B`axSjLyj*9O_5 zI^a8IV@9tBs18C@pb91cBfM7vTJF}01Q<%mf&G9p0==19c=@sA{t zRcZYa=Y&*1l6_tpv6^r^q^AP4&1B2&*Cksh+mnGWZ|HySCWi`Nq40MVz7iqc7isTG z3r0+31sQ`>X7()TL31_}T(+QS(XE-rb^i z^(z06Z&3M1d;`+)(S@2mTZrvc`9{78BBVGenSm^U0TyeK~nEfDr<;Vw*zBB4eknw5EL64}*jM7%8s zy?+opS) zbv^7gv{1XGZEr`wXe-MPn+16^fFkO7r@#Etf=e1H!s+^h1#V)%bY z_z<#nf3&QOMzt;pnJPa@Z>anjKd$l<^7bTbMz0H!-OYh;!tn$?Pa!)Wt;`t!yJYR{ z@U?{^C`D`w=g(L97w`~Jd0ORX_*s>IDM%cbx%|R0Jwryd##DZee$nT;;f&YuiX#>kFm0yx+Q@($^(o*sPP+xf2;Cq`~VX1>Y%R01WsN#?27Bbws5RKiwU(3Eo_L> z#W6=}>liyPL&-{Nj3eJor zj!N(IJ1V~`2>1*CHEEh5b(qTUNu>Lm;A7HY>#Z74pp8!txamV;xc9}5e^=?B^e>e^ z;Hy;rkZ(uuJbJ80iJR;ZpGJ8%=fsb}NgSBwON+}BzvM8Qj-B+nZ^Xdb1Et}{!Nu3; zA^tKz|7kTT)7VV);sAcvwncv<9o88T^}7_!1+}&EoOzr#6kriY+kyHRRZuwiiemhr zNoj}vu>~2A`QBq$f@$-KT*-W;`;DAIY@4ThUIpd^2m>x%tZWYfhe1G7g0K$~&dzY&FFb$7<%=EI^9#N< zE-$PFcky<=W=Khe4hwQlf5JEH1%-0`GJ0SPHLoCt8IhI!H!-;dwHdMF#1W{~XfrV_ zdx_5E06VduYUupb)7fI?3-cT_gxJQRn$5_5ZYD;2rvF?#CPO#W(Jo&>xr)*|1ExVO1LMn#ve|NvBUm&qQVNH}xM6`~0R#CTVcA4~S_I8Y z3jqUf5yxjfxyycI`iBx%LelFJzo;|seU_XMW`^7zNf4?}UZ|y+5;5L%z2OO0Pks*! zyjJgGxzkA&Eavg=xLhK6FXcJv@m}sFBCS>+S)C}9nPwap{l!Ufti&jBT5ieKHKu-F zNgG&f28p^z2cmx@>Yx5S(&uE{LqTDqcdlwViUZb~_j54|Fd6TtJO$~d8F)K1vQ3NC zN1}R7P!GWd0RFJB-f1L02OA^h%?i|I-KRN2TdjLtPd|)?TmzM-%R1n$>u7j&_<|A9 zlA{ArTc?v~I~5Xtax_<2k*khq8-$$=#GQY&1RFL+U;nUR1n~l%kS-#oaWr z9?htRqKAKI1YSm0*gf`c%BgV3V@!n;{lh)ZUK-m}yZl-o_?9;3Vm2Ju-4 z8H+%<1iY5gL@ER4vrIl$TDztATuN8dHQ>lWi|AUq4piJUkFJM)ZCG1GKcthm~rU2`woPbXDmd$PM}n*BB!=21q?>ZX%7cogZHzF~)pclEvCQMxH#)M7K$ zvVwm`qum6y&!v&H8PM1Q6KXV-nrSBapeR3`Lak6ofKI3LXbo+}j3B;3bUsC>3w;++ z)Kp;$1eDdcLrK|m2F<5C=qKb7p;KzTl=6 zLNgSq{EUVVu*rk;Py%DW0x>~nLlI2jXed2EGZ2%!Nax#RF*{v%-Pd0*MYVZVs-)NUkBnYpelH1zi%|8l+$2hiOsitP-1@; zR<5LO>Vb0hqgOQ*Cp`zyBWlQ|tRpmCp@UNfh}cUHCq#S+Ius^qN}r~xqLLmeudtVj z-^{v^<^oc)H{{GwOi79xo9yVA+t}nNZESLS>>^o(V=v7UM9#PGrv-abuqiUJd;Z3BkNl9NrkDAd- z-o%r#%(ur(kGtQDZ~Si%OqcU573e<+OWd`_jt3U-+EI_f;$1=8)3bhr~rRl&BFGJ z@O~OVp0r>ATN$WOk!m8@`p~3+d{Ch$8A_P^DFlAX z3}3&%-_!Jr6{fR>W20>J9|r7BMGV^0OLdMCSdpzMD2k$Jq^lTp?n!(o0RCcuvm7Zu*)a z43BQw)J^BgV9sW|?16;2fYIgDXg;r{bqS!IOL=JTf+_BX;_gApx)*;l?gPknQw8F| zazum;P~;ZcgUGZOQRN}5?**l9XsZWs>;<*`2;2u?j)O4U!=UyEaB~Rm|CnjSb^vk% z9P?TFC3L$5$?G{YG_=uVIt=)^u-h7Xo?d{lE9gvm(e$b-F!yEpHTtfHSzo4COgJA- z0pKt&DF7IQoquD(ITwHY{5A#W8gjY%u&*m=dHk5W7NZ^M^(&4ipS`$^kN&E4dyE6(wElb&@aIqbV_yHg&VW3u}s zSbvJf0b=6;Fj2-hi$X#Sl}6C-Os^D{U4)Q2UO32-p8^uQ?uUOO9C$7)Ha)5gkCuAd z(#a*rO(zwZ=q#B$2k`76yX3VS$zj{Q!zSK_sk8f8nr7w-(fna-{5*on3$VqDaI%+> zhn}Z7(4Me358LHq)*tCl5Ml(vs^l3P*36(c=`B-$g(*8Mq(7T>5CMjh?QE^c*AFSSZ$ZnIz=kgRZ?WQ71;wlW|Sv>Yh3lk!Bk68Q*2yl#rX zFJ9@Tb~x4fO#{YyDB43Ylqw#(bQvCJ55>j7w(X+fmaA>^D39*yyG}OkkWQER=5al` z2SRQ_nvH_HC>iF{4WLT}cNwa;%Tfw#0N4nYaYHqupbCEvlrf#&hVtH_Qz7XxdJmM| z2dxi~x<3S^50I5UL`M1u^gc!`{{+-NhT=X!_45x?*=wMe>2x;zh5ibSZ9-x29{mj# zABDX3KK&g#LuV;vUjvF(2D=7y1@u?r%bMqgQ+`DM1!=?-8Y!RCD1|;&sPzbyD-`Ub z*`%c5Ttv=Un+GeslV_$U1jzvLvbm}t)>O`n`lHysab4w?&|vp!0bY(+SZ@(tX%%zX#WrK{!>(Y|3<}&3^Ux$>mecqQ(Zu=F$I}h0cHdrjH?qBmVC_Lb=?}^e7j0`ZU3G z4OWS!m7-$ga94j>>om8RXtLn7rqE{z78LsbgfSE_E9FEgcY^p4Fm5?;Ii51hA@Mjf zY1Nuwk;dXLs4v4}=W#8@d`>K`NCYgWS%76;Z3QG}N-C!%73gHKEfXF4?h~YAXGcQK ztD#jL35zbM8HWhlo{W^NvdxvELJ+q8-N2LSN5uaDv)#P90t#<#84xAGQWNk@UY^YQo56_xXe-jKOgpI5vk`tkoi6=?d z2qi4p+9Au=syWP6mJ)(bX)5(WBAA+6-p!P@`Ogr3TiB8L-IQHVxwdtGcO~xQt(u5~ zf3Mk4x8yhor%3h;EM`mR%Sce>Of<$~i4Ux2ILLa2RMoMb(P3Cu@npgp^KH6{(G3b$e`!3M)OK_* zZkUWFJbe)C-UnYk0Cw=q+UKkrtdO|!n%_i!08mQ<1PTBE2nYZG06_p9O4#BS2LJ$D z4gdfq0F$BXB7ZJpY+-YAl~!qE8&?%QV@n!Y9>jK->nrHBoEX z!CP_C)*V|Dc@lY~jz){g;JbqVVi?~G>MHMe8 zI67Te(|_}mX34S5)v{w2ECpKHvJ877ua`G1`%v{;lMR#=YZBYl_5^#yx+FmOP8k%Vcs35$mmy8)*0vXQItS7eGg#z8My_G6| zaDU}XSInY<+4x(t_b&Wwka@Li=P&7{bxMq(CtX|xRFq%S$EAcNmR^>nLt0u|x)cx; zrKLkcx?!b}hKG=p7U^z5T0o?m1(r~{MC32^t-RsucX#*f%=F}`tuhcJBwo{m==G@#31Lf#jPj$2A4Fw|u z{Nz}vf^L70ZTDc#r@EB0ZH$TOQa67w!C#h0bNXsS>4B+Rl92i4vmmPOfMo>Pkdtz2G61InoGdwt&0RM8RBkW4Qzxq^*nrm8F1IKu1IHWd?t*w@Z~>9*&qKRde5 zhq{kCgRy;fW!7js?J5M9sz!-9qpn%*K6?7J^H8GYG~l%=nJa#)bf)wuKm*l3-iIu_ z=^uOU1@D7->|ADbkaR5}?#V3Bz_9OJYjPdWE6n>JPk-9@jLN2;AL<&1w0TfQ5Ua_9 zlcV&Ixy>dQ+f97zGIyO@A-MJZ-C?rERO}aA{PFtwv!Zj%tidN;1pkBe+rI z9f@C#qspj}UKrnUR)P(MP2+cn_w?@kV@_58|4Qe})F0mDbKg4Dl}{`5IK9*-&^$zN zG#q2USDD>5n^ZcI|BIQ);=MyYVa0n)IHUs-HYEmPa{4QtIP-YfDS4{gBbSeP+Dq?FODsb zgSFNvlB$D*I7+XhSO zbY8ljterql_*Jfjd}d|e=AGnbmuJI*=_FrMm|=~DzFSlq_i7G#P&-tJJ=j5_lMAK| zXgzZ?&Y{Lzv=9*2m0e{GA*;-{m7rE{DJ5#A8Ov>jR+w2B)O<_T5$bhuvD@))O3uv==1Rs7{J() z6r%U(js}?)F=Q@`*_x=0G%u9^%H>snq6li%=@nTd|R2XSx z?(RFEI4@Eq-ok?i!-+-1Sh4t+WV@JS`w?*JF#VnSr_n6b^R;1Vn(OM`g*j|VK`Btd z=^0CZ*3zx*OnXWl*ND1sVWV0lD4RHP>Uyebj?2GZup1glppV1 zstB?#ut-DQBy=+L!Na#ssVK9r$%W}_>3VgPBB>AJ2|789o()$A8FH5tOdV{SuOryu zk%BpJ8@hz_K^5pWp?_zhI#;8qZP*w7zR~s3SPoMCkRLUIt(+YRWY~HmcPP0ZPkfh8 z&gAtR;i?+It+fBDxXO9BIYiwcEv7@MU(qA6wICtRBvX@DHvEa^^xcYASCt2^&I+B~ zUQfnW~^dNpn8~4>Y4FFBV~!=cmu<=A%#%bbCz>!YJe{NoWQqg9EY;QPlDo z1}~sEMzr3!m0Nu&0SZ!eba(JqIVb8ms zDM%-E^16xmi?hC$^p0vt^Sca{mYD4!d90m3k1dLm&$s4V4U5&8|1SaX-A_T89|*U2qswvWb?Ixdua;8l-Q2!!(54kP}-`QmB-tZ} zy&XAy!3V`zdsl&nyl2|cELZFcvdB~30@mEdsE^v*8f7Lwv#SCYJan5#DPP&+sM}Kx zbBEu~Xhgmi4?9H;tWeGm5XvIi2IXb%kaW^^Z822P@7`mA*E0sr>A8q1KhX~C-Xo|A zCYZ!$7csVqJjhtxWOA~4tR;aP6v^oLt!bfev(s{3dB7f1s!#v5Yw&4?G@d86!Y9pp z+MD5Cp@n=3#~6FWj7u*$qJK%gDU#X&u0XZd47V@|3;O$4ZlT+EWPmX+B!Nd#EI|7* z&2N9xUQi;xnx-scj_PT86VgyXBz()Sb1~5*g9SU?z}ZRvP958a0Xb(1dokuAb>rN) z(MA>Pc}9+jf@|@K#?9q+c?l^lF%z@khirnE`ADhrt<0DAv!uikSLZum3|B7(^V?B( zHWLy^I+>I;!a`W7^$dwt@he>XSSW|!2rkfRH39XEXa%lQ@LEx{!I?rI#zfl6?gRF55j z;vk!8qC^~y>&4O##Q~K?r6qc*R1QOYT+JFoN%wvbE%$U*>QgWph2Q_>;f|}x)J*0X zoDzyTC6ONNzip?kWR(+wTaj@^jI- z5>y*6na4-{C@R$Y5)lI5|M(ERN0HeX5oDVAwP`3`Jf&!L!xvxVP^_N{Uv1VR!e{IK zigdfhCoiQIYsZWq{Z^@;C0%Dzf?T0wW$mUaa5|H@5+S=5+vmP{Tji3Xh#41ZhYghy zwWi{XS7Wb_`g(-qZSL!VH^N)2T>VOA6!jD)p}gL7wtMlIgr3WgfcLtFezmKkV8(mv z$L3Sx#H8Z(=KE)jS-feUEp%z3yb-45?;OEV=z zfvE*0f){!TVhr zy|mU|=OJ&of9ZD5%Y(`BIEjNhtqh$mwz zX&pfyUZo_mSBAq+`xoImwW}vb)N&F~zvd_>)wTp!f-&Q=_cmN@R0^!&Ca54BrIpeR zHIPd*Qi?Sy{Oa7B+ksdiUC6IA2LTEOpNnk1%0n_Dm{p~fRkO*9-n*+y;QqJ_hKKi% zK=A_=G973eWprOF?Qiy<{@9_4oy#N>-IqHBxxPr!ln502N{iiVe~T*fuG`p=GamhD zliUj;twz5TZhlxl>zKNALQavlQWHKlL(7&?Qr51s)@B}CMz_1*fiAwwBCpW6O1&E_cnWUo~*?lWV}<1dIuWvkQb z037X>itDe$OB&w6rRM0y9Kyd0{`wJ;E6x#xH8U)+w7UnCB-s2!93XpFGZ~Q;J55)^ zQs6f}wT_z_sUK5gLI}>a|W?s&+k5`urvx z{)i{*r?kMwCDP(0(>LZ})>D}_j-2)~0Z)$|J_)syE40;_#RxX2-rZz|g6caxhq z9wwWJ%cs{*+?Dd~A++|%08GTjPHW}5GDw#a4;wySw-f8>z47^w(X6;e+gA3i5J)9# zvtQp3;l0L=$Za~w;NU89m=e315)*RB6c%VX#H7w}CYJ9YLFgvzaU)X-=~hp>l*26- zU#knGkMZVX?pVSkX)Nq}Xa#;jC~Vpr)VD(B1n4F%2xeF48`-uDkHqW7AHBTC?)8Bf)k?Pl6#f zdJo;fWd^zi`6*fDHO9)dPx|X@)Tk<61cr4vj2);>JpbumfqV)6Zd8Q4vxgp z$~M(berZ;HSYXAH$@uKq-Qn=&N>aJfqch3BmV@v@I-yH5%{olL#uEh%=bEk-RzEajpvu1PnrcGmTn z%ev~hUE6qm|MXjvEb_=(*L!+%vq|T_%7%M5k%m^i+{M-|O+G}f;*F}WgbL;_P8pKJ z&VNHPD0ff1B+oy_tM2)yq`~I$UDJ8qGJkk(9)@6bh4h%&c7L@g8sDy@YN+eL$r-K8 z;=4@eRAh}44dV13Y<>=N+fTL1IxiHv9oTxCw+l%@At`C}g(}87R>2PUcU;R zjzoBB-HpxH%l!C3p_YV|n_g-b)C5^DT9>o;t_D}{-J&wvdGM*FylJHIh{e|QmyH+x zhH}*}ruEK2*1#cMab5Tomi9F^cZO2$a>lzVNq@sM--k}N-wB?Qn-b&cd&ja+wl)@9 z5=T6a@3F19@-HK^9%Yr5sXQC~AY1=V$~73l-jJJWzo6lSp=dV)X~QI0p7#$SJ1M{8 z-^uYrJts_fBuC!_^nllFilOzNFjvOe~>Rr5JikF_Yp>irdpU&pR zw}upCpD)Zkv{$3njI0R?YG>#i!HIibyl!xMPI0=21+z-q7OP>aWFGQD+^Wx>o+61u z>?!#esiWAaW~oH8JUY|e9!-BG9wab=`tt}jrA<90affH(wfWv{Ho6wzL*Llvkw!k9Tr?V5go_WC2^fXp8@_kzO^NKg5z1{r1s6_*AeH zuu5NWyPm-5FKm`I#>Q7rA81_D##M2tDgC@*YLa$BOg;tUqSEpL<^}GcXn+DaC1zPM zQ-n?(0(3z?ENTo6X~+J3>KJHWfdP-D7y(=qO>KaHG$odWe#T_&M-@s^ipbHN%bQ@C^)D-h#mYDuVxG41Gy==6?f!*Tgdd2WVXjo*NOvHa+~W8mqtbGD z5J;~1J2e1s?DByepQ4Ql05Q9qpa25^HGCIX*nt3NyD*TqF`9(~e0vZ;8A$~H?_LPn z5c3~n=zB0}4RC{5fvp8jkdO_K3FZQ17CAxwHh}%21gOjgcuPbL@DRX&MYQyu9h#Lw zlMp}$2?K4u1e}pZpa5q;O9~F`BE>*UE;r=2bvW>R2?k>EMzhR7B^>u=?f>3!`7?`x zKw=;C{);uJ!5{s_4wx;|!v6{w|3J|be*fymX#27v2rKl4WM2`6|M!`Z1Or3rFMNIY z4JdO(2}B-uL*k+oL1c+&k^`_2rUYV8oFHiO4eKBZ0n}F@fRe=j{)O4!-2CV-L4w9Y z0Gm}9h&3H85(b)AIYEqBX!0)LC-W~=p*0u?l7kj81JC9ErI3UcS>)Y_Cf0c2za5p5 z6a&Ma<9C|p&nO7YE<}TvfcxuwptDB6d0iWX{Sn|d=LIyE(93A`zjNZ~2Ix1eK$krL zhmFMV6=DDXuKgdw1%ab|e>wW(0zdq>Xa8pu1Tyvi1t \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -105,84 +140,101 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 24467a14..53a6b238 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +25,13 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,38 +64,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/libwallet/go.mod b/libwallet/go.mod index 5b88265f..0bd19427 100644 --- a/libwallet/go.mod +++ b/libwallet/go.mod @@ -3,11 +3,9 @@ module github.com/muun/libwallet go 1.14 require ( - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 github.com/btcsuite/btcutil v1.0.2 github.com/fiatjaf/go-lnurl v1.3.1 - github.com/google/uuid v1.1.1 github.com/jinzhu/gorm v1.9.16 github.com/lightningnetwork/lightning-onion v1.0.1 github.com/lightningnetwork/lnd v0.10.4-beta @@ -15,14 +13,13 @@ require ( github.com/pdfcpu/pdfcpu v0.3.11 github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.2.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/crypto v0.25.0 + golang.org/x/image v0.18.0 // indirect golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13 // indirect - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - golang.org/x/tools v0.1.10 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/net v0.27.0 // indirect google.golang.org/protobuf v1.25.0 gopkg.in/gormigrate.v1 v1.6.0 ) // Fork that includes the -cache flag for quicker builds -replace golang.org/x/mobile => github.com/muun/mobile v0.0.0-20220913162405-8cc629edd37b +replace golang.org/x/mobile => github.com/muun/mobile v0.0.0-20240709203120-049ae58602a0 diff --git a/libwallet/go.sum b/libwallet/go.sum index 92e1994a..f7420546 100644 --- a/libwallet/go.sum +++ b/libwallet/go.sum @@ -1,36 +1,22 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= -github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU= github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= @@ -64,7 +50,6 @@ github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgr github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8 h1:nOsAWScwueMVk/VLm/dvQQD7DuanyvAUb6B3P3eT274= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= @@ -74,23 +59,10 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62 h1:6CturfaAc1IXi5udu7IMLekMFx6uB81XE7w9AGOqpyc= -github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -github.com/champo/mobile v0.0.0-20220503145505-51a7737dc434 h1:7KHSIWZ0Lc70SI4dTuF/8ZDBZWUKJfewCi3FpYHaqwk= -github.com/champo/mobile v0.0.0-20220503145505-51a7737dc434/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -github.com/champo/mobile v0.0.0-20220505154254-6a5f99bae305 h1:YgqwiwLKFqs1/d9BNXUduBA7YZ+uzfrjsKPzXBYAHsw= -github.com/champo/mobile v0.0.0-20220505154254-6a5f99bae305/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -github.com/champo/mobile v0.0.0-20220913162405-8cc629edd37b h1:lBk5LxUbwRPdx8jikAiwae6Z7Z9qupNlgJC2QZXJpVg= -github.com/champo/mobile v0.0.0-20220913162405-8cc629edd37b/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -99,49 +71,35 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fiatjaf/go-lnurl v1.3.1 h1:9Qn4n1ZyzTMW/YuVX2Wr9cE+LEAzpE1hrCbxVK/yBKE= github.com/fiatjaf/go-lnurl v1.3.1/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs= -github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -151,23 +109,19 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc= github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -176,15 +130,12 @@ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEd github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= -github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jezek/xgb v1.0.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= @@ -198,34 +149,22 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= -github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU= github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d h1:irPlN9z5VCe6BTsqVsxheCZH99OFSmqSVyTigW4mEoY= github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag= github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -237,13 +176,11 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQ github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww= github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0= -github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d h1:QWD/5MPnaZfUVP7P8wLa4M8Td2DI7XXHXt2vhVtUgGI= github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604= github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= github.com/lightningnetwork/lnd v0.10.4-beta h1:Af2zOCPePeaU8Tkl8IqtTjr4BP3zYfi+hAtQYcCMM58= github.com/lightningnetwork/lnd v0.10.4-beta/go.mod h1:4d02pduRVtZwgTJ+EimKJTsEAY0jDwi0SPE9h5aRneM= -github.com/lightningnetwork/lnd/cert v1.0.2 h1:g2rEu+sM2Uyz0bpfuvwri/ks6R/26H5iY1NcGbpDJ+c= github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= @@ -254,25 +191,19 @@ github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qp github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= -github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6 h1:b/Op1jKdoE6tzGyjzFx8gc7ZyW3hVFs1jUCQfM/Z2Jo= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/muun/mobile v0.0.0-20220913162405-8cc629edd37b h1:vAbL/T5aP3yNt1TAhukQvsnxLR4pAf1+EmVWecDMr+Q= -github.com/muun/mobile v0.0.0-20220913162405-8cc629edd37b/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= +github.com/muun/mobile v0.0.0-20240709203120-049ae58602a0 h1:1Wc7cbXYLR73MAFL8ztWyYiVAKj6JspMNEjrbFE2Y1w= +github.com/muun/mobile v0.0.0-20240709203120-049ae58602a0/go.mod h1:TCsc78+c4cqb8IKEosz2LwJ6YRNkIjMuAYeHYjchGDE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= @@ -288,32 +219,23 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -325,17 +247,12 @@ github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= -github.com/urfave/cli v1.18.0 h1:m9MfmZWX7bwr9kUcs/Asr95j0IVXzGNNc+/5ku2m26Q= github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -343,36 +260,35 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -388,27 +304,28 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -420,68 +337,71 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= -golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -489,28 +409,21 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso= gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= -gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc= gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= -gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8= gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -521,5 +434,4 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/libwallet/newop/state.go b/libwallet/newop/state.go index 5e072d0f..6241c803 100644 --- a/libwallet/newop/state.go +++ b/libwallet/newop/state.go @@ -918,18 +918,11 @@ type ConfirmState struct { func (s *ConfirmState) OpenFeeEditor() error { - maxFeeRate := newPaymentAnalyzer(s.PaymentContext).MaxFeeRateToAddress(&operation.PaymentToAddress{ - TakeFeeFromAmount: s.TakeFeeFromAmount, - AmountInSat: s.Amount.InSat, - FeeRateInSatsPerVByte: s.FeeRateInSatsPerVByte, - }) - next := &EditFeeState{ - Resolved: s.Resolved, - AmountInfo: s.AmountInfo, - Validated: s.Validated, - Note: s.Note, - MaxFeeRateInSatsPerVByte: maxFeeRate, + Resolved: s.Resolved, + AmountInfo: s.AmountInfo, + Validated: s.Validated, + Note: s.Note, } next.emit() diff --git a/libwallet/operation/payment_analyzer.go b/libwallet/operation/payment_analyzer.go index 142dcef5..7d18cd7b 100644 --- a/libwallet/operation/payment_analyzer.go +++ b/libwallet/operation/payment_analyzer.go @@ -580,30 +580,3 @@ func (a *PaymentAnalyzer) computeFeeForTFFASwap(payment *PaymentToInvoice, feeRa return a.feeCalculator.Fee(onChainAmount, feeRate, true) } - -// MaxFeeRateToAddress computes the maximum fee rate that can be used when -// paying a given amount. This does not imply that the payment _can be made_. -// When given invalid parameters, it's likely to still obtain a value here and -// the resulting analysis would be Unpayable. It's up to the caller to first -// verify the amount is payable, and only then call this method. -func (a *PaymentAnalyzer) MaxFeeRateToAddress(payment *PaymentToAddress) float64 { - - if payment.AmountInSat > a.totalBalance() { - return 0 - } - - var restInSat int64 - if payment.TakeFeeFromAmount { - restInSat = payment.AmountInSat - } else { - restInSat = a.totalBalance() - payment.AmountInSat - } - - for _, sizeForAmount := range a.nextTransactionSize.SizeProgression { - if sizeForAmount.AmountInSat >= payment.AmountInSat { - return float64(restInSat) / float64(sizeForAmount.SizeInVByte) - } - } - - return 0 -} diff --git a/libwallet/operation/payment_analyzer_test.go b/libwallet/operation/payment_analyzer_test.go index 419d4296..34d5d163 100644 --- a/libwallet/operation/payment_analyzer_test.go +++ b/libwallet/operation/payment_analyzer_test.go @@ -2239,170 +2239,3 @@ func TestAnalyzeOffChain(t *testing.T) { }) } } - -func TestMaxFeeRate(t *testing.T) { - testCases := []struct { - desc string - nts *NextTransactionSize - payment *PaymentToAddress - expected float64 - }{ - { - desc: "small amount with one coin", - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 10_000, - }, - expected: 9_900, - }, - { - desc: "take fee from amount one coin", - payment: &PaymentToAddress{ - AmountInSat: 1_000_000, - TakeFeeFromAmount: true, - }, - expected: 10_000, - }, - { - desc: "zero amount", - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 0, - }, - expected: 10_000, - }, - { - desc: "zero amount using TFFA", - payment: &PaymentToAddress{ - TakeFeeFromAmount: true, - AmountInSat: 0, - }, - expected: 0, - }, - { - desc: "amount greater than balance", - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 1_000_000_000, - }, - expected: 0, - }, - { - desc: "small amount with one coin and debt > 0", - nts: &NextTransactionSize{ - SizeProgression: defaultNTS.SizeProgression, - ExpectedDebtInSat: 10_000, - }, - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 10_000, - }, - expected: 9_800, - }, - { - desc: "take fee from amount success with debt > 0", - nts: &NextTransactionSize{ - SizeProgression: defaultNTS.SizeProgression, - ExpectedDebtInSat: 10_000, - }, - payment: &PaymentToAddress{ - AmountInSat: 990_000, - TakeFeeFromAmount: true, - }, - expected: 9_900, - }, - { - desc: "amount greater than balance because debt > 0", - nts: &NextTransactionSize{ - SizeProgression: defaultNTS.SizeProgression, - ExpectedDebtInSat: 10_000, - }, - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 999_900, - }, - expected: 0, - }, - { - desc: "needs 2 coins to spend", - nts: &NextTransactionSize{ - SizeProgression: []SizeForAmount{ - { - AmountInSat: 10_000, - SizeInVByte: 240, - }, - { - AmountInSat: 20_000, - SizeInVByte: 450, - }, - }, - ExpectedDebtInSat: 0, - }, - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 11_000, - }, - expected: 20, - }, - { - desc: "needs 2 coins to spend with debt", - nts: &NextTransactionSize{ - SizeProgression: []SizeForAmount{ - { - AmountInSat: 10_000, - SizeInVByte: 240, - }, - { - AmountInSat: 20_000, - SizeInVByte: 400, - }, - }, - ExpectedDebtInSat: 8_000, - }, - payment: &PaymentToAddress{ - TakeFeeFromAmount: false, - AmountInSat: 11_000, - FeeRateInSatsPerVByte: 0, - }, - expected: 2.5, - }, - { - desc: "TFFA needs 2 coins to spend with debt", - nts: &NextTransactionSize{ - SizeProgression: []SizeForAmount{ - { - AmountInSat: 10_000, - SizeInVByte: 240, - }, - { - AmountInSat: 20_000, - SizeInVByte: 400, - }, - }, - ExpectedDebtInSat: 8_000, - }, - payment: &PaymentToAddress{ - TakeFeeFromAmount: true, - AmountInSat: 12_000, - }, - expected: 30, - }, - } - - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - - var analyzer *PaymentAnalyzer - if tC.nts != nil { - analyzer = NewPaymentAnalyzer(defaultFeeWindow, tC.nts) - } else { - analyzer = NewPaymentAnalyzer(defaultFeeWindow, defaultNTS) - } - - maxFeeRate := analyzer.MaxFeeRateToAddress(tC.payment) - if maxFeeRate != tC.expected { - t.Fatalf("Max fee rate %v != %v", maxFeeRate, tC.expected) - } - }) - } -} diff --git a/tools/bootstrap-gomobile.sh b/tools/bootstrap-gomobile.sh index c5f8dd16..afef6846 100755 --- a/tools/bootstrap-gomobile.sh +++ b/tools/bootstrap-gomobile.sh @@ -11,4 +11,4 @@ mkdir -p "$build_dir/pkg" GOMODCACHE="$build_dir/pkg" \ go install golang.org/x/mobile/cmd/gomobile && \ - go install golang.org/x/mobile/cmd/gobind \ No newline at end of file + go install golang.org/x/mobile/cmd/gobind diff --git a/tools/libwallet-android.sh b/tools/libwallet-android.sh index f348a99c..e835ae72 100755 --- a/tools/libwallet-android.sh +++ b/tools/libwallet-android.sh @@ -5,16 +5,18 @@ set -e repo_root=$(git rev-parse --show-toplevel) build_dir="$repo_root/libwallet/.build" +# Install and setup gomobile on demand (no-op if already installed and up-to-date) +. "$repo_root/tools/bootstrap-gomobile.sh" + # OSS project has a different folder libwallet aar, so we receive it as param libwallet="$1" if [[ ! -s "$1" ]]; then libwallet="$repo_root/android/libwallet/libs/libwallet.aar" fi - cd "$repo_root/libwallet" -mkdir -p "$(dirname $libwallet)" +mkdir -p "$(dirname "$libwallet")" # Create the cache folders mkdir -p "$build_dir/android" @@ -35,10 +37,17 @@ if [[ -z $GOCACHE ]]; then GOCACHE="$build_dir/android" fi +# gomobile bind generates the src-android-* directories several times, leading to fail with: +# /tmp/go-build3034672677/b001/exe/gomobile: mkdir $GOCACHE/src-android-arm64: file exists +# exit status 1 +# There is no significant change in build times without these folders. +rm -rf "$GOCACHE"/src-android-* 2>/dev/null \ + || echo "No src-android-* directories found in GOCACHE." + GOMODCACHE="$GOMODCACHE" \ go run golang.org/x/mobile/cmd/gomobile bind \ -target="android" -o "$libwallet" \ - -cache "$GOCACHE"\ + -androidapi 19 \ -trimpath -ldflags="-buildid=. -v" \ . ./newop