diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt index a0a0cb05e..d678eb0d7 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt @@ -42,6 +42,7 @@ import io.batteryapi.apis.BatteryApi import io.batteryapi.apis.BatteryApi.UnitsGetBalance import io.batteryapi.models.Balance import io.batteryapi.models.Config +import io.batteryapi.models.PromoCodeBatteryPurchaseRequest import io.batteryapi.models.RechargeMethods import io.tonapi.infrastructure.ClientException import io.tonapi.infrastructure.Serializer @@ -504,6 +505,21 @@ class API( } } + fun batteryApplyPromoCode(token: String, testnet: Boolean, code: String): Boolean { + return withRetry { + battery(testnet).promoCodeBatteryPurchase(token, PromoCodeBatteryPurchaseRequest( + promoCode = code + )).success + } ?: false + } + + fun batteryVerifyPurchasePromo(testnet: Boolean, code: String): Boolean { + return withRetry { + battery(testnet).verifyPurchasePromo(code) + true + } ?: false + } + fun tonconnectProof(address: String, proof: String): String { val url = "${config.tonapiMainnetHost}/v2/wallet/auth/proof" val data = "{\"address\":\"$address\",\"proof\":$proof}" diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt index 53631a7d3..f5071b59c 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt @@ -352,12 +352,12 @@ class AccountRepository( } private fun createTonProofToken(wallet: WalletEntity): String? { + val payload = api.tonconnectPayload() ?: return null return try { val publicKey = wallet.publicKey val contract = BaseWalletContract.create(publicKey, WalletVersion.V4R2.title, wallet.testnet) val secretKey = vaultSource.getPrivateKey(publicKey) ?: throw Exception("private key not found") val address = contract.address - val payload = api.tonconnectPayload() ?: throw Exception("payload not found") val proof = WalletProof.signTonkeeper( address = address, secretKey = secretKey, diff --git a/apps/wallet/instance/app/build.gradle.kts b/apps/wallet/instance/app/build.gradle.kts index 84735d3df..1a80f3b46 100644 --- a/apps/wallet/instance/app/build.gradle.kts +++ b/apps/wallet/instance/app/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation(Dependence.GooglePlay.review) implementation(Dependence.GooglePlay.billing) + implementation(Dependence.GooglePlay.update) implementation(Dependence.Squareup.okhttp) implementation(Dependence.Squareup.sse) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/Environment.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/Environment.kt index ac3814bd2..1fbb6d69d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/Environment.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/Environment.kt @@ -17,7 +17,6 @@ class Environment(context: Context) { } val isGooglePlayAvailable: Boolean by lazy { - // installerSource == AppInstall.Source.GOOGLE_PLAY && isGooglePlayServicesAvailable - true + installerSource == AppInstall.Source.GOOGLE_PLAY && isGooglePlayServicesAvailable } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/billing/BillingManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/billing/BillingManager.kt index bc27b4459..b375d6190 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/billing/BillingManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/billing/BillingManager.kt @@ -15,6 +15,7 @@ import com.android.billingclient.api.QueryPurchasesParams import com.android.billingclient.api.consumePurchase import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.filterList +import com.tonapps.wallet.data.account.entities.WalletEntity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow @@ -108,6 +109,7 @@ class BillingManager( suspend fun requestPurchase( activity: Activity, + wallet: WalletEntity, productDetails: ProductDetails ) = billingClient.ready { client -> val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder() @@ -115,6 +117,8 @@ class BillingManager( .build() val billingFlowParams = BillingFlowParams.newBuilder() + .setObfuscatedAccountId(wallet.accountId) + .setObfuscatedProfileId(wallet.accountId) .setProductDetailsParamsList(listOf(productDetailsParams)) .build() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/RawMessageEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/RawMessageEntity.kt index 2f50f663e..974726d76 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/RawMessageEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/RawMessageEntity.kt @@ -1,7 +1,10 @@ package com.tonapps.tonkeeper.extensions +import android.util.Log import com.tonapps.blockchain.ton.TONOpCode import com.tonapps.blockchain.ton.TonTransferHelper +import com.tonapps.blockchain.ton.extensions.loadAddress +import com.tonapps.blockchain.ton.extensions.loadCoins import com.tonapps.blockchain.ton.extensions.loadMaybeRef import com.tonapps.blockchain.ton.extensions.loadOpCode import com.tonapps.blockchain.ton.extensions.storeAddress @@ -29,20 +32,19 @@ private fun RawMessageEntity.rebuildBodyWithCustomExcessesAccount( TONOpCode.STONFI_SWAP -> { builder .storeOpCode(TONOpCode.STONFI_SWAP) - .storeAddress(slice.loadTlb(MsgAddressInt.tlbCodec())) - .storeCoins(slice.loadTlb(Coins.tlbCodec())) - .storeAddress(slice.loadTlb(MsgAddressInt.tlbCodec())) + .storeAddress(slice.loadAddress()) + .storeCoins(slice.loadCoins()) + .storeAddress(slice.loadAddress()) if (slice.loadBit()) { - slice.loadTlb(MsgAddressInt.tlbCodec()) + slice.loadAddress() } slice.endParse() builder .storeBit(true) - .storeTlb(MsgAddressInt, excessesAddress) - - builder.endCell() + .storeAddress(excessesAddress) + .endCell() } TONOpCode.NFT_TRANSFER -> payload TONOpCode.JETTON_TRANSFER -> payload @@ -60,15 +62,15 @@ private fun RawMessageEntity.rebuildJettonTransferWithCustomPayload( } val queryId = slice.loadUInt(64) - val jettonAmount = slice.loadTlb(Coins.tlbCodec()) - val receiverAddress = slice.loadTlb(MsgAddressInt.tlbCodec()) - val excessesAddress = slice.loadTlb(MsgAddressInt.tlbCodec()) + val jettonAmount = slice.loadCoins() + val receiverAddress = slice.loadAddress() + val excessesAddress = slice.loadAddress() val customPayload = slice.loadMaybeRef() if (customPayload != null) { return payload } - val forwardAmount = slice.loadTlb(Coins.tlbCodec()).amount.toLong() + val forwardAmount = slice.loadCoins().amount.toLong() val forwardBody = slice.loadMaybeRef() return TonTransferHelper.jetton( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt index e272cb522..4f59698f1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt @@ -176,7 +176,7 @@ class BatteryRechargeViewModel( uiItems.addAll(uiItemsPacks(packs, selectedPackType, customAmount)) - if (!api.config.batteryPromoDisable) { + if (true) { // !api.config.batteryPromoDisable uiItems.add(Item.Space) uiItems.add(Item.Promo(promoState)) } @@ -519,8 +519,10 @@ class BatteryRechargeViewModel( } promoStateFlow.tryEmit(PromoState.Loading()) try { - api.battery(wallet.testnet).verifyPurchasePromo(promo) + api.batteryVerifyPurchasePromo(wallet.testnet, promo) + val token = accountRepository.requestTonProofToken(wallet) ?: throw IllegalStateException("proof token is null") batteryRepository.setAppliedPromo(wallet.testnet, promo) + api.batteryApplyPromoCode(token, wallet.testnet, promo) promoStateFlow.tryEmit(PromoState.Applied(promo)) } catch (_: Exception) { batteryRepository.setAppliedPromo(wallet.testnet, null) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/BatteryRefillViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/BatteryRefillViewModel.kt index 5491e9530..0152cc34e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/BatteryRefillViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/BatteryRefillViewModel.kt @@ -81,7 +81,7 @@ class BatteryRefillViewModel( uiItems.add(uiItemBattery(batteryBalance, api.config)) uiItems.add(Item.Space) - if (!api.config.batteryPromoDisable) { + if (true) { // !api.config.batteryPromoDisable uiItems.add(Item.Promo(promoState)) uiItems.add(Item.Space) } @@ -295,7 +295,9 @@ class BatteryRefillViewModel( if (isInitial) { delay(2000) } - api.battery(wallet.testnet).verifyPurchasePromo(promo) + val token = accountRepository.requestTonProofToken(wallet) ?: throw IllegalStateException("proof token is null") + api.batteryVerifyPurchasePromo(wallet.testnet, promo) + api.batteryApplyPromoCode(token, wallet.testnet, promo) batteryRepository.setAppliedPromo(wallet.testnet, promo) promoStateFlow.tryEmit(PromoState.Applied(promo)) } catch (_: Exception) { @@ -336,7 +338,7 @@ class BatteryRefillViewModel( fun makePurchase(productId: String, activity: Activity) { billingManager.productFlow(productId).collectFlow { product -> - billingManager.requestPurchase(activity, product) + billingManager.requestPurchase(activity, wallet, product) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/PromoHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/PromoHolder.kt index 36d6e8ab9..d236acabb 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/PromoHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/PromoHolder.kt @@ -62,6 +62,8 @@ class PromoHolder( private fun applyPromoCode(item: Item.Promo) { if (inputView.text.isNotBlank() && inputView.text != item.appliedPromo && !item.isLoading) { onSubmitPromo(inputView.text) + } else if (inputView.text.isBlank()) { + onSubmitPromo("") } } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt index 0e02c3159..369a875c7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt @@ -11,6 +11,8 @@ import androidx.core.app.ActivityCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateManagerFactory import com.tonapps.extensions.toUriOrNull import com.tonapps.tonkeeper.App import com.tonapps.tonkeeper.deeplink.DeepLink diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt index f326ccd73..fccf2fb74 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt @@ -9,6 +9,12 @@ import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.net.toUri import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.google.android.play.core.appupdate.AppUpdateInfo +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.appupdate.AppUpdateOptions +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.UpdateAvailability import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.crashlytics.setCustomKeys @@ -22,6 +28,7 @@ import com.tonapps.extensions.locale import com.tonapps.extensions.setLocales import com.tonapps.extensions.toUriOrNull import com.tonapps.ledger.ton.LedgerConnectData +import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.core.history.HistoryHelper @@ -72,6 +79,7 @@ import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.token.TokenRepository import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine @@ -84,7 +92,9 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext +import uikit.extensions.activity class RootViewModel( app: Application, @@ -99,11 +109,16 @@ class RootViewModel( private val browserRepository: BrowserRepository, private val pushManager: PushManager, private val tokenRepository: TokenRepository, + private val environment: Environment, savedStateHandle: SavedStateHandle, ): BaseWalletVM(app) { private val savedState = RootModelState(savedStateHandle) + private val appUpdateManager: AppUpdateManager by lazy { + AppUpdateManagerFactory.create(context) + } + private val selectedWalletFlow: Flow = accountRepository.selectedWalletFlow private val _hasWalletFlow = MutableEffectFlow() @@ -165,6 +180,35 @@ class RootViewModel( }.filterNotNull().onEach { settingsRepository.country = it }.flowOn(Dispatchers.IO).launchIn(viewModelScope) + + + viewModelScope.launch(Dispatchers.IO) { + if (environment.isGooglePlayAvailable) { + delay(2000) + checkAppUpdate() + } + } + } + + private suspend fun checkAppUpdate() = withContext(Dispatchers.IO) { + try { + val updateInfo = appUpdateManager.appUpdateInfo.await() + if (updateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { + startUpdateFlow(updateInfo) + } + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + } + } + + private suspend fun startUpdateFlow(appUpdateInfo: AppUpdateInfo) = withContext(Dispatchers.Main) { + val activity = context.activity ?: return@withContext + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + activity, + AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE), + 0 + ) } fun connectTonConnectBridge() { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/SwapScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/SwapScreen.kt index fdcfb976b..b62113983 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/SwapScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/SwapScreen.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.swap import android.net.Uri import android.os.Bundle +import android.util.Log import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/emulation/EmulationUseCase.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/emulation/EmulationUseCase.kt index e4f7567d1..e2642cc27 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/emulation/EmulationUseCase.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/emulation/EmulationUseCase.kt @@ -1,7 +1,9 @@ package com.tonapps.tonkeeper.usecase.emulation import android.util.Log +import com.tonapps.blockchain.ton.AndroidSecureRandom import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 +import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.icu.Coins import com.tonapps.icu.Coins.Companion.sumOf import com.tonapps.tonkeeper.extensions.toGrams @@ -19,6 +21,7 @@ import com.tonapps.wallet.data.settings.SettingsRepository import io.tonapi.models.JettonQuantity import io.tonapi.models.MessageConsequences import io.tonapi.models.Risk +import org.ton.api.pk.PrivateKeyEd25519 import org.ton.cell.Cell import org.ton.contract.wallet.WalletTransfer import java.math.BigDecimal @@ -56,7 +59,7 @@ class EmulationUseCase( internalMessage: Boolean ): Cell { return message.createSignedBody( - privateKey = EmptyPrivateKeyEd25519, + privateKey = PrivateKeyEd25519(AndroidSecureRandom), internalMessage = internalMessage ) } diff --git a/buildSrc/src/main/kotlin/Dependence.kt b/buildSrc/src/main/kotlin/Dependence.kt index 40e6d3f09..57977ede6 100644 --- a/buildSrc/src/main/kotlin/Dependence.kt +++ b/buildSrc/src/main/kotlin/Dependence.kt @@ -25,6 +25,7 @@ object Dependence { const val cronetOkhttp = "com.google.net.cronet:cronet-okhttp:0.1.0" const val review = "com.google.android.play:review-ktx:2.0.1" const val billing = "com.android.billingclient:billing-ktx:7.1.1" + const val update = "com.google.android.play:app-update-ktx:2.1.0" } object UI { diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellSlice.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellSlice.kt index 443ed6630..13f5b9552 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellSlice.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellSlice.kt @@ -1,8 +1,12 @@ package com.tonapps.blockchain.ton.extensions import com.tonapps.blockchain.ton.TONOpCode +import org.ton.block.Coins +import org.ton.block.MsgAddress +import org.ton.block.MsgAddressInt import org.ton.cell.Cell import org.ton.cell.CellSlice +import org.ton.tlb.loadTlb fun CellSlice.loadOpCode(): TONOpCode { val code = loadUInt32() @@ -15,4 +19,14 @@ fun CellSlice.loadMaybeRef(): Cell? { return null } return loadRef() +} + +fun CellSlice.loadAddress(): MsgAddressInt { + // loadTlb(MsgAddressInt.tlbCodec()) + return loadTlb(MsgAddressInt) + +} + +fun CellSlice.loadCoins(): Coins { + return loadTlb(Coins) } \ No newline at end of file