From e62218c99a6f6a3673f88ef5735d590345c50cee Mon Sep 17 00:00:00 2001 From: polstianka Date: Mon, 14 Oct 2024 11:51:22 -0700 Subject: [PATCH] bug fixeds --- .../main/java/com/tonapps/wallet/api/API.kt | 8 --- .../data/core/entity/SignRequestEntity.kt | 2 + .../tonkeeper/billing/BillingManager.kt | 49 +++++++++++------ .../tonkeeper/core/history/HistoryHelper.kt | 14 ++++- .../tonapps/tonkeeper/deeplink/DeepLink.kt | 1 + .../tonkeeper/deeplink/DeepLinkRoute.kt | 1 + .../recharge/BatteryRechargeViewModel.kt | 15 +++--- .../battery/refill/BatteryRefillViewModel.kt | 19 ++++--- .../tonkeeper/ui/screen/nft/NftScreen.kt | 14 ++++- .../tonkeeper/ui/screen/root/RootActivity.kt | 52 +++++++++++++++++-- .../tonkeeper/ui/screen/root/RootEvent.kt | 4 +- .../tonkeeper/ui/screen/root/RootViewModel.kt | 20 ++++++- .../tonkeeper/ui/screen/send/main/SendArgs.kt | 11 +++- .../ui/screen/send/main/SendScreen.kt | 15 ++++-- .../ui/screen/send/main/SendViewModel.kt | 7 +++ .../com/tonapps/blockchain/ton/TONOpCode.kt | 3 +- .../ton/contract/BaseWalletContract.kt | 26 +++++----- .../blockchain/ton/extensions/CellBuilder.kt | 18 +++++++ .../main/java/com/tonapps/extensions/Uri.kt | 2 +- 19 files changed, 213 insertions(+), 68 deletions(-) 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 d678eb0d7..befc73fad 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 @@ -505,14 +505,6 @@ 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) diff --git a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/SignRequestEntity.kt b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/SignRequestEntity.kt index 1f0e1127e..120386e4d 100644 --- a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/SignRequestEntity.kt +++ b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/SignRequestEntity.kt @@ -57,6 +57,8 @@ data class SignRequestEntity( fun setNetwork(network: TonNetwork) = apply { this.network = network } + fun setTestnet(testnet: Boolean) = setNetwork(if (testnet) TonNetwork.TESTNET else TonNetwork.MAINNET) + fun addMessage(message: RawMessageEntity) = apply { messages.add(message) } fun build(): SignRequestEntity { 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 97f407980..19f2ca699 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 @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.time.Duration.Companion.seconds @@ -45,9 +47,7 @@ class BillingManager( .build() private val _productsFlow = MutableStateFlow?>(null) - val productsFlow = _productsFlow.asStateFlow().filterNotNull().filter { - it.isNotEmpty() - } + val productsFlow = _productsFlow.asStateFlow().filterNotNull() private val _purchasesUpdatedFlow = MutableEffectFlow() val purchasesUpdatedFlow = _purchasesUpdatedFlow.shareIn(scope, SharingStarted.Lazily, 0).distinctUntilChanged() @@ -63,12 +63,37 @@ class BillingManager( client.consumePurchase(params) } - suspend fun getProducts( + suspend fun loadProducts( productIds: List, productType: String = ProductType.INAPP - ) = billingClient.ready { client -> + ) { + val products = withTimeoutOrNull(5.seconds) { + getProducts(productIds, productType) + } ?: emptyList() + + _productsFlow.value = products + } + + fun setEmptyProducts() { + _productsFlow.value = emptyList() + } + + private suspend fun getProductDetails(client: BillingClient, params: QueryProductDetailsParams): List = suspendCancellableCoroutine { continuation -> + client.queryProductDetailsAsync(params) { billingResult, productDetailsList -> + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { + continuation.resume(productDetailsList) + } else { + continuation.resumeWithException(BillingException(billingResult)) + } + } + } + + private suspend fun getProducts( + productIds: List, + productType: String = ProductType.INAPP + ): List = billingClient.ready { client -> if (productIds.isEmpty()) { - return@ready + return@ready emptyList() } val productList = productIds.map { productId -> @@ -82,17 +107,7 @@ class BillingManager( .setProductList(productList) .build() - client.queryProductDetailsAsync(params) { billingResult, productDetailsList -> - if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { - _productsFlow.value = productDetailsList - } else { - _productsFlow.value = emptyList() // In case of an error - } - } - } - - fun setEmptyProducts() { - _productsFlow.value = emptyList() + getProductDetails(client, params) } private suspend fun getPendingPurchases(client: BillingClient): List { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt index 8cdf42ca6..2c861f4a0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt @@ -113,7 +113,7 @@ class HistoryHelper( if (burnAddress.equalsAddress(account.address) || (account.name != null && burnAddress == account.name)) { return true } - return "UQCNzZIsoe75gjl8KIwUJW1Fawt-7IbsFwd0ubGIFkig159E".equalsAddress(account.address) + return "UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ".equalsAddress(account.address) } private fun sort(list: List): List { @@ -410,7 +410,17 @@ class HistoryHelper( if (action.jettonSwap != null) { val jettonSwap = action.jettonSwap!! - val jettonPreview = jettonSwap.jettonPreview!! + val jettonPreview = jettonSwap.jettonPreview ?: return createUnknown( + index = index, + txId = txId, + action = action, + date = date, + timestamp = timestamp, + simplePreview = simplePreview, + dateDetails = dateDetails, + isScam = isScam, + wallet = wallet + ) val token = jettonSwap.jettonPreview!!.address val amount = Coins.ofNano(jettonSwap.amount, jettonPreview.decimals) val tokenIn = jettonSwap.tokenIn diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLink.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLink.kt index 898d3de2d..2a9949e60 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLink.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLink.kt @@ -15,6 +15,7 @@ data class DeepLink( // fix for bad tg scheme url = url.replace("tg:resolve", "tg://resolve") + url = url.replace("\\u0026", "&") return Uri.parse(url) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLinkRoute.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLinkRoute.kt index 1dce285e7..2e020066f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLinkRoute.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/deeplink/DeepLinkRoute.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.deeplink import android.net.Uri +import android.util.Log import androidx.core.net.toUri import com.tonapps.blockchain.ton.extensions.safePublicKey import com.tonapps.extensions.hostOrNull 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 eb3531028..224676d55 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 @@ -22,6 +22,7 @@ import com.tonapps.tonkeeper.ui.screen.battery.recharge.list.Item import com.tonapps.tonkeeper.ui.screen.battery.refill.entity.PromoState import com.tonapps.tonkeeper.ui.screen.send.main.state.SendDestination import com.tonapps.tonkeeper.ui.screen.send.transaction.SendTransactionScreen +import com.tonapps.tonkeeperx.BuildConfig import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.TokenEntity @@ -176,7 +177,7 @@ class BatteryRechargeViewModel( uiItems.addAll(uiItemsPacks(packs, selectedPackType, customAmount)) - if (!api.config.batteryPromoDisable) { + if (BuildConfig.DEBUG || !api.config.batteryPromoDisable) { uiItems.add(Item.Space) uiItems.add(Item.Promo(promoState)) } @@ -519,11 +520,13 @@ class BatteryRechargeViewModel( } promoStateFlow.tryEmit(PromoState.Loading()) try { - 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)) + if (api.batteryVerifyPurchasePromo(wallet.testnet, promo)) { + batteryRepository.setAppliedPromo(wallet.testnet, promo) + promoStateFlow.tryEmit(PromoState.Applied(promo)) + } else { + throw IllegalStateException("promo code is invalid") + } + } catch (_: Exception) { batteryRepository.setAppliedPromo(wallet.testnet, null) promoStateFlow.tryEmit(PromoState.Error) 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 99a2cf03d..590357db1 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 @@ -84,7 +84,7 @@ class BatteryRefillViewModel( uiItems.add(uiItemBattery(batteryBalance, api.config)) uiItems.add(Item.Space) - if (!api.config.batteryPromoDisable) { + if (BuildConfig.DEBUG || !api.config.batteryPromoDisable) { uiItems.add(Item.Promo(promoState)) uiItems.add(Item.Space) } @@ -137,7 +137,11 @@ class BatteryRefillViewModel( init { viewModelScope.launch(Dispatchers.IO) { - billingManager.getProducts(api.config.iapPackages.map { it.productId }) + if (environment.isGooglePlayAvailable) { + billingManager.loadProducts(api.config.iapPackages.map { it.productId }) + } else { + billingManager.setEmptyProducts() + } val appliedPromo = batteryRepository.getAppliedPromo(wallet.testnet) @@ -296,11 +300,12 @@ class BatteryRefillViewModel( if (isInitial) { delay(2000) } - 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.value = PromoState.Applied(promo) + if (api.batteryVerifyPurchasePromo(wallet.testnet, promo)) { + batteryRepository.setAppliedPromo(wallet.testnet, promo) + _promoStateFlow.value = PromoState.Applied(promo) + } else { + throw IllegalStateException("promo code is invalid") + } } catch (_: Exception) { batteryRepository.setAppliedPromo(wallet.testnet, null) _promoStateFlow.value = PromoState.Error diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt index 32dd70fc1..6a66d789c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt @@ -14,6 +14,9 @@ import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.toWalletAddress import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.short4 +import com.tonapps.extensions.toUriOrNull +import com.tonapps.tonkeeper.deeplink.DeepLink +import com.tonapps.tonkeeper.deeplink.DeepLinkRoute import com.tonapps.tonkeeper.extensions.copyWithToast import com.tonapps.tonkeeper.extensions.showToast import com.tonapps.tonkeeper.extensions.toastLoading @@ -23,6 +26,7 @@ import com.tonapps.tonkeeper.popup.ActionSheet import com.tonapps.tonkeeper.ui.base.WalletContextScreen import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppArgs import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppScreen +import com.tonapps.tonkeeper.ui.screen.root.RootViewModel import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentBlueColor @@ -39,6 +43,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.activityViewModel import org.koin.core.parameter.parametersOf import uikit.base.BaseFragment import uikit.dialog.alert.AlertDialog @@ -61,6 +66,8 @@ class NftScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_nft private val isCanSend: Boolean get() = !wallet.isWatchOnly && !nftEntity.inSale && nftEntity.ownerAddress.equalsAddress(wallet.address) + private val rootViewModel: RootViewModel by activityViewModel() + override val viewModel: NftViewModel by walletViewModel { parametersOf(nftEntity) } private val verificationIcon: Drawable by lazy { @@ -220,7 +227,12 @@ class NftScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_nft } private fun mustOpenButtonDApp(url: String) { - navigation?.add(DAppScreen.newInstance(wallet, url = url.toUri())) + if (url.startsWith("ton:")) { + val uri = url.toUriOrNull() ?: return + rootViewModel.processDeepLink(uri, false, null, false) + } else { + navigation?.add(DAppScreen.newInstance(wallet, url = url.toUri())) + } finish() } 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 369a875c7..32925916b 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 @@ -5,6 +5,7 @@ import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.os.Handler +import android.util.Log import android.view.View import androidx.biometric.BiometricPrompt import androidx.core.app.ActivityCompat @@ -13,7 +14,10 @@ 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.blockchain.ton.extensions.base64 +import com.tonapps.extensions.currentTimeSeconds import com.tonapps.extensions.toUriOrNull +import com.tonapps.icu.Coins import com.tonapps.tonkeeper.App import com.tonapps.tonkeeper.deeplink.DeepLink import com.tonapps.tonkeeper.extensions.isDarkMode @@ -34,6 +38,8 @@ import com.tonapps.tonkeeperx.R import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.core.Theme +import com.tonapps.wallet.data.core.entity.RawMessageEntity +import com.tonapps.wallet.data.core.entity.SignRequestEntity import com.tonapps.wallet.data.passcode.LockScreen import com.tonapps.wallet.data.passcode.PasscodeBiometric import com.tonapps.wallet.data.passcode.PasscodeManager @@ -43,6 +49,7 @@ import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.localization.Localization import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel +import org.ton.cell.Cell import uikit.base.BaseFragment import uikit.dialog.alert.AlertDialog import uikit.extensions.collectFlow @@ -200,7 +207,8 @@ class RootActivity: BaseWalletActivity() { tokenAddress = event.jettonAddress ?: TokenEntity.TON.address, amountNano = event.amount ?: 0L, text = event.text, - wallet = event.wallet + wallet = event.wallet, + bin = event.bin ) is RootEvent.CloseCurrentTonConnect -> closeCurrentTonConnect {} else -> { } @@ -211,14 +219,48 @@ class RootActivity: BaseWalletActivity() { removeByClass(runnable, SendTransactionScreen::class.java, TonConnectScreen::class.java) } + private fun openSign( + wallet: WalletEntity, + targetAddress: String, + amountNano: Long, + bin: Cell + ) { + + val request = SignRequestEntity.Builder() + .setFrom(wallet.contract.address) + .setValidUntil(currentTimeSeconds()) + .addMessage(RawMessageEntity( + addressValue = targetAddress, + amount = amountNano, + stateInitValue = null, + payloadValue = bin.base64() + )) + .setTestnet(wallet.testnet) + .build() + + val screen = SendTransactionScreen.newInstance(wallet, request) + add(screen) + } + private fun openSend( wallet: WalletEntity, targetAddress: String? = null, tokenAddress: String = TokenEntity.TON.address, amountNano: Long = 0, text: String? = null, - nftAddress: String? = null + nftAddress: String? = null, + bin: Cell? = null ) { + if (bin != null && 0 >= amountNano) { + toast(Localization.invalid_link) + return + } + + if (targetAddress != null && amountNano > 0 && bin != null) { + openSign(wallet, targetAddress, amountNano, bin) + return + } + val fragment = supportFragmentManager.findFragment() if (fragment == null) { add( @@ -229,6 +271,7 @@ class RootActivity: BaseWalletActivity() { amountNano = amountNano, text = text, nftAddress = nftAddress, + bin = bin ) ) } else { @@ -237,6 +280,7 @@ class RootActivity: BaseWalletActivity() { tokenAddress = tokenAddress, amountNano = amountNano, text = text, + bin = bin ) } } @@ -320,9 +364,7 @@ class RootActivity: BaseWalletActivity() { } private fun openTelegramLink(uri: Uri) { - val intent = Intent(Intent.ACTION_VIEW, uri) - intent.`package` = "org.telegram.messenger" - if (!safeStartActivity(intent)) { + if (!safeStartActivity(Intent(Intent.ACTION_VIEW, uri))) { BrowserHelper.open(this, uri) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootEvent.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootEvent.kt index 08e8e4ae9..193834831 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootEvent.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootEvent.kt @@ -8,6 +8,7 @@ import com.tonapps.tonkeeper.ui.screen.init.list.AccountItem import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.purchase.entity.PurchaseMethodEntity import org.ton.api.pub.PublicKeyEd25519 +import org.ton.cell.Cell sealed class RootEvent { data class OpenTab( @@ -39,7 +40,8 @@ sealed class RootEvent { val address: String, val amount: Long?, val text: String?, - val jettonAddress: String? + val jettonAddress: String?, + val bin: Cell? ): RootEvent() data object CloseCurrentTonConnect: RootEvent() 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 fccf2fb74..f5cdb51bd 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 @@ -21,6 +21,7 @@ import com.google.firebase.crashlytics.setCustomKeys import com.google.firebase.ktx.Firebase import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.blockchain.ton.extensions.parseCell import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.currentTimeSeconds @@ -94,6 +95,7 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext +import org.ton.cell.Cell import uikit.extensions.activity class RootViewModel( @@ -432,7 +434,7 @@ class RootViewModel( to = route.to )) } else if (route is DeepLinkRoute.Battery && !wallet.isWatchOnly) { - openScreen(BatteryScreen.newInstance(wallet, route.promocode)) + openBattery(wallet, route) } else if (route is DeepLinkRoute.Purchase && !wallet.isWatchOnly) { openScreen(PurchaseScreen.newInstance(wallet)) } else if (route is DeepLinkRoute.Exchange && !wallet.isWatchOnly) { @@ -492,6 +494,10 @@ class RootViewModel( } } + private suspend fun openBattery(wallet: WalletEntity, route: DeepLinkRoute.Battery) { + openScreen(BatteryScreen.newInstance(wallet, route.promocode)) + } + private suspend fun openTokenViewer(wallet: WalletEntity, route: DeepLinkRoute.Jetton) { val token = tokenRepository.getToken(wallet.accountId, wallet.testnet, route.address) ?: return openScreen(TokenScreen.newInstance(wallet, token.address, token.name, token.symbol)) @@ -502,12 +508,24 @@ class RootViewModel( toast(Localization.expired_link) return } + val bin: Cell? = if (route.bin.isNullOrEmpty()) { + null + } else { + try { + route.bin.parseCell() + } catch (e: Throwable) { + toast(Localization.invalid_link) + return + } + } + _eventFlow.tryEmit(RootEvent.Transfer( wallet = wallet, address = route.address, amount = route.amount, text = route.text, jettonAddress = route.jettonAddress, + bin = bin )) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt index 21173d55a..92cb5c564 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt @@ -1,7 +1,10 @@ package com.tonapps.tonkeeper.ui.screen.send.main import android.os.Bundle +import com.tonapps.blockchain.ton.extensions.base64 +import com.tonapps.blockchain.ton.extensions.safeParseCell import com.tonapps.blockchain.ton.extensions.toRawAddress +import org.ton.cell.Cell import uikit.base.BaseArgs data class SendArgs( @@ -9,7 +12,8 @@ data class SendArgs( val tokenAddress: String, val amountNano: Long, val text: String?, - val nftAddress: String + val nftAddress: String, + val bin: Cell? ): BaseArgs() { private companion object { @@ -18,6 +22,7 @@ data class SendArgs( private const val ARG_AMOUNT_NANO = "amount_nano" private const val ARG_TEXT = "text" private const val ARG_NFT_ADDRESS = "nft_address" + private const val ARG_BIN = "bin" private fun normalizeTokenAddress(address: String?): String { return if (address.isNullOrBlank()) "TON" else address.toRawAddress() @@ -36,7 +41,8 @@ data class SendArgs( tokenAddress = normalizeTokenAddress(bundle.getString(ARG_TOKEN_ADDRESS)), amountNano = normalizeAmount(bundle.getLong(ARG_AMOUNT_NANO)), text = bundle.getString(ARG_TEXT), - nftAddress = bundle.getString(ARG_NFT_ADDRESS) ?: "" + nftAddress = bundle.getString(ARG_NFT_ADDRESS) ?: "", + bin = bundle.getString(ARG_BIN)?.safeParseCell() ) override fun toBundle(): Bundle { @@ -48,6 +54,7 @@ data class SendArgs( } text?.let { bundle.putString(ARG_TEXT, it) } bundle.putString(ARG_NFT_ADDRESS, nftAddress) + bin?.let { bundle.putString(ARG_BIN, it.base64()) } return bundle } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt index 837ca0101..4abf17706 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt @@ -43,6 +43,7 @@ import com.tonapps.wallet.data.core.HIDDEN_BALANCE import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.flow.map import org.koin.core.parameter.parametersOf +import org.ton.cell.Cell import uikit.base.BaseFragment import uikit.extensions.collectFlow import uikit.extensions.doKeyboardAnimation @@ -257,7 +258,7 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s } } - initializeArgs(args.targetAddress, args.amountNano, args.text, args.tokenAddress) + initializeArgs(args.targetAddress, args.amountNano, args.text, args.tokenAddress, args.bin) } private fun signAndSend() { @@ -275,7 +276,11 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s } fun initializeArgs( - targetAddress: String?, amountNano: Long, text: String?, tokenAddress: String + targetAddress: String?, + amountNano: Long, + text: String?, + tokenAddress: String, + bin: Cell? ) { viewModel.initializeTokenAndAmount( tokenAddress = tokenAddress, @@ -284,6 +289,7 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s text?.let { commentInput.text = it } targetAddress?.let { addressInput.text = it } + bin?.let { viewModel.userInputBin(it) } } private fun applyCommentEncryptState(enabled: Boolean) { @@ -545,12 +551,13 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s tokenAddress: String = TokenEntity.TON.address, amountNano: Long = 0, text: String? = null, - nftAddress: String? = null + nftAddress: String? = null, + bin: Cell? = null ): SendScreen { val screen = SendScreen(wallet) screen.setArgs( SendArgs( - targetAddress, tokenAddress, amountNano, text, nftAddress ?: "" + targetAddress, tokenAddress, amountNano, text, nftAddress ?: "", bin ) ) return screen diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt index 6b3d11463..e8843c53c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt @@ -104,6 +104,7 @@ class SendViewModel( val encryptedComment: Boolean = false, val max: Boolean = false, val amountCurrency: Boolean = false, + val bin: Cell? = null ) private val currency = settingsRepository.currency @@ -792,6 +793,12 @@ class SendViewModel( }.flowOn(Dispatchers.IO).launchIn(viewModelScope) } + fun userInputBin(bin: Cell?) { + _userInputFlow.update { + it.copy(bin = bin) + } + } + fun userInputEncryptedComment(encrypted: Boolean) { _userInputFlow.update { it.copy(encryptedComment = encrypted) diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TONOpCode.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TONOpCode.kt index 42166786c..45fa38542 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TONOpCode.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TONOpCode.kt @@ -13,5 +13,6 @@ enum class TONOpCode(val code: Long) { LIQUID_TF_BURN(0x595f07bc), WHALES_DEPOSIT(2077040623), WHALES_WITHDRAW(3665837821), - GASLESS(0x878da6e3) + GASLESS(0x878da6e3), + BATTERY_PAYLOAD(0xb7b2515f), } diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/contract/BaseWalletContract.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/contract/BaseWalletContract.kt index 99a089801..d125b8ad7 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/contract/BaseWalletContract.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/contract/BaseWalletContract.kt @@ -1,6 +1,12 @@ package com.tonapps.blockchain.ton.contract +import android.util.Log +import com.tonapps.blockchain.ton.TONOpCode import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.blockchain.ton.extensions.storeAddress +import com.tonapps.blockchain.ton.extensions.storeMaybeAddress +import com.tonapps.blockchain.ton.extensions.storeMaybeStringTail +import com.tonapps.blockchain.ton.extensions.storeOpCode import com.tonapps.blockchain.ton.extensions.storeStringTail import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.blockchain.ton.tlb.CellStringTlbConstructor @@ -256,18 +262,14 @@ abstract class BaseWalletContract( return cell } - fun createBatteryBody(address: MsgAddressInt? = null, appliedPromo: String? = null): Cell { - val cell = buildCell { - storeUInt(0xb7b2515f, 32) - storeBit(address != null) - if (address != null) { - storeTlb(MsgAddressInt, address) - } - storeBit(appliedPromo.isNullOrEmpty()) - if (!appliedPromo.isNullOrEmpty()) { - storeStringTail(appliedPromo) - } + fun createBatteryBody( + address: MsgAddressInt? = null, + appliedPromo: String? = null + ): Cell { + return buildCell { + storeOpCode(TONOpCode.BATTERY_PAYLOAD) + storeMaybeAddress(address) + storeMaybeStringTail(appliedPromo) } - return cell } } diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt index a3f80fac9..d43b58bfd 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt @@ -44,6 +44,15 @@ fun CellBuilder.storeStringTail(src: String) = apply { writeBytes(src.toByteArray(), this) } +fun CellBuilder.storeMaybeStringTail(src: String?) = apply { + if (src.isNullOrEmpty()) { + storeBit(false) + } else { + storeBit(true) + storeStringTail(src) + } +} + private fun writeBytes(src: ByteArray, builder: CellBuilder) { if (src.isNotEmpty()) { val bytes = floor(builder.availableBits / 8f).toInt() @@ -78,6 +87,15 @@ fun CellBuilder.storeMaybeRef(value: Cell?) = apply { } } +fun CellBuilder.storeMaybeAddress(value: MsgAddressInt?) = apply { + if (value == null) { + storeBit(false) + } else { + storeBit(true) + storeAddress(value) + } +} + fun CellBuilder.storeCoins(value: Coins) = apply { storeTlb(Coins, value) } diff --git a/lib/extensions/src/main/java/com/tonapps/extensions/Uri.kt b/lib/extensions/src/main/java/com/tonapps/extensions/Uri.kt index a5cab7095..969528eba 100644 --- a/lib/extensions/src/main/java/com/tonapps/extensions/Uri.kt +++ b/lib/extensions/src/main/java/com/tonapps/extensions/Uri.kt @@ -53,7 +53,7 @@ val Uri.hostOrNull: String? get() = host?.ifBlank { null } fun Uri.query(key: String): String? { - return getQueryParameter(key)?.ifBlank { null } + return getQueryParameter(key)?.trim()?.ifBlank { null } } fun Uri.queryLong(key: String): Long? {