From 9f6f5b236f4cf41469b4fe247c963906b72dee6f Mon Sep 17 00:00:00 2001 From: mirror-bot Date: Wed, 11 Mar 2026 09:34:36 +0000 Subject: [PATCH] Release v26.02.1_1250 --- .github/dependabot.yml | 17 + .github/workflows/baselineprofile.yml | 10 +- .github/workflows/beta.yml | 58 ++ .github/workflows/cd.yml | 60 -- .github/workflows/claude.yml | 82 +++ .github/workflows/debug.yml | 64 ++ .github/workflows/mirror-public-build.yml | 82 +++ .github/workflows/mirror-public-push.yml | 58 ++ .github/workflows/publish.yml | 74 +- .github/workflows/release.yml | 82 --- .github/workflows/uk.yml | 10 +- .gitignore | 6 +- .idea/.gitignore | 5 - .idea/.name | 1 - .idea/compiler.xml | 6 - .idea/deploymentTargetDropDown.xml | 26 - .idea/gradle.xml | 92 --- .idea/kotlinc.xml | 6 - .idea/misc.xml | 10 - .idea/vcs.xml | 6 - AGENTS.MD | 61 ++ README.md | 212 +++++- apps/signer/build.gradle.kts | 54 +- apps/signer/proguard-rules.pro | 12 +- .../src/main/java/com/tonapps/signer/App.kt | 7 +- .../signer/core/source/BackupSource.kt | 1 - .../com/tonapps/signer/deeplink/TKDeepLink.kt | 2 +- .../screen/create/child/CreateNameFragment.kt | 2 +- .../signer/screen/emulate/EmulateFragment.kt | 2 +- .../signer/screen/name/NameFragment.kt | 2 +- .../signer/screen/root/RootActivity.kt | 2 +- apps/wallet/api/build.gradle.kts | 45 +- .../main/java/com/tonapps/wallet/api/API.kt | 348 +++++---- .../com/tonapps/wallet/api/BatteryProvider.kt | 8 +- .../java/com/tonapps/wallet/api/CoreAPI.kt | 160 +++-- .../java/com/tonapps/wallet/api/Extensions.kt | 27 +- .../com/tonapps/wallet/api/FileDownloader.kt | 2 +- .../java/com/tonapps/wallet/api/Provider.kt | 34 +- .../tonapps/wallet/api/ServerTimeProvider.kt | 17 +- .../wallet/api/backoff/ExponentialBackoff.kt | 43 ++ .../com/tonapps/wallet/api/core/SourceAPI.kt | 13 +- .../wallet/api/cronet/CronetCallFactory.java | 312 -------- .../wallet/api/cronet/CronetInterceptor.java | 153 ---- .../api/cronet/CronetTimeoutException.java | 5 - .../cronet/CronetTransportResponseBody.java | 39 - .../cronet/OkHttpBridgeRequestCallback.java | 300 -------- .../wallet/api/cronet/RedirectStrategy.java | 73 -- .../api/cronet/RequestBodyConverter.java | 11 - .../api/cronet/RequestBodyConverterImpl.java | 319 --------- .../api/cronet/RequestResponseConverter.java | 147 ---- .../RequestResponseConverterBasedBuilder.java | 74 -- .../wallet/api/cronet/ResponseConverter.java | 249 ------- .../api/cronet/UploadBodyDataBroker.java | 161 ----- .../wallet/api/entity/AccountDetailsEntity.kt | 34 +- .../wallet/api/entity/AccountEntity.kt | 17 +- .../tonapps/wallet/api/entity/AppVersion.kt | 1 - .../wallet/api/entity/BalanceEntity.kt | 36 +- .../tonapps/wallet/api/entity/ConfigEntity.kt | 11 +- .../wallet/api/entity/ConfigResponseEntity.kt | 20 + .../api/entity/EmulateWithBatteryResult.kt | 9 + .../tonapps/wallet/api/entity/FlagsEntity.kt | 9 +- .../api/entity/QRScannerExtendsEntity.kt | 2 +- .../tonapps/wallet/api/entity/TokenEntity.kt | 53 +- .../api/entity/value/BlockchainAddress.kt | 19 +- .../api/interceptors/LoggingInterceptor.kt | 166 +++++ .../api/interceptors/RateLimitInterceptor.kt | 60 ++ .../wallet/api/internal/ConfigRepository.kt | 46 +- .../wallet/api/internal/InternalApi.kt | 64 +- .../com/tonapps/wallet/api/tron/TronApi.kt | 280 +++++++- .../api/tron/entity/TronEstimationEntity.kt | 9 +- .../api/tron/entity/TronResourcePrices.kt | 8 + .../api/src/main/res/drawable/ic_trx.png | Bin 0 -> 10892 bytes apps/wallet/data/account/build.gradle.kts | 33 +- .../wallet/data/account/AccountRepository.kt | 51 +- .../com/tonapps/wallet/data/account/Wallet.kt | 2 +- .../data/account/entities/WalletEntity.kt | 20 +- .../data/account/source/DatabaseSource.kt | 2 +- apps/wallet/data/backup/build.gradle.kts | 14 +- .../data/backup/source/LocalDataSource.kt | 2 +- apps/wallet/data/battery/build.gradle.kts | 23 +- .../wallet/data/battery/BatteryMapper.kt | 9 + .../wallet/data/battery/BatteryRepository.kt | 85 ++- .../battery/entity/BatteryConfigEntity.kt | 5 +- .../battery/entity/RechargeMethodEntity.kt | 23 +- .../data/battery/source/LocalDataSource.kt | 38 +- .../data/battery/source/RemoteDataSource.kt | 16 +- apps/wallet/data/browser/build.gradle.kts | 18 +- .../wallet/data/browser/BrowserRepository.kt | 35 +- .../data/browser/entities/BrowserAppEntity.kt | 2 +- .../browser/entities/BrowserDataEntity.kt | 2 +- .../data/browser/source/RemoteDataSource.kt | 7 +- .../wallet/data/collectibles/build.gradle.kts | 20 +- .../collectibles/CollectiblesRepository.kt | 59 +- .../data/collectibles/entities/NftEntity.kt | 35 +- .../entities/NftMetadataEntity.kt | 2 +- .../collectibles/entities/NftPreviewEntity.kt | 2 +- .../collectibles/source/LocalDataSource.kt | 2 +- apps/wallet/data/contacts/build.gradle.kts | 17 +- .../data/contacts/ContactsRepository.kt | 15 +- apps/wallet/data/core/build.gradle.kts | 32 +- .../wallet/data/core/ScreenCacheSource.kt | 2 +- .../data/core/currency/WalletCurrency.kt | 4 +- .../data/core/entity/SignRequestEntity.kt | 10 +- apps/wallet/data/dapps/build.gradle.kts | 27 +- .../wallet/data/dapps/DAppsRepository.kt | 68 +- .../tonapps/wallet/data/dapps/LegacyHelper.kt | 6 +- .../data/dapps/entities/AppConnectEntity.kt | 3 +- .../data/dapps/source/DatabaseSource.kt | 105 ++- apps/wallet/data/events/build.gradle.kts | 36 +- .../wallet/data/events/EventsRepository.kt | 80 +-- .../tonapps/wallet/data/events/Extensions.kt | 5 +- .../data/events/source/RemoteDataSource.kt | 24 +- .../wallet/data/events/tx/TxActionMapper.kt | 79 ++- apps/wallet/data/passcode/build.gradle.kts | 25 +- .../wallet/data/passcode/LockScreen.kt | 2 +- .../wallet/data/passcode/PasscodeHelper.kt | 2 +- .../wallet/data/passcode/PasscodeManager.kt | 2 +- .../data/passcode/dialog/PasscodeDialog.kt | 2 +- .../data/passcode/source/PasscodeStore.kt | 2 +- .../wallet/data/passcode/ui/PasscodeView.kt | 2 +- apps/wallet/data/plugins/build.gradle.kts | 16 +- .../wallet/data/plugins/PluginsRepository.kt | 11 +- apps/wallet/data/purchase/build.gradle.kts | 16 +- .../data/purchase/PurchaseRepository.kt | 60 +- .../wallet/data/purchase/entity/OnRamp.kt | 2 +- .../data/purchase/entity/OnRampCurrencies.kt | 2 +- apps/wallet/data/rates/build.gradle.kts | 20 +- .../wallet/data/rates/RatesRepository.kt | 60 +- .../wallet/data/rates/entity/RatesEntity.kt | 2 +- .../data/rates/source/BlobDataSource.kt | 18 +- apps/wallet/data/rn/build.gradle.kts | 15 +- .../com/tonapps/wallet/data/rn/RNLegacy.kt | 2 +- .../tonapps/wallet/data/rn/RNSeedStorage.kt | 2 +- .../tonapps/wallet/data/rn/data/RNWallet.kt | 2 +- .../wallet/data/rn/expo/SecureStoreModule.kt | 8 +- .../data/rn/expo/encryptors/AESEncryptor.kt | 1 - apps/wallet/data/settings/build.gradle.kts | 16 +- .../data/settings/SettingsRepository.kt | 7 + .../entities/PreferredTronFeeMethod.kt | 16 + .../data/settings/folder/TokenPrefsFolder.kt | 2 +- .../data/settings/folder/WalletPrefsFolder.kt | 18 +- apps/wallet/data/staking/build.gradle.kts | 21 +- .../wallet/data/staking/StakingPool.kt | 1 + .../wallet/data/staking/StakingRepository.kt | 14 +- .../staking/entities/StakingInfoEntity.kt | 2 +- .../data/staking/source/RemoteDataSource.kt | 15 +- apps/wallet/data/swap/build.gradle.kts | 16 +- .../wallet/data/swap/SwapRepository.kt | 10 +- apps/wallet/data/tokens/build.gradle.kts | 22 +- .../wallet/data/token/TokenRepository.kt | 174 +++-- .../data/token/entities/AccountTokenEntity.kt | 3 + .../data/token/source/RemoteDataSource.kt | 17 +- apps/wallet/features/sample/build.gradle.kts | 15 + apps/wallet/instance/app/.gitignore | 2 +- apps/wallet/instance/app/build.gradle.kts | 140 ++-- .../instance/app/src/main/AndroidManifest.xml | 9 +- apps/wallet/instance/app/src/main/assets/key | Bin 0 -> 294 bytes .../main/java/com/tonapps/tonkeeper/App.kt | 69 +- .../java/com/tonapps/tonkeeper/Environment.kt | 2 +- .../com/tonapps/tonkeeper/RemoteConfig.kt | 11 +- .../tonkeeper/billing/BillingManager.kt | 28 +- .../client/safemode/SafeModeClient.kt | 2 +- .../java/com/tonapps/tonkeeper/core/Amount.kt | 3 + .../com/tonapps/tonkeeper/core/DevSettings.kt | 26 +- .../tonkeeper/core/entities/AssetsEntity.kt | 20 +- .../core/entities/AssetsExtendedEntity.kt | 3 + .../tonkeeper/core/entities/StakedEntity.kt | 4 +- .../tonkeeper/core/entities/TransferEntity.kt | 15 + .../entities/WalletPurchaseMethodEntity.kt | 2 +- .../tonkeeper/core/history/Extensions.kt | 5 +- .../tonkeeper/core/history/HistoryHelper.kt | 109 +-- .../history/list/HistoryItemDecoration.kt | 2 +- .../core/signer/SingerResultContract.kt | 2 +- .../tonapps/tonkeeper/deeplink/DeepLink.kt | 29 +- .../tonkeeper/deeplink/DeepLinkRoute.kt | 2 +- .../tonapps/tonkeeper/extensions/Context.kt | 3 +- .../tonkeeper/extensions/DAppsRepository.kt | 4 +- .../extensions/PurchaseRepository.kt | 2 +- .../tonapps/tonkeeper/extensions/SendFee.kt | 12 +- .../extensions/SettingsRepository.kt | 5 +- .../tonkeeper/extensions/SignRequestEntity.kt | 4 +- .../tonapps/tonkeeper/extensions/Wallet.kt | 2 +- .../tonkeeper/extensions/WalletCurrency.kt | 2 +- .../tonapps/tonkeeper/helper/BatteryHelper.kt | 6 +- .../tonapps/tonkeeper/helper/BrowserHelper.kt | 7 +- .../tonkeeper/helper/NotificationsHelper.kt | 2 +- .../com/tonapps/tonkeeper/helper/TwinInput.kt | 2 +- .../com/tonapps/tonkeeper/koin/Extension.kt | 7 +- .../com/tonapps/tonkeeper/koin/KoinModule.kt | 11 +- .../tonkeeper/koin/viewModelWalletModule.kt | 2 + .../tonkeeper/manager/apk/APKManager.kt | 3 - .../tonkeeper/manager/assets/AssetsManager.kt | 13 +- .../tonkeeper/manager/push/FirebasePush.kt | 2 +- .../tonkeeper/manager/push/PushManager.kt | 8 +- .../manager/theme/MainContextWrapper.kt | 4 +- .../manager/tonconnect/TonConnect.kt | 2 +- .../manager/tonconnect/TonConnectManager.kt | 96 +-- .../manager/tonconnect/bridge/Bridge.kt | 2 +- .../manager/tonconnect/bridge/JsonBuilder.kt | 2 +- .../tonconnect/bridge/model/BridgeEvent.kt | 36 +- .../bridge/model/SignDataRequestPayload.kt | 6 +- .../manager/tx/BaseTransactionManager.kt | 14 +- .../manager/tx/TransactionManager.kt | 20 +- .../tonkeeper/manager/tx/model/PendingHash.kt | 4 +- .../tonkeeper/manager/widget/WidgetManager.kt | 2 +- .../manager/widget/WidgetSettings.kt | 2 +- .../tonapps/tonkeeper/os/AndroidCurrency.kt | 2 +- .../com/tonapps/tonkeeper/os/AppInstall.kt | 1 + .../tonkeeper/ui/base/BaseListWalletScreen.kt | 2 - .../tonkeeper/ui/base/BaseWalletScreen.kt | 2 +- .../tonapps/tonkeeper/ui/base/BaseWalletVM.kt | 16 - .../ui/base/InjectedTonConnectScreen.kt | 74 +- .../tonkeeper/ui/base/QRCameraScreen.kt | 2 +- .../ui/base/compose/ComposeScreen.kt | 6 +- .../tonkeeper/ui/component/SnackBarView.kt | 2 +- .../ui/component/TonConnectWebView.kt | 2 +- .../ui/component/UpdateAvailableDialog.kt | 2 +- .../tonkeeper/ui/component/WordEditText.kt | 2 +- .../ui/component/chart/ChartDrawable.kt | 2 +- .../tonkeeper/ui/component/chart/ChartView.kt | 2 +- .../ui/component/coin/CoinInputView.kt | 2 +- .../coin/format/CoinFormattingTextWatcher.kt | 2 +- .../ui/component/label/LabelEditorView.kt | 2 +- .../ui/component/token/TokenPickerView.kt | 2 +- .../ui/component/wallet/WalletHeaderView.kt | 2 +- .../ui/screen/add/AddWalletScreen.kt | 1 + .../ui/screen/add/AddWalletViewModel.kt | 14 +- .../tonkeeper/ui/screen/add/list/Adapter.kt | 2 + .../tonkeeper/ui/screen/add/list/Item.kt | 17 + .../add/list/holder/SectionTitleHolder.kt | 21 + .../screen/backup/check/BackupCheckScreen.kt | 11 - .../ui/screen/backup/main/BackupScreen.kt | 4 +- .../ui/screen/backup/main/BackupViewModel.kt | 15 +- .../ui/screen/battery/BatteryScreen.kt | 2 +- .../ui/screen/battery/BatteryViewModel.kt | 15 +- .../recharge/BatteryRechargeViewModel.kt | 78 +- .../recharge/list/holder/AddressHolder.kt | 2 - .../battery/refill/BatteryRefillViewModel.kt | 83 ++- .../battery/refill/list/holder/Holder.kt | 2 +- .../settings/BatterySettingsViewModel.kt | 4 +- .../screen/browser/base/BrowserBaseScreen.kt | 2 +- .../browser/base/BrowserBaseViewModel.kt | 2 +- .../browser/confirm/DAppConfirmComposable.kt | 9 +- .../ui/screen/browser/dapp/DAppBridge.kt | 5 +- .../ui/screen/browser/dapp/DAppScreen.kt | 6 +- .../screen/browser/main/BrowserMainScreen.kt | 6 +- .../browser/main/BrowserMainViewModel.kt | 13 +- .../explore/list/holder/ExploreAdsHolder.kt | 4 +- .../screen/browser/more/BrowserMoreScreen.kt | 2 +- .../browser/more/BrowserMoreViewModel.kt | 2 +- .../screen/browser/safe/DAppSafeComposable.kt | 7 +- .../browser/search/BrowserSearchScreen.kt | 2 +- .../browser/search/BrowserSearchViewModel.kt | 5 +- .../browser/share/DAppShareComposable.kt | 7 +- .../ui/screen/camera/CameraScreen.kt | 5 +- .../tonkeeper/ui/screen/card/CardBridge.kt | 7 +- .../collectibles/main/CollectiblesScreen.kt | 2 +- .../main/CollectiblesViewModel.kt | 8 +- .../ui/screen/collectibles/main/list/Item.kt | 4 +- .../manage/CollectiblesManageViewModel.kt | 6 +- .../tonkeeper/ui/screen/dev/DevScreen.kt | 66 ++ .../tonkeeper/ui/screen/dev/DevViewModel.kt | 12 +- .../ui/screen/dns/renew/DNSRenewViewModel.kt | 4 +- .../compose/details/TxDetailsViewModel.kt | 13 +- .../compose/history/TxEventsViewModel.kt | 44 +- .../compose/history/paging/TxPagingSource.kt | 8 - .../history/paging/TxTronParamsProvider.kt | 2 +- .../compose/history/ui/TxEventsItems.kt | 17 +- .../compose/history/ui/TxHistoryEmpty.kt | 4 +- .../ui/screen/events/main/EventsScreen.kt | 4 +- .../ui/screen/events/main/EventsViewModel.kt | 8 +- .../ui/screen/events/spam/SpamEventsScreen.kt | 2 +- .../screen/events/spam/SpamEventsViewModel.kt | 4 +- .../qr/keystone/sign/KeystoneSignArgs.kt | 2 +- .../qr/keystone/sign/KeystoneSignScreen.kt | 2 +- .../tonkeeper/ui/screen/init/InitArgs.kt | 2 +- .../tonkeeper/ui/screen/init/InitViewModel.kt | 98 +-- .../ui/screen/init/step/LabelScreen.kt | 4 +- .../ui/screen/init/step/WordsScreen.kt | 4 +- .../ledger/steps/LedgerConnectionViewModel.kt | 19 +- .../tonkeeper/ui/screen/main/MainScreen.kt | 6 +- .../tonkeeper/ui/screen/main/MainViewModel.kt | 11 +- .../ui/screen/name/base/NameFragment.kt | 2 +- .../ui/screen/name/edit/EditNameScreen.kt | 2 +- .../tonkeeper/ui/screen/nft/NftScreen.kt | 10 +- .../tonkeeper/ui/screen/nft/NftViewModel.kt | 6 +- .../ui/screen/onramp/main/BaseOnRampScreen.kt | 2 - .../ui/screen/onramp/main/OnRampScreen.kt | 4 +- .../ui/screen/onramp/main/OnRampViewModel.kt | 4 +- .../onramp/main/view/CurrencyInputView.kt | 6 +- .../picker/currency/OnRampPickerScreen.kt | 2 +- .../picker/currency/OnRampPickerViewModel.kt | 4 +- .../main/OnRampCurrencyPickerScreen.kt | 2 +- .../ui/screen/phrase/PhraseScreen.kt | 39 +- .../ui/screen/purchase/PurchaseScreen.kt | 6 +- .../ui/screen/purchase/PurchaseViewModel.kt | 2 +- .../ui/screen/qr/EnableTronDialog.kt | 15 + .../tonkeeper/ui/screen/qr/QRComposable.kt | 43 +- .../tonkeeper/ui/screen/qr/QRScreen.kt | 27 +- .../tonkeeper/ui/screen/qr/QRViewModel.kt | 8 +- .../tonkeeper/ui/screen/root/RootActivity.kt | 23 +- .../tonkeeper/ui/screen/root/RootEvent.kt | 3 + .../tonkeeper/ui/screen/root/RootViewModel.kt | 88 ++- .../ui/screen/send/InsufficientFundsDialog.kt | 62 +- .../screen/send/boc/RemoveExtensionScreen.kt | 2 +- .../send/boc/RemoveExtensionViewModel.kt | 6 +- .../send/contacts/add/AddContactScreen.kt | 1 - .../send/contacts/add/AddContactViewModel.kt | 4 +- .../send/contacts/edit/EditContactScreen.kt | 2 +- .../contacts/main/SendContactsViewModel.kt | 12 +- .../tonkeeper/ui/screen/send/main/SendArgs.kt | 10 +- .../ui/screen/send/main/SendEvent.kt | 5 +- .../ui/screen/send/main/SendScreen.kt | 160 ++++- .../ui/screen/send/main/SendViewModel.kt | 469 +++++++++--- .../main/helper/InsufficientBalanceType.kt | 2 +- .../ui/screen/send/main/state/SendFee.kt | 36 +- .../send/transaction/SendTransactionArgs.kt | 11 +- .../send/transaction/SendTransactionScreen.kt | 37 +- .../transaction/SendTransactionViewModel.kt | 62 +- .../ui/screen/settings/apps/AppsScreen.kt | 1 - .../ui/screen/settings/apps/AppsViewModel.kt | 7 - .../extensions/ExtensionsViewModel.kt | 2 +- .../ui/screen/settings/main/SettingsScreen.kt | 2 +- .../screen/settings/main/SettingsViewModel.kt | 60 +- .../ui/screen/settings/main/list/Item.kt | 2 +- .../settings/main/list/holder/TronHolder.kt | 11 + .../settings/security/SecurityScreen.kt | 2 +- .../settings/security/SecurityViewModel.kt | 3 +- .../ui/screen/settings/theme/ThemeScreen.kt | 2 +- .../settings/theme/list/holder/IconHolder.kt | 2 +- .../ui/screen/sign/SignDataScreen.kt | 4 +- .../ui/screen/sign/SignDataViewModel.kt | 2 +- .../ui/screen/staking/stake/StakingScreen.kt | 2 +- .../screen/staking/stake/StakingViewModel.kt | 12 +- .../staking/unstake/UnStakeViewModel.kt | 18 +- .../unstake/amount/UnStakeAmountFragment.kt | 2 +- .../staking/viewer/StakeViewerViewModel.kt | 23 +- .../ui/screen/staking/viewer/list/Item.kt | 4 +- .../viewer/list/holder/ActionsHolder.kt | 2 - .../withdraw/StakeWithdrawViewModel.kt | 10 +- .../stories/remote/RemoteStoriesScreen.kt | 6 +- .../ui/screen/support/SupportComposable.kt | 6 +- .../ui/screen/support/SupportScreen.kt | 2 +- .../tonkeeper/ui/screen/swap/StonfiBridge2.kt | 2 +- .../ui/screen/swap/omniston/OmnistonScreen.kt | 4 +- .../screen/swap/omniston/OmnistonViewModel.kt | 23 +- .../ui/screen/swap/picker/SwapPickerScreen.kt | 2 +- .../screen/swap/picker/SwapPickerViewModel.kt | 2 +- .../token/picker/TokenPickerViewModel.kt | 8 +- .../ui/screen/token/viewer/TokenScreen.kt | 5 +- .../ui/screen/token/viewer/TokenViewModel.kt | 97 ++- .../ui/screen/token/viewer/list/Item.kt | 12 +- .../screen/token/viewer/list/TokenAdapter.kt | 4 +- .../token/viewer/list/holder/ActionsHolder.kt | 34 +- .../token/viewer/list/holder/BalanceHolder.kt | 18 + .../viewer/list/holder/TronBannerHolder.kt | 55 ++ .../transaction/TransactionViewModel.kt | 6 +- .../ui/screen/tronfees/TronFeesEmulation.kt | 12 + .../ui/screen/tronfees/TronFeesScreen.kt | 47 ++ .../ui/screen/tronfees/TronFeesScreenType.kt | 8 + .../ui/screen/tronfees/TronFeesViewModel.kt | 126 ++++ .../ui/screen/tronfees/list/Adapter.kt | 28 + .../ui/screen/tronfees/list/BatteryHolder.kt | 35 + .../ui/screen/tronfees/list/HeaderHolder.kt | 32 + .../ui/screen/tronfees/list/Holder.kt | 11 + .../tonkeeper/ui/screen/tronfees/list/Item.kt | 46 ++ .../screen/tronfees/list/LearnMoreHolder.kt | 20 + .../ui/screen/tronfees/list/TokenHolder.kt | 48 ++ .../tonkeeper/ui/screen/wallet/main/State.kt | 10 +- .../ui/screen/wallet/main/WalletScreen.kt | 2 +- .../ui/screen/wallet/main/WalletViewModel.kt | 24 +- .../ui/screen/wallet/main/list/Item.kt | 9 +- .../wallet/main/list/holder/ActionsHolder.kt | 12 +- .../wallet/main/list/holder/ApkHolder.kt | 1 - .../wallet/main/list/holder/BalanceHolder.kt | 3 +- .../wallet/manage/TokensManageScreen.kt | 2 +- .../wallet/manage/TokensManageViewModel.kt | 6 +- .../ui/screen/wallet/picker/PickerScreen.kt | 2 +- .../screen/wallet/picker/PickerViewModel.kt | 2 +- .../wallet/picker/list/holder/WalletHolder.kt | 2 +- .../screen/watchonly/WatchInfoComposable.kt | 4 +- .../tonkeeper/usecase/emulation/Emulated.kt | 13 +- .../emulation/EmulationContractExecution.kt | 11 +- .../usecase/emulation/EmulationUseCase.kt | 135 +++- .../emulation/Trc20TransferDefaultFees.kt | 33 + .../tonkeeper/usecase/sign/SignTransaction.kt | 10 +- .../tonkeeper/worker/ApkDownloadWorker.kt | 24 +- .../tonkeeper/worker/DAppPushToggleWorker.kt | 6 +- .../tonkeeper/worker/PushToggleWorker.kt | 2 +- .../tonkeeper/worker/WidgetUpdaterWorker.kt | 7 +- .../app/src/main/res/drawable/ic_tetra_24.xml | 9 + .../app/src/main/res/drawable/round_mask.xml | 4 + .../main/res/layout/dialog_enable_tron.xml | 1 + .../app/src/main/res/layout/fragment_dev.xml | 105 ++- .../main/res/layout/view_token_balance.xml | 32 +- .../res/layout/view_token_battery_banner.xml | 13 + .../main/res/layout/view_tron_fee_header.xml | 14 + .../res/layout/view_tron_fee_learn_more.xml | 17 + .../main/res/layout/view_tron_fee_option.xml | 81 +++ .../src/main/res/layout/view_tron_toggle.xml | 1 + .../app/src/main/res/xml/filepaths.xml | 6 + apps/wallet/instance/main/build.gradle.kts | 63 +- .../wallet/instance/main/google-services.json | 19 + apps/wallet/instance/main/proguard-rules.pro | 13 + apps/wallet/localization/build.gradle.kts | 13 +- .../wallet/localization/Localization.kt | 2 + .../src/main/res/values-bg/strings.xml | 33 +- .../src/main/res/values-es/strings.xml | 32 + .../src/main/res/values-in/strings.xml | 33 +- .../src/main/res/values-ru/strings.xml | 33 + .../src/main/res/values-tr/strings.xml | 32 + .../src/main/res/values-uk/strings.xml | 32 + .../src/main/res/values-uz/strings.xml | 32 + .../src/main/res/values-zh/strings.xml | 32 + .../src/main/res/values/strings.xml | 35 + baselineprofile/main/build.gradle.kts | 31 +- build.gradle.kts | 94 ++- buildLogic/plugin/build.gradle.kts | 18 +- .../src/main/kotlin/AndroidLibraryPlugin.kt | 1 - .../plugin/src/main/kotlin/Extensions.kt | 5 - .../src/main/kotlin/WalletDataPlugin.kt | 26 +- .../src/main/kotlin/common/ProjectExt.kt | 39 + .../plugin/src/main/kotlin/common/versions.kt | 11 + .../plugin/src/main/kotlin/plarform/Deps.kt | 25 + .../src/main/kotlin/plarform/androidTarget.kt | 117 +++ .../main/kotlin/plarform/platformTarget.kt | 17 + .../main/kotlin/target.android.app.gradle.kts | 25 + .../kotlin/target.android.compose.gradle.kts | 25 + .../kotlin/target.android.library.gradle.kts | 25 + buildLogic/settings.gradle.kts | 19 + buildSrc/.gitignore | 2 - buildSrc/build.gradle.kts | 7 - buildSrc/src/main/kotlin/Build.kt | 11 - buildSrc/src/main/kotlin/ProjectModules.kt | 63 -- buildSrc/src/main/kotlin/Signing.kt | 10 - detekt/detekt.yml | 250 +++++++ fastlane/Fastfile | 31 +- gradle.properties | 16 +- gradle/libs.versions.toml | 373 ++++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- kmp/async/build.gradle.kts | 23 + .../com/tonapps/async/AndroidExecutors.kt | 59 ++ .../tonapps/async/AsyncPlatform.android.kt | 34 + .../kotlin/com/tonapps/async/Async.kt | 166 +++++ .../kotlin/com/tonapps/async/AsyncPlatform.kt | 14 + .../kotlin/com/tonapps/async/Relay.kt | 19 + kmp/core/build.gradle.kts | 59 +- kmp/mvi/README.md | 313 ++++++++ kmp/mvi/build.gradle.kts | 38 + .../kotlin/com/tonapps/DataState.kt | 25 + .../kotlin/com/tonapps/mvi/AsyncViewModel.kt | 66 ++ .../kotlin/com/tonapps/mvi/ChangeStrategy.kt | 35 + .../kotlin/com/tonapps/mvi/Initializer.kt | 30 + .../commonMain/kotlin/com/tonapps/mvi/Mvi.kt | 28 + .../kotlin/com/tonapps/mvi/MviBinder.kt | 116 +++ .../kotlin/com/tonapps/mvi/MviFeature.kt | 187 +++++ .../com/tonapps/mvi/MviFeatureDelegate.kt | 47 ++ .../kotlin/com/tonapps/mvi/MviRelay.kt | 28 + .../kotlin/com/tonapps/mvi/MviSubject.kt | 21 + .../com/tonapps/mvi/contract/MviAction.kt | 3 + .../com/tonapps/mvi/contract/MviState.kt | 3 + .../com/tonapps/mvi/contract/MviViewState.kt | 6 + .../tonapps/mvi/contract/internal/Stateful.kt | 25 + .../com/tonapps/mvi/props/MviProperty.kt | 17 + .../tonapps/mvi/props/MviPropertyLiveData.kt | 50 ++ .../kotlin/com/tonapps/mvi/thread/BgThread.kt | 4 + .../tonapps/mvi/thread/ComputationThread.kt | 4 + .../com/tonapps/mvi/thread/MainThread.kt | 4 + .../com/tonapps/mvi/thread/MviThread.kt | 43 ++ .../com/tonapps/mvi/thread/StateThread.kt | 4 + .../kotlin/com/tonapps/paging/Key.kt | 11 + .../kotlin/com/tonapps/paging/MviPaging.kt | 85 +++ .../com/tonapps/paging/PagingAccessor.kt | 42 ++ .../com/tonapps/paging/PagingController.kt | 181 +++++ .../kotlin/com/tonapps/paging/PagingState.kt | 42 ++ .../com/tonapps/paging/StateFlowCollect.kt | 64 ++ .../kotlin/com/tonapps/paging/ViewCell.kt | 6 + .../collection/MviPagingMutationProxy.kt | 105 +++ .../paging/collection/PagingArrayList.kt | 307 ++++++++ .../paging/contract/MviPagingMutation.kt | 28 + .../kotlin/com/tonapps/paging/providers.kt | 25 + kmp/ui/build.gradle.kts | 50 +- .../commonizedNativeDistributionLocation.txt | 1 - .../src/androidMain/kotlin/ui/CellsPreview.kt | 175 +++++ .../androidMain/kotlin/ui/DetailsPreview.kt | 109 +++ .../androidMain/kotlin/ui/EventsPreview.kt | 295 ++++++++ .../androidMain/kotlin/ui/FilterPreview.kt | 39 + .../androidMain/kotlin/ui/MoonCellPreview.kt | 287 ++++++++ .../src/androidMain/kotlin/ui/ResourcesExt.kt | 5 +- .../androidMain/kotlin/ui/ThemedPreview.kt | 30 + .../drawable/ic_magnifying_glass_16.xml | 10 + .../drawable/ic_xmark_circle_16.xml | 10 + .../composeResources/values/strings.xml | 4 + .../src/commonMain/kotlin/ui/ResourcesExt.kt | 1 + .../kotlin/ui/components/base/SimpleText.kt | 2 +- .../ui/components/details/TKDetailsItem.kt | 4 +- .../kotlin/ui/components/events/EventItem.kt | 16 - .../kotlin/ui/components/image/AsyncImage.kt | 2 +- .../ui/components/moon/DefaultButtons.kt | 2 + .../components/moon/DefaultCellComonentes.kt | 418 +++++++++++ .../ui/components/moon/MoonAsyncImage.kt | 60 ++ .../kotlin/ui/components/moon/MoonBadge.kt | 44 ++ .../ui/components/moon/MoonBadgedLayout.kt | 112 +++ .../{Checkbox.kt => moon/MoonCheckbox.kt} | 4 +- .../MoonDivider.kt} | 15 +- .../kotlin/ui/components/moon/MoonEditText.kt | 108 +++ .../kotlin/ui/components/moon/MoonLabel.kt | 73 ++ .../MoonLoader.kt} | 4 +- .../MoonRefresh.kt} | 6 +- .../{Header.kt => moon/MoonTopAppBar.kt} | 44 +- .../ui/components/moon/cell/BaseTextCell.kt | 97 +++ .../ui/components/moon/cell/MoonBundle.kt | 132 ++++ .../moon/cell/MoonDescriptionCell.kt | 34 + .../cell/MoonEmptyCell.kt} | 4 +- .../cell/MoonLoaderCell.kt} | 8 +- .../components/moon/cell/MoonPropertyCell.kt | 138 ++++ .../cell/MoonRetryCell.kt} | 6 +- .../ui/components/moon/cell/MoonSearchCell.kt | 205 ++++++ .../ui/components/moon/cell/TextCell.kt | 96 +++ .../ui/components/moon/cell/TextCheckCell.kt | 42 ++ .../kotlin/ui/components/moon/list/items.kt | 33 + .../ui/components/popup/ActionMenuContent.kt | 4 +- .../kotlin/ui/lifecycle/LifecycleEvents.kt | 26 + .../kotlin/ui/text/emptyTextValue.kt | 77 ++ .../src/commonMain/kotlin/ui/theme/UIKit.kt | 102 ++- .../src/commonMain/kotlin/ui/utils/SizeExt.kt | 11 + .../kotlin/ui/workaround/PainterExt.kt | 8 + kmp/ui/src/iosMain/kotlin/ui/ResourcesExt.kt | 1 + lib/base64/build.gradle.kts | 14 +- lib/blockchain/build.gradle.kts | 21 +- .../tonapps/blockchain/ton/EntropyHelper.kt | 2 +- .../tonapps/blockchain/ton/SignatureDomain.kt | 43 ++ .../com/tonapps/blockchain/ton/TonNetwork.kt | 12 +- .../ton/contract/BaseWalletContract.kt | 43 +- .../tonapps/blockchain/ton/extensions/Cell.kt | 2 +- .../blockchain/ton/extensions/Message.kt | 2 +- .../blockchain/tron/TronTransaction.kt | 5 + lib/bus/build.gradle.kts | 16 + .../com/tonapps/bus/core/AnalyticsHelper.kt | 53 ++ .../tonapps/bus/core/AptabaseEventExecutor.kt | 96 +++ .../tonapps/bus/core/DefaultEventDelegate.kt | 194 ++--- .../tonapps/bus/core/EmptyEventDelegate.kt | 79 +++ .../bus/core/contract/EventDelegate.kt | 68 ++ .../bus/core/contract/EventExecutor.kt | 5 + .../tonapps/bus/generated/DefaultEvents.kt | 666 ++++++++++++++++++ .../com/tonapps/bus/generated/Events.kt | 432 ++++++++++++ lib/emoji/build.gradle.kts | 19 +- .../java/com/tonapps/emoji/CustomIcons.kt | 1 + .../src/main/java/com/tonapps/emoji/Emoji.kt | 11 +- .../java/com/tonapps/emoji/ui/EmojiView.kt | 2 +- .../com/tonapps/emoji/ui/style/NotoStyle.kt | 2 +- lib/extensions/build.gradle.kts | 30 +- .../java/com/tonapps/extensions/Context.kt | 13 +- .../main/java/com/tonapps/extensions/File.kt | 16 +- .../main/java/com/tonapps/extensions/Long.kt | 9 +- .../java/com/tonapps/extensions/Parcelable.kt | 2 +- .../java/com/tonapps/extensions/TimedCache.kt | 48 ++ .../main/java/com/tonapps/extensions/Uri.kt | 2 +- lib/icu/build.gradle.kts | 15 +- .../src/main/java/com/tonapps/icu/Coins.kt | 2 +- .../java/com/tonapps/icu/CurrencyFormatter.kt | 10 +- .../com/tonapps/icu/format/CurrencyFormat.kt | 2 +- .../com/tonapps/icu/format/TONSymbolSpan.kt | 2 +- lib/ledger/build.gradle.kts | 22 +- lib/ledger/src/main/AndroidManifest.xml | 3 +- .../java/com/tonapps/ledger/ble/BleManager.kt | 52 +- .../java/com/tonapps/ledger/ble/LedgerBle.kt | 6 - .../tonapps/ledger/ble/model/BleCommand.kt | 8 +- .../ledger/ble/model/BleTransportError.kt | 3 +- .../tonapps/ledger/ble/model/FrameCommand.kt | 6 +- .../ledger/ble/service/BleGattCallbackFlow.kt | 24 +- .../tonapps/ledger/ble/service/BleSender.kt | 16 +- .../tonapps/ledger/ble/service/BleService.kt | 28 +- .../ble/service/BleServiceStateMachine.kt | 43 +- .../ledger/ble/service/GattInteractor.kt | 10 +- .../com/tonapps/ledger/ton/TonTransport.kt | 2 +- .../java/com/tonapps/ledger/usb/LedgerUsb.kt | 2 +- lib/log/build.gradle.kts | 15 + lib/log/src/main/kotlin/com/tonapps/log/L.kt | 265 +++++++ .../main/kotlin/com/tonapps/log/LogTarget.kt | 18 + .../kotlin/com/tonapps/log/LoggerConfig.kt | 28 + .../log/targets/console/ConsoleLogTarget.kt | 12 + .../tonapps/log/targets/file/FileLogTarget.kt | 87 +++ .../targets/file/engine/CustomFileWritable.kt | 76 ++ .../log/targets/file/engine/FileWritable.kt | 28 + .../targets/file/engine/LogcatFileWritable.kt | 143 ++++ .../com/tonapps/log/utils/FileManager.kt | 103 +++ .../com/tonapps/log/utils/LogArchiver.kt | 149 ++++ .../com/tonapps/log/utils/LogHeaderBuilder.kt | 215 ++++++ lib/network/build.gradle.kts | 20 +- .../java/com/tonapps/network/OkHttpClient.kt | 251 +++++-- .../interceptor/AcceptLanguageInterceptor.kt | 1 - .../interceptor/AuthorizationInterceptor.kt | 2 - .../com/tonapps/network/ws/WSConnection.kt | 3 +- lib/qr/build.gradle.kts | 22 +- lib/qr/src/main/java/com/tonapps/qr/QR.kt | 5 +- .../main/java/com/tonapps/qr/ui/QRDrawable.kt | 2 - .../src/main/java/com/tonapps/qr/ui/QRView.kt | 14 +- lib/security/build.gradle.kts | 52 +- lib/security/src/main/cpp/CMakeLists.txt | 12 +- .../java/com/tonapps/security/KeyHelper.kt | 4 +- .../java/com/tonapps/security/Security.kt | 2 +- .../main/java/com/tonapps/security/Sodium.kt | 2 +- .../src/main/jniLibs/arm64-v8a/libsodium.so | Bin 0 -> 266496 bytes .../main/jniLibs/arm64-v8a/libsodium_jni.so | Bin 0 -> 263672 bytes .../src/main/jniLibs/armeabi-v7a/libsodium.so | Bin 0 -> 277964 bytes .../main/jniLibs/armeabi-v7a/libsodium_jni.so | Bin 0 -> 152048 bytes .../src/main/jniLibs/x86/libsodium.so | Bin 0 -> 419324 bytes .../src/main/jniLibs/x86/libsodium_jni.so | Bin 0 -> 222568 bytes .../src/main/jniLibs/x86_64/libsodium.so | Bin 0 -> 367016 bytes .../src/main/jniLibs/x86_64/libsodium_jni.so | Bin 0 -> 259592 bytes lib/sqlite/build.gradle.kts | 16 +- .../java/com/tonapps/sqlite/SQLiteHelper.kt | 2 +- lib/ur/build.gradle.kts | 17 +- rules/CHECK.MD | 48 ++ rules/OVERVIEW.MD | 9 + rules/REVIEW.MD | 16 + settings.gradle.kts | 133 ++-- tonapi/battery.sh | 3 - tonapi/battery/build.gradle.kts | 11 + .../kotlin/io/batteryapi/apis/ConnectApi.kt | 23 +- .../kotlin/io/batteryapi/apis/DefaultApi.kt | 515 ++++++++++---- .../kotlin/io/batteryapi/apis/EmulationApi.kt | 60 +- .../kotlin/io/batteryapi/apis/WalletApi.kt | 23 +- .../infrastructure/ApiAbstractions.kt | 34 + .../io/batteryapi/infrastructure/ApiClient.kt | 394 +++++++++++ .../batteryapi/infrastructure/ApiResponse.kt | 42 ++ .../infrastructure/AtomicBooleanAdapter.kt | 21 + .../infrastructure/AtomicIntegerAdapter.kt | 19 + .../infrastructure/AtomicLongAdapter.kt | 19 + .../infrastructure/BigDecimalAdapter.kt | 18 + .../infrastructure/BigIntegerAdapter.kt | 22 + .../io/batteryapi/infrastructure/Errors.kt | 25 + .../infrastructure/LocalDateAdapter.kt | 23 + .../infrastructure/LocalDateTimeAdapter.kt | 23 + .../infrastructure/OffsetDateTimeAdapter.kt | 23 + .../batteryapi/infrastructure/PartConfig.kt | 11 + .../infrastructure/RequestConfig.kt | 19 + .../infrastructure/RequestMethod.kt | 8 + .../infrastructure/ResponseExtensions.kt | 24 + .../batteryapi/infrastructure/Serializer.kt | 73 ++ .../infrastructure/StringBuilderAdapter.kt | 20 + .../batteryapi/infrastructure/URIAdapter.kt | 19 + .../batteryapi/infrastructure/URLAdapter.kt | 19 + .../batteryapi/infrastructure/UUIDAdapter.kt | 22 + .../models/AndroidBatteryPurchaseRequest.kt | 23 + ...oidBatteryPurchaseRequestPurchasesInner.kt | 27 + .../models/AndroidBatteryPurchaseStatus.kt | 23 + ...roidBatteryPurchaseStatusPurchasesInner.kt | 29 + ...atteryPurchaseStatusPurchasesInnerError.kt | 44 ++ .../models/AppStoreNotificationRequest.kt | 23 + .../io/batteryapi/models/ApplyPromoRequest.kt | 25 + .../kotlin/io/batteryapi/models/Balance.kt | 39 + .../io/batteryapi/models/BatteryCharged.kt | 23 + .../kotlin/io/batteryapi/models/Config.kt | 43 +- .../batteryapi/models/ConfigGasProxyInner.kt | 23 + .../io/batteryapi/models/ConfigMeanPrices.kt | 31 + .../models/CreateCustomRefundRequest.kt | 33 + .../models/CreatePromoCampaign200Response.kt | 23 + ...omoCampaign200ResponseParticipantsInner.kt | 27 + .../models/CreatePromoCampaignRequest.kt | 25 + ...tePromoCampaignRequestParticipantsInner.kt | 27 + .../models/EmulateMessageToWalletRequest.kt | 23 + .../models/EnterpriseEstimate200Response.kt | 27 + .../models/EnterpriseEstimateRequest.kt | 25 + .../models/EnterpriseGetMessage200Response.kt | 44 ++ .../models/EnterpriseGetStatus200Response.kt | 25 + .../models/EnterpriseSend200Response.kt | 23 + .../models/EnterpriseWalletConfig.kt | 24 + .../main/kotlin/io/batteryapi/models/Error.kt | 23 + .../models/EstimateGaslessCostRequest.kt | 25 + .../io/batteryapi/models/EstimatedTronTx.kt | 31 + .../models/EstimatedTronTxInstantFee.kt | 25 + ...atedTronTxInstantFeeAcceptedAssetsInner.kt | 41 ++ .../io/batteryapi/models/GaslessEstimation.kt | 23 + .../models/GetTonConnectPayload200Response.kt | 23 + .../GetTonConnectPayloadDefaultResponse.kt | 27 + .../models/GetTronConfig200Response.kt | 23 + .../models/IOSBatteryPurchaseStatus.kt | 23 + ...SBatteryPurchaseStatusTransactionsInner.kt | 26 + ...eryPurchaseStatusTransactionsInnerError.kt | 47 ++ .../models/IncreaseUserBalanceRequest.kt | 28 + .../models/IosBatteryPurchaseRequest.kt | 23 + ...BatteryPurchaseRequestTransactionsInner.kt | 25 + .../models/PromoCodeBatteryPurchaseRequest.kt | 23 + .../models/PromoCodeBatteryPurchaseStatus.kt | 27 + .../PromoCodeBatteryPurchaseStatusError.kt | 38 + .../kotlin/io/batteryapi/models/PromoUsed.kt | 25 + .../kotlin/io/batteryapi/models/Purchases.kt | 25 + .../models/PurchasesPurchasesInner.kt | 68 ++ ...urchasesPurchasesInnerRefundInformation.kt | 31 + ...PurchasesInnerRefundInformationRefunded.kt | 25 + .../io/batteryapi/models/RechargeMethods.kt | 23 + .../models/RechargeMethodsMethodsInner.kt | 49 ++ .../models/RelayerSendingEstimation.kt | 23 + .../batteryapi/models/RequestRefundRequest.kt | 26 + .../models/ResetUserBalanceRequest.kt | 23 + .../kotlin/io/batteryapi/models/SentTronTx.kt | 23 + .../kotlin/io/batteryapi/models/Status.kt | 23 + .../models/StatusPendingTransactionsInner.kt | 23 + .../models/TonConnectProof200Response.kt | 23 + .../models/TonConnectProofRequest.kt | 25 + .../models/TonConnectProofRequestProof.kt | 31 + .../TonConnectProofRequestProofDomain.kt | 25 + .../io/batteryapi/models/Transactions.kt | 29 + .../models/TransactionsTransactionsInner.kt | 44 ++ .../io/batteryapi/models/TronSendRequest.kt | 33 + .../batteryapi/models/TronTransactionsList.kt | 23 + .../TronTransactionsListTransactionsInner.kt | 47 ++ .../models/VerifyPurchasePromo200Response.kt | 23 + tonapi/build.gradle.kts | 27 +- tonapi/core/build.gradle.kts | 10 + .../{ => core}/src/main/kotlin/io/JsonAny.kt | 0 .../src/main/kotlin/io/Serializer.kt | 0 .../io/infrastructure/ApiAbstractions.kt | 0 .../kotlin/io/infrastructure/ApiClient.kt | 7 +- .../kotlin/io/infrastructure/ApiResponse.kt | 0 .../io/infrastructure/AtomicBooleanAdapter.kt | 0 .../io/infrastructure/AtomicIntegerAdapter.kt | 0 .../io/infrastructure/AtomicLongAdapter.kt | 0 .../io/infrastructure/BigDecimalAdapter.kt | 0 .../io/infrastructure/BigIntegerAdapter.kt | 0 .../main/kotlin/io/infrastructure/Errors.kt | 0 .../io/infrastructure/LocalDateAdapter.kt | 0 .../io/infrastructure/LocalDateTimeAdapter.kt | 0 .../infrastructure/OffsetDateTimeAdapter.kt | 0 .../kotlin/io/infrastructure/PartConfig.kt | 0 .../kotlin/io/infrastructure/RequestConfig.kt | 0 .../kotlin/io/infrastructure/RequestMethod.kt | 0 .../io/infrastructure/ResponseExtensions.kt | 0 .../io/infrastructure/StringBuilderAdapter.kt | 0 .../kotlin/io/infrastructure/URIAdapter.kt | 0 .../kotlin/io/infrastructure/URLAdapter.kt | 0 .../kotlin/io/infrastructure/UUIDAdapter.kt | 0 .../kotlin/io/serializers/AnySerializer.kt | 0 tonapi/generate.sh | 58 -- tonapi/legacy/build.gradle.kts | 11 + tonapi/src/main/AndroidManifest.xml | 4 - .../models/AndroidBatteryPurchaseRequest.kt | 41 -- ...oidBatteryPurchaseRequestPurchasesInner.kt | 46 -- .../models/AndroidBatteryPurchaseStatus.kt | 41 -- ...roidBatteryPurchaseStatusPurchasesInner.kt | 50 -- ...atteryPurchaseStatusPurchasesInnerError.kt | 70 -- .../models/AppStoreNotificationRequest.kt | 40 -- .../io/batteryapi/models/ApplyPromoRequest.kt | 43 -- .../kotlin/io/batteryapi/models/Balance.kt | 72 -- .../io/batteryapi/models/BatteryCharged.kt | 40 -- .../batteryapi/models/ConfigGasProxyInner.kt | 40 -- .../io/batteryapi/models/ConfigMeanPrices.kt | 52 -- .../models/CreateCustomRefundRequest.kt | 55 -- .../models/CreatePromoCampaign200Response.kt | 41 -- ...omoCampaign200ResponseParticipantsInner.kt | 46 -- .../models/CreatePromoCampaignRequest.kt | 44 -- ...tePromoCampaignRequestParticipantsInner.kt | 44 -- .../models/EmulateMessageToWalletRequest.kt | 40 -- .../models/EnterpriseEstimate200Response.kt | 44 -- .../models/EnterpriseEstimateRequest.kt | 43 -- .../models/EnterpriseGetMessage200Response.kt | 78 -- .../models/EnterpriseGetStatus200Response.kt | 43 -- .../models/EnterpriseSend200Response.kt | 40 -- .../models/EnterpriseWalletConfig.kt | 41 -- .../main/kotlin/io/batteryapi/models/Error.kt | 40 -- .../models/EstimateGaslessCostRequest.kt | 43 -- .../io/batteryapi/models/EstimatedTronTx.kt | 53 -- .../models/EstimatedTronTxInstantFee.kt | 44 -- ...atedTronTxInstantFeeAcceptedAssetsInner.kt | 77 -- .../io/batteryapi/models/GaslessEstimation.kt | 40 -- .../models/GetTonConnectPayload200Response.kt | 40 -- .../GetTonConnectPayloadDefaultResponse.kt | 46 -- .../models/GetTronConfig200Response.kt | 40 -- .../models/IOSBatteryPurchaseStatus.kt | 41 -- ...SBatteryPurchaseStatusTransactionsInner.kt | 47 -- ...eryPurchaseStatusTransactionsInnerError.kt | 71 -- .../models/IncreaseUserBalanceRequest.kt | 45 -- .../io/batteryapi/models/InlineObject.kt | 41 -- .../models/IosBatteryPurchaseRequest.kt | 41 -- ...BatteryPurchaseRequestTransactionsInner.kt | 43 -- .../models/PromoCodeBatteryPurchaseRequest.kt | 40 -- .../models/PromoCodeBatteryPurchaseStatus.kt | 47 -- .../PromoCodeBatteryPurchaseStatusError.kt | 69 -- .../kotlin/io/batteryapi/models/PromoUsed.kt | 43 -- .../kotlin/io/batteryapi/models/Purchases.kt | 44 -- .../models/PurchasesPurchasesInner.kt | 100 --- ...urchasesPurchasesInnerRefundInformation.kt | 53 -- ...PurchasesInnerRefundInformationRefunded.kt | 43 -- .../io/batteryapi/models/RechargeMethods.kt | 41 -- .../models/RechargeMethodsMethodsInner.kt | 89 --- .../models/RelayerSendingEstimation.kt | 40 -- .../batteryapi/models/RequestRefundRequest.kt | 44 -- .../models/ResetUserBalanceRequest.kt | 40 -- .../kotlin/io/batteryapi/models/SentTronTx.kt | 40 -- .../kotlin/io/batteryapi/models/Status.kt | 41 -- .../models/StatusPendingTransactionsInner.kt | 40 -- .../models/TonConnectProof200Response.kt | 40 -- .../models/TonConnectProofRequest.kt | 44 -- .../models/TonConnectProofRequestProof.kt | 53 -- .../TonConnectProofRequestProofDomain.kt | 43 -- .../io/batteryapi/models/Transactions.kt | 48 -- .../models/TransactionsTransactionsInner.kt | 76 -- .../io/batteryapi/models/TronSendRequest.kt | 53 -- .../batteryapi/models/TronTransactionsList.kt | 41 -- .../TronTransactionsListTransactionsInner.kt | 72 -- .../models/VerifyPurchasePromo200Response.kt | 40 -- .../io/tonapi/models/AccStatusChange.kt | 87 --- .../main/kotlin/io/tonapi/models/Account.kt | 83 --- .../kotlin/io/tonapi/models/AccountAddress.kt | 54 -- .../kotlin/io/tonapi/models/AccountEvent.kt | 69 -- .../kotlin/io/tonapi/models/AccountEvents.kt | 44 -- .../tonapi/models/AccountInfoByStateInit.kt | 43 -- .../io/tonapi/models/AccountPurchases.kt | 44 -- .../kotlin/io/tonapi/models/AccountStaking.kt | 41 -- .../io/tonapi/models/AccountStakingInfo.kt | 52 -- .../kotlin/io/tonapi/models/AccountStatus.kt | 90 --- .../io/tonapi/models/AccountStorageInfo.kt | 53 -- .../main/kotlin/io/tonapi/models/Accounts.kt | 41 -- .../kotlin/io/tonapi/models/ActionPhase.kt | 58 -- .../io/tonapi/models/ActionSimplePreview.kt | 58 -- .../io/tonapi/models/AddExtensionAction.kt | 44 -- .../tonapi/models/AddressParse200Response.kt | 53 -- .../AddressParse200ResponseBounceable.kt | 43 -- .../kotlin/io/tonapi/models/ApyHistory.kt | 43 -- .../main/kotlin/io/tonapi/models/Auction.kt | 52 -- .../io/tonapi/models/AuctionBidAction.kt | 82 --- .../main/kotlin/io/tonapi/models/Auctions.kt | 44 -- .../tonapi/models/BlockCurrencyCollection.kt | 44 -- .../BlockCurrencyCollectionOtherInner.kt | 43 -- .../kotlin/io/tonapi/models/BlockLimits.kt | 47 -- .../io/tonapi/models/BlockParamLimits.kt | 46 -- .../main/kotlin/io/tonapi/models/BlockRaw.kt | 52 -- .../tonapi/models/BlockchainAccountInspect.kt | 83 --- .../io/tonapi/models/BlockchainBlock.kt | 128 ---- .../io/tonapi/models/BlockchainBlockShards.kt | 41 -- .../BlockchainBlockShardsShardsInner.kt | 44 -- .../io/tonapi/models/BlockchainBlocks.kt | 41 -- .../io/tonapi/models/BlockchainConfig10.kt | 40 -- .../io/tonapi/models/BlockchainConfig11.kt | 44 -- .../io/tonapi/models/BlockchainConfig12.kt | 41 -- .../io/tonapi/models/BlockchainConfig13.kt | 46 -- .../io/tonapi/models/BlockchainConfig14.kt | 43 -- .../io/tonapi/models/BlockchainConfig15.kt | 49 -- .../io/tonapi/models/BlockchainConfig16.kt | 46 -- .../io/tonapi/models/BlockchainConfig17.kt | 49 -- .../io/tonapi/models/BlockchainConfig18.kt | 41 -- .../BlockchainConfig18StoragePricesInner.kt | 52 -- .../io/tonapi/models/BlockchainConfig20.kt | 41 -- .../io/tonapi/models/BlockchainConfig21.kt | 41 -- .../io/tonapi/models/BlockchainConfig22.kt | 41 -- .../io/tonapi/models/BlockchainConfig23.kt | 41 -- .../io/tonapi/models/BlockchainConfig24.kt | 41 -- .../io/tonapi/models/BlockchainConfig25.kt | 41 -- .../io/tonapi/models/BlockchainConfig28.kt | 55 -- .../io/tonapi/models/BlockchainConfig29.kt | 73 -- .../io/tonapi/models/BlockchainConfig31.kt | 40 -- .../io/tonapi/models/BlockchainConfig40.kt | 41 -- .../io/tonapi/models/BlockchainConfig43.kt | 41 -- .../io/tonapi/models/BlockchainConfig44.kt | 43 -- .../io/tonapi/models/BlockchainConfig45.kt | 41 -- .../BlockchainConfig45ContractsInner.kt | 43 -- .../io/tonapi/models/BlockchainConfig5.kt | 46 -- .../io/tonapi/models/BlockchainConfig6.kt | 43 -- .../io/tonapi/models/BlockchainConfig7.kt | 41 -- .../io/tonapi/models/BlockchainConfig71.kt | 41 -- .../io/tonapi/models/BlockchainConfig79.kt | 41 -- .../BlockchainConfig7CurrenciesInner.kt | 43 -- .../io/tonapi/models/BlockchainConfig8.kt | 43 -- .../io/tonapi/models/BlockchainConfig9.kt | 40 -- .../io/tonapi/models/BlockchainLibrary.kt | 40 -- .../io/tonapi/models/BlockchainRawAccount.kt | 74 -- .../BlockchainRawAccountLibrariesInner.kt | 43 -- .../io/tonapi/models/BouncePhaseType.kt | 87 --- .../kotlin/io/tonapi/models/ComputePhase.kt | 62 -- .../io/tonapi/models/ComputeSkipReason.kt | 90 --- .../io/tonapi/models/ConfigProposalSetup.kt | 61 -- .../io/tonapi/models/ContractDeployAction.kt | 43 -- .../kotlin/io/tonapi/models/CreditPhase.kt | 43 -- .../kotlin/io/tonapi/models/CurrencyType.kt | 90 --- .../kotlin/io/tonapi/models/DecodedMessage.kt | 48 -- .../models/DecodedMessageExtInMsgDecoded.kt | 53 -- ...dMessageExtInMsgDecodedWalletHighloadV2.kt | 47 -- .../DecodedMessageExtInMsgDecodedWalletV3.kt | 50 -- .../DecodedMessageExtInMsgDecodedWalletV4.kt | 53 -- .../DecodedMessageExtInMsgDecodedWalletV5.kt | 44 -- .../io/tonapi/models/DecodedRawMessage.kt | 44 -- .../tonapi/models/DecodedRawMessageMessage.kt | 49 -- .../io/tonapi/models/DepositStakeAction.kt | 51 -- .../tonapi/models/DepositTokenStakeAction.kt | 49 -- .../kotlin/io/tonapi/models/DnsExpiring.kt | 41 -- .../io/tonapi/models/DnsExpiringItemsInner.kt | 47 -- .../main/kotlin/io/tonapi/models/DnsRecord.kt | 51 -- .../main/kotlin/io/tonapi/models/DomainBid.kt | 53 -- .../kotlin/io/tonapi/models/DomainBids.kt | 41 -- .../kotlin/io/tonapi/models/DomainInfo.kt | 48 -- .../kotlin/io/tonapi/models/DomainNames.kt | 40 -- .../io/tonapi/models/DomainRenewAction.kt | 47 -- .../main/kotlin/io/tonapi/models/EcPreview.kt | 49 -- .../models/ElectionsDepositStakeAction.kt | 44 -- .../models/ElectionsRecoverStakeAction.kt | 44 -- .../models/EmulateMessageToWalletRequest.kt | 45 -- ...mulateMessageToWalletRequestParamsInner.kt | 43 -- .../io/tonapi/models/EncryptedComment.kt | 43 -- .../src/main/kotlin/io/tonapi/models/Error.kt | 40 -- .../src/main/kotlin/io/tonapi/models/Event.kt | 65 -- .../io/tonapi/models/ExecGetMethodArg.kt | 45 -- ...thodWithBodyForBlockchainAccountRequest.kt | 41 -- .../io/tonapi/models/ExtraCurrencies.kt | 41 -- .../kotlin/io/tonapi/models/ExtraCurrency.kt | 44 -- .../models/ExtraCurrencyTransferAction.kt | 59 -- .../kotlin/io/tonapi/models/FoundAccounts.kt | 41 -- .../models/FoundAccountsAddressesInner.kt | 50 -- .../kotlin/io/tonapi/models/GasLimitPrices.kt | 64 -- .../kotlin/io/tonapi/models/GasRelayAction.kt | 47 -- .../kotlin/io/tonapi/models/GaslessConfig.kt | 46 -- .../models/GaslessConfigGasJettonsInner.kt | 40 -- .../tonapi/models/GaslessEstimateRequest.kt | 54 -- .../GaslessEstimateRequestMessagesInner.kt | 40 -- .../io/tonapi/models/GaslessSendRequest.kt | 44 -- .../main/kotlin/io/tonapi/models/GaslessTx.kt | 40 -- .../models/GetAccountDiff200Response.kt | 40 -- .../GetAccountInfoByStateInitRequest.kt | 40 -- .../models/GetAccountPublicKey200Response.kt | 40 -- .../io/tonapi/models/GetAccountsRequest.kt | 40 -- .../models/GetAllRawShardsInfo200Response.kt | 47 -- .../tonapi/models/GetChartRates200Response.kt | 41 -- .../models/GetMarketsRates200Response.kt | 41 -- .../models/GetOpenapiJsonDefaultResponse.kt | 43 -- .../models/GetOutMsgQueueSizes200Response.kt | 44 -- ...tOutMsgQueueSizes200ResponseShardsInner.kt | 44 -- .../io/tonapi/models/GetRates200Response.kt | 41 -- .../models/GetRawAccountState200Response.kt | 53 -- .../models/GetRawBlockProof200Response.kt | 51 -- .../GetRawBlockProof200ResponseStepsInner.kt | 45 -- ...sponseStepsInnerLiteServerBlockLinkBack.kt | 56 -- ...nseStepsInnerLiteServerBlockLinkForward.kt | 57 -- ...nerLiteServerBlockLinkForwardSignatures.kt | 47 -- ...ockLinkForwardSignaturesSignaturesInner.kt | 43 -- .../GetRawBlockchainBlock200Response.kt | 44 -- .../GetRawBlockchainBlockHeader200Response.kt | 47 -- .../GetRawBlockchainBlockState200Response.kt | 50 -- .../tonapi/models/GetRawConfig200Response.kt | 50 -- .../GetRawListBlockTransactions200Response.kt | 54 -- ...istBlockTransactions200ResponseIdsInner.kt | 49 -- .../GetRawMasterchainInfo200Response.kt | 48 -- .../GetRawMasterchainInfoExt200Response.kt | 63 -- .../GetRawShardBlockProof200Response.kt | 45 -- ...RawShardBlockProof200ResponseLinksInner.kt | 44 -- .../models/GetRawShardInfo200Response.kt | 50 -- .../io/tonapi/models/GetRawTime200Response.kt | 40 -- .../models/GetRawTransactions200Response.kt | 44 -- .../GetStakingPoolHistory200Response.kt | 41 -- .../models/GetStakingPoolInfo200Response.kt | 45 -- .../models/GetStakingPools200Response.kt | 45 -- .../models/GetStorageProviders200Response.kt | 41 -- .../models/GetTonConnectPayload200Response.kt | 40 -- .../kotlin/io/tonapi/models/ImagePreview.kt | 43 -- .../kotlin/io/tonapi/models/InitStateRaw.kt | 46 -- .../kotlin/io/tonapi/models/InlineObject.kt | 43 -- .../kotlin/io/tonapi/models/JettonBalance.kt | 59 -- .../io/tonapi/models/JettonBalanceLock.kt | 43 -- .../io/tonapi/models/JettonBridgeParams.kt | 60 -- .../io/tonapi/models/JettonBridgePrices.kt | 55 -- .../io/tonapi/models/JettonBurnAction.kt | 52 -- .../kotlin/io/tonapi/models/JettonHolders.kt | 45 -- .../models/JettonHoldersAddressesInner.kt | 48 -- .../kotlin/io/tonapi/models/JettonInfo.kt | 61 -- .../kotlin/io/tonapi/models/JettonMetadata.kt | 68 -- .../io/tonapi/models/JettonMintAction.kt | 52 -- .../io/tonapi/models/JettonOperation.kt | 98 --- .../io/tonapi/models/JettonOperations.kt | 44 -- .../kotlin/io/tonapi/models/JettonPreview.kt | 62 -- .../kotlin/io/tonapi/models/JettonQuantity.kt | 48 -- .../io/tonapi/models/JettonSwapAction.kt | 66 -- .../io/tonapi/models/JettonTransferPayload.kt | 45 -- .../tonapi/models/JettonVerificationType.kt | 90 --- .../main/kotlin/io/tonapi/models/Jettons.kt | 41 -- .../io/tonapi/models/JettonsBalances.kt | 41 -- .../kotlin/io/tonapi/models/MarketTonRates.kt | 46 -- .../main/kotlin/io/tonapi/models/Message.kt | 124 ---- .../io/tonapi/models/MessageConsequences.kt | 49 -- .../main/kotlin/io/tonapi/models/Metadata.kt | 45 -- .../main/kotlin/io/tonapi/models/Method.kt | 43 -- .../io/tonapi/models/MethodExecutionResult.kt | 51 -- .../models/MisbehaviourPunishmentConfig.kt | 70 -- .../io/tonapi/models/MsgForwardPrices.kt | 55 -- .../main/kotlin/io/tonapi/models/Multisig.kt | 56 -- .../kotlin/io/tonapi/models/MultisigOrder.kt | 75 -- .../models/MultisigOrderChangingParameters.kt | 46 -- .../main/kotlin/io/tonapi/models/Multisigs.kt | 41 -- .../kotlin/io/tonapi/models/NftCollection.kt | 85 --- .../kotlin/io/tonapi/models/NftCollections.kt | 41 -- .../main/kotlin/io/tonapi/models/NftItem.kt | 106 --- .../io/tonapi/models/NftItemCollection.kt | 46 -- .../io/tonapi/models/NftItemTransferAction.kt | 62 -- .../main/kotlin/io/tonapi/models/NftItems.kt | 41 -- .../kotlin/io/tonapi/models/NftOperation.kt | 60 -- .../kotlin/io/tonapi/models/NftOperations.kt | 44 -- .../io/tonapi/models/NftPurchaseAction.kt | 82 --- .../main/kotlin/io/tonapi/models/Oracle.kt | 43 -- .../io/tonapi/models/OracleBridgeParams.kt | 50 -- .../io/tonapi/models/PoolImplementation.kt | 49 -- .../tonapi/models/PoolImplementationType.kt | 87 --- .../src/main/kotlin/io/tonapi/models/Price.kt | 60 -- .../main/kotlin/io/tonapi/models/Protocol.kt | 43 -- .../main/kotlin/io/tonapi/models/Purchase.kt | 64 -- .../kotlin/io/tonapi/models/PurchaseAction.kt | 55 -- .../io/tonapi/models/RawBlockchainConfig.kt | 40 -- .../kotlin/io/tonapi/models/ReducedBlock.kt | 61 -- .../kotlin/io/tonapi/models/ReducedBlocks.kt | 41 -- .../main/kotlin/io/tonapi/models/Refund.kt | 69 -- .../io/tonapi/models/RemoveExtensionAction.kt | 44 -- .../src/main/kotlin/io/tonapi/models/Risk.kt | 52 -- .../src/main/kotlin/io/tonapi/models/Sale.kt | 51 -- .../models/SendBlockchainMessageRequest.kt | 46 -- .../models/SendRawMessage200Response.kt | 40 -- .../io/tonapi/models/SendRawMessageRequest.kt | 40 -- .../src/main/kotlin/io/tonapi/models/Seqno.kt | 40 -- .../kotlin/io/tonapi/models/ServiceStatus.kt | 46 -- .../models/SetSignatureAllowedAction.kt | 44 -- .../kotlin/io/tonapi/models/SignRawMessage.kt | 52 -- .../kotlin/io/tonapi/models/SignRawParams.kt | 61 -- .../io/tonapi/models/SizeLimitsConfig.kt | 61 -- .../io/tonapi/models/SmartContractAction.kt | 58 -- .../main/kotlin/io/tonapi/models/Source.kt | 41 -- .../kotlin/io/tonapi/models/SourceFile.kt | 52 -- .../main/kotlin/io/tonapi/models/StateInit.kt | 43 -- .../kotlin/io/tonapi/models/StoragePhase.kt | 47 -- .../io/tonapi/models/StorageProvider.kt | 55 -- .../kotlin/io/tonapi/models/Subscription.kt | 103 --- .../io/tonapi/models/SubscriptionAction.kt | 61 -- .../kotlin/io/tonapi/models/Subscriptions.kt | 41 -- .../kotlin/io/tonapi/models/TokenRates.kt | 49 -- .../models/TonConnectProof200Response.kt | 40 -- .../tonapi/models/TonConnectProofRequest.kt | 44 -- .../models/TonConnectProofRequestProof.kt | 53 -- .../TonConnectProofRequestProofDomain.kt | 43 -- .../io/tonapi/models/TonTransferAction.kt | 59 -- .../src/main/kotlin/io/tonapi/models/Trace.kt | 50 -- .../main/kotlin/io/tonapi/models/TraceID.kt | 43 -- .../main/kotlin/io/tonapi/models/TraceIDs.kt | 41 -- .../kotlin/io/tonapi/models/Transactions.kt | 41 -- .../main/kotlin/io/tonapi/models/TrustType.kt | 90 --- .../kotlin/io/tonapi/models/TvmStackRecord.kt | 80 --- .../io/tonapi/models/UnSubscriptionAction.kt | 50 -- .../main/kotlin/io/tonapi/models/Validator.kt | 49 -- .../kotlin/io/tonapi/models/Validators.kt | 53 -- .../kotlin/io/tonapi/models/ValidatorsSet.kt | 56 -- .../tonapi/models/ValidatorsSetListInner.kt | 46 -- .../main/kotlin/io/tonapi/models/ValueFlow.kt | 51 -- .../io/tonapi/models/ValueFlowJettonsInner.kt | 52 -- .../main/kotlin/io/tonapi/models/Wallet.kt | 84 --- .../main/kotlin/io/tonapi/models/WalletDNS.kt | 56 -- .../kotlin/io/tonapi/models/WalletPlugin.kt | 47 -- .../kotlin/io/tonapi/models/WalletStats.kt | 49 -- .../main/kotlin/io/tonapi/models/Wallets.kt | 41 -- .../io/tonapi/models/WithdrawStakeAction.kt | 51 -- .../models/WithdrawStakeRequestAction.kt | 51 -- .../models/WithdrawTokenStakeRequestAction.kt | 49 -- .../kotlin/io/tonapi/models/WorkchainDescr.kt | 73 -- tonapi/tonkeeper/build.gradle.kts | 11 + .../src/main/kotlin/io/extensions/NftItem.kt | 0 .../main/kotlin/io/tonapi/apis/AccountsApi.kt | 456 +++++++++--- .../kotlin/io/tonapi/apis/BlockchainApi.kt | 186 +++-- .../main/kotlin/io/tonapi/apis/ConnectApi.kt | 25 +- .../src/main/kotlin/io/tonapi/apis/DNSApi.kt | 51 +- .../kotlin/io/tonapi/apis/EmulationApi.kt | 114 ++- .../main/kotlin/io/tonapi/apis/EventsApi.kt | 51 +- .../kotlin/io/tonapi/apis/ExtraCurrencyApi.kt | 31 +- .../main/kotlin/io/tonapi/apis/GaslessApi.kt | 43 +- .../main/kotlin/io/tonapi/apis/JettonsApi.kt | 79 ++- .../kotlin/io/tonapi/apis/LiteServerApi.kt | 191 +++-- .../main/kotlin/io/tonapi/apis/MultisigApi.kt | 37 +- .../src/main/kotlin/io/tonapi/apis/NFTApi.kt | 111 ++- .../kotlin/io/tonapi/apis/PurchasesApi.kt | 45 +- .../main/kotlin/io/tonapi/apis/RatesApi.kt | 60 +- .../main/kotlin/io/tonapi/apis/StakingApi.kt | 72 +- .../main/kotlin/io/tonapi/apis/StorageApi.kt | 25 +- .../main/kotlin/io/tonapi/apis/TracesApi.kt | 48 +- .../kotlin/io/tonapi/apis/UtilitiesApi.kt | 31 +- .../main/kotlin/io/tonapi/apis/WalletApi.kt | 72 +- .../tonapi/infrastructure/ApiAbstractions.kt | 34 + .../io/tonapi/infrastructure/ApiClient.kt | 394 +++++++++++ .../io/tonapi/infrastructure/ApiResponse.kt | 42 ++ .../infrastructure/AtomicBooleanAdapter.kt | 21 + .../infrastructure/AtomicIntegerAdapter.kt | 19 + .../infrastructure/AtomicLongAdapter.kt | 19 + .../infrastructure/BigDecimalAdapter.kt | 18 + .../infrastructure/BigIntegerAdapter.kt | 22 + .../kotlin/io/tonapi/infrastructure/Errors.kt | 25 + .../tonapi/infrastructure/LocalDateAdapter.kt | 23 + .../infrastructure/LocalDateTimeAdapter.kt | 23 + .../infrastructure/OffsetDateTimeAdapter.kt | 23 + .../io/tonapi/infrastructure/PartConfig.kt | 11 + .../io/tonapi/infrastructure/RequestConfig.kt | 19 + .../io/tonapi/infrastructure/RequestMethod.kt | 8 + .../infrastructure/ResponseExtensions.kt | 24 + .../io/tonapi/infrastructure/Serializer.kt | 73 ++ .../infrastructure/StringBuilderAdapter.kt | 20 + .../io/tonapi/infrastructure/URIAdapter.kt | 19 + .../io/tonapi/infrastructure/URLAdapter.kt | 19 + .../io/tonapi/infrastructure/UUIDAdapter.kt | 22 + .../io/tonapi/models/AccStatusChange.kt | 56 ++ .../main/kotlin/io/tonapi/models/Account.kt | 54 ++ .../kotlin/io/tonapi/models/AccountAddress.kt | 35 + .../kotlin/io/tonapi/models/AccountEvent.kt | 47 ++ .../kotlin/io/tonapi/models/AccountEvents.kt | 25 + .../tonapi/models/AccountInfoByStateInit.kt | 25 + .../io/tonapi/models/AccountPurchases.kt | 25 + .../kotlin/io/tonapi/models/AccountStaking.kt | 23 + .../io/tonapi/models/AccountStakingInfo.kt | 31 + .../kotlin/io/tonapi/models/AccountStatus.kt | 59 ++ .../io/tonapi/models/AccountStorageInfo.kt | 33 + .../main/kotlin/io/tonapi/models/Accounts.kt | 23 + .../main/kotlin/io/tonapi/models/Action.kt | 150 +--- .../kotlin/io/tonapi/models/ActionPhase.kt | 35 + .../io/tonapi/models/ActionSimplePreview.kt | 37 + .../io/tonapi/models/AddExtensionAction.kt | 25 + .../tonapi/models/AddressParse200Response.kt | 31 + .../AddressParse200ResponseBounceable.kt | 25 + .../kotlin/io/tonapi/models/ApyHistory.kt | 25 + .../main/kotlin/io/tonapi/models/Auction.kt | 31 + .../io/tonapi/models/AuctionBidAction.kt | 47 ++ .../main/kotlin/io/tonapi/models/Auctions.kt | 25 + .../tonapi/models/BlockCurrencyCollection.kt | 25 + .../BlockCurrencyCollectionOtherInner.kt | 25 + .../kotlin/io/tonapi/models/BlockLimits.kt | 27 + .../io/tonapi/models/BlockParamLimits.kt | 27 + .../main/kotlin/io/tonapi/models/BlockRaw.kt | 31 + .../kotlin/io/tonapi/models/BlockValueFlow.kt | 37 +- .../tonapi/models/BlockchainAccountInspect.kt | 46 ++ .../io/tonapi/models/BlockchainBlock.kt | 81 +++ .../io/tonapi/models/BlockchainBlockShards.kt | 23 + .../BlockchainBlockShardsShardsInner.kt | 25 + .../io/tonapi/models/BlockchainBlocks.kt | 23 + .../io/tonapi/models/BlockchainConfig.kt | 108 +-- .../io/tonapi/models/BlockchainConfig10.kt | 23 + .../io/tonapi/models/BlockchainConfig11.kt | 25 + .../io/tonapi/models/BlockchainConfig12.kt | 23 + .../io/tonapi/models/BlockchainConfig13.kt | 27 + .../io/tonapi/models/BlockchainConfig14.kt | 25 + .../io/tonapi/models/BlockchainConfig15.kt | 29 + .../io/tonapi/models/BlockchainConfig16.kt | 27 + .../io/tonapi/models/BlockchainConfig17.kt | 29 + .../io/tonapi/models/BlockchainConfig18.kt | 23 + .../BlockchainConfig18StoragePricesInner.kt | 31 + .../io/tonapi/models/BlockchainConfig20.kt | 23 + .../io/tonapi/models/BlockchainConfig21.kt | 23 + .../io/tonapi/models/BlockchainConfig22.kt | 23 + .../io/tonapi/models/BlockchainConfig23.kt | 23 + .../io/tonapi/models/BlockchainConfig24.kt | 23 + .../io/tonapi/models/BlockchainConfig25.kt | 23 + .../io/tonapi/models/BlockchainConfig28.kt | 33 + .../io/tonapi/models/BlockchainConfig29.kt | 45 ++ .../io/tonapi/models/BlockchainConfig31.kt | 23 + .../io/tonapi/models/BlockchainConfig40.kt | 23 + .../io/tonapi/models/BlockchainConfig43.kt | 23 + .../io/tonapi/models/BlockchainConfig44.kt | 25 + .../io/tonapi/models/BlockchainConfig45.kt | 23 + .../BlockchainConfig45ContractsInner.kt | 25 + .../io/tonapi/models/BlockchainConfig5.kt | 27 + .../io/tonapi/models/BlockchainConfig6.kt | 25 + .../io/tonapi/models/BlockchainConfig7.kt | 23 + .../io/tonapi/models/BlockchainConfig71.kt | 23 + .../io/tonapi/models/BlockchainConfig79.kt | 23 + .../BlockchainConfig7CurrenciesInner.kt | 25 + .../io/tonapi/models/BlockchainConfig8.kt | 25 + .../io/tonapi/models/BlockchainConfig9.kt | 23 + .../io/tonapi/models/BlockchainLibrary.kt | 23 + .../io/tonapi/models/BlockchainRawAccount.kt | 44 ++ .../BlockchainRawAccountLibrariesInner.kt | 25 + .../io/tonapi/models/BouncePhaseType.kt | 56 ++ .../kotlin/io/tonapi/models/ComputePhase.kt | 38 + .../io/tonapi/models/ComputeSkipReason.kt | 59 ++ .../io/tonapi/models/ConfigProposalSetup.kt | 37 + .../io/tonapi/models/ContractDeployAction.kt | 25 + .../kotlin/io/tonapi/models/CreditPhase.kt | 25 + .../kotlin/io/tonapi/models/CurrencyType.kt | 59 ++ .../kotlin/io/tonapi/models/DecodedMessage.kt | 27 + .../models/DecodedMessageExtInMsgDecoded.kt | 29 + ...dMessageExtInMsgDecodedWalletHighloadV2.kt | 27 + .../DecodedMessageExtInMsgDecodedWalletV3.kt | 29 + .../DecodedMessageExtInMsgDecodedWalletV4.kt | 31 + .../DecodedMessageExtInMsgDecodedWalletV5.kt | 25 + .../io/tonapi/models/DecodedRawMessage.kt | 25 + .../tonapi/models/DecodedRawMessageMessage.kt | 30 + .../io/tonapi/models/DepositStakeAction.kt | 30 + .../tonapi/models/DepositTokenStakeAction.kt | 27 + .../kotlin/io/tonapi/models/DnsExpiring.kt | 23 + .../io/tonapi/models/DnsExpiringItemsInner.kt | 27 + .../main/kotlin/io/tonapi/models/DnsRecord.kt | 31 + .../main/kotlin/io/tonapi/models/DomainBid.kt | 31 + .../kotlin/io/tonapi/models/DomainBids.kt | 23 + .../kotlin/io/tonapi/models/DomainInfo.kt | 29 + .../kotlin/io/tonapi/models/DomainNames.kt | 23 + .../io/tonapi/models/DomainRenewAction.kt | 27 + .../main/kotlin/io/tonapi/models/EcPreview.kt | 29 + .../models/ElectionsDepositStakeAction.kt | 25 + .../models/ElectionsRecoverStakeAction.kt | 25 + .../models/EmulateMessageToWalletRequest.kt | 27 + ...mulateMessageToWalletRequestParamsInner.kt | 25 + .../io/tonapi/models/EncryptedComment.kt | 25 + .../src/main/kotlin/io/tonapi/models/Error.kt | 23 + .../src/main/kotlin/io/tonapi/models/Event.kt | 45 ++ .../io/tonapi/models/ExecGetMethodArg.kt | 27 + .../io/tonapi/models/ExecGetMethodArgType.kt | 57 +- ...thodWithBodyForBlockchainAccountRequest.kt | 23 + .../io/tonapi/models/ExtraCurrencies.kt | 23 + .../kotlin/io/tonapi/models/ExtraCurrency.kt | 25 + .../models/ExtraCurrencyTransferAction.kt | 35 + .../models/FlawedJettonTransferAction.kt | 45 ++ .../kotlin/io/tonapi/models/FoundAccounts.kt | 23 + .../models/FoundAccountsAddressesInner.kt | 30 + .../kotlin/io/tonapi/models/GasLimitPrices.kt | 39 + .../kotlin/io/tonapi/models/GasRelayAction.kt | 27 + .../kotlin/io/tonapi/models/GaslessConfig.kt | 28 + .../models/GaslessConfigGasJettonsInner.kt | 23 + .../tonapi/models/GaslessEstimateRequest.kt | 33 + .../GaslessEstimateRequestMessagesInner.kt | 23 + .../io/tonapi/models/GaslessSendRequest.kt | 26 + .../main/kotlin/io/tonapi/models/GaslessTx.kt | 23 + .../models/GetAccountDiff200Response.kt | 23 + .../GetAccountInfoByStateInitRequest.kt | 23 + .../models/GetAccountPublicKey200Response.kt | 23 + .../io/tonapi/models/GetAccountsRequest.kt | 23 + .../models/GetAllRawShardsInfo200Response.kt | 27 + .../tonapi/models/GetChartRates200Response.kt | 24 + .../models/GetMarketsRates200Response.kt | 23 + .../models/GetOpenapiJsonDefaultResponse.kt | 25 + .../models/GetOutMsgQueueSizes200Response.kt | 25 + ...tOutMsgQueueSizes200ResponseShardsInner.kt | 25 + .../io/tonapi/models/GetRates200Response.kt | 23 + .../models/GetRawAccountState200Response.kt | 31 + .../models/GetRawBlockProof200Response.kt | 29 + .../GetRawBlockProof200ResponseStepsInner.kt | 25 + ...sponseStepsInnerLiteServerBlockLinkBack.kt | 33 + ...nseStepsInnerLiteServerBlockLinkForward.kt | 33 + ...nerLiteServerBlockLinkForwardSignatures.kt | 27 + ...ockLinkForwardSignaturesSignaturesInner.kt | 25 + .../GetRawBlockchainBlock200Response.kt | 25 + .../GetRawBlockchainBlockHeader200Response.kt | 27 + .../GetRawBlockchainBlockState200Response.kt | 29 + .../tonapi/models/GetRawConfig200Response.kt | 29 + .../GetRawListBlockTransactions200Response.kt | 31 + ...istBlockTransactions200ResponseIdsInner.kt | 29 + .../GetRawMasterchainInfo200Response.kt | 27 + .../GetRawMasterchainInfoExt200Response.kt | 37 + .../GetRawShardBlockProof200Response.kt | 25 + ...RawShardBlockProof200ResponseLinksInner.kt | 25 + .../models/GetRawShardInfo200Response.kt | 29 + .../io/tonapi/models/GetRawTime200Response.kt | 23 + .../models/GetRawTransactions200Response.kt | 25 + .../GetStakingPoolHistory200Response.kt | 23 + .../models/GetStakingPoolInfo200Response.kt | 25 + .../models/GetStakingPools200Response.kt | 26 + .../models/GetStorageProviders200Response.kt | 23 + .../models/GetTonConnectPayload200Response.kt | 23 + .../kotlin/io/tonapi/models/ImagePreview.kt | 25 + .../kotlin/io/tonapi/models/InitStateRaw.kt | 27 + .../kotlin/io/tonapi/models/JettonBalance.kt | 33 + .../io/tonapi/models/JettonBalanceLock.kt | 25 + .../io/tonapi/models/JettonBridgeParams.kt | 35 + .../io/tonapi/models/JettonBridgePrices.kt | 33 + .../io/tonapi/models/JettonBurnAction.kt | 31 + .../kotlin/io/tonapi/models/JettonHolders.kt | 27 + .../models/JettonHoldersAddressesInner.kt | 29 + .../kotlin/io/tonapi/models/JettonInfo.kt | 38 + .../kotlin/io/tonapi/models/JettonMetadata.kt | 43 ++ .../io/tonapi/models/JettonMintAction.kt | 31 + .../io/tonapi/models/JettonOperation.kt | 57 ++ .../io/tonapi/models/JettonOperations.kt | 25 + .../kotlin/io/tonapi/models/JettonPreview.kt | 40 ++ .../kotlin/io/tonapi/models/JettonQuantity.kt | 27 + .../io/tonapi/models/JettonSwapAction.kt | 39 + .../io/tonapi/models/JettonTransferAction.kt | 46 +- .../io/tonapi/models/JettonTransferPayload.kt | 28 + .../tonapi/models/JettonVerificationType.kt | 60 ++ .../main/kotlin/io/tonapi/models/Jettons.kt | 23 + .../io/tonapi/models/JettonsBalances.kt | 23 + .../tonapi/models/LiquidityDepositAction.kt | 27 + .../kotlin/io/tonapi/models/MarketTonRates.kt | 27 + .../main/kotlin/io/tonapi/models/Message.kt | 75 ++ .../io/tonapi/models/MessageConsequences.kt | 27 + .../main/kotlin/io/tonapi/models/Metadata.kt | 28 + .../main/kotlin/io/tonapi/models/Method.kt | 25 + .../io/tonapi/models/MethodExecutionResult.kt | 32 + .../models/MisbehaviourPunishmentConfig.kt | 43 ++ .../io/tonapi/models/MsgForwardPrices.kt | 33 + .../main/kotlin/io/tonapi/models/Multisig.kt | 33 + .../kotlin/io/tonapi/models/MultisigOrder.kt | 45 ++ .../models/MultisigOrderChangingParameters.kt | 27 + .../main/kotlin/io/tonapi/models/Multisigs.kt | 23 + .../kotlin/io/tonapi/models/NftCollection.kt | 46 ++ .../kotlin/io/tonapi/models/NftCollections.kt | 23 + .../main/kotlin/io/tonapi/models/NftItem.kt | 64 ++ .../io/tonapi/models/NftItemCollection.kt | 27 + .../io/tonapi/models/NftItemTransferAction.kt | 37 + .../main/kotlin/io/tonapi/models/NftItems.kt | 23 + .../kotlin/io/tonapi/models/NftOperation.kt | 35 + .../kotlin/io/tonapi/models/NftOperations.kt | 25 + .../io/tonapi/models/NftPurchaseAction.kt | 47 ++ .../main/kotlin/io/tonapi/models/Oracle.kt | 25 + .../io/tonapi/models/OracleBridgeParams.kt | 29 + .../io/tonapi/models/PoolImplementation.kt | 29 + .../tonapi/models/PoolImplementationType.kt | 57 ++ .../main/kotlin/io/tonapi/models/PoolInfo.kt | 60 +- .../src/main/kotlin/io/tonapi/models/Price.kt | 36 + .../main/kotlin/io/tonapi/models/Protocol.kt | 25 + .../main/kotlin/io/tonapi/models/Purchase.kt | 37 + .../kotlin/io/tonapi/models/PurchaseAction.kt | 31 + .../io/tonapi/models/RawBlockchainConfig.kt | 23 + .../kotlin/io/tonapi/models/ReducedBlock.kt | 37 + .../kotlin/io/tonapi/models/ReducedBlocks.kt | 23 + .../main/kotlin/io/tonapi/models/Refund.kt | 38 + .../io/tonapi/models/RemoveExtensionAction.kt | 25 + .../src/main/kotlin/io/tonapi/models/Risk.kt | 40 ++ .../src/main/kotlin/io/tonapi/models/Sale.kt | 29 + .../main/kotlin/io/tonapi/models/ScaledUI.kt | 25 + .../models/SendBlockchainMessageRequest.kt | 27 + .../models/SendRawMessage200Response.kt | 23 + .../io/tonapi/models/SendRawMessageRequest.kt | 23 + .../src/main/kotlin/io/tonapi/models/Seqno.kt | 23 + .../kotlin/io/tonapi/models/ServiceStatus.kt | 27 + .../models/SetSignatureAllowedAction.kt | 25 + .../kotlin/io/tonapi/models/SignRawMessage.kt | 35 + .../kotlin/io/tonapi/models/SignRawParams.kt | 37 + .../io/tonapi/models/SizeLimitsConfig.kt | 37 + .../io/tonapi/models/SmartContractAction.kt | 35 + .../main/kotlin/io/tonapi/models/Source.kt | 23 + .../kotlin/io/tonapi/models/SourceFile.kt | 31 + .../main/kotlin/io/tonapi/models/StateInit.kt | 25 + .../kotlin/io/tonapi/models/StoragePhase.kt | 28 + .../io/tonapi/models/StorageProvider.kt | 33 + .../kotlin/io/tonapi/models/Subscription.kt | 64 ++ .../io/tonapi/models/SubscriptionAction.kt | 36 + .../kotlin/io/tonapi/models/Subscriptions.kt | 23 + .../kotlin/io/tonapi/models/TokenRates.kt | 29 + .../models/TonConnectProof200Response.kt | 23 + .../tonapi/models/TonConnectProofRequest.kt | 25 + .../models/TonConnectProofRequestProof.kt | 31 + .../TonConnectProofRequestProofDomain.kt | 25 + .../io/tonapi/models/TonTransferAction.kt | 35 + .../src/main/kotlin/io/tonapi/models/Trace.kt | 29 + .../main/kotlin/io/tonapi/models/TraceID.kt | 25 + .../main/kotlin/io/tonapi/models/TraceIDs.kt | 23 + .../kotlin/io/tonapi/models/Transaction.kt | 85 +-- .../io/tonapi/models/TransactionType.kt | 59 +- .../kotlin/io/tonapi/models/Transactions.kt | 23 + .../main/kotlin/io/tonapi/models/TrustType.kt | 59 ++ .../kotlin/io/tonapi/models/TvmStackRecord.kt | 50 ++ .../io/tonapi/models/UnSubscriptionAction.kt | 29 + .../main/kotlin/io/tonapi/models/Validator.kt | 29 + .../kotlin/io/tonapi/models/Validators.kt | 31 + .../kotlin/io/tonapi/models/ValidatorsSet.kt | 33 + .../tonapi/models/ValidatorsSetListInner.kt | 27 + .../main/kotlin/io/tonapi/models/ValueFlow.kt | 29 + .../io/tonapi/models/ValueFlowJettonsInner.kt | 30 + .../io/tonapi/models/VaultDepositInfo.kt | 25 + .../main/kotlin/io/tonapi/models/Wallet.kt | 53 ++ .../main/kotlin/io/tonapi/models/WalletDNS.kt | 33 + .../kotlin/io/tonapi/models/WalletPlugin.kt | 28 + .../kotlin/io/tonapi/models/WalletStats.kt | 29 + .../main/kotlin/io/tonapi/models/Wallets.kt | 23 + .../io/tonapi/models/WithdrawStakeAction.kt | 30 + .../models/WithdrawStakeRequestAction.kt | 30 + .../models/WithdrawTokenStakeRequestAction.kt | 27 + .../kotlin/io/tonapi/models/WorkchainDescr.kt | 45 ++ tonapi/upd.sh | 4 - tools/hooks/README.md | 205 ++++++ tools/hooks/pre-commit | 24 + tools/scripts/decrypt_logs.py | 112 +++ .../analytic/generate_event_delegate.py | 478 +++++++++++++ .../analytic/generate_event_delegate.sh | 41 ++ tools/scripts/generators/tonapi/README.md | 205 ++++++ .../generators/tonapi/generate-battery.sh | 7 + .../generators/tonapi/generate-tonapi.sh | 190 +++++ .../tonapi/openapi}/battery-api.yml | 0 .../generators/tonapi/openapi/openapi.yml | 0 .../tonapi}/templates/data_class.mustache | 0 .../templates/data_class_opt_var.mustache | 0 .../templates/data_class_req_var.mustache | 0 .../libraries/jvm-okhttp/api.mustache | 0 .../tonapi/templates/licenseInfo.mustache | 9 + .../scripts/generators/tonapi}/tonapi.sh | 0 tools/scripts/generators/tonapi/update-all.sh | 19 + tools/scripts/pre-commit-exec | 114 +++ ui/blur/build.gradle.kts | 58 +- ui/blur/src/main/java/blur/BlurCompat.kt | 5 +- ui/blur/src/main/java/blur/SimpleCanvas.kt | 2 +- .../arm64-v8a/librenderscript-toolkit.so | Bin 0 -> 483136 bytes .../armeabi-v7a/librenderscript-toolkit.so | Bin 0 -> 303804 bytes .../jniLibs/x86/librenderscript-toolkit.so | Bin 0 -> 395064 bytes .../jniLibs/x86_64/librenderscript-toolkit.so | Bin 0 -> 437464 bytes ui/shimmer/build.gradle.kts | 13 +- ui/uikit/color/build.gradle.kts | 12 +- .../com/tonapps/uikit/color/UIKitColor.kt | 1 + ui/uikit/core/build.gradle.kts | 37 +- .../src/main/java/uikit/base/BaseFragment.kt | 2 - .../uikit/drawable/CellBackgroundDrawable.kt | 2 +- .../main/java/uikit/drawable/TextDrawable.kt | 2 +- .../java/uikit/extensions/CharSequence.kt | 2 +- .../src/main/java/uikit/extensions/Context.kt | 2 +- .../src/main/java/uikit/extensions/View.kt | 2 +- .../uikit/navigation/NavigationActivity.kt | 2 +- .../java/uikit/widget/BottomSheetLayout.kt | 3 - .../src/main/java/uikit/widget/HeaderView.kt | 2 +- .../main/java/uikit/widget/LoadableButton.kt | 2 +- .../src/main/java/uikit/widget/LoaderView.kt | 2 +- .../src/main/java/uikit/widget/PhraseWords.kt | 37 +- .../main/java/uikit/widget/PinInputView.kt | 2 +- .../java/uikit/widget/SimpleRecyclerView.kt | 6 +- .../main/java/uikit/widget/SwipeBackLayout.kt | 2 +- .../src/main/java/uikit/widget/ToastView.kt | 2 +- .../java/uikit/widget/input/HintDrawable.kt | 2 +- .../java/uikit/widget/webview/WebViewFixed.kt | 2 +- .../widget/webview/bridge/BridgeWebView.kt | 4 - ui/uikit/flag/build.gradle.kts | 11 +- .../java/com/tonapps/uikit/flag/UIKitFlag.kt | 2 + ui/uikit/icon/build.gradle.kts | 13 +- .../java/com/tonapps/uikit/icon/UIKitIcon.kt | 2 + ui/uikit/list/build.gradle.kts | 15 +- .../com/tonapps/uikit/list/BaseListAdapter.kt | 6 +- 1419 files changed, 30207 insertions(+), 23570 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/beta.yml delete mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/claude.yml create mode 100644 .github/workflows/debug.yml create mode 100644 .github/workflows/mirror-public-build.yml create mode 100644 .github/workflows/mirror-public-push.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/deploymentTargetDropDown.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml create mode 100644 AGENTS.MD create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/backoff/ExponentialBackoff.kt delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetCallFactory.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetInterceptor.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTimeoutException.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTransportResponseBody.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/OkHttpBridgeRequestCallback.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RedirectStrategy.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverter.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverterImpl.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverter.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverterBasedBuilder.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/ResponseConverter.java delete mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/UploadBodyDataBroker.java create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigResponseEntity.kt create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EmulateWithBatteryResult.kt create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/LoggingInterceptor.kt create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/RateLimitInterceptor.kt create mode 100644 apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronResourcePrices.kt create mode 100644 apps/wallet/api/src/main/res/drawable/ic_trx.png create mode 100644 apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/PreferredTronFeeMethod.kt create mode 100644 apps/wallet/features/sample/build.gradle.kts create mode 100644 apps/wallet/instance/app/src/main/assets/key create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/holder/SectionTitleHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/TronBannerHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/TronFeesEmulation.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/TronFeesScreen.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/TronFeesScreenType.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/TronFeesViewModel.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/Adapter.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/BatteryHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/HeaderHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/Holder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/Item.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/LearnMoreHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tronfees/list/TokenHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/emulation/Trc20TransferDefaultFees.kt create mode 100644 apps/wallet/instance/app/src/main/res/drawable/ic_tetra_24.xml create mode 100644 apps/wallet/instance/app/src/main/res/drawable/round_mask.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_tron_fee_header.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_tron_fee_learn_more.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_tron_fee_option.xml delete mode 100644 buildLogic/plugin/src/main/kotlin/Extensions.kt create mode 100644 buildLogic/plugin/src/main/kotlin/common/ProjectExt.kt create mode 100644 buildLogic/plugin/src/main/kotlin/common/versions.kt create mode 100644 buildLogic/plugin/src/main/kotlin/plarform/Deps.kt create mode 100644 buildLogic/plugin/src/main/kotlin/plarform/androidTarget.kt create mode 100644 buildLogic/plugin/src/main/kotlin/plarform/platformTarget.kt create mode 100644 buildLogic/plugin/src/main/kotlin/target.android.app.gradle.kts create mode 100644 buildLogic/plugin/src/main/kotlin/target.android.compose.gradle.kts create mode 100644 buildLogic/plugin/src/main/kotlin/target.android.library.gradle.kts delete mode 100644 buildSrc/.gitignore delete mode 100644 buildSrc/build.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/Build.kt delete mode 100644 buildSrc/src/main/kotlin/ProjectModules.kt delete mode 100644 buildSrc/src/main/kotlin/Signing.kt create mode 100644 detekt/detekt.yml create mode 100644 kmp/async/build.gradle.kts create mode 100644 kmp/async/src/androidMain/kotlin/com/tonapps/async/AndroidExecutors.kt create mode 100644 kmp/async/src/androidMain/kotlin/com/tonapps/async/AsyncPlatform.android.kt create mode 100644 kmp/async/src/commonMain/kotlin/com/tonapps/async/Async.kt create mode 100644 kmp/async/src/commonMain/kotlin/com/tonapps/async/AsyncPlatform.kt create mode 100644 kmp/async/src/commonMain/kotlin/com/tonapps/async/Relay.kt create mode 100644 kmp/mvi/README.md create mode 100644 kmp/mvi/build.gradle.kts create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/DataState.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/AsyncViewModel.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/ChangeStrategy.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/Initializer.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/Mvi.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/MviBinder.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/MviFeature.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/MviFeatureDelegate.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/MviRelay.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/MviSubject.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/contract/MviAction.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/contract/MviState.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/contract/MviViewState.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/contract/internal/Stateful.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/props/MviProperty.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/props/MviPropertyLiveData.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/thread/BgThread.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/thread/ComputationThread.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/thread/MainThread.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/thread/MviThread.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/thread/StateThread.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/Key.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/MviPaging.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/PagingAccessor.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/PagingController.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/PagingState.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/StateFlowCollect.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/ViewCell.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/collection/MviPagingMutationProxy.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/collection/PagingArrayList.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/contract/MviPagingMutation.kt create mode 100644 kmp/mvi/src/commonMain/kotlin/com/tonapps/paging/providers.kt delete mode 100644 kmp/ui/build/kotlin/commonizedNativeDistributionLocation.txt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/CellsPreview.kt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/DetailsPreview.kt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/EventsPreview.kt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/FilterPreview.kt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/MoonCellPreview.kt create mode 100644 kmp/ui/src/androidMain/kotlin/ui/ThemedPreview.kt create mode 100644 kmp/ui/src/commonMain/composeResources/drawable/ic_magnifying_glass_16.xml create mode 100644 kmp/ui/src/commonMain/composeResources/drawable/ic_xmark_circle_16.xml create mode 100644 kmp/ui/src/commonMain/composeResources/values/strings.xml create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/DefaultButtons.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/DefaultCellComonentes.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/MoonAsyncImage.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/MoonBadge.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/MoonBadgedLayout.kt rename kmp/ui/src/commonMain/kotlin/ui/components/{Checkbox.kt => moon/MoonCheckbox.kt} (98%) rename kmp/ui/src/commonMain/kotlin/ui/components/{TKHorizontalDivider.kt => moon/MoonDivider.kt} (65%) create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/MoonEditText.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/MoonLabel.kt rename kmp/ui/src/commonMain/kotlin/ui/components/{TKCircularProgressIndicator.kt => moon/MoonLoader.kt} (87%) rename kmp/ui/src/commonMain/kotlin/ui/components/{PullToRefreshIndicator.kt => moon/MoonRefresh.kt} (94%) rename kmp/ui/src/commonMain/kotlin/ui/components/{Header.kt => moon/MoonTopAppBar.kt} (89%) create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/BaseTextCell.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/MoonBundle.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/MoonDescriptionCell.kt rename kmp/ui/src/commonMain/kotlin/ui/components/{TKEmptyPlaceholder.kt => moon/cell/MoonEmptyCell.kt} (97%) rename kmp/ui/src/commonMain/kotlin/ui/components/{TKFooterLoader.kt => moon/cell/MoonLoaderCell.kt} (84%) create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/MoonPropertyCell.kt rename kmp/ui/src/commonMain/kotlin/ui/components/{TKFooterRetry.kt => moon/cell/MoonRetryCell.kt} (96%) create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/MoonSearchCell.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/TextCell.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/cell/TextCheckCell.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/components/moon/list/items.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/lifecycle/LifecycleEvents.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/text/emptyTextValue.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/utils/SizeExt.kt create mode 100644 kmp/ui/src/commonMain/kotlin/ui/workaround/PainterExt.kt create mode 100644 lib/blockchain/src/main/java/com/tonapps/blockchain/ton/SignatureDomain.kt create mode 100644 lib/bus/build.gradle.kts create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/core/AnalyticsHelper.kt create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/core/AptabaseEventExecutor.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/AnalyticsHelper.kt => lib/bus/src/main/kotlin/com/tonapps/bus/core/DefaultEventDelegate.kt (62%) create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/core/EmptyEventDelegate.kt create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/core/contract/EventDelegate.kt create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/core/contract/EventExecutor.kt create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/generated/DefaultEvents.kt create mode 100644 lib/bus/src/main/kotlin/com/tonapps/bus/generated/Events.kt create mode 100644 lib/extensions/src/main/java/com/tonapps/extensions/TimedCache.kt create mode 100644 lib/log/build.gradle.kts create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/L.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/LogTarget.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/LoggerConfig.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/targets/console/ConsoleLogTarget.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/targets/file/FileLogTarget.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/targets/file/engine/CustomFileWritable.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/targets/file/engine/FileWritable.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/targets/file/engine/LogcatFileWritable.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/utils/FileManager.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/utils/LogArchiver.kt create mode 100644 lib/log/src/main/kotlin/com/tonapps/log/utils/LogHeaderBuilder.kt create mode 100644 lib/security/src/main/jniLibs/arm64-v8a/libsodium.so create mode 100644 lib/security/src/main/jniLibs/arm64-v8a/libsodium_jni.so create mode 100644 lib/security/src/main/jniLibs/armeabi-v7a/libsodium.so create mode 100644 lib/security/src/main/jniLibs/armeabi-v7a/libsodium_jni.so create mode 100644 lib/security/src/main/jniLibs/x86/libsodium.so create mode 100644 lib/security/src/main/jniLibs/x86/libsodium_jni.so create mode 100644 lib/security/src/main/jniLibs/x86_64/libsodium.so create mode 100644 lib/security/src/main/jniLibs/x86_64/libsodium_jni.so create mode 100644 rules/CHECK.MD create mode 100644 rules/OVERVIEW.MD create mode 100644 rules/REVIEW.MD delete mode 100644 tonapi/battery.sh create mode 100644 tonapi/battery/build.gradle.kts rename tonapi/{ => battery}/src/main/kotlin/io/batteryapi/apis/ConnectApi.kt (87%) rename tonapi/{ => battery}/src/main/kotlin/io/batteryapi/apis/DefaultApi.kt (82%) rename tonapi/{ => battery}/src/main/kotlin/io/batteryapi/apis/EmulationApi.kt (74%) rename tonapi/{ => battery}/src/main/kotlin/io/batteryapi/apis/WalletApi.kt (88%) create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/ApiAbstractions.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/ApiClient.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/ApiResponse.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/AtomicBooleanAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/AtomicIntegerAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/AtomicLongAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/BigDecimalAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/BigIntegerAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/Errors.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/LocalDateAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/LocalDateTimeAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/OffsetDateTimeAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/PartConfig.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/RequestConfig.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/RequestMethod.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/ResponseExtensions.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/Serializer.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/StringBuilderAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/URIAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/URLAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/infrastructure/UUIDAdapter.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseRequestPurchasesInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatus.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatusPurchasesInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatusPurchasesInnerError.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/AppStoreNotificationRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/ApplyPromoRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/Balance.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/BatteryCharged.kt rename tonapi/{ => battery}/src/main/kotlin/io/batteryapi/models/Config.kt (54%) create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/ConfigGasProxyInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/ConfigMeanPrices.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/CreateCustomRefundRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/CreatePromoCampaign200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/CreatePromoCampaign200ResponseParticipantsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/CreatePromoCampaignRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/CreatePromoCampaignRequestParticipantsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EmulateMessageToWalletRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseEstimate200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseEstimateRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseGetMessage200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseGetStatus200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseSend200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EnterpriseWalletConfig.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/Error.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EstimateGaslessCostRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EstimatedTronTx.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EstimatedTronTxInstantFee.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/EstimatedTronTxInstantFeeAcceptedAssetsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/GaslessEstimation.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/GetTonConnectPayload200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/GetTonConnectPayloadDefaultResponse.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/GetTronConfig200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatus.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatusTransactionsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatusTransactionsInnerError.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IncreaseUserBalanceRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IosBatteryPurchaseRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/IosBatteryPurchaseRequestTransactionsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseStatus.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseStatusError.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PromoUsed.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/Purchases.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInnerRefundInformation.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInnerRefundInformationRefunded.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/RechargeMethods.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/RechargeMethodsMethodsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/RelayerSendingEstimation.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/RequestRefundRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/ResetUserBalanceRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/SentTronTx.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/Status.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/StatusPendingTransactionsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TonConnectProof200Response.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TonConnectProofRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TonConnectProofRequestProof.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TonConnectProofRequestProofDomain.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/Transactions.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TransactionsTransactionsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TronSendRequest.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TronTransactionsList.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/TronTransactionsListTransactionsInner.kt create mode 100644 tonapi/battery/src/main/kotlin/io/batteryapi/models/VerifyPurchasePromo200Response.kt create mode 100644 tonapi/core/build.gradle.kts rename tonapi/{ => core}/src/main/kotlin/io/JsonAny.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/Serializer.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/ApiAbstractions.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/ApiClient.kt (98%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/ApiResponse.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/AtomicBooleanAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/AtomicIntegerAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/AtomicLongAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/BigDecimalAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/BigIntegerAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/Errors.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/LocalDateAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/LocalDateTimeAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/OffsetDateTimeAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/PartConfig.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/RequestConfig.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/RequestMethod.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/ResponseExtensions.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/StringBuilderAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/URIAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/URLAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/infrastructure/UUIDAdapter.kt (100%) rename tonapi/{ => core}/src/main/kotlin/io/serializers/AnySerializer.kt (100%) delete mode 100644 tonapi/generate.sh create mode 100644 tonapi/legacy/build.gradle.kts delete mode 100644 tonapi/src/main/AndroidManifest.xml delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseRequestPurchasesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatus.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatusPurchasesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AndroidBatteryPurchaseStatusPurchasesInnerError.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/AppStoreNotificationRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/ApplyPromoRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/Balance.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/BatteryCharged.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/ConfigGasProxyInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/ConfigMeanPrices.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/CreateCustomRefundRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/CreatePromoCampaign200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/CreatePromoCampaign200ResponseParticipantsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/CreatePromoCampaignRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/CreatePromoCampaignRequestParticipantsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EmulateMessageToWalletRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseEstimate200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseEstimateRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseGetMessage200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseGetStatus200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseSend200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EnterpriseWalletConfig.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/Error.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EstimateGaslessCostRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EstimatedTronTx.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EstimatedTronTxInstantFee.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/EstimatedTronTxInstantFeeAcceptedAssetsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/GaslessEstimation.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/GetTonConnectPayload200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/GetTonConnectPayloadDefaultResponse.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/GetTronConfig200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatus.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatusTransactionsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IOSBatteryPurchaseStatusTransactionsInnerError.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IncreaseUserBalanceRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/InlineObject.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IosBatteryPurchaseRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/IosBatteryPurchaseRequestTransactionsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseStatus.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PromoCodeBatteryPurchaseStatusError.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PromoUsed.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/Purchases.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInnerRefundInformation.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/PurchasesPurchasesInnerRefundInformationRefunded.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/RechargeMethods.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/RechargeMethodsMethodsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/RelayerSendingEstimation.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/RequestRefundRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/ResetUserBalanceRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/SentTronTx.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/Status.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/StatusPendingTransactionsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TonConnectProof200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TonConnectProofRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TonConnectProofRequestProof.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TonConnectProofRequestProofDomain.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/Transactions.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TransactionsTransactionsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TronSendRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TronTransactionsList.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/TronTransactionsListTransactionsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/batteryapi/models/VerifyPurchasePromo200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccStatusChange.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Account.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountAddress.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountEvent.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountEvents.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountInfoByStateInit.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountPurchases.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountStaking.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountStakingInfo.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountStatus.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AccountStorageInfo.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Accounts.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ActionPhase.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ActionSimplePreview.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AddExtensionAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AddressParse200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AddressParse200ResponseBounceable.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ApyHistory.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Auction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Auctions.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockCurrencyCollection.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockCurrencyCollectionOtherInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockLimits.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockParamLimits.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockRaw.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainAccountInspect.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainBlock.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainBlockShards.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainBlockShardsShardsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainBlocks.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig10.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig11.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig12.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig13.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig14.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig15.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig16.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig17.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig18.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig18StoragePricesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig20.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig21.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig22.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig23.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig24.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig25.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig28.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig29.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig31.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig40.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig43.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig44.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig45.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig45ContractsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig5.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig6.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig7.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig71.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig79.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig7CurrenciesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig8.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainConfig9.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainLibrary.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainRawAccount.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BlockchainRawAccountLibrariesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/BouncePhaseType.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ComputePhase.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ComputeSkipReason.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ConfigProposalSetup.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ContractDeployAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/CreditPhase.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/CurrencyType.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessage.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecoded.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletHighloadV2.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV3.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV4.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV5.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedRawMessage.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DecodedRawMessageMessage.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DepositStakeAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DepositTokenStakeAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DnsExpiring.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DnsExpiringItemsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DnsRecord.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DomainBid.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DomainBids.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DomainInfo.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DomainNames.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/DomainRenewAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/EcPreview.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ElectionsDepositStakeAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ElectionsRecoverStakeAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/EmulateMessageToWalletRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/EmulateMessageToWalletRequestParamsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/EncryptedComment.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Error.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Event.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ExecGetMethodArg.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ExecGetMethodWithBodyForBlockchainAccountRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ExtraCurrencies.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ExtraCurrency.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ExtraCurrencyTransferAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/FoundAccounts.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/FoundAccountsAddressesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GasLimitPrices.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GasRelayAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessConfig.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessConfigGasJettonsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessEstimateRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessEstimateRequestMessagesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessSendRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GaslessTx.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetAccountDiff200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetAccountInfoByStateInitRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetAccountPublicKey200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetAccountsRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetAllRawShardsInfo200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetChartRates200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetMarketsRates200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetOpenapiJsonDefaultResponse.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetOutMsgQueueSizes200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetOutMsgQueueSizes200ResponseShardsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRates200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawAccountState200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkBack.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForward.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForwardSignatures.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForwardSignaturesSignaturesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlock200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlockHeader200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlockState200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawConfig200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawListBlockTransactions200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawListBlockTransactions200ResponseIdsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawMasterchainInfo200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawMasterchainInfoExt200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawShardBlockProof200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawShardBlockProof200ResponseLinksInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawShardInfo200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawTime200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetRawTransactions200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetStakingPoolHistory200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetStakingPoolInfo200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetStakingPools200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetStorageProviders200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/GetTonConnectPayload200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ImagePreview.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/InitStateRaw.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/InlineObject.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonBalance.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonBalanceLock.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonBridgeParams.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonBridgePrices.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonBurnAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonHolders.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonHoldersAddressesInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonInfo.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonMetadata.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonMintAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonOperation.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonOperations.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonPreview.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonQuantity.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonSwapAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonTransferPayload.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonVerificationType.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Jettons.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/JettonsBalances.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MarketTonRates.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Message.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MessageConsequences.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Metadata.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Method.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MethodExecutionResult.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MisbehaviourPunishmentConfig.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MsgForwardPrices.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Multisig.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MultisigOrder.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/MultisigOrderChangingParameters.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Multisigs.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftCollection.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftCollections.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftItem.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftItemCollection.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftItemTransferAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftItems.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftOperation.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftOperations.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Oracle.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/OracleBridgeParams.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/PoolImplementation.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/PoolImplementationType.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Price.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Protocol.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Purchase.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/PurchaseAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/RawBlockchainConfig.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ReducedBlock.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ReducedBlocks.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Refund.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/RemoveExtensionAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Risk.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Sale.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SendBlockchainMessageRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SendRawMessage200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SendRawMessageRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Seqno.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ServiceStatus.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SetSignatureAllowedAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SignRawMessage.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SignRawParams.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SizeLimitsConfig.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SmartContractAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Source.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SourceFile.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/StateInit.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/StoragePhase.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/StorageProvider.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Subscription.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/SubscriptionAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Subscriptions.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TokenRates.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TonConnectProof200Response.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TonConnectProofRequest.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TonConnectProofRequestProof.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TonConnectProofRequestProofDomain.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TonTransferAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Trace.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TraceID.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TraceIDs.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Transactions.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TrustType.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/TvmStackRecord.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/UnSubscriptionAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Validator.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Validators.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ValidatorsSet.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ValidatorsSetListInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ValueFlow.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/ValueFlowJettonsInner.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Wallet.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WalletDNS.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WalletPlugin.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WalletStats.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/Wallets.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WithdrawStakeAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WithdrawStakeRequestAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WithdrawTokenStakeRequestAction.kt delete mode 100644 tonapi/src/main/kotlin/io/tonapi/models/WorkchainDescr.kt create mode 100644 tonapi/tonkeeper/build.gradle.kts rename tonapi/{ => tonkeeper}/src/main/kotlin/io/extensions/NftItem.kt (100%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/AccountsApi.kt (79%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/BlockchainApi.kt (89%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/ConnectApi.kt (91%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/DNSApi.kt (84%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/EmulationApi.kt (78%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/EventsApi.kt (79%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/ExtraCurrencyApi.kt (80%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/GaslessApi.kt (88%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/JettonsApi.kt (86%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/LiteServerApi.kt (86%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/MultisigApi.kt (84%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/NFTApi.kt (86%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/PurchasesApi.kt (76%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/RatesApi.kt (82%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/StakingApi.kt (83%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/StorageApi.kt (85%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/TracesApi.kt (80%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/UtilitiesApi.kt (92%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/apis/WalletApi.kt (85%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/ApiAbstractions.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/ApiClient.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/ApiResponse.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/AtomicBooleanAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/AtomicIntegerAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/AtomicLongAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/BigDecimalAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/BigIntegerAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/Errors.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/LocalDateAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/LocalDateTimeAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/OffsetDateTimeAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/PartConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/RequestConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/RequestMethod.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/ResponseExtensions.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/Serializer.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/StringBuilderAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/URIAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/URLAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/infrastructure/UUIDAdapter.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccStatusChange.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Account.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountAddress.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountEvent.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountEvents.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountInfoByStateInit.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountPurchases.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountStaking.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountStakingInfo.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountStatus.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AccountStorageInfo.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Accounts.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/Action.kt (56%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ActionPhase.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ActionSimplePreview.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AddExtensionAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AddressParse200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AddressParse200ResponseBounceable.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ApyHistory.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Auction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Auctions.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockCurrencyCollection.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockCurrencyCollectionOtherInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockLimits.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockParamLimits.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockRaw.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/BlockValueFlow.kt (65%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainAccountInspect.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainBlock.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainBlockShards.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainBlockShardsShardsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainBlocks.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/BlockchainConfig.kt (63%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig10.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig11.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig12.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig13.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig14.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig15.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig16.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig17.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig18.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig18StoragePricesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig20.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig21.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig22.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig23.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig24.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig25.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig28.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig29.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig31.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig40.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig43.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig44.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig45.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig45ContractsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig5.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig6.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig7.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig71.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig79.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig7CurrenciesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig8.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainConfig9.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainLibrary.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainRawAccount.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BlockchainRawAccountLibrariesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/BouncePhaseType.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ComputePhase.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ComputeSkipReason.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ConfigProposalSetup.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ContractDeployAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/CreditPhase.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/CurrencyType.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessage.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecoded.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletHighloadV2.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV3.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV4.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedMessageExtInMsgDecodedWalletV5.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedRawMessage.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DecodedRawMessageMessage.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DepositStakeAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DepositTokenStakeAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DnsExpiring.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DnsExpiringItemsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DnsRecord.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DomainBid.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DomainBids.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DomainInfo.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DomainNames.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/DomainRenewAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/EcPreview.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ElectionsDepositStakeAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ElectionsRecoverStakeAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/EmulateMessageToWalletRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/EmulateMessageToWalletRequestParamsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/EncryptedComment.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Error.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Event.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ExecGetMethodArg.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/ExecGetMethodArgType.kt (55%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ExecGetMethodWithBodyForBlockchainAccountRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ExtraCurrencies.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ExtraCurrency.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ExtraCurrencyTransferAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/FlawedJettonTransferAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/FoundAccounts.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/FoundAccountsAddressesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GasLimitPrices.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GasRelayAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessConfigGasJettonsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessEstimateRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessEstimateRequestMessagesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessSendRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GaslessTx.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetAccountDiff200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetAccountInfoByStateInitRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetAccountPublicKey200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetAccountsRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetAllRawShardsInfo200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetChartRates200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetMarketsRates200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetOpenapiJsonDefaultResponse.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetOutMsgQueueSizes200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetOutMsgQueueSizes200ResponseShardsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRates200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawAccountState200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkBack.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForward.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForwardSignatures.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockProof200ResponseStepsInnerLiteServerBlockLinkForwardSignaturesSignaturesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlock200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlockHeader200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawBlockchainBlockState200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawConfig200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawListBlockTransactions200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawListBlockTransactions200ResponseIdsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawMasterchainInfo200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawMasterchainInfoExt200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawShardBlockProof200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawShardBlockProof200ResponseLinksInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawShardInfo200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawTime200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetRawTransactions200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetStakingPoolHistory200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetStakingPoolInfo200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetStakingPools200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetStorageProviders200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/GetTonConnectPayload200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ImagePreview.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/InitStateRaw.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonBalance.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonBalanceLock.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonBridgeParams.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonBridgePrices.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonBurnAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonHolders.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonHoldersAddressesInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonInfo.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonMetadata.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonMintAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonOperation.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonOperations.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonPreview.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonQuantity.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonSwapAction.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/JettonTransferAction.kt (50%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonTransferPayload.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonVerificationType.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Jettons.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/JettonsBalances.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/LiquidityDepositAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MarketTonRates.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Message.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MessageConsequences.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Metadata.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Method.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MethodExecutionResult.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MisbehaviourPunishmentConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MsgForwardPrices.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Multisig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MultisigOrder.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/MultisigOrderChangingParameters.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Multisigs.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftCollection.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftCollections.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftItem.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftItemCollection.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftItemTransferAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftItems.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftOperation.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftOperations.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Oracle.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/OracleBridgeParams.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/PoolImplementation.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/PoolImplementationType.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/PoolInfo.kt (55%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Price.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Protocol.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Purchase.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/PurchaseAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/RawBlockchainConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ReducedBlock.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ReducedBlocks.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Refund.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/RemoveExtensionAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Risk.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Sale.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ScaledUI.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SendBlockchainMessageRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SendRawMessage200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SendRawMessageRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Seqno.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ServiceStatus.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SetSignatureAllowedAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SignRawMessage.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SignRawParams.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SizeLimitsConfig.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SmartContractAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Source.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SourceFile.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/StateInit.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/StoragePhase.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/StorageProvider.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Subscription.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/SubscriptionAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Subscriptions.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TokenRates.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TonConnectProof200Response.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TonConnectProofRequest.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TonConnectProofRequestProof.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TonConnectProofRequestProofDomain.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TonTransferAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Trace.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TraceID.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TraceIDs.kt rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/Transaction.kt (54%) rename tonapi/{ => tonkeeper}/src/main/kotlin/io/tonapi/models/TransactionType.kt (51%) create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Transactions.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TrustType.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/TvmStackRecord.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/UnSubscriptionAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Validator.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Validators.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ValidatorsSet.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ValidatorsSetListInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ValueFlow.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/ValueFlowJettonsInner.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/VaultDepositInfo.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Wallet.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WalletDNS.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WalletPlugin.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WalletStats.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/Wallets.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WithdrawStakeAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WithdrawStakeRequestAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WithdrawTokenStakeRequestAction.kt create mode 100644 tonapi/tonkeeper/src/main/kotlin/io/tonapi/models/WorkchainDescr.kt delete mode 100644 tonapi/upd.sh create mode 100644 tools/hooks/README.md create mode 100755 tools/hooks/pre-commit create mode 100755 tools/scripts/decrypt_logs.py create mode 100644 tools/scripts/generators/analytic/generate_event_delegate.py create mode 100755 tools/scripts/generators/analytic/generate_event_delegate.sh create mode 100644 tools/scripts/generators/tonapi/README.md create mode 100755 tools/scripts/generators/tonapi/generate-battery.sh create mode 100755 tools/scripts/generators/tonapi/generate-tonapi.sh rename {tonapi => tools/scripts/generators/tonapi/openapi}/battery-api.yml (100%) rename openapi.yml => tools/scripts/generators/tonapi/openapi/openapi.yml (100%) rename {tonapi => tools/scripts/generators/tonapi}/templates/data_class.mustache (100%) rename {tonapi => tools/scripts/generators/tonapi}/templates/data_class_opt_var.mustache (100%) rename {tonapi => tools/scripts/generators/tonapi}/templates/data_class_req_var.mustache (100%) rename {tonapi => tools/scripts/generators/tonapi}/templates/libraries/jvm-okhttp/api.mustache (100%) create mode 100644 tools/scripts/generators/tonapi/templates/licenseInfo.mustache rename {tonapi => tools/scripts/generators/tonapi}/tonapi.sh (100%) mode change 100644 => 100755 create mode 100755 tools/scripts/generators/tonapi/update-all.sh create mode 100755 tools/scripts/pre-commit-exec create mode 100644 ui/blur/src/main/jniLibs/arm64-v8a/librenderscript-toolkit.so create mode 100644 ui/blur/src/main/jniLibs/armeabi-v7a/librenderscript-toolkit.so create mode 100644 ui/blur/src/main/jniLibs/x86/librenderscript-toolkit.so create mode 100644 ui/blur/src/main/jniLibs/x86_64/librenderscript-toolkit.so diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..caa02b166 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/baselineprofile.yml b/.github/workflows/baselineprofile.yml index 70d5ab21e..d6b9c6917 100644 --- a/.github/workflows/baselineprofile.yml +++ b/.github/workflows/baselineprofile.yml @@ -6,7 +6,7 @@ on: jobs: baseline: name: generate-baselineprofile - runs-on: macOS-latest + runs-on: macos-latest-xlarge env: EMULATOR_API_LEVEL: '30' @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout to git repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: lfs: 'true' @@ -23,20 +23,20 @@ jobs: uses: actionsdesk/lfs-warning@v3.3 - name: Install JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'zulu' java-version: '22' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 - name: Build benchmark id: gradle run: ./gradlew :apps:wallet:instance:main:generateReleaseBaselineProfile - name: AVD cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: avd-cache with: path: | diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml new file mode 100644 index 000000000..669b740eb --- /dev/null +++ b/.github/workflows/beta.yml @@ -0,0 +1,58 @@ +name: Android Beta Build + +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + beta-build: + name: Build Beta APK + runs-on: macos-latest-xlarge + + steps: + - name: Checkout to git repository + uses: actions/checkout@v6 + + - name: Get VERSION_CODE from commit timestamp + run: echo "VERSION_CODE=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Setup Java + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Cache Gradle Wrapper + uses: actions/cache@v5 + with: + path: ~/.gradle/wrapper + key: gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + + - name: Accept Android SDK licenses + run: yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Decode Keystore + run: echo "${{ secrets.ANDROID_DIST_SIGNING_KEY }}" | base64 -d > ${{ github.workspace }}/release.keystore + + - name: Build Beta APK + run: | + ./gradlew :apps:wallet:instance:main:assembleDefaultRelease \ + --no-daemon \ + --no-build-cache \ + --no-configuration-cache \ + -Pandroid.injected.signing.store.file=${{ github.workspace }}/release.keystore \ + -Pandroid.injected.signing.store.password=${{ secrets.TONKEEPER_UPLOAD_STORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.TONKEEPER_UPLOAD_KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.TONKEEPER_UPLOAD_KEY_PASSWORD }} + + - name: Upload Beta APK to artifacts + uses: actions/upload-artifact@v6 + with: + name: tonkeeper-beta-${{ env.VERSION_CODE }} + path: apps/wallet/instance/main/build/outputs/apk/default/release/*.apk \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index 9372310aa..000000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Android APK build CD - -on: - push: - branches: - - 'dev' - workflow_dispatch: - -jobs: - android-build: - name: android-build - runs-on: macos-latest - - steps: - - name: Checkout to git repository - uses: actions/checkout@v4 - - - name: Set up ruby env - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.2 - bundler-cache: true - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 20.0.2+9 - - - name: Accept Android SDK licenses - run: yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses - - - name: Decode signing certificate into a file - env: - CERTIFICATE_BASE64: ${{ secrets.ANDROID_DIST_SIGNING_KEY }} - run: | - echo $CERTIFICATE_BASE64 | base64 --decode > google-release.keystore - - - name: Build android beta - run: bundle exec fastlane android beta - env: - KEYSTORE_FILE: ${{ github.workspace }}/google-release.keystore - KEYSTORE_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_STORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.TONKEEPER_UPLOAD_KEY_ALIAS}} - KEY_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_KEY_PASSWORD }} - ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} - - - name: Upload android aab to artifacts - uses: actions/upload-artifact@v4 - with: - name: Tonkeeper aab ${{ env.VERSION_CODE }} - path: | - ${{ env.AAB_OUTPUT_PATH }} - - - name: Upload android apk to artifacts - uses: actions/upload-artifact@v4 - with: - name: Tonkeeper apk ${{ env.VERSION_CODE }} - path: | - ${{ env.APK_OUTPUT_PATH }} \ No newline at end of file diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 000000000..cb3c06969 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,82 @@ +name: Claude PR Review + +on: + pull_request: + types: [ opened, reopened ] + branches: + - dev + - 'release/**' + +permissions: + pull-requests: write + contents: read + id-token: write + +jobs: + overview: + if: contains(github.event.pull_request.body, '/claude-overview') || contains(github.event.pull_request.body, '@claude') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Main changes summary + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + settings: '{"permissions":{"allow":["Bash(gh pr comment:*)","Bash(gh pr diff:*)","Bash(gh pr view:*)"]}}' + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + You are a senior software engineer reviewing a pull request. + + Provide a concise summary of the main changes. + Group changes by area/module when possible. + Use bullet points. Be specific but brief. + Do NOT mention security concerns. + + If commit messages or the diff contain task numbers (e.g. TK-123, TK-456), + list them at the top of your summary under a "Related Tasks" heading. + + After composing your summary, post it as a single PR comment using: + gh pr comment --repo ${{ github.repository }} --body "" + claude_args: | + --max-turns 8 + --model claude-sonnet-4-5-20250929 + --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" + + review: + if: contains(github.event.pull_request.body, '/claude-review') || contains(github.event.pull_request.body, '@claude') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + settings: '{"permissions":{"allow":["Bash(gh pr comment:*)","Bash(gh pr diff:*)","Bash(gh pr view:*)"]}}' + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + You are a senior software engineer performing a thorough code review on an Android (Kotlin) project. + + Provide actionable feedback. Focus on: + - Bugs and logic errors + - Performance issues + - Security issues + - Code readability and maintainability + - Kotlin/Android best practices + - Error handling gaps + - Potential race conditions or threading issues + + For each issue found, reference the file name and provide a clear explanation. + Do NOT comment on things that are correct or well-written — only comment on actual problems. + If the code looks good and has no issues, post a single brief PR comment saying so. + Do not invent problems. Be constructive and concise. + + Use inline comments for specific issues found in the code. + claude_args: | + --max-turns 25 + --model claude-opus-4-6 + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml new file mode 100644 index 000000000..46963db5c --- /dev/null +++ b/.github/workflows/debug.yml @@ -0,0 +1,64 @@ +name: Android Debug Build + +on: + push: + branches: + - 'dev' + - 'release/**' + pull_request: + types: [ opened, synchronize ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + debug-build: + name: Build Debug APK + runs-on: macos-latest-xlarge + + steps: + - name: Checkout to git repository + uses: actions/checkout@v6 + + - name: Get VERSION_CODE from commit timestamp + run: echo "VERSION_CODE=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Setup Java + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Cache Gradle Wrapper + uses: actions/cache@v5 + with: + path: ~/.gradle/wrapper + key: gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + + - name: Accept Android SDK licenses + run: yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Decode Keystore + run: echo "${{ secrets.TONKEEPER_DEBUG_STORE_FILE }}" | base64 -d > ${{ github.workspace }}/internal.keystore + + - name: Build Debug APK + run: | + ./gradlew :apps:wallet:instance:main:assembleDefaultDebug \ + --no-daemon \ + --no-build-cache \ + --no-configuration-cache \ + -Pandroid.injected.signing.debug.file=${{ github.workspace }}/internal.keystore \ + -Pandroid.injected.signing.debug.password=${{ secrets.TONKEEPER_DEBUG_STORE_PASSWORD }} \ + -Pandroid.injected.signing.debug.key.alias=${{ secrets.TONKEEPER_DEBUG_KEY_ALIAS }} \ + -Pandroid.injected.signing.debug.key.password=${{ secrets.TONKEEPER_DEBUG_KEY_PASSWORD }} + + - name: Upload Debug APK to artifacts + uses: actions/upload-artifact@v6 + with: + name: tonkeeper-debug-${{ env.VERSION_CODE }} + path: apps/wallet/instance/main/build/outputs/apk/default/debug/*.apk \ No newline at end of file diff --git a/.github/workflows/mirror-public-build.yml b/.github/workflows/mirror-public-build.yml new file mode 100644 index 000000000..ae4300462 --- /dev/null +++ b/.github/workflows/mirror-public-build.yml @@ -0,0 +1,82 @@ +name: Mirror APK release build + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build: + name: Build & Release APK + runs-on: macos-latest-xlarge + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Parse tag into VERSION_NAME and VERSION_CODE + run: | + TAG="${{ github.ref_name }}" + # Tag format: v{version_name}_{version_code} + VERSION_NAME="${TAG#v}" + VERSION_NAME="${VERSION_NAME%_*}" + VERSION_CODE="${TAG##*_}" + echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV + echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV + echo "Parsed tag '$TAG' -> VERSION_NAME=$VERSION_NAME, VERSION_CODE=$VERSION_CODE" + + - name: Setup Java + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Cache Gradle Wrapper + uses: actions/cache@v5 + with: + path: ~/.gradle/wrapper + key: gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + + - name: Accept Android SDK licenses + run: yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Decode Keystore + run: echo "${{ secrets.TONKEEPER_UPLOAD_STORE_FILE }}" | base64 -d > ${{ github.workspace }}/release.keystore + + - name: Build Site APK + run: | + ./gradlew :apps:wallet:instance:main:assembleSiteRelease \ + --no-daemon \ + --no-build-cache \ + --no-configuration-cache \ + -Pandroid.injected.signing.store.file=${{ github.workspace }}/release.keystore \ + -Pandroid.injected.signing.store.password=${{ secrets.TONKEEPER_UPLOAD_STORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.TONKEEPER_UPLOAD_KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.TONKEEPER_UPLOAD_KEY_PASSWORD }} + + - name: Prepare APK for release + run: | + mkdir -p release + APK=$(find apps/wallet/instance/main/build/outputs/apk/site/release -name "*.apk" | head -1) + cp "$APK" release/Tonkeeper.apk + + - name: Upload APK artifact + uses: actions/upload-artifact@v6 + with: + name: Tonkeeper-apk-${{ github.ref_name }} + path: release/Tonkeeper.apk + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ env.VERSION_NAME }} + generate_release_notes: true + files: release/Tonkeeper.apk diff --git a/.github/workflows/mirror-public-push.yml b/.github/workflows/mirror-public-push.yml new file mode 100644 index 000000000..6cc788e90 --- /dev/null +++ b/.github/workflows/mirror-public-push.yml @@ -0,0 +1,58 @@ +name: Mirror GitHub release tag + +on: + workflow_dispatch: + +jobs: + push: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Verify running on a tag + run: | + if [[ "${{ github.ref }}" != refs/tags/* ]]; then + echo "::error::This workflow must be run on a tag, not a branch. Select a tag in 'Use workflow from'." + exit 1 + fi + + - name: Checkout private repo + uses: actions/checkout@v6 + + - name: Configure deploy key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.PUBLIC_REPO_DEPLOY_KEY }} + + - name: Remove HTTPS override from actions/checkout + run: | + git config --global --unset-all url."https://github.com/".insteadOf || true + git config --global --unset-all http.https://github.com/.extraheader || true + + - name: Push squashed commit to public repo + env: + TAG: ${{ github.ref_name }} + run: | + git config --global user.name "mirror-bot" + git config --global user.email "mirror-bot@example.com" + + git remote add public git@github.com:tonkeeper/android.git + git fetch public main || true + + # Get the tree (snapshot) of the current tag + TREE=$(git log -1 --format='%T' HEAD) + + # Create a squashed commit on top of public's main history + if git rev-parse public/main >/dev/null 2>&1; then + COMMIT=$(git commit-tree "$TREE" -p public/main -m "Release $TAG") + else + # First push — no history yet + COMMIT=$(git commit-tree "$TREE" -m "Release $TAG") + fi + + # Push the commit to release branch and main + git push --force public "$COMMIT":refs/heads/release/"$TAG" + + # Tag the commit and push + git tag -f "$TAG" "$COMMIT" + git push --force public "refs/tags/$TAG" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1747e9ae6..a1f36ab16 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,29 +1,67 @@ -name: Publish tagged commit to public repo +name: Android Publish on: - push: - tags: - - '*' + workflow_dispatch: + inputs: + version_name: + description: 'Version name (e.g. 1.2.3). Required for manual runs.' + required: true + type: string jobs: - mirror: - runs-on: ubuntu-latest - permissions: - contents: read + android-build: + name: android-build + runs-on: macos-latest-xlarge + steps: - - name: Checkout private repo at tag - uses: actions/checkout@v4 + - name: Checkout to git repository + uses: actions/checkout@v6 + + - name: Set VERSION_NAME + run: echo "VERSION_NAME=${{ inputs.version_name || github.event.release.tag_name }}" >> $GITHUB_ENV + + - name: Set up ruby env + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + + - name: Setup Java + uses: actions/setup-java@v5 with: - fetch-depth: 0 + java-version: '21' + distribution: 'temurin' + cache: gradle - - name: Push tag to public repo + - name: Accept Android SDK licenses + run: yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + + - name: Decode signing certificate into a file env: - TOKEN: ${{ secrets.PUBLIC_REPO_TOKEN }} - TAG: ${{ github.ref_name }} + CERTIFICATE_BASE64: ${{ secrets.ANDROID_DIST_SIGNING_KEY }} run: | - git config --global user.name "mirror-bot" - git config --global user.email "mirror-bot@example.com" + echo $CERTIFICATE_BASE64 | base64 --decode > google-release.keystore - git remote add public https://$TOKEN@github.com/ORG/PUBLIC_REPO.git + - name: Build android beta + run: bundle exec fastlane android beta + env: + GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.caching=false -Dorg.gradle.configuration-cache=false" + KEYSTORE_FILE: ${{ github.workspace }}/google-release.keystore + KEYSTORE_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_STORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.TONKEEPER_UPLOAD_KEY_ALIAS}} + KEY_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_KEY_PASSWORD }} + ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} - git push --force --tags public $TAG \ No newline at end of file + - name: Upload android aab to artifacts + uses: actions/upload-artifact@v6 + with: + name: Tonkeeper aab ${{ env.VERSION_CODE }} + path: | + ${{ env.AAB_OUTPUT_PATH }} + + - name: Upload android apk to artifacts + uses: actions/upload-artifact@v6 + with: + name: Tonkeeper apk ${{ env.VERSION_CODE }} + path: | + ${{ env.APK_OUTPUT_PATH }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 193e23736..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Android APK release CD - -on: - push: - tags: - - 'v*' - -jobs: - build: - name: apk-release - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - with: { fetch-depth: 0 } - - - name: Set up ruby env - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.2 - bundler-cache: true - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 20.0.2+9 - - - name: Decode signing certificate into a file - env: - CERTIFICATE_BASE64: ${{ secrets.ANDROID_DIST_SIGNING_KEY }} - run: | - echo $CERTIFICATE_BASE64 | base64 --decode > google-release.keystore - - - name: Build android beta - run: bundle exec fastlane android beta - env: - KEYSTORE_FILE: ${{ github.workspace }}/google-release.keystore - KEYSTORE_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_STORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.TONKEEPER_UPLOAD_KEY_ALIAS}} - KEY_PASSWORD: ${{ secrets.TONKEEPER_UPLOAD_KEY_PASSWORD }} - ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} - - - name: Upload android aab to artifacts - uses: actions/upload-artifact@v4 - with: - name: Tonkeeper aab ${{ env.VERSION_CODE }} - path: | - ${{ env.AAB_OUTPUT_PATH }} - - - name: Upload android apk to artifacts - uses: actions/upload-artifact@v4 - with: - name: Tonkeeper apk ${{ env.VERSION_CODE }} - path: | - ${{ env.APK_OUTPUT_PATH }} - - - name: Fix filename - run: | - mkdir -p release - cp "$APK_OUTPUT_PATH" release/Tonkeeper.apk - - - uses: actions/upload-artifact@v4 - with: - name: apk - path: release/Tonkeeper.apk - - publish: - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/download-artifact@v4 - with: { name: apk } - - - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - files: Tonkeeper.apk \ No newline at end of file diff --git a/.github/workflows/uk.yml b/.github/workflows/uk.yml index afb0584b5..0b7948ca6 100644 --- a/.github/workflows/uk.yml +++ b/.github/workflows/uk.yml @@ -6,11 +6,11 @@ on: jobs: android-build: name: android-build - runs-on: macos-latest + runs-on: macos-latest-xlarge steps: - name: Checkout to git repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up ruby env uses: ruby/setup-ruby@v1 @@ -19,7 +19,7 @@ jobs: bundler-cache: true - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 20.0.2+9 @@ -40,14 +40,14 @@ jobs: ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} - name: Upload android aab to artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: Tonkeeper UK aab ${{ env.VERSION_CODE }} path: | ${{ env.AAB_OUTPUT_PATH }} - name: Upload android apk to artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: Tonkeeper UK apk ${{ env.VERSION_CODE }} path: | diff --git a/.gitignore b/.gitignore index 0e69164b8..61f46fbfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.iml .gradle local.properties -.idea +.idea/ .DS_Store captures .externalNativeBuild @@ -9,4 +9,6 @@ captures *.apk .kotlin /build - +**/build/ +.build-cache +tools/build/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 8f00030d5..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# GitHub Copilot persisted chat sessions -/copilot/chatSessions diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 72af3841f..000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -TON Apps \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 312bf2eab..000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index e922b6a83..000000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 8cfc672e9..000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index c224ad564..000000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 99b6bd53c..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/AGENTS.MD b/AGENTS.MD new file mode 100644 index 000000000..36ffaac12 --- /dev/null +++ b/AGENTS.MD @@ -0,0 +1,61 @@ +# Agent Guide + +This file helps AI agents navigate the project based on the task at hand. + +## Task-Based Navigation + +### Understanding Project Structure + +- [README.MD](README.MD) — project overview, module map, tech stack, build instructions +- [settings.gradle.kts](settings.gradle.kts) — all modules and their include paths +- [gradle/libs.versions.toml](gradle/libs.versions.toml) — centralized dependency versions +- [kmp/mvi/README.MD](kmp/mvi/README.MD) — MVI architecture guide with examples + +### Build Configuration + +- [build.gradle.kts](build.gradle.kts) — root build config, signing, git hooks +- [gradle.properties](gradle.properties) — JVM args, Kotlin settings, caching +- [apps/wallet/instance/main/build.gradle.kts](apps/wallet/instance/main/build.gradle.kts) — main + app build (flavors, signing, R8) +- [apps/wallet/instance/main/proguard-rules.pro](apps/wallet/instance/main/proguard-rules.pro) — + ProGuard/R8 rules + +### Networking & API + +- [tonapi/](tonapi/) — TON API client (core, tonkeeper, battery, legacy) +- [lib/network/](lib/network/) — OkHttp/SSE networking utilities +- [apps/wallet/api/](apps/wallet/api/) — wallet API layer + +### Security & Crypto + +- [lib/security/](lib/security/) — encryption, vault, KeyStore +- [lib/blockchain/](lib/blockchain/) — TON blockchain operations + +### KMP Modules + +- [kmp/mvi/](kmp/mvi/) — MVI architecture framework +- [kmp/async/](kmp/async/) — coroutine scope management (Async.Io, Async.stateDispatcher) +- [kmp/ui/](kmp/ui/) — Compose Multiplatform UI components +- [kmp/core/](kmp/core/) — platform abstractions + +### Threading Model + +- [kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/AsyncViewModel.kt](kmp/mvi/src/commonMain/kotlin/com/tonapps/mvi/AsyncViewModel.kt) — + bgScope, stateScope, mainScope +- [kmp/async/src/commonMain/kotlin/com/tonapps/async/Async.kt](kmp/async/src/commonMain/kotlin/com/tonapps/async/Async.kt) — + dispatcher definitions + +### Code Quality & CI + +- [detekt/detekt.yml](detekt/detekt.yml) — Detekt static analysis rules +- [tools/hooks/](tools/hooks/) — Git pre-commit hooks +- [.github/workflows/](/.github/workflows/) — CI/CD workflows (debug, ci, release, baselineprofile) + +### Localization + +- [apps/wallet/localization/](apps/wallet/localization/) — i18n string resources + +### API Code Generation + +- [tools/scripts/generators/tonapi/](tools/scripts/generators/tonapi/) — TON API client generation +- [tools/scripts/generators/battery/](tools/scripts/generators/battery/) — Battery API generation diff --git a/README.md b/README.md index 41d247ebc..e8b3c7b65 100644 --- a/README.md +++ b/README.md @@ -1 +1,211 @@ -# tonkeeper +# Tonkeeper + +A production-grade Android wallet application for TON blockchain, built with Kotlin and Jetpack +Compose. + +## Project Overview + +Tonkeeper is a multi-app Android project consisting of: + +- **Tonkeeper Wallet** — main cryptocurrency wallet application +- **Tonkeeper Signer** — companion offline signer application +- Multiple shared libraries and Kotlin Multiplatform modules + +## Project Structure + +``` +android_private/ +├── apps/ # Applications +│ ├── wallet/ # Main wallet app +│ │ ├── instance/ +│ │ │ ├── app/ # Core wallet (com.tonapps.tonkeeperx) +│ │ │ └── main/ # Main release (com.ton_keeper) +│ │ ├── data/ # Data layer (18 modules) +│ │ │ ├── core/ # Core data infrastructure +│ │ │ ├── account/ # Account management +│ │ │ ├── tokens/ # Token management +│ │ │ ├── settings/ # User settings +│ │ │ ├── rates/ # Exchange rates +│ │ │ ├── events/ # Transaction history +│ │ │ ├── collectibles/ # NFT support +│ │ │ ├── browser/ # DApp browser +│ │ │ ├── backup/ # Backup/restore +│ │ │ ├── passcode/ # Biometric/PIN +│ │ │ ├── staking/ # Staking operations +│ │ │ ├── purchase/ # In-app purchases +│ │ │ ├── battery/ # Battery/status +│ │ │ ├── dapps/ # DApp connections +│ │ │ ├── contacts/ # Address book +│ │ │ ├── swap/ # Token swap +│ │ │ ├── plugins/ # Plugin system +│ │ │ └── rn/ # React Native bridge +│ │ ├── features/ # Feature modules +│ │ ├── api/ # API layer +│ │ └── localization/ # i18n/localization +│ └── signer/ # Signer app (com.tonapps.signer) +│ +├── kmp/ # Kotlin Multiplatform modules +│ ├── mvi/ # MVI architecture framework +│ ├── async/ # Coroutine scope management +│ ├── ui/ # Compose Multiplatform UI +│ └── core/ # Platform abstractions +│ +├── lib/ # Shared libraries +│ ├── extensions/ # Kotlin extensions +│ ├── security/ # Crypto, vault, KeyStore +│ ├── network/ # OkHttp/SSE networking +│ ├── blockchain/ # TON blockchain operations +│ ├── sqlite/ # Database layer +│ ├── qr/ # QR code handling +│ ├── ledger/ # Ledger hardware wallet +│ ├── log/ # Logging +│ ├── icu/ # ICU internationalization +│ ├── emoji/ # Emoji handling +│ ├── base64/ # Base64 encoding +│ ├── bus/ # Event bus +│ └── ur/ # Uniform Resources +│ +├── ui/ # UI modules +│ ├── uikit/ +│ │ ├── core/ # BaseFragment, Navigation, components +│ │ ├── color/ # Color themes +│ │ ├── icon/ # Icon set +│ │ ├── list/ # List components +│ │ └── flag/ # Flag components +│ ├── blur/ # Blur effects +│ └── shimmer/ # Shimmer animations +│ +├── tonapi/ # TON API client library +│ ├── core/ # Core API client +│ ├── tonkeeper/ # Tonkeeper-specific API +│ ├── battery/ # Battery API +│ └── legacy/ # Legacy API +│ +├── buildLogic/ # Custom Gradle plugins +├── baselineprofile/ # Performance optimization profiles +├── detekt/ # Code quality configuration +├── fastlane/ # CI/CD automation +└── tools/ # Build tools and scripts + ├── hooks/ # Git pre-commit hooks + └── scripts/ # Code generators (tonapi, battery) +``` + +## Tech Stack + +| Category | Technologies | +|---------------|---------------------------------------------------------------| +| Language | Kotlin, KMP (Kotlin Multiplatform) | +| UI | Jetpack Compose, Compose Multiplatform, Material 3 | +| Architecture | MVI (custom KMP implementation) | +| DI | Koin | +| Async | Kotlin Coroutines, Flow | +| Networking | OkHttp (Cronet), SSE | +| Serialization | Kotlin Serialization | +| Database | SQLite (bundled) | +| Blockchain | ton-kotlin (tvm, crypto, tlb, contract), Web3j, Bouncy Castle | +| Firebase | Analytics, Crashlytics, Messaging, Performance, Remote Config | +| Image Loading | Coil | +| Code Quality | Detekt, R8/ProGuard, Baseline Profiles | +| CI/CD | GitHub Actions, Fastlane | + +## Build Configuration + +**Requirements:** JDK 21 (Temurin/Zulu), Android SDK (API 36), 6 GB RAM + +### Build Flavors + +| Flavor | Description | +|-----------|--------------------------| +| `default` | Standard build | +| `site` | Web distribution variant | + +### Build Types + +| Type | Minification | ABIs | +|-----------|--------------|-------------------------------------| +| `debug` | No | arm64-v8a | +| `beta` | Yes | arm64-v8a, x86_64 | +| `release` | Yes | arm64-v8a, armeabi-v7a, x86, x86_64 | + +### Building + +```bash +# Wallet debug +./gradlew :apps:wallet:instance:main:assembleDefaultDebug + +# Wallet release +./gradlew :apps:wallet:instance:main:assembleDefaultRelease + +# Signer +./gradlew :apps:signer:assembleDebug +``` + +### Signing Configuration + +The project supports flexible signing configuration for both debug and release builds: + +#### Release Builds + +Release builds use injected signing properties for CI/CD: + +```properties +android.injected.signing.store.file= +android.injected.signing.store.password= +android.injected.signing.key.alias= +android.injected.signing.key.password= +``` + +#### Debug Builds + +Debug builds support two signing methods: + +1. **Injected Properties** (for CI/CD): + +```properties +android.injected.signing.debug.file= +android.injected.signing.debug.password= +android.injected.signing.debug.key.alias= +android.injected.signing.debug.key.password= +``` + +2. **Default Debug Keystore** (local development): + - File: [debug.keystore](debug.keystore) (in project root) + - Store Password: `android` + - Key Alias: `androiddebugkey` + - Key Password: `android` + +The build system automatically falls back to the default debug keystore if no injected properties +are provided. + +## Architecture + +The project uses a custom **MVI (Model-View-Intent)** architecture built as a KMP module. +See [kmp/mvi/README.md](kmp/mvi/README.md) for detailed documentation. + +Key patterns: + +- **Unidirectional data flow:** Action → State → ViewState → Compose +- **Repository pattern:** Data access abstracted through 18 focused data modules +- **Koin DI:** ViewModels and repositories registered in module-level Koin modules +- **Thread safety:** Dedicated dispatchers for state mutations, computation, IO + +## CI/CD + +GitHub Actions workflows in `.github/workflows/`: + +| Workflow | Trigger | Purpose | +|-----------------------|----------------|--------------------------------| +| `debug.yml` | Push to `dev` | Dev builds | +| `ci.yml` | Tagged commits | Release builds | +| `baselineprofile.yml` | Manual | Performance profile generation | +| `publish.yml` | Manual | Mirror to public repository | + +## Key Configuration Files + +| File | Purpose | +|-----------------------------|---------------------------------------------| +| `settings.gradle.kts` | Module definitions | +| `build.gradle.kts` | Global plugins, signing, git hooks | +| `gradle.properties` | JVM args (-Xmx6g), caching, Kotlin settings | +| `gradle/libs.versions.toml` | Centralized dependency versions | +| `detekt/detekt.yml` | Static analysis rules | diff --git a/apps/signer/build.gradle.kts b/apps/signer/build.gradle.kts index 97604812e..6bba6a822 100644 --- a/apps/signer/build.gradle.kts +++ b/apps/signer/build.gradle.kts @@ -1,18 +1,14 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") + id("target.android.app") + alias(libs.plugins.kotlin.compose) } android { - namespace = Build.namespacePrefix("signer") - compileSdk = Build.compileSdkVersion - + namespace = "com.tonapps.signer" defaultConfig { - applicationId = Build.namespacePrefix("signer") + applicationId = "com.tonapps.signer" minSdk = 26 - targetSdk = Build.compileSdkVersion versionCode = 23 versionName = "0.2.3" } @@ -51,36 +47,36 @@ android { } dependencies { - implementation(libs.androidX.core) - implementation(libs.androidX.appCompat) - implementation(libs.androidX.activity) - implementation(libs.androidX.fragment) - implementation(libs.androidX.recyclerView) - implementation(libs.androidX.viewPager2) - implementation(libs.androidX.splashscreen) + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity) + implementation(libs.androidx.fragment) + implementation(libs.androidx.recyclerView) + implementation(libs.androidx.viewPager2) + implementation(libs.androidx.splashscreen) implementation(libs.material) implementation(libs.flexbox) - implementation(libs.cameraX.base) - implementation(libs.cameraX.core) - implementation(libs.cameraX.lifecycle) - implementation(libs.cameraX.view) - implementation(libs.androidX.security) - implementation(libs.androidX.constraintlayout) - implementation(libs.androidX.lifecycleSavedState) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) + implementation(libs.camerax.base) + implementation(libs.camerax.core) + implementation(libs.camerax.lifecycle) + implementation(libs.camerax.view) + implementation(libs.androidx.security) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.lifecycleSavedState) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) - implementation(libs.kotlinX.coroutines.guava) + implementation(libs.kotlinx.coroutines.core) - implementation(project(ProjectModules.UIKit.core)) { + implementation(projects.ui.uikit.core) { exclude("com.airbnb.android", "lottie") exclude("com.facebook.fresco", "fresco") } - implementation(project(ProjectModules.Lib.qr)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.icu)) + implementation(projects.lib.qr) + implementation(projects.lib.security) + implementation(projects.lib.icu) implementation(libs.koin.core) } diff --git a/apps/signer/proguard-rules.pro b/apps/signer/proguard-rules.pro index 71a6747bd..2d84b92b6 100644 --- a/apps/signer/proguard-rules.pro +++ b/apps/signer/proguard-rules.pro @@ -12,4 +12,14 @@ -keep class com.fasterxml.jackson.databind.ext.** { *; } -dontwarn org.slf4j.** -dontwarn org.w3c.dom.** --dontwarn com.fasterxml.jackson.databind.ext.DOMSerializer \ No newline at end of file +-dontwarn com.fasterxml.jackson.databind.ext.DOMSerializer + +# Strip all Android logging for security and performance +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int i(...); + public static int w(...); + public static int d(...); + public static int e(...); +} \ No newline at end of file diff --git a/apps/signer/src/main/java/com/tonapps/signer/App.kt b/apps/signer/src/main/java/com/tonapps/signer/App.kt index 2fd862965..0b8e6901d 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/App.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/App.kt @@ -1,7 +1,6 @@ package com.tonapps.signer import android.app.Application -import android.content.Intent import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.camera.camera2.Camera2Config @@ -11,7 +10,7 @@ import com.tonapps.signer.screen.crash.CrashActivity import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -class App: Application(), CameraXConfig.Provider { +class App : Application(), CameraXConfig.Provider { companion object { lateinit var instance: App @@ -25,7 +24,7 @@ class App: Application(), CameraXConfig.Provider { } AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) - startKoin{ + startKoin { androidContext(this@App) modules(koinModel) } @@ -37,4 +36,4 @@ class App: Application(), CameraXConfig.Provider { .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) .setMinimumLoggingLevel(Log.ERROR).build() } -} \ No newline at end of file +} diff --git a/apps/signer/src/main/java/com/tonapps/signer/core/source/BackupSource.kt b/apps/signer/src/main/java/com/tonapps/signer/core/source/BackupSource.kt index b3a42c620..0fef1986b 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/core/source/BackupSource.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/core/source/BackupSource.kt @@ -1,7 +1,6 @@ package com.tonapps.signer.core.source import android.content.Context -import android.util.Log import com.tonapps.signer.core.entities.KeyEntity import org.json.JSONArray import org.json.JSONObject diff --git a/apps/signer/src/main/java/com/tonapps/signer/deeplink/TKDeepLink.kt b/apps/signer/src/main/java/com/tonapps/signer/deeplink/TKDeepLink.kt index c31942b23..3160858a5 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/deeplink/TKDeepLink.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/deeplink/TKDeepLink.kt @@ -3,7 +3,7 @@ package com.tonapps.signer.deeplink import android.content.Context import android.content.Intent import android.net.Uri -import android.util.Log +import com.tonapps.log.L import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.blockchain.ton.extensions.hex import com.tonapps.security.hex diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/create/child/CreateNameFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/create/child/CreateNameFragment.kt index d592e7e6d..f48f3a3a5 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/screen/create/child/CreateNameFragment.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/screen/create/child/CreateNameFragment.kt @@ -1,7 +1,7 @@ package com.tonapps.signer.screen.create.child import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.widget.Button import androidx.lifecycle.lifecycleScope diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/emulate/EmulateFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/emulate/EmulateFragment.kt index 76b2894ac..e2f559c51 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/screen/emulate/EmulateFragment.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/screen/emulate/EmulateFragment.kt @@ -1,7 +1,7 @@ package com.tonapps.signer.screen.emulate import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.widget.Button import com.tonapps.qr.ui.QRView diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/name/NameFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/name/NameFragment.kt index 24479395f..6f3a99d68 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/screen/name/NameFragment.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/screen/name/NameFragment.kt @@ -1,7 +1,7 @@ package com.tonapps.signer.screen.name import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.inputmethod.EditorInfo import android.widget.Button diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/root/RootActivity.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/root/RootActivity.kt index ea978bd0f..81f6f9cf9 100644 --- a/apps/signer/src/main/java/com/tonapps/signer/screen/root/RootActivity.kt +++ b/apps/signer/src/main/java/com/tonapps/signer/screen/root/RootActivity.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.WindowManager import androidx.appcompat.widget.AppCompatImageView diff --git a/apps/wallet/api/build.gradle.kts b/apps/wallet/api/build.gradle.kts index 1eadc13f7..441e8d666 100644 --- a/apps/wallet/api/build.gradle.kts +++ b/apps/wallet/api/build.gradle.kts @@ -1,38 +1,39 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("target.android.library") id("com.google.devtools.ksp") id("kotlin-parcelize") kotlin("plugin.serialization") } android { - namespace = Build.namespacePrefix("wallet.api") - compileSdk = Build.compileSdkVersion - - defaultConfig { - minSdk = Build.minSdkVersion - consumerProguardFiles("consumer-rules.pro") + buildFeatures { + buildConfig = true } } dependencies { - implementation(libs.kotlinX.serialization.core) - implementation(libs.kotlinX.serialization.json) - implementation(libs.kotlinX.coroutines.guava) +// configurations.all { +// exclude(mapOf("group" to "org.chromium.net", "module" to "cronet-shared")) +// } +// implementation(libs.cronet.okhttp) +// implementation(libs.google.play.cronet) + + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.core) implementation(libs.koin.core) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) - implementation(libs.google.play.cronet) implementation(libs.okhttp) implementation(libs.okhttp.sse) + implementation(libs.koin.core) + + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) - implementation(libs.androidX.room.runtime) - implementation(libs.androidX.room.ktx) - ksp(libs.androidX.room.compiler) + implementation(projects.tonapi.legacy) + implementation(projects.lib.network) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) + implementation(projects.lib.log) } 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 e10e6dece..83da858b2 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 @@ -3,6 +3,7 @@ package com.tonapps.wallet.api import android.content.Context import android.net.Uri import androidx.collection.ArrayMap +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.contract.BaseWalletContract import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 @@ -13,6 +14,7 @@ import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.extensions.map import com.tonapps.extensions.toUriOrNull import com.tonapps.icu.Coins +import com.tonapps.log.L import com.tonapps.network.SSEvent import com.tonapps.network.execute import com.tonapps.network.get @@ -25,6 +27,7 @@ import com.tonapps.wallet.api.entity.AccountEventEntity import com.tonapps.wallet.api.entity.BalanceEntity import com.tonapps.wallet.api.entity.ChartEntity import com.tonapps.wallet.api.entity.ConfigEntity +import com.tonapps.wallet.api.entity.EmulateWithBatteryResult import com.tonapps.wallet.api.entity.EthenaEntity import com.tonapps.wallet.api.entity.OnRampArgsEntity import com.tonapps.wallet.api.entity.OnRampMerchantEntity @@ -57,14 +60,19 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import okhttp3.internal.toLongOrDefault import org.json.JSONArray import org.json.JSONObject import org.ton.api.pub.PublicKeyEd25519 @@ -81,29 +89,64 @@ class API( private val swapApi = SwapApi(defaultHttpClient) private val configRepository = ConfigRepository(context, scope, internalApi) - val config: ConfigEntity - get() = configRepository.configEntity - val configFlow: Flow get() = configRepository.stream + private val mainnetConfig: ConfigEntity + get() = getConfig(TonNetwork.MAINNET) + private val tonAPIHttpClient: OkHttpClient by lazy { - tonAPIHttpClient { config } + tonAPIHttpClient { getConfig(TonNetwork.MAINNET) } } private val bridgeUrl: String - get() = "${config.tonConnectBridgeHost}/bridge" + get() = "${mainnetConfig.tonConnectBridgeHost}/bridge" val country: String get() = internalApi.country + private fun buildProvider() = Provider( + mainnetHost = getConfig(TonNetwork.MAINNET).tonapiMainnetHost, + testnetHost = getConfig(TonNetwork.TESTNET).tonapiTestnetHost, + tetraHost = getConfig(TonNetwork.TETRA).tonapiMainnetHost, + okHttpClient = tonAPIHttpClient + ) + + private fun buildBatteryProvider() = BatteryProvider( + mainnetHost = getConfig(TonNetwork.MAINNET).batteryHost, + testnetHost = getConfig(TonNetwork.TESTNET).batteryTestnetHost, + tetraHost = getConfig(TonNetwork.TETRA).batteryHost, + okHttpClient = tonAPIHttpClient + ) + + //TODO TK-371 + private val _providerState: StateFlow = configRepository.stream + .map { buildProvider() } + .stateIn(scope, SharingStarted.Eagerly, buildProvider()) + + private val _batteryProviderState: StateFlow = configRepository.stream + .map { buildBatteryProvider() } + .stateIn(scope, SharingStarted.Eagerly, buildBatteryProvider()) + + private val provider: Provider + get() = _providerState.value + private val batteryProvider: BatteryProvider + get() = _batteryProviderState.value + + val tron: TronApi by lazy { + TronApi(mainnetConfig, defaultHttpClient, batteryProvider.default.get(TonNetwork.MAINNET)) + } + + fun getConfig(network: TonNetwork) = configRepository.getConfig(network) + fun setCountry(deviceCountry: String, storeCountry: String?) = internalApi.setCountry(deviceCountry, storeCountry) suspend fun initConfig() = configRepository.initConfig() suspend fun tonapiFetch( url: String, - options: String + options: String, + network: TonNetwork ): Response = withContext(Dispatchers.IO) { val uri = url.toUriOrNull() ?: throw Exception("Invalid URL") if (uri.scheme != "https") { @@ -132,7 +175,7 @@ class API( builder.addHeader(key, value) } } - builder.addHeader("Authorization", "Bearer ${config.tonApiV2Key}") + builder.addHeader("Authorization", "Bearer ${getConfig(network).tonApiV2Key}") if (methodOptions.equals("POST", ignoreCase = true)) { builder.post(bodyOptions.toRequestBody(contentTypeOptions.toMediaType())) @@ -141,65 +184,54 @@ class API( tonAPIHttpClient.newCall(builder.build()).execute() } - private val provider: Provider by lazy { - Provider(config.tonapiMainnetHost, config.tonapiTestnetHost, tonAPIHttpClient) - } - - private val batteryProvider: BatteryProvider by lazy { - BatteryProvider(config.batteryHost, config.batteryTestnetHost, tonAPIHttpClient) - } - - val tron: TronApi by lazy { - TronApi(config, defaultHttpClient, batteryProvider.default.get(false)) - } - - fun accounts(testnet: Boolean) = provider.accounts.get(testnet) + fun accounts(network: TonNetwork) = provider.accounts.get(network) - fun jettons(testnet: Boolean) = provider.jettons.get(testnet) + fun jettons(network: TonNetwork) = provider.jettons.get(network) - fun wallet(testnet: Boolean) = provider.wallet.get(testnet) + fun wallet(network: TonNetwork) = provider.wallet.get(network) - fun nft(testnet: Boolean) = provider.nft.get(testnet) + fun nft(network: TonNetwork) = provider.nft.get(network) - fun blockchain(testnet: Boolean) = provider.blockchain.get(testnet) + fun blockchain(network: TonNetwork) = provider.blockchain.get(network) - fun emulation(testnet: Boolean) = provider.emulation.get(testnet) + fun emulation(network: TonNetwork) = provider.emulation.get(network) - fun liteServer(testnet: Boolean) = provider.liteServer.get(testnet) + fun liteServer(network: TonNetwork) = provider.liteServer.get(network) - fun staking(testnet: Boolean) = provider.staking.get(testnet) + fun staking(network: TonNetwork) = provider.staking.get(network) - fun events(testnet: Boolean) = provider.events.get(testnet) + fun events(network: TonNetwork) = provider.events.get(network) - fun rates() = provider.rates.get(false) + fun rates(network: TonNetwork) = provider.rates.get(network) - fun battery(testnet: Boolean) = batteryProvider.default.get(testnet) + fun battery(network: TonNetwork) = batteryProvider.default.get(network) - fun batteryWallet(testnet: Boolean) = batteryProvider.wallet.get(testnet) + fun batteryWallet(network: TonNetwork) = batteryProvider.wallet.get(network) - fun batteryEmulation(testnet: Boolean) = batteryProvider.emulation.get(testnet) + fun batteryEmulation(network: TonNetwork) = batteryProvider.emulation.get(network) - fun getBatteryConfig(testnet: Boolean): Config? { - return withRetry { battery(testnet).getConfig() } + fun getBatteryConfig(network: TonNetwork): Config? { + return withRetry { battery(network).getConfig() } } - fun getBatteryRechargeMethods(testnet: Boolean): RechargeMethods? { - return withRetry { battery(testnet).getRechargeMethods(false) } + fun getBatteryRechargeMethods(network: TonNetwork): RechargeMethods? { + return withRetry { battery(network).getRechargeMethods(false) } } - fun getOnRampData() = internalApi.getOnRampData(config.webSwapsUrl) + fun getOnRampData() = internalApi.getOnRampData(mainnetConfig.webSwapsUrl) - fun getOnRampPaymentMethods(currency: String) = internalApi.getOnRampPaymentMethods(config.webSwapsUrl, currency) + fun getOnRampPaymentMethods(currency: String) = + internalApi.getOnRampPaymentMethods(mainnetConfig.webSwapsUrl, currency) - fun getOnRampMerchants() = internalApi.getOnRampMerchants(config.webSwapsUrl) + fun getOnRampMerchants() = internalApi.getOnRampMerchants(mainnetConfig.webSwapsUrl) fun getSwapAssets(): JSONArray = runCatching { - swapApi.getSwapAssets(config.webSwapsUrl)?.let(::JSONArray) + swapApi.getSwapAssets(mainnetConfig.webSwapsUrl)?.let(::JSONArray) }.getOrNull() ?: JSONArray() @Throws suspend fun calculateOnRamp(args: OnRampArgsEntity): OnRampMerchantEntity.Data = withContext(Dispatchers.IO) { - val data = internalApi.calculateOnRamp(config.webSwapsUrl, args) ?: throw Exception("Empty response") + val data = internalApi.calculateOnRamp(mainnetConfig.webSwapsUrl, args) ?: throw Exception("Empty response") val json = JSONObject(data) val items = json.getJSONArray("items").map { OnRampMerchantEntity(it) } val suggested = json.optJSONArray("suggested")?.map { OnRampMerchantEntity(it) } ?: emptyList() @@ -215,20 +247,20 @@ class API( fun getBatteryBalance( tonProofToken: String, - testnet: Boolean, + network: TonNetwork, units: DefaultApi.UnitsGetBalance = DefaultApi.UnitsGetBalance.ton ): Balance? { - return withRetry { battery(testnet).getBalance(tonProofToken, units, region = config.region) } + return withRetry { battery(network).getBalance(tonProofToken, units, region = getConfig(network).region) } } fun getAlertNotifications() = withRetry { internalApi.getNotifications() } ?: emptyList() - private fun isOkStatus(testnet: Boolean): Boolean { + private fun isOkStatus(network: TonNetwork): Boolean { try { val status = withRetry { - provider.utilities.get(testnet).status() + provider.utilities.get(network).status() } ?: return false if (!status.restOnline) { return false @@ -244,24 +276,28 @@ class API( fun realtime( accountId: String, - testnet: Boolean, + network: TonNetwork, config: ConfigEntity, onFailure: ((Throwable) -> Unit)? ): Flow { - val endpoint = if (testnet) config.tonapiSSETestnetEndpoint else config.tonapiSSEEndpoint + val endpoint = when (network) { + TonNetwork.TESTNET -> config.tonapiSSETestnetEndpoint + TonNetwork.MAINNET -> config.tonapiSSEEndpoint + TonNetwork.TETRA -> config.tonapiSSEEndpoint + } val url = "$endpoint/sse/traces?account=$accountId&token=${config.tonApiV2Key}" return seeHttpClient.sse(url, onFailure = onFailure) } - suspend fun refreshConfig(testnet: Boolean) { - configRepository.refresh(testnet) + suspend fun refreshConfig() { + configRepository.refresh() } fun swapStream( from: SwapAssetParam, to: SwapAssetParam, userAddress: String - ) = swapApi.stream(config.webSwapsUrl, from, to, userAddress) + ) = swapApi.stream(mainnetConfig.webSwapsUrl, from, to, userAddress) suspend fun getPageTitle(url: String): String = withContext(Dispatchers.IO) { try { @@ -271,21 +307,27 @@ class API( val html = defaultHttpClient.get(url, headers) val ogTitle = Regex("""]*>(.*?)""", RegexOption.DOT_MATCHES_ALL) - .find(html)?.groupValues?.get(1) + .find(html) + ?.groupValues + ?.get(1) title?.trim() ?: "" } catch (e: Throwable) { @@ -300,25 +342,25 @@ class API( return defaultHttpClient.get(url, headers) } - fun getBurnAddress() = config.burnZeroDomain.ifBlank { + fun getBurnAddress() = mainnetConfig.burnZeroDomain.ifBlank { "UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ" } suspend fun getDnsExpiring( accountId: String, - testnet: Boolean, + network: TonNetwork, period: Int ) = withContext(Dispatchers.IO) { - withRetry { accounts(testnet).getAccountDnsExpiring(accountId, period).items } ?: emptyList() + withRetry { accounts(network).getAccountDnsExpiring(accountId, period).items } ?: emptyList() } fun getEvents( accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null, limit: Int = 20 ): AccountEvents? = withRetry { - accounts(testnet).getAccountEvents( + accounts(network).getAccountEvents( accountId = accountId, limit = limit, beforeLt = beforeLt, @@ -328,14 +370,14 @@ class API( fun fetchTonEvents( accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null, beforeTimestamp: Timestamp? = null, afterTimestamp: Timestamp? = null, limit: Int, ): List { val response = withRetry { - accounts(testnet).getAccountEvents( + accounts(network).getAccountEvents( accountId = accountId, beforeLt = beforeLt, endDate = beforeTimestamp?.seconds(), @@ -347,7 +389,7 @@ class API( return response.events } - fun fetchTronTransactions( + suspend fun fetchTronTransactions( tronAddress: String, tonProofToken: String, beforeTimestamp: Timestamp? = null, @@ -357,13 +399,13 @@ class API( suspend fun getTransactionByHash( accountId: String, - testnet: Boolean, + network: TonNetwork, hash: String, attempt: Int = 0 ): AccountEventEntity? { try { - val body = accounts(testnet).getAccountEvent(accountId, hash) - return AccountEventEntity(accountId, testnet, hash, body) + val body = accounts(network).getAccountEvent(accountId, hash) + return AccountEventEntity(accountId, network.isTestnet, hash, body) } catch (e: Throwable) { if (attempt >= 10 || e is CancellationException) { return null @@ -372,15 +414,15 @@ class API( } else { delay(1000) } - return getTransactionByHash(accountId, testnet, hash, attempt + 1) + return getTransactionByHash(accountId, network, hash, attempt + 1) } } suspend fun getSingleEvent( eventId: String, - testnet: Boolean + network: TonNetwork ): List? = withContext(Dispatchers.IO) { - val event = withRetry { events(testnet).getEvent(eventId) } ?: return@withContext null + val event = withRetry { events(network).getEvent(eventId) } ?: return@withContext null val accountEvent = AccountEvent( eventId = eventId, account = AccountAddress( @@ -402,11 +444,11 @@ class API( fun getTokenEvents( tokenAddress: String, accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null, limit: Int = 10 ): AccountEvents { - return accounts(testnet).getAccountJettonHistoryByID( + return accounts(network).getAccountJettonHistoryByID( jettonId = tokenAddress, accountId = accountId, limit = limit, @@ -416,10 +458,10 @@ class API( fun getTonBalance( accountId: String, - testnet: Boolean, + network: TonNetwork, currency: String, ): BalanceEntity? { - val account = getAccount(accountId, testnet, currency) ?: return null + val account = getAccount(accountId, network, currency) ?: return null val initializedAccount = account.status != AccountStatus.uninit && account.status != AccountStatus.nonexist return BalanceEntity( @@ -435,9 +477,9 @@ class API( fun getJetton( accountId: String, - testnet: Boolean + network: TonNetwork ): TokenEntity? { - val jettonsAPI = jettons(testnet) + val jettonsAPI = jettons(network) val jetton = withRetry { jettonsAPI.getJettonInfo(accountId) } ?: return null @@ -446,10 +488,10 @@ class API( fun getJettonCustomPayload( accountId: String, - testnet: Boolean, + network: TonNetwork, jettonId: String ): TokenEntity.TransferPayload? { - val jettonsAPI = jettons(testnet) + val jettonsAPI = jettons(network) val payload = withRetry { jettonsAPI.getJettonTransferPayload(accountId, jettonId) } ?: return null @@ -458,12 +500,12 @@ class API( fun getJettonsBalances( accountId: String, - testnet: Boolean, + network: TonNetwork, currency: String? = null, extensions: List? = null ): List? { val jettonsBalances = withRetry { - accounts(testnet).getAccountJettonsBalances( + accounts(network).getAccountJettonsBalances( accountId = accountId, currencies = currency?.let { listOf(it) }, supportedExtensions = extensions, @@ -474,16 +516,16 @@ class API( fun resolveAddressOrName( query: String, - testnet: Boolean + network: TonNetwork ): AccountDetailsEntity? { return try { - val account = getAccount(query, testnet, null) ?: return null - val details = AccountDetailsEntity(query, account, testnet) + val account = getAccount(query, network, null) ?: return null + val details = AccountDetailsEntity(query, account, network) if (details.walletVersion != WalletVersion.UNKNOWN) { details } else { details.copy( - walletVersion = getWalletVersionByAddress(account.address, testnet) + walletVersion = getWalletVersionByAddress(account.address, network) ) } } catch (e: Throwable) { @@ -491,31 +533,31 @@ class API( } } - private fun getWalletVersionByAddress(address: String, testnet: Boolean): WalletVersion { - val pk = getPublicKey(address, testnet) ?: return WalletVersion.UNKNOWN - return BaseWalletContract.resolveVersion(pk, address.toRawAddress(), testnet) + private fun getWalletVersionByAddress(address: String, network: TonNetwork): WalletVersion { + val pk = getPublicKey(address, network) ?: return WalletVersion.UNKNOWN + return BaseWalletContract.resolveVersion(pk, address.toRawAddress(), network) } fun resolvePublicKey( pk: PublicKeyEd25519, - testnet: Boolean + network: TonNetwork ): List { return try { val query = pk.hex() val wallets = withRetry { - wallet(testnet).getWalletsByPublicKey(query).accounts + wallet(network).getWalletsByPublicKey(query).accounts } ?: return emptyList() wallets.map { AccountDetailsEntity( query = query, wallet = it, - testnet = testnet + network = network ) }.map { if (it.walletVersion == WalletVersion.UNKNOWN) { it.copy( walletVersion = BaseWalletContract.resolveVersion( pk, it.address.toRawAddress(), - testnet + network ) ) } else { @@ -527,36 +569,36 @@ class API( } } - fun getRates(currency: String, tokens: List): Map? { + fun getRates(network: TonNetwork, currency: String, tokens: List): Map? { val currencies = listOf(currency, "TON") return withRetry { - rates().getRates( + rates(network).getRates( tokens = tokens, currencies = currencies ).rates } } - fun getRates(from: String, to: String): Map? { + fun getRates(network: TonNetwork, from: String, to: String): Map? { return withRetry { - rates().getRates( + rates(network).getRates( tokens = listOf(from), currencies = listOf(to) ).rates } } - fun getNft(address: String, testnet: Boolean): NftItem? { - return withRetry { nft(testnet).getNftItemByAddress(address) } + fun getNft(address: String, network: TonNetwork): NftItem? { + return withRetry { nft(network).getNftItemByAddress(address) } } fun getNftItems( address: String, - testnet: Boolean, + network: TonNetwork, limit: Int = 1000 ): List? { return withRetry { - accounts(testnet).getAccountNftItems( + accounts(network).getAccountNftItems( accountId = address, limit = limit, indirectOwnership = true, @@ -566,18 +608,18 @@ class API( private fun getPublicKey( accountId: String, - testnet: Boolean + network: TonNetwork ): PublicKeyEd25519? { val hex = withRetry { - accounts(testnet).getAccountPublicKey(accountId) + accounts(network).getAccountPublicKey(accountId) }?.publicKey ?: return null return PublicKeyEd25519(hex(hex)) } fun safeGetPublicKey( accountId: String, - testnet: Boolean - ) = getPublicKey(accountId, testnet) ?: EmptyPrivateKeyEd25519.publicKey() + network: TonNetwork + ) = getPublicKey(accountId, network) ?: EmptyPrivateKeyEd25519.publicKey() fun tonconnectEvents( publicKeys: List, @@ -594,7 +636,7 @@ class API( fun tonconnectPayload(): String? { try { - val url = "${config.tonapiMainnetHost}/v2/tonconnect/payload" + val url = "${mainnetConfig.tonapiMainnetHost}/v2/tonconnect/payload" val json = withRetry { JSONObject(tonAPIHttpClient.get(url)) } ?: return null @@ -604,10 +646,10 @@ class API( } } - suspend fun batteryVerifyPurchasePromo(testnet: Boolean, code: String): Boolean = + suspend fun batteryVerifyPurchasePromo(network: TonNetwork, code: String): Boolean = withContext(Dispatchers.IO) { try { - battery(testnet).verifyPurchasePromo(code) + battery(network).verifyPurchasePromo(code) true } catch (e: Throwable) { false @@ -615,7 +657,7 @@ class API( } fun tonconnectProof(address: String, proof: String): String { - val url = "${config.tonapiMainnetHost}/v2/wallet/auth/proof" + val url = "${mainnetConfig.tonapiMainnetHost}/v2/wallet/auth/proof" val data = "{\"address\":\"$address\",\"proof\":$proof}" val response = withRetry { tonAPIHttpClient.postJSON(url, data) @@ -643,29 +685,33 @@ class API( tonProofToken: String, jettonMaster: String, cell: Cell, - testnet: Boolean, + network: TonNetwork, ): String? { val request = EstimateGaslessCostRequest(cell.base64(), false) return withRetry { - battery(testnet).estimateGaslessCost(jettonMaster, request, tonProofToken).commission + battery(network).estimateGaslessCost(jettonMaster, request, tonProofToken).commission } } fun emulateWithBattery( tonProofToken: String, cell: Cell, - testnet: Boolean, + network: TonNetwork, safeModeEnabled: Boolean, - ) = emulateWithBattery(tonProofToken, cell.base64(), testnet, safeModeEnabled) + ) = emulateWithBattery(tonProofToken, cell.base64(), network, safeModeEnabled) fun emulateWithBattery( tonProofToken: String, boc: String, - testnet: Boolean, + network: TonNetwork, safeModeEnabled: Boolean, - ): Pair? { - val host = if (testnet) config.batteryTestnetHost else config.batteryHost + ): EmulateWithBatteryResult? { + val host = when (network) { + TonNetwork.TESTNET -> mainnetConfig.batteryTestnetHost + TonNetwork.MAINNET -> mainnetConfig.batteryHost + TonNetwork.TETRA -> mainnetConfig.batteryHost + } val url = "$host/wallet/emulate" val data = "{\"boc\":\"$boc\",\"safe_mode\":$safeModeEnabled}" @@ -677,20 +723,22 @@ class API( val supportedByBattery = response.headers["supported-by-battery"] == "true" val allowedByBattery = response.headers["allowed-by-battery"] == "true" + val excess = response.headers["excess"]?.toLongOrNull() val withBattery = supportedByBattery && allowedByBattery val string = response.body?.string() ?: return null val consequences = try { Serializer.JSON.decodeFromString(string) } catch (e: Throwable) { + L.e(e) return null } - return Pair(consequences, withBattery) + return EmulateWithBatteryResult(consequences, withBattery, excess) } suspend fun emulate( boc: String, - testnet: Boolean, + network: TonNetwork, address: String? = null, balance: Long? = null, safeModeEnabled: Boolean, @@ -705,28 +753,28 @@ class API( // safeMode = safeModeEnabled ) withRetry { - emulation(testnet).emulateMessageToWallet(request) + emulation(network).emulateMessageToWallet(request) } } suspend fun emulate( cell: Cell, - testnet: Boolean, + network: TonNetwork, address: String? = null, balance: Long? = null, safeModeEnabled: Boolean, ): MessageConsequences? { - return emulate(cell.hex(), testnet, address, balance, safeModeEnabled) + return emulate(cell.hex(), network, address, balance, safeModeEnabled) } suspend fun sendToBlockchainWithBattery( boc: String, tonProofToken: String, - testnet: Boolean, + network: TonNetwork, source: String, confirmationTime: Double, ): SendBlockchainState = withContext(Dispatchers.IO) { - if (!isOkStatus(testnet)) { + if (!isOkStatus(network)) { return@withContext SendBlockchainState.STATUS_ERROR } @@ -735,18 +783,18 @@ class API( ) withRetry { - battery(testnet).sendMessage(tonProofToken, request) + battery(network).sendMessage(tonProofToken, request) SendBlockchainState.SUCCESS } ?: SendBlockchainState.UNKNOWN_ERROR } suspend fun sendToBlockchain( boc: String, - testnet: Boolean, + network: TonNetwork, source: String, confirmationTime: Double, ): SendBlockchainState = withContext(Dispatchers.IO) { - if (!isOkStatus(testnet)) { + if (!isOkStatus(network)) { return@withContext SendBlockchainState.STATUS_ERROR } @@ -761,25 +809,25 @@ class API( meta = meta ) withRetry { - blockchain(testnet).sendBlockchainMessage(request) + blockchain(network).sendBlockchainMessage(request) SendBlockchainState.SUCCESS } ?: SendBlockchainState.UNKNOWN_ERROR } fun getAccountSeqno( accountId: String, - testnet: Boolean, - ): Int = withRetry { wallet(testnet).getAccountSeqno(accountId).seqno } ?: 0 + network: TonNetwork, + ): Int = withRetry { wallet(network).getAccountSeqno(accountId).seqno } ?: 0 suspend fun resolveAccount( value: String, - testnet: Boolean, + network: TonNetwork, ): Account? = withContext(Dispatchers.IO) { /*if (value.isValidTonAddress()) { - return@withContext getAccount(value, testnet) + return@withContext getAccount(value, network) } - return@withContext resolveDomain(value.lowercase().trim(), testnet)*/ - getAccount(value, testnet, null) + return@withContext resolveDomain(value.lowercase().trim(), network)*/ + getAccount(value, network, null) } /*private suspend fun resolveDomain(domain: String, testnet: Boolean): Account? { @@ -788,7 +836,7 @@ class API( private fun getAccount( accountId: String, - testnet: Boolean, + network: TonNetwork, currency: String?, ): Account? { var normalizedAccountId = accountId @@ -802,7 +850,7 @@ class API( if (!normalizedAccountId.isValidTonAddress()) { normalizedAccountId = normalizedAccountId.lowercase().trim() } - return withRetry { accounts(testnet).getAccount(normalizedAccountId) } + return withRetry { accounts(network).getAccount(normalizedAccountId) } } fun pushSubscribe( @@ -814,7 +862,7 @@ class API( if (accounts.isEmpty()) { return true } - val url = "${config.tonapiMainnetHost}/v1/internal/pushes/plain/subscribe" + val url = "${mainnetConfig.tonapiMainnetHost}/v1/internal/pushes/plain/subscribe" val accountsArray = JSONArray() for (account in accounts) { @@ -843,7 +891,7 @@ class API( return true } - val url = "${config.tonapiMainnetHost}/v1/internal/pushes/plain/unsubscribe" + val url = "${mainnetConfig.tonapiMainnetHost}/v1/internal/pushes/plain/unsubscribe" val accountsArray = JSONArray() for (account in accounts) { @@ -873,7 +921,7 @@ class API( commercial: Boolean, silent: Boolean ): Boolean { - val url = "${config.tonapiMainnetHost}/v1/internal/pushes/tonconnect" + val url = "${mainnetConfig.tonapiMainnetHost}/v1/internal/pushes/tonconnect" val json = JSONObject() json.put("app_url", appUrl) @@ -904,7 +952,7 @@ class API( ): Boolean { return try { val uriBuilder = - Uri.parse("${config.tonapiMainnetHost}/v1/internal/pushes/tonconnect").buildUpon() + Uri.parse("${mainnetConfig.tonapiMainnetHost}/v1/internal/pushes/tonconnect").buildUpon() uriBuilder.appendQueryParameter("firebase_token", firebaseToken) uriBuilder.appendQueryParameter("app_url", appUrl) uriBuilder.appendQueryParameter("account", accountId) @@ -924,7 +972,7 @@ class API( accountId: String, ): JSONArray { return try { - val url = "${config.tonapiMainnetHost}/v1/messages/history?account=$accountId" + val url = "${mainnetConfig.tonapiMainnetHost}/v1/messages/history?account=$accountId" val response = withRetry { tonAPIHttpClient.get(url, ArrayMap().apply { set("X-TonConnect-Auth", token) @@ -937,17 +985,17 @@ class API( } } - fun getBrowserApps(testnet: Boolean, locale: Locale): JSONObject { - return internalApi.getBrowserApps(testnet, locale) + fun getBrowserApps(network: TonNetwork, locale: Locale): JSONObject { + return internalApi.getBrowserApps(network, locale) } - fun getFiatMethods(testnet: Boolean, locale: Locale): JSONObject? { - return withRetry { internalApi.getFiatMethods(testnet, locale) } + fun getFiatMethods(network: TonNetwork, locale: Locale): JSONObject? { + return withRetry { internalApi.getFiatMethods(network, locale) } } - fun getTransactionEvents(accountId: String, testnet: Boolean, eventId: String): AccountEvent? { + fun getTransactionEvents(accountId: String, network: TonNetwork, eventId: String): AccountEvent? { return try { - accounts(testnet).getAccountEvent(accountId, eventId) + accounts(network).getAccountEvent(accountId, eventId) } catch (e: Throwable) { null } @@ -965,7 +1013,7 @@ class API( ): List { try { val url = - "${config.tonapiMainnetHost}/v2/rates/chart?token=$token¤cy=$currency&start_date=$startDate&end_date=$endDate" + "${mainnetConfig.tonapiMainnetHost}/v2/rates/chart?token=$token¤cy=$currency&start_date=$startDate&end_date=$endDate" val array = JSONObject(tonAPIHttpClient.get(url)).getJSONArray("points") return (0 until array.length()).map { index -> ChartEntity(array.getJSONArray(index)) @@ -975,18 +1023,18 @@ class API( } } - fun getServerTime(testnet: Boolean): Int { - /*val time = serverTimeProvider.getServerTime(testnet) + fun getServerTime(network: TonNetwork): Int { + /*val time = serverTimeProvider.getServerTime(network) if (time == null) { - val serverTimeSeconds = withRetry { liteServer(testnet).getRawTime().time } + val serverTimeSeconds = withRetry { liteServer(network).getRawTime().time } if (serverTimeSeconds == null) { return (System.currentTimeMillis() / 1000).toInt() } - serverTimeProvider.setServerTime(testnet, serverTimeSeconds) + serverTimeProvider.setServerTime(network, serverTimeSeconds) return serverTimeSeconds } return time*/ - val serverTimeSeconds = withRetry { liteServer(testnet).getRawTime().time } + val serverTimeSeconds = withRetry { liteServer(network).getRawTime().time } if (serverTimeSeconds == null) { return (System.currentTimeMillis() / 1000).toInt() } @@ -999,7 +1047,7 @@ class API( nftAddress: String, scam: Boolean ) = withContext(Dispatchers.IO) { - val url = config.scamEndpoint + "/v1/report/$nftAddress" + val url = mainnetConfig.scamEndpoint + "/v1/report/$nftAddress" val data = "{\"is_scam\":$scam}" val response = withRetry { tonAPIHttpClient.postJSON(url, data) @@ -1015,7 +1063,7 @@ class API( comment: String?, recipient: String, ) = withContext(Dispatchers.IO) { - val url = config.scamEndpoint + "/v1/report/tx/$txId" + val url = mainnetConfig.scamEndpoint + "/v1/report/tx/$txId" val json = JSONObject() json.put("recipient", recipient) comment?.let { json.put("comment", it) } @@ -1028,4 +1076,4 @@ class API( } response.body?.string() ?: throw Exception("Empty response") } -} \ No newline at end of file +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/BatteryProvider.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/BatteryProvider.kt index 0ef717b48..612cdc849 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/BatteryProvider.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/BatteryProvider.kt @@ -7,17 +7,19 @@ import okhttp3.OkHttpClient internal class BatteryProvider( mainnetHost: String, testnetHost: String, + tetraHost: String, okHttpClient: OkHttpClient, ) { private val main = BatteryAPI(mainnetHost, okHttpClient) private val test = BatteryAPI(testnetHost, okHttpClient) + private val tetra = BatteryAPI(tetraHost, okHttpClient) - val default = SourceAPI(main.default, test.default) + val default = SourceAPI(main.default, test.default, tetra.default) - val emulation = SourceAPI(main.emulation, test.emulation) + val emulation = SourceAPI(main.emulation, test.emulation, tetra.emulation) - val wallet = SourceAPI(main.wallet, test.wallet) + val wallet = SourceAPI(main.wallet, test.wallet, tetra.wallet) } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/CoreAPI.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/CoreAPI.kt index e11e3a200..f319ddb9a 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/CoreAPI.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/CoreAPI.kt @@ -2,51 +2,68 @@ package com.tonapps.wallet.api import android.content.Context import android.os.Build -import com.google.android.gms.net.CronetProviderInstaller -import com.google.firebase.crashlytics.BuildConfig +import com.tonapps.apps.wallet.api.BuildConfig import com.tonapps.extensions.appVersionName import com.tonapps.extensions.cacheFolder import com.tonapps.extensions.locale import com.tonapps.network.interceptor.AcceptLanguageInterceptor import com.tonapps.network.interceptor.AuthorizationInterceptor -import com.tonapps.wallet.api.cronet.CronetInterceptor import com.tonapps.wallet.api.entity.ConfigEntity +import com.tonapps.wallet.api.interceptors.LoggingInterceptor +import com.tonapps.wallet.api.interceptors.RateLimitInterceptor +import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient -import org.chromium.net.CronetEngine import java.util.concurrent.TimeUnit +//import org.chromium.net.CronetEngine +//import com.google.android.gms.net.CronetProviderInstaller +//import com.google.net.cronet.okhttptransport.CronetInterceptor + abstract class CoreAPI(private val context: Context) { val appVersionName = context.appVersionName - private val userAgent = "Tonkeeper/${appVersionName} (Linux; Android ${Build.VERSION.RELEASE}; ${Build.MODEL})" - private var cronetEngine: CronetEngine? = null +// private var cronetEngine: CronetEngine? = null val defaultHttpClient = baseOkHttpClientBuilder( - cronetEngine = { cronetEngine }, +// cronetEngine = { cronetEngine }, timeoutSeconds = 30, + rateLimit = 15, + context = context, + interceptors = listOf( + UserAgentInterceptor(userAgent), + ) + ).build() + + + val tronHttpClient = baseOkHttpClientBuilder( +// cronetEngine = { cronetEngine }, + timeoutSeconds = 30, + rateLimit = 10, + context = context, interceptors = listOf( UserAgentInterceptor(userAgent), ) ).build() val seeHttpClient = baseOkHttpClientBuilder( - cronetEngine = { null }, +// cronetEngine = { null }, timeoutSeconds = 60, + callTimeoutSeconds = 0, + context = context, interceptors = listOf( UserAgentInterceptor(userAgent), ) - ).build() + ) + .build() - init { - if (!BuildConfig.DEBUG) { - requestCronet(context, userAgent) { - cronetEngine = it - } - } - } +// init { +// requestCronet(context, userAgent) { +// cronetEngine = it +// } +// } fun tonAPIHttpClient(config: () -> ConfigEntity): OkHttpClient { return createTonAPIHttpClient( @@ -54,12 +71,16 @@ abstract class CoreAPI(private val context: Context) { userAgent = userAgent, tonApiV2Key = { config().tonApiV2Key }, allowDomains = { config().domains }, - cronetEngine = { cronetEngine } +// cronetEngine = { cronetEngine } ) } private companion object { + const val MAX_CACHE_SIZE = 500L * 1024 * 1024 + + private val loggingInterceptor by lazy { LoggingInterceptor() } + class UserAgentInterceptor(private val userAgent: String) : Interceptor { override fun intercept(chain: Interceptor.Chain): okhttp3.Response { val request = chain.request().newBuilder() @@ -70,17 +91,34 @@ abstract class CoreAPI(private val context: Context) { } private fun baseOkHttpClientBuilder( - cronetEngine: () -> CronetEngine?, timeoutSeconds: Long = 30, - interceptors: List = emptyList() + callTimeoutSeconds: Long = timeoutSeconds, + rateLimit: Int = 10, + interceptors: List = emptyList(), + context: Context, +// cronetEngine: () -> CronetEngine?, ): OkHttpClient.Builder { val builder = OkHttpClient().newBuilder() .retryOnConnectionFailure(true) .connectTimeout(timeoutSeconds, TimeUnit.SECONDS) .readTimeout(timeoutSeconds, TimeUnit.SECONDS) .writeTimeout(timeoutSeconds, TimeUnit.SECONDS) - .callTimeout(timeoutSeconds, TimeUnit.SECONDS) + .callTimeout(callTimeoutSeconds, TimeUnit.SECONDS) .pingInterval(timeoutSeconds, TimeUnit.SECONDS) + .cache( + Cache( + context.cacheFolder("cronet"), + MAX_CACHE_SIZE + ) + ) + // TODO additional setup like connectionPool etc +// .connectionPool( +// ConnectionPool( +// maxIdleConnections = 16, +// keepAliveDuration = 30, +// timeUnit = TimeUnit.SECONDS, +// ) +// ) .followSslRedirects(true) .followRedirects(true) @@ -88,10 +126,23 @@ abstract class CoreAPI(private val context: Context) { builder.addInterceptor(interceptor) } - builder.addInterceptor { chain -> - cronetEngine()?.let { engine -> - CronetInterceptor.newBuilder(engine).build() - }?.intercept(chain) ?: chain.proceed(chain.request()) + if (BuildConfig.DEBUG) { + builder.addInterceptor(LoggingInterceptor()) + } + + builder.addInterceptor( + RateLimitInterceptor(requestsPerSecondLimit = rateLimit) + ) + +// cronetEngine()?.let { engine -> +// builder.addInterceptor( +// CronetInterceptor.newBuilder(engine) +// .build() +// ) +// } + + if (BuildConfig.DEBUG) { + builder.addInterceptor(loggingInterceptor) } return builder @@ -100,47 +151,50 @@ abstract class CoreAPI(private val context: Context) { private fun createTonAPIHttpClient( userAgent: String, context: Context, - cronetEngine: () -> CronetEngine?, tonApiV2Key: () -> String, - allowDomains: () -> List + allowDomains: () -> List, +// cronetEngine: () -> CronetEngine?, ): OkHttpClient { - val interceptors = listOf( + val interceptors = mutableListOf( UserAgentInterceptor(userAgent), AcceptLanguageInterceptor(context.locale), AuthorizationInterceptor.bearer( token = tonApiV2Key, allowDomains = allowDomains - ) + ), ) return baseOkHttpClientBuilder( - cronetEngine = cronetEngine, - interceptors = interceptors + context = context, + interceptors = interceptors, +// cronetEngine = cronetEngine, ).build() } - private fun requestCronet(context: Context, userAgent: String, callback: (CronetEngine) -> Unit) { - CronetProviderInstaller.installProvider(context).addOnSuccessListener { - build(context, userAgent)?.let(callback) - } - } - - private fun build(context: Context, userAgent: String): CronetEngine? { - if (!CronetProviderInstaller.isInstalled()) { - return null - } - return try { - CronetEngine.Builder(context) - .setUserAgent(userAgent) - .enableQuic(true) - .enableHttp2(true) - .enableBrotli(true) - .setStoragePath(context.cacheFolder("cronet").absolutePath) - .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 500 * 1024 * 1024) - .build() - } catch (e: Throwable) { - null - } - } +// private fun requestCronet(context: Context, userAgent: String, callback: (CronetEngine) -> Unit) { +// CronetProviderInstaller.installProvider(context) +// .addOnSuccessListener { +// build(context, userAgent)?.let(callback) +// } +// } +// +// private fun build(context: Context, userAgent: String): CronetEngine? { +// if (!CronetProviderInstaller.isInstalled()) { +// return null +// } +// +// return try { +// CronetEngine.Builder(context) +// .setUserAgent(userAgent) +// .enableQuic(true) +// .enableHttp2(true) +// .enableBrotli(true) +// .setStoragePath(context.cacheFolder("cronet").absolutePath) +// .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 500 * 1024 * 1024) +// .build() +// } catch (e: Throwable) { +// null +// } +// } } } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt index 3719a108f..7546b1f68 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt @@ -1,19 +1,21 @@ package com.tonapps.wallet.api import android.os.SystemClock -import android.util.Log import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.log.L import com.tonapps.network.OkHttpError +import com.tonapps.wallet.api.backoff.ExponentialBackoff import io.infrastructure.ClientError import io.infrastructure.ClientException import io.infrastructure.ServerError import kotlinx.coroutines.CancellationException import kotlinx.io.IOException +import okhttp3.Response import java.net.SocketTimeoutException fun withRetry( times: Int = 5, - delay: Long = 500, + backoff: ExponentialBackoff = ExponentialBackoff(), retryBlock: () -> R ): R? { var index = -1 @@ -24,27 +26,28 @@ fun withRetry( } catch (e: CancellationException) { throw e } catch (e: SocketTimeoutException) { - Log.e("RetryLogNew", "SocketTimeoutException occurred: ${e.message}", e) - SystemClock.sleep(delay + 100) + L.e("RetryLogNew", "SocketTimeoutException occurred: ${e.message}", e) return null } catch (e: IOException) { - Log.e("RetryLogNew", "IOException occurred: ${e.message}", e) - SystemClock.sleep(delay + 100) + L.e("RetryLogNew", "IOException occurred: ${e.message}", e) return null } catch (e: Throwable) { - Log.e("RetryLogNew", "Error occurred: ${e.message}", e) + L.e("RetryLogNew", "Error occurred: ${e.message}", e) val statusCode = e.getHttpStatusCode() + if (statusCode == 429 || statusCode == 401 || statusCode == 502 || statusCode == 520) { - SystemClock.sleep(delay + 100) + SystemClock.sleep(backoff.getDelayMs(index)) continue } + if (statusCode >= 500 || statusCode == 404 || statusCode == 400) { return null } + FirebaseCrashlytics.getInstance().recordException(e) } - } while (index < times) + return null } @@ -64,10 +67,14 @@ fun Throwable.getDebugMessage(): String? { } } +fun Response.readBody(): String { + return use { body.string() } +} + private fun ClientException.getHttpBodyMessage(): String { return when (response) { is ClientError<*> -> (response as ClientError<*>).body.toString() is ServerError<*> -> (response as ServerError<*>).body.toString() else -> response.toString() } -} \ No newline at end of file +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/FileDownloader.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/FileDownloader.kt index f1bd47e2c..02ad47487 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/FileDownloader.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/FileDownloader.kt @@ -1,6 +1,6 @@ package com.tonapps.wallet.api -import android.util.Log +import com.tonapps.log.L import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import okhttp3.OkHttpClient diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Provider.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Provider.kt index dd14fd69f..cb6c990e5 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Provider.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Provider.kt @@ -7,41 +7,43 @@ import okhttp3.OkHttpClient internal class Provider( mainnetHost: String, testnetHost: String, + tetraHost: String, okHttpClient: OkHttpClient, ) { private val main = BaseAPI(mainnetHost, okHttpClient) private val test = BaseAPI(testnetHost, okHttpClient) + private val tetra = BaseAPI(tetraHost, okHttpClient) - val accounts = SourceAPI(main.accounts, test.accounts) + val accounts = SourceAPI(main.accounts, test.accounts, tetra.accounts) - val blockchain = SourceAPI(main.blockchain, test.blockchain) + val blockchain = SourceAPI(main.blockchain, test.blockchain, tetra.blockchain) - val connect = SourceAPI(main.connect, test.connect) + val connect = SourceAPI(main.connect, test.connect, tetra.connect) - val dns = SourceAPI(main.dns, test.dns) + val dns = SourceAPI(main.dns, test.dns, tetra.dns) - val emulation = SourceAPI(main.emulation, test.emulation) + val emulation = SourceAPI(main.emulation, test.emulation, tetra.emulation) - val events = SourceAPI(main.events, test.events) + val events = SourceAPI(main.events, test.events, tetra.events) - val jettons = SourceAPI(main.jettons, test.jettons) + val jettons = SourceAPI(main.jettons, test.jettons, tetra.jettons) - val liteServer = SourceAPI(main.liteServer, test.liteServer) + val liteServer = SourceAPI(main.liteServer, test.liteServer, tetra.liteServer) - val nft = SourceAPI(main.nft, test.nft) + val nft = SourceAPI(main.nft, test.nft, tetra.nft) - val rates = SourceAPI(main.rates, test.rates) + val rates = SourceAPI(main.rates, test.rates, tetra.rates) - val staking = SourceAPI(main.staking, test.staking) + val staking = SourceAPI(main.staking, test.staking, tetra.staking) - val storage = SourceAPI(main.storage, test.storage) + val storage = SourceAPI(main.storage, test.storage, tetra.storage) - val traces = SourceAPI(main.traces, test.traces) + val traces = SourceAPI(main.traces, test.traces, tetra.traces) - val wallet = SourceAPI(main.wallet, test.wallet) + val wallet = SourceAPI(main.wallet, test.wallet, tetra.wallet) - val gasless = SourceAPI(main.gasless, test.gasless) + val gasless = SourceAPI(main.gasless, test.gasless, tetra.gasless) - val utilities = SourceAPI(main.utilities, test.utilities) + val utilities = SourceAPI(main.utilities, test.utilities, tetra.utilities) } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/ServerTimeProvider.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/ServerTimeProvider.kt index b409a3e61..69908ae15 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/ServerTimeProvider.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/ServerTimeProvider.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.api import android.content.Context import android.os.SystemClock +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.prefs import androidx.core.content.edit @@ -14,26 +15,26 @@ internal class ServerTimeProvider(context: Context) { // 24 hours in milliseconds private const val CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1000L - private fun getServerTimePrefKey(testnet: Boolean) = "${SERVER_TIME_KEY}_${if (testnet) "test" else "main"}" + private fun getServerTimePrefKey(network: TonNetwork) = "${SERVER_TIME_KEY}_${network.name.lowercase()}" - private fun getLocalTimePrefKey(testnet: Boolean) = "${LOCAL_TIME_KEY}_${if (testnet) "test" else "main"}" + private fun getLocalTimePrefKey(network: TonNetwork) = "${LOCAL_TIME_KEY}_${network.name.lowercase()}" } private val prefs = context.prefs("server_time") - fun setServerTime(testnet: Boolean, serverTimeSeconds: Int) { + fun setServerTime(network: TonNetwork, serverTimeSeconds: Int) { val localTimeMillis = SystemClock.elapsedRealtime() prefs.edit { - putInt(getServerTimePrefKey(testnet), serverTimeSeconds) - putLong(getLocalTimePrefKey(testnet), localTimeMillis) + putInt(getServerTimePrefKey(network), serverTimeSeconds) + putLong(getLocalTimePrefKey(network), localTimeMillis) } } - fun getServerTime(testnet: Boolean): Int? { - val savedServerSeconds = prefs.getInt(getServerTimePrefKey(testnet), 0) - val savedLocalMillis = prefs.getLong(getLocalTimePrefKey(testnet), 0L) + fun getServerTime(network: TonNetwork): Int? { + val savedServerSeconds = prefs.getInt(getServerTimePrefKey(network), 0) + val savedLocalMillis = prefs.getLong(getLocalTimePrefKey(network), 0L) if (0 >= savedServerSeconds || 0 >= savedLocalMillis) { return null } diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/backoff/ExponentialBackoff.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/backoff/ExponentialBackoff.kt new file mode 100644 index 000000000..9f846f870 --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/backoff/ExponentialBackoff.kt @@ -0,0 +1,43 @@ +package com.tonapps.wallet.api.backoff + +import kotlin.math.min +import kotlin.random.Random + +/** + * Generates exponential backoff delays with jitter for retry logic. + * + * @param minDelayMs Minimum delay in milliseconds + * @param maxDelayMs Maximum delay in milliseconds + * @param multiplier Multiplier for exponential growth + */ +class ExponentialBackoff( + private val minDelayMs: Long = 100, + private val maxDelayMs: Long = 32000, + private val multiplier: Double = 2.0 +) { + init { + require(minDelayMs > 0) { "minDelayMs must be positive" } + require(maxDelayMs >= minDelayMs) { "maxDelayMs must be >= minDelayMs" } + require(multiplier > 1.0) { "multiplier must be > 1.0" } + } + + /** + * Calculate the backoff delay for the given attempt number. + * Adds jitter to prevent thundering herd problem. + * + * @param attempt The attempt number (0-indexed) + * @return Delay in milliseconds + */ + fun getDelayMs(attempt: Int): Long { + require(attempt >= 0) { "attempt must be non-negative" } + + val exponentialDelay = minDelayMs * Math.pow(multiplier, attempt.toDouble()) + val cappedDelay = min(exponentialDelay.toLong(), maxDelayMs) + + // Add jitter (±25% randomization) + val jitterRange = (cappedDelay * 0.25).toLong() + val jitter = Random.Default.nextLong(-jitterRange, jitterRange + 1) + + return (cappedDelay + jitter).coerceAtLeast(minDelayMs) + } +} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/SourceAPI.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/SourceAPI.kt index 4997d24c1..575a85536 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/SourceAPI.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/SourceAPI.kt @@ -1,11 +1,18 @@ package com.tonapps.wallet.api.core +import com.tonapps.blockchain.ton.TonNetwork + data class SourceAPI( private val mainnetAPI: A, - private val testnetAPI: A + private val testnetAPI: A, + private val tetraAPI: A, ) { - fun get(testnet: Boolean): A { - return if (testnet) testnetAPI else mainnetAPI + fun get(network: TonNetwork): A { + return when (network) { + TonNetwork.TESTNET -> testnetAPI + TonNetwork.MAINNET -> mainnetAPI + TonNetwork.TETRA -> tetraAPI + } } } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetCallFactory.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetCallFactory.java deleted file mode 100644 index f5dd25629..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetCallFactory.java +++ /dev/null @@ -1,312 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static com.google.firebase.components.Preconditions.checkArgument; -import static com.google.firebase.components.Preconditions.checkNotNull; -import static com.google.firebase.components.Preconditions.checkState; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import android.util.Log; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Request; -import okhttp3.Response; -import okio.AsyncTimeout; -import okio.Timeout; -import org.chromium.net.CronetEngine; - -/** A {@link Call.Factory} implementation using Cronet as the transport layer. */ -public final class CronetCallFactory implements Call.Factory { - - private static final String TAG = "CronetCallFactory"; - - private final RequestResponseConverter converter; - private final ExecutorService responseCallbackExecutor; - private final int readTimeoutMillis; - private final int writeTimeoutMillis; - private final int callTimeoutMillis; - - private CronetCallFactory( - RequestResponseConverter converter, - ExecutorService responseCallbackExecutor, - int readTimeoutMillis, - int writeTimeoutMillis, - int callTimeoutMillis) { - checkArgument(readTimeoutMillis >= 0, "Read timeout mustn't be negative!"); - checkArgument(writeTimeoutMillis >= 0, "Write timeout mustn't be negative!"); - checkArgument(callTimeoutMillis >= 0, "Call timeout mustn't be negative!"); - - this.converter = converter; - this.responseCallbackExecutor = responseCallbackExecutor; - this.readTimeoutMillis = readTimeoutMillis; - this.writeTimeoutMillis = writeTimeoutMillis; - this.callTimeoutMillis = callTimeoutMillis; - } - - public static Builder newBuilder(CronetEngine cronetEngine) { - return new Builder(cronetEngine); - } - - @Override - public Call newCall(Request request) { - return new CronetCall(request, this, converter, responseCallbackExecutor); - } - - private static class CronetCall implements Call { - - private final Request okHttpRequest; - private final CronetCallFactory motherFactory; - private final RequestResponseConverter converter; - private final ExecutorService responseCallbackExecutor; - - private final AtomicBoolean executed = new AtomicBoolean(); - private final AtomicBoolean canceled = new AtomicBoolean(); - private final AtomicReference convertedRequestAndResponse = - new AtomicReference<>(); - private final AsyncTimeout timeout; - - private CronetCall( - Request okHttpRequest, - CronetCallFactory motherFactory, - RequestResponseConverter converter, - ExecutorService responseCallbackExecutor) { - this.okHttpRequest = okHttpRequest; - this.motherFactory = motherFactory; - this.converter = converter; - this.responseCallbackExecutor = responseCallbackExecutor; - - this.timeout = - new AsyncTimeout() { - @Override - protected void timedOut() { - CronetCall.this.cancel(); // Timeout has its own method named cancel - } - }; - timeout.timeout(motherFactory.callTimeoutMillis, MILLISECONDS); - } - - @Override - public Request request() { - return okHttpRequest; - } - - @Override - public Response execute() throws IOException { - evaluateExecutionPreconditions(); - try { - timeout.enter(); - RequestResponseConverter.CronetRequestAndOkHttpResponse requestAndOkHttpResponse = - converter.convert( - request(), motherFactory.readTimeoutMillis, motherFactory.writeTimeoutMillis); - convertedRequestAndResponse.set(requestAndOkHttpResponse); - - startRequestIfNotCanceled(); - - return toCronetCallFactoryResponse(this, requestAndOkHttpResponse.getResponse()); - } catch (RuntimeException | IOException e) { - // If the request finished successfully don't exit the timeout yet. Reading the body also - // needs to be considered and the body object will take care of exiting it. See - // toCronetCallFactoryResponse() for details. - timeout.exit(); - throw e; - } - } - - @Override - public void enqueue(Callback responseCallback) { - try { - timeout.enter(); - evaluateExecutionPreconditions(); - RequestResponseConverter.CronetRequestAndOkHttpResponse requestAndOkHttpResponse = - converter.convert( - request(), motherFactory.readTimeoutMillis, motherFactory.writeTimeoutMillis); - convertedRequestAndResponse.set(requestAndOkHttpResponse); - CronetCall call = this; - - Futures.addCallback( - requestAndOkHttpResponse.getResponseAsync(), - new FutureCallback() { - @Override - public void onSuccess(Response result) { - try { - responseCallback.onResponse(call, toCronetCallFactoryResponse(call, result)); - } catch (IOException e) { - // The call factory doesn't really mind this - the application code - // threw an exception while handling the response, they should have taken care - // of it. Just logging the error is consistent with plain OkHttp implementation. - Log.i(TAG, "Callback failure for " + toLoggableString(), e); - } - } - - @Override - public void onFailure(Throwable t) { - if (t instanceof IOException) { - responseCallback.onFailure(call, (IOException) t); - } else { - responseCallback.onFailure(call, new IOException(t)); - } - } - }, - responseCallbackExecutor); - - startRequestIfNotCanceled(); - } catch (IOException e) { - // If the request finished successfully don't exit the timeout yet. Reading the body also - // needs to be considered and the body object will take care of exiting it. See - // toCronetCallFactoryResponse() for details. - timeout.exit(); - responseCallback.onFailure(this, e); - } - } - - @Override - public Call clone() { - return motherFactory.newCall(request()); - } - - @Override - public void cancel() { - if (canceled.getAndSet(true)) { - // already canceled - return; - } - RequestResponseConverter.CronetRequestAndOkHttpResponse localConverted = convertedRequestAndResponse.get(); - if (localConverted != null) { - localConverted.getRequest().cancel(); - } // else the cancel signal will be picked up by the execute() / enqueue() methods. - } - - @Override - public boolean isExecuted() { - return executed.get(); - } - - @Override - public boolean isCanceled() { - return canceled.get(); - } - - @Override - public Timeout timeout() { - return timeout; - } - - private String toLoggableString() { - return "call to " + request().url().redact(); - } - - /** - * Verifies that the call can be executed and sets the state of the call to "being executed". - * - * @throws IllegalStateException if the request has already been executed. - * @throws IOException if the request was canceled - */ - private void evaluateExecutionPreconditions() throws IOException { - if (canceled.get()) { - throw new IOException("Can't execute canceled requests"); - } - checkState(!executed.getAndSet(true), "Already Executed"); - } - - private void startRequestIfNotCanceled() { - RequestResponseConverter.CronetRequestAndOkHttpResponse requestAndOkHttpResponse = convertedRequestAndResponse.get(); - checkState(requestAndOkHttpResponse != null, "convertedRequestAndResponse must be set!"); - - // There might be a race between the execution and cancellation - // evaluateExecutionPreconditions check didn't capture and cancel() might have missed that - // as well. Check once again that the request isn't canceled. - // This way, no matter how the instructions of the two threads are interleaved, we always end - // up with a serialized-like outcome (either cancel() was fully run before execute(), or vice - // versa). - - // Thread 1 (cancel() call) | Thread 2 (execute() call) - // ------------------------------------------------------------------------------- - // canceled = true | if (canceled) throw; - // convertedRequest?.cancel() | convertedRequest = convert(request) - // | if (canceled) convertedRequest.cancel() - if (canceled.get()) { - requestAndOkHttpResponse.getRequest().cancel(); - } else { - requestAndOkHttpResponse.getRequest().start(); - } - } - } - - private static Response toCronetCallFactoryResponse(CronetCall call, Response response) { - assert response.body() != null; - - return response - .newBuilder() - .body( - new CronetTransportResponseBody(response.body()) { - @Override - void customCloseHook() { - call.timeout.exit(); - } - }) - .build(); - } - - public static final class Builder - extends RequestResponseConverterBasedBuilder { - private static final int DEFAULT_READ_WRITE_TIMEOUT_MILLIS = 10000; - - private int readTimeoutMillis = DEFAULT_READ_WRITE_TIMEOUT_MILLIS; - private int writeTimeoutMillis = DEFAULT_READ_WRITE_TIMEOUT_MILLIS; - private int callTimeoutMillis = 0; // No timeout - private ExecutorService callbackExecutorService = null; - - Builder(CronetEngine cronetEngine) { - super(cronetEngine, CronetCallFactory.Builder.class); - } - - public Builder setReadTimeoutMillis(int readTimeoutMillis) { - checkArgument(readTimeoutMillis >= 0, "Read timeout mustn't be negative!"); - this.readTimeoutMillis = readTimeoutMillis; - return this; - } - - public Builder setWriteTimeoutMillis(int writeTimeoutMillis) { - checkArgument(writeTimeoutMillis >= 0, "Write timeout mustn't be negative!"); - this.writeTimeoutMillis = writeTimeoutMillis; - return this; - } - - public Builder setCallbackExecutorService(ExecutorService callbackExecutorService) { - checkNotNull(callbackExecutorService); - this.callbackExecutorService = callbackExecutorService; - return this; - } - - public Builder setCallTimeoutMillis(int callTimeoutMillis) { - checkArgument(callTimeoutMillis >= 0, "Call timeout mustn't be negative!"); - this.callTimeoutMillis = callTimeoutMillis; - - return this; - } - - @Override - CronetCallFactory build(RequestResponseConverter converter) { - ExecutorService localCallbackExecutorService; - if (callbackExecutorService == null) { - // Consistent with OkHttp impl - localCallbackExecutorService = Executors.newCachedThreadPool(); - } else { - localCallbackExecutorService = callbackExecutorService; - } - - return new CronetCallFactory( - converter, - localCallbackExecutorService, - readTimeoutMillis, - writeTimeoutMillis, - callTimeoutMillis); - } - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetInterceptor.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetInterceptor.java deleted file mode 100644 index a7cc7ecea..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetInterceptor.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static com.google.firebase.components.Preconditions.checkNotNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import android.util.Log; -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import okhttp3.Call; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.chromium.net.CronetEngine; -import org.chromium.net.UrlRequest; - -/** - * An OkHttp interceptor that redirects HTTP traffic to use Cronet instead of using the OkHttp - * network stack. - * - *

The interceptor should be used as the last application interceptor to ensure that all other - * interceptors are visited before sending the request on wire and after a response is returned. - * - *

The interceptor is a plug-and-play replacement for the OkHttp stack for the most part, - * however, there are some caveats to keep in mind: - * - *

    - *
  1. The entirety of OkHttp core is bypassed. This includes caching configuration and network - * interceptors. - *
  2. Some response fields are not being populated due to mismatches between Cronet's and - * OkHttp's architecture. TODO(danstahr): add a concrete list). - *
- */ -public final class CronetInterceptor implements Interceptor, AutoCloseable { - private static final String TAG = "CronetInterceptor"; - - private static final int CANCELLATION_CHECK_INTERVAL_MILLIS = 500; - - private final RequestResponseConverter converter; - private final Map activeCalls = new ConcurrentHashMap<>(); - private final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1); - - private CronetInterceptor(RequestResponseConverter converter) { - this.converter = checkNotNull(converter); - - // TODO(danstahr): There's no other way to know if the call is canceled but polling - // (https://github.com/square/okhttp/issues/7164). - ScheduledFuture unusedFuture = - scheduledExecutor.scheduleAtFixedRate( - () -> { - Iterator> activeCallsIterator = - activeCalls.entrySet().iterator(); - - while (activeCallsIterator.hasNext()) { - try { - Entry activeCall = activeCallsIterator.next(); - if (activeCall.getKey().isCanceled()) { - activeCallsIterator.remove(); - activeCall.getValue().cancel(); - } - } catch (RuntimeException e) { - Log.w(TAG, "Unable to propagate cancellation status", e); - } - } - }, - CANCELLATION_CHECK_INTERVAL_MILLIS, - CANCELLATION_CHECK_INTERVAL_MILLIS, - MILLISECONDS); - } - - @Override - public Response intercept(Chain chain) throws IOException { - if (chain.call().isCanceled()) { - throw new CancellationException(); - } - - Request request = chain.request(); - - RequestResponseConverter.CronetRequestAndOkHttpResponse requestAndOkHttpResponse = - converter.convert(request, chain.readTimeoutMillis(), chain.writeTimeoutMillis()); - - activeCalls.put(chain.call(), requestAndOkHttpResponse.getRequest()); - - try { - requestAndOkHttpResponse.getRequest().start(); - return toInterceptorResponse(requestAndOkHttpResponse.getResponse(), chain.call()); - } catch (RuntimeException | IOException e) { - // If the response is retrieved successfully the caller is responsible for closing - // the response, which will remove it from the active calls map. - activeCalls.remove(chain.call()); - throw e; - } - } - - /** Creates a {@link CronetInterceptor} builder. */ - public static Builder newBuilder(CronetEngine cronetEngine) { - return new Builder(cronetEngine); - } - - @Override - public void close() { - scheduledExecutor.shutdown(); - } - - /** A builder for {@link CronetInterceptor}. */ - public static final class Builder - extends RequestResponseConverterBasedBuilder { - - Builder(CronetEngine cronetEngine) { - super(cronetEngine, Builder.class); - } - - /** Builds the interceptor. The same builder can be used to build multiple interceptors. */ - @Override - public CronetInterceptor build(RequestResponseConverter converter) { - return new CronetInterceptor(converter); - } - } - - private Response toInterceptorResponse(Response response, Call call) { - assert response.body() != null; - - if (response.body() instanceof CronetInterceptorResponseBody) { - return response; - } - - return response - .newBuilder() - .body(new CronetInterceptorResponseBody(response.body(), call)) - .build(); - } - - private class CronetInterceptorResponseBody extends CronetTransportResponseBody { - private final Call call; - - private CronetInterceptorResponseBody(ResponseBody delegate, Call call) { - super(delegate); - this.call = call; - } - - @Override - void customCloseHook() { - activeCalls.remove(call); - } - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTimeoutException.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTimeoutException.java deleted file mode 100644 index bbcb590d6..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTimeoutException.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import java.io.IOException; - -public class CronetTimeoutException extends IOException {} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTransportResponseBody.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTransportResponseBody.java deleted file mode 100644 index 094f2b531..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/CronetTransportResponseBody.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import androidx.annotation.Nullable; -import okhttp3.MediaType; -import okhttp3.ResponseBody; -import okio.BufferedSource; - -abstract class CronetTransportResponseBody extends ResponseBody { - - private final ResponseBody delegate; - - protected CronetTransportResponseBody(ResponseBody delegate) { - this.delegate = delegate; - } - - @Nullable - @Override - public final MediaType contentType() { - return delegate.contentType(); - } - - @Override - public final long contentLength() { - return delegate.contentLength(); - } - - @Override - public final BufferedSource source() { - return delegate.source(); - } - - @Override - public final void close() { - delegate.close(); - customCloseHook(); - } - - abstract void customCloseHook(); -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/OkHttpBridgeRequestCallback.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/OkHttpBridgeRequestCallback.java deleted file mode 100644 index 8bf53da5c..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/OkHttpBridgeRequestCallback.java +++ /dev/null @@ -1,300 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static com.google.android.gms.common.internal.Preconditions.checkArgument; -import static com.google.android.gms.common.internal.Preconditions.checkState; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import java.io.IOException; -import java.net.ProtocolException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import okio.Buffer; -import okio.Source; -import okio.Timeout; -import org.chromium.net.CronetException; -import org.chromium.net.UrlRequest; -import org.chromium.net.UrlResponseInfo; - -/** - * An implementation of Cronet's callback. This is the heart of the bridge and deals with most of - * the async-sync paradigm translation. - * - *

Translating the UrlResponseInfo is relatively straightforward as the entire object is - * available immediately and is relatively small, so it can easily fit in memory. - * - *

Translating the body is a bit more tricky because of the mismatch between OkHttp and Cronet - * designs. We invoke Cronet's read and wait for the result using synchronization primitives (see - * BodySource implementation). The implementation is assuming that there's always at most one read() - * request in flight (which is safe to assume), and relies on reasonable fairness of thread - * scheduling, especially when handling cancellations. - */ -class OkHttpBridgeRequestCallback extends UrlRequest.Callback { - - /** - * The byte buffer capacity for reading Cronet response bodies. Each response callback will - * allocate its own buffer of this size once the response starts being processed. - */ - private static final int CRONET_BYTE_BUFFER_CAPACITY = 32 * 1024; - - /** A bridge between Cronet's asynchronous callbacks and OkHttp's blocking stream-like reads. */ - private final SettableFuture bodySourceFuture = SettableFuture.create(); - - /** Signal whether the request is finished and the response has been fully read. */ - private final AtomicBoolean finished = new AtomicBoolean(false); - - /** Signal whether the request was canceled. */ - private final AtomicBoolean canceled = new AtomicBoolean(false); - - /** - * An internal, blocking, thread safe way of passing data between the callback methods and {@link - * #bodySourceFuture}. - * - *

Has a capacity of 2 - at most one slot for a read result and at most 1 slot for cancellation - * signal, this guarantees that all inserts are non blocking. - */ - private final BlockingQueue callbackResults = new ArrayBlockingQueue<>(2); - - /** The response headers. */ - private final SettableFuture headersFuture = SettableFuture.create(); - - /** The read timeout as specified by OkHttp. * */ - private final long readTimeoutMillis; - - /** The previous responses as reported to {@link #onRedirectReceived}, from oldest to newest. * */ - private final List urlResponseInfoChain = new ArrayList<>(); - - private final RedirectStrategy redirectStrategy; - - /** The request being processed. Set when the request is first seen by the callback. */ - private volatile UrlRequest request; - - OkHttpBridgeRequestCallback(long readTimeoutMillis, RedirectStrategy redirectStrategy) { - checkArgument(readTimeoutMillis >= 0); - - // So that we don't have to special case infinity. Int.MAX_VALUE is ~infinity for all practical - // use cases. - if (readTimeoutMillis == 0) { - this.readTimeoutMillis = Integer.MAX_VALUE; - } else { - this.readTimeoutMillis = readTimeoutMillis; - } - this.redirectStrategy = redirectStrategy; - } - - /** Returns the {@link UrlResponseInfo} for the request associated with this callback. */ - ListenableFuture getUrlResponseInfo() { - return headersFuture; - } - - /** - * Returns the OkHttp {@link Source} for the request associated with this callback. - * - *

Note that retrieving data from the {@code Source} instance might block further as the - * response body is streamed. - */ - ListenableFuture getBodySource() { - return bodySourceFuture; - } - - List getUrlResponseInfoChain() { - return Collections.unmodifiableList(urlResponseInfoChain); - } - - @Override - public void onRedirectReceived( - UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, String nextUrl) { - // We shouldn't follow redirects - pass the given UrlResponseInfo as the ultimate result - if (!redirectStrategy.followRedirects()) { - checkState(headersFuture.set(urlResponseInfo)); - // Note: This might not match the content length headers but we have no way of accessing - // the actual body with current Cronet's APIs (see RedirectStrategy). - checkState(bodySourceFuture.set(new Buffer())); - urlRequest.cancel(); - return; - } - - // We should follow redirects and we haven't hit the cap yet - urlResponseInfoChain.add(urlResponseInfo); - if (urlResponseInfo.getUrlChain().size() <= redirectStrategy.numberOfRedirectsToFollow()) { - urlRequest.followRedirect(); - return; - } - - // Cap reached - cancel the request and fail. Exception crafted to match OkHttp. - urlRequest.cancel(); - - IOException e = - new ProtocolException( - "Too many follow-up requests: " + (redirectStrategy.numberOfRedirectsToFollow() + 1)); - headersFuture.setException(e); - bodySourceFuture.setException(e); - } - - @Override - public void onResponseStarted(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) { - request = urlRequest; - - checkState(headersFuture.set(urlResponseInfo)); - checkState(bodySourceFuture.set(new CronetBodySource())); - } - - @Override - public void onReadCompleted( - UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, ByteBuffer byteBuffer) { - callbackResults.add(new CallbackResult(CallbackStep.ON_READ_COMPLETED, byteBuffer, null)); - } - - @Override - public void onSucceeded(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) { - callbackResults.add(new CallbackResult(CallbackStep.ON_SUCCESS, null, null)); - } - - @Override - public void onFailed(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, CronetException e) { - Log.e("CronetLog", "onFailed: ", e); - // If this was called before we start reading the body, the exception will - // propagate in the future providing headers and the body wrapper. - if (headersFuture.setException(e) && bodySourceFuture.setException(e)) { - return; - } - - // If this was called as a reaction to a read() call, the read result will propagate - // the exception. - callbackResults.add(new CallbackResult(CallbackStep.ON_FAILED, null, e)); - } - - @Override - public void onCanceled(UrlRequest urlRequest, UrlResponseInfo responseInfo) { - canceled.set(true); - callbackResults.add(new CallbackResult(CallbackStep.ON_CANCELED, null, null)); - - // If there's nobody listening it's possible that the cancellation happened before we even - // received anything from the server. In that case inform the thread that's awaiting server - // response about the cancellation as well. This becomes a no-op if the futures - // were already set. - IOException e = new IOException("The request was canceled!"); - headersFuture.setException(e); - bodySourceFuture.setException(e); - } - - private class CronetBodySource implements Source { - - private ByteBuffer buffer = ByteBuffer.allocateDirect(CRONET_BYTE_BUFFER_CAPACITY); - - /** Whether the close() method has been called. */ - private volatile boolean closed = false; - - @Override - public long read(Buffer sink, long byteCount) throws IOException { - if (canceled.get()) { - throw new IOException("The request was canceled!"); - } - - // Using IAE instead of NPE (checkNotNull) for okio.RealBufferedSource consistency - checkArgument(sink != null, "sink == null"); - checkArgument(byteCount >= 0, "byteCount < 0: %s", byteCount); - checkState(!closed, "closed"); - - if (finished.get()) { - return -1; - } - - if (byteCount < buffer.limit()) { - buffer.limit((int) byteCount); - } - - request.read(buffer); - - CallbackResult result; - try { - result = callbackResults.poll(readTimeoutMillis, MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - result = null; - } - - if (result == null) { - // Either readResult.poll() was interrupted or it timed out. - request.cancel(); - throw new CronetTimeoutException(); - } - - switch (result.callbackStep) { - // We null the buffer in final statuses to allow fast GC of the buffer even if the callback - // is still in use. - case ON_FAILED: - finished.set(true); - buffer = null; - throw new IOException(result.exception); - case ON_SUCCESS: - finished.set(true); - buffer = null; - return -1; - case ON_CANCELED: - // The canceled flag is already set by the onCanceled method - // so not setting it here. - - buffer = null; - throw new IOException("The request was canceled!"); - case ON_READ_COMPLETED: - result.buffer.flip(); - int bytesWritten = sink.write(result.buffer); - result.buffer.clear(); - return bytesWritten; - } - - throw new AssertionError("The switch block above is exhaustive!"); - } - - @Override - public Timeout timeout() { - // TODO(danstahr): This should likely respect the OkHttp timeout somehow - return Timeout.NONE; - } - - @Override - public void close() { - if (closed) { - return; - } - closed = true; - if (!finished.get()) { - request.cancel(); - } - } - } - - private static class CallbackResult { - private final CallbackStep callbackStep; - @Nullable - private final ByteBuffer buffer; - @Nullable private final CronetException exception; - - private CallbackResult( - CallbackStep callbackStep, - @Nullable ByteBuffer buffer, - @Nullable CronetException exception) { - this.callbackStep = callbackStep; - this.buffer = buffer; - this.exception = exception; - } - } - - private enum CallbackStep { - ON_READ_COMPLETED, - ON_SUCCESS, - ON_FAILED, - ON_CANCELED - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RedirectStrategy.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RedirectStrategy.java deleted file mode 100644 index a79e842ad..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RedirectStrategy.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -/** Defines a redirect strategy for the Cronet OkHttp transport layer. */ -public abstract class RedirectStrategy { - - /** The default number of redirects to follow. Should be less than the Chromium wide safeguard. */ - private static final int DEFAULT_REDIRECTS = 16; - - /** - * Returns whether redirects should be followed at all. If set to false, the redirect response - * will be returned. - */ - abstract boolean followRedirects(); - - /** - * Returns the maximum number of redirects to follow. If more redirects are attempted an exception - * should be thrown by the component handling the request. Shouldn't be called at all if {@link - * #followRedirects()} return false. - */ - abstract int numberOfRedirectsToFollow(); - - /** - * Returns a strategy which will not follow redirects. - * - *

Note that because of Cronet's limitations - * (https://developer.android.com/guide/topics/connectivity/cronet/lifecycle#overview) it is - * impossible to retrieve the body of a redirect response. As a result, a dummy empty body will - * always be provided. - */ - public static RedirectStrategy withoutRedirects() { - return WithoutRedirectsHolder.INSTANCE; - } - - /** - * Returns a strategy which will follow redirects up to {@link #DEFAULT_REDIRECTS} times. If more - * redirects are attempted an exception is thrown. - */ - public static RedirectStrategy defaultStrategy() { - return DefaultRedirectsHolder.INSTANCE; - } - - private static class WithoutRedirectsHolder { - private static final RedirectStrategy INSTANCE = - new RedirectStrategy() { - @Override - boolean followRedirects() { - return false; - } - - @Override - int numberOfRedirectsToFollow() { - throw new UnsupportedOperationException(); - } - }; - } - - private static class DefaultRedirectsHolder { - private static final RedirectStrategy INSTANCE = - new RedirectStrategy() { - @Override - boolean followRedirects() { - return true; - } - - @Override - int numberOfRedirectsToFollow() { - return DEFAULT_REDIRECTS; - } - }; - } - - private RedirectStrategy() {} -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverter.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverter.java deleted file mode 100644 index cbac69bd5..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import java.io.IOException; -import okhttp3.RequestBody; -import org.chromium.net.UploadDataProvider; - -/** An interface for classes converting from OkHttp to Cronet request bodies. */ -interface RequestBodyConverter { - UploadDataProvider convertRequestBody(RequestBody requestBody, int writeTimeoutMillis) - throws IOException; -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverterImpl.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverterImpl.java deleted file mode 100644 index 89babfe9d..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestBodyConverterImpl.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import androidx.annotation.VisibleForTesting; -import com.google.common.base.Verify; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.Uninterruptibles; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeoutException; -import okhttp3.RequestBody; -import okio.Buffer; -import okio.BufferedSink; -import okio.Okio; -import org.chromium.net.UploadDataProvider; -import org.chromium.net.UploadDataSink; - -final class RequestBodyConverterImpl implements RequestBodyConverter { - - private static final long IN_MEMORY_BODY_LENGTH_THRESHOLD_BYTES = 1024 * 1024; - - private final InMemoryRequestBodyConverter inMemoryRequestBodyConverter; - private final StreamingRequestBodyConverter streamingRequestBodyConverter; - - RequestBodyConverterImpl( - InMemoryRequestBodyConverter inMemoryConverter, - StreamingRequestBodyConverter streamingConverter) { - this.inMemoryRequestBodyConverter = inMemoryConverter; - this.streamingRequestBodyConverter = streamingConverter; - } - - static RequestBodyConverterImpl create(ExecutorService bodyReaderExecutor) { - return new RequestBodyConverterImpl( - new InMemoryRequestBodyConverter(), new StreamingRequestBodyConverter(bodyReaderExecutor)); - } - - @Override - public UploadDataProvider convertRequestBody(RequestBody requestBody, int writeTimeoutMillis) - throws IOException { - long contentLength = requestBody.contentLength(); - if (contentLength == -1 || contentLength > IN_MEMORY_BODY_LENGTH_THRESHOLD_BYTES) { - return streamingRequestBodyConverter.convertRequestBody(requestBody, writeTimeoutMillis); - } else { - return inMemoryRequestBodyConverter.convertRequestBody(requestBody, writeTimeoutMillis); - } - } - - /** - * Implementation of {@link RequestBodyConverter} that doesn't need to hold the entire request - * body in memory. - * - *

- * - *

    - *
  1. {@link RequestBody#writeTo(BufferedSink)} is invoked on the body, but the sink doesn't - * accept any data - *
  2. A call to {@link UploadDataProvider#read(UploadDataSink, ByteBuffer)} unblocks the sink, - * which accepts a part of the body (size depends on the buffer's capacity), then blocks - * again. Buffer is sent to Cronet. - *
- * - * This is repeated until the entire body has been read. - */ - @VisibleForTesting - static final class StreamingRequestBodyConverter implements RequestBodyConverter { - - private final ExecutorService readerExecutor; - - StreamingRequestBodyConverter(ExecutorService readerExecutor) { - this.readerExecutor = readerExecutor; - } - - @Override - public UploadDataProvider convertRequestBody(RequestBody requestBody, int writeTimeoutMillis) { - return new StreamingUploadDataProvider( - requestBody, new UploadBodyDataBroker(), readerExecutor, writeTimeoutMillis); - } - - private static class StreamingUploadDataProvider extends UploadDataProvider { - private final RequestBody okHttpRequestBody; - private final UploadBodyDataBroker broker; - private final ListeningExecutorService readTaskExecutor; - private final long writeTimeoutMillis; - - /** The future for the task that reads the OkHttp request body in the background. */ - private ListenableFuture readTaskFuture; - - /** The number of bytes we read from the OkHttp body thus far. */ - private long totalBytesReadFromOkHttp; - - private StreamingUploadDataProvider( - RequestBody okHttpRequestBody, - UploadBodyDataBroker broker, - ExecutorService readTaskExecutor, - long writeTimeoutMillis) { - this.okHttpRequestBody = okHttpRequestBody; - this.broker = broker; - if (readTaskExecutor instanceof ListeningExecutorService) { - this.readTaskExecutor = (ListeningExecutorService) readTaskExecutor; - } else { - this.readTaskExecutor = MoreExecutors.listeningDecorator(readTaskExecutor); - } - - // So that we don't have to special case infinity. Int.MAX_VALUE is ~infinity for all - // practical use cases. - this.writeTimeoutMillis = writeTimeoutMillis == 0 ? Integer.MAX_VALUE : writeTimeoutMillis; - } - - @Override - public long getLength() throws IOException { - return okHttpRequestBody.contentLength(); - } - - @Override - public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException { - ensureReadTaskStarted(); - - if (getLength() == -1) { - readUnknownBodyLength(uploadDataSink, byteBuffer); - } else { - readKnownBodyLength(uploadDataSink, byteBuffer); - } - } - - private void readKnownBodyLength(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) - throws IOException { - try { - UploadBodyDataBroker.ReadResult readResult = readFromOkHttp(byteBuffer); - - if (totalBytesReadFromOkHttp > getLength()) { - throw prepareBodyTooLongException(getLength(), totalBytesReadFromOkHttp); - } - - if (totalBytesReadFromOkHttp < getLength()) { - switch (readResult) { - case SUCCESS: - uploadDataSink.onReadSucceeded(false); - break; - case END_OF_BODY: - throw new IOException("The source has been exhausted but we expected more data!"); - } - return; - } - // Else we're handling what's supposed to be the last chunk - handleLastBodyRead(uploadDataSink, byteBuffer); - - } catch (TimeoutException | ExecutionException e) { - readTaskFuture.cancel(true); - uploadDataSink.onReadError(new IOException(e)); - } - } - - /** - * The last body read is special for fixed length bodies - if Cronet receives exactly the - * right amount of data it won't ask for more, even if there is more data in the stream. As a - * result, when we read the advertised number of bytes, we need to make sure that the stream - * is indeed finished. - */ - private void handleLastBodyRead(UploadDataSink uploadDataSink, ByteBuffer filledByteBuffer) - throws IOException, TimeoutException, ExecutionException { - // We reuse the same buffer for the END_OF_DATA read (it should be non-destructive and if - // it overwrites what's in there we don't mind as that's an error anyway). We just need - // to make sure we restore the original position afterwards. We don't use mark() / reset() - // as the mark position can be invalidated by limit manipulation. - int bufferPosition = filledByteBuffer.position(); - filledByteBuffer.position(0); - - UploadBodyDataBroker.ReadResult readResult = readFromOkHttp(filledByteBuffer); - - if (!readResult.equals(UploadBodyDataBroker.ReadResult.END_OF_BODY)) { - throw prepareBodyTooLongException(getLength(), totalBytesReadFromOkHttp); - } - - Verify.verify( - filledByteBuffer.position() == 0, - "END_OF_BODY reads shouldn't write anything to the buffer"); - - // revert the position change - filledByteBuffer.position(bufferPosition); - - uploadDataSink.onReadSucceeded(false); - } - - private void readUnknownBodyLength(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { - try { - UploadBodyDataBroker.ReadResult readResult = readFromOkHttp(byteBuffer); - uploadDataSink.onReadSucceeded(readResult.equals(UploadBodyDataBroker.ReadResult.END_OF_BODY)); - } catch (TimeoutException | ExecutionException e) { - readTaskFuture.cancel(true); - uploadDataSink.onReadError(new IOException(e)); - } - } - - private void ensureReadTaskStarted() { - // We don't expect concurrent calls so a simple flag is sufficient - if (readTaskFuture == null) { - readTaskFuture = - readTaskExecutor.submit( - (Callable) - () -> { - BufferedSink bufferedSink = Okio.buffer(broker); - okHttpRequestBody.writeTo(bufferedSink); - bufferedSink.flush(); - broker.handleEndOfStreamSignal(); - return null; - }); - - Futures.addCallback( - readTaskFuture, - new FutureCallback() { - @Override - public void onSuccess(Object result) {} - - @Override - public void onFailure(Throwable t) { - broker.setBackgroundReadError(t); - } - }, - MoreExecutors.directExecutor()); - } - } - - private UploadBodyDataBroker.ReadResult readFromOkHttp(ByteBuffer byteBuffer) - throws TimeoutException, ExecutionException { - int positionBeforeRead = byteBuffer.position(); - UploadBodyDataBroker.ReadResult readResult = - Uninterruptibles.getUninterruptibly( - broker.enqueueBodyRead(byteBuffer), writeTimeoutMillis, MILLISECONDS); - int bytesRead = byteBuffer.position() - positionBeforeRead; - totalBytesReadFromOkHttp += bytesRead; - return readResult; - } - - private static IOException prepareBodyTooLongException( - long expectedLength, long minActualLength) { - return new IOException( - "Expected " + expectedLength + " bytes but got at least " + minActualLength); - } - - @Override - public void rewind(UploadDataSink uploadDataSink) { - // TODO(danstahr): OkHttp 4 can use isOneShot flag here and rewind safely. - uploadDataSink.onRewindError(new UnsupportedOperationException("Rewind is not supported!")); - } - } - } - - /** - * Converts OkHttp's {@link RequestBody} to Cronet's {@link UploadDataProvider} by materializing - * the body in memory first. - * - *

This strategy shouldn't be used for large requests (and for requests with uncapped length) - * to avoid OOM issues. - */ - @VisibleForTesting - static final class InMemoryRequestBodyConverter implements RequestBodyConverter { - - @Override - public UploadDataProvider convertRequestBody(RequestBody requestBody, int writeTimeoutMillis) - throws IOException { - - // content length is immutable by contract - long length = requestBody.contentLength(); - if (length < 0 || length > IN_MEMORY_BODY_LENGTH_THRESHOLD_BYTES) { - throw new IOException( - "Expected definite length less than " - + IN_MEMORY_BODY_LENGTH_THRESHOLD_BYTES - + "but got " - + length); - } - - return new UploadDataProvider() { - private volatile boolean isMaterialized = false; - private final Buffer materializedBody = new Buffer(); - - @Override - public long getLength() { - return length; - } - - @Override - public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException { - // We're not expecting any concurrent calls here so a simple flag should be sufficient. - if (!isMaterialized) { - requestBody.writeTo(materializedBody); - materializedBody.flush(); - isMaterialized = true; - long reportedLength = getLength(); - long actualLength = materializedBody.size(); - if (actualLength != reportedLength) { - throw new IOException( - "Expected " + reportedLength + " bytes but got " + actualLength); - } - } - if (materializedBody.read(byteBuffer) == -1) { - // This should never happen - for known body length we shouldn't be called at all - // if there's no more data to read. - throw new IllegalStateException("The source has been exhausted but we expected more!"); - } - uploadDataSink.onReadSucceeded(false); - } - - @Override - public void rewind(UploadDataSink uploadDataSink) { - // TODO(danstahr): OkHttp 4 can use isOneShot flag here and rewind safely. - uploadDataSink.onRewindError(new UnsupportedOperationException()); - } - }; - } - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverter.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverter.java deleted file mode 100644 index e53617ca8..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverter.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import java.io.IOException; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import org.chromium.net.CronetEngine; -import org.chromium.net.UrlRequest; - -/** Converts OkHttp requests to Cronet requests. */ -final class RequestResponseConverter { - private static final String CONTENT_LENGTH_HEADER_NAME = "Content-Length"; - private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type"; - private static final String CONTENT_TYPE_HEADER_DEFAULT_VALUE = "application/octet-stream"; - - private final CronetEngine cronetEngine; - private final Executor uploadDataProviderExecutor; - private final ResponseConverter responseConverter; - private final RequestBodyConverter requestBodyConverter; - private final RedirectStrategy redirectStrategy; - - RequestResponseConverter( - CronetEngine cronetEngine, - Executor uploadDataProviderExecutor, - RequestBodyConverter requestBodyConverter, - ResponseConverter responseConverter, - RedirectStrategy redirectStrategy) { - this.cronetEngine = cronetEngine; - this.uploadDataProviderExecutor = uploadDataProviderExecutor; - this.requestBodyConverter = requestBodyConverter; - this.responseConverter = responseConverter; - this.redirectStrategy = redirectStrategy; - } - - /** - * Converts OkHttp's {@link Request} to a corresponding Cronet's {@link UrlRequest}. - * - *

Since Cronet doesn't have a notion of a Response, which is handled entirely from the - * callbacks, this method also returns a {@link java.util.concurrent.Future} like object the - * caller should use to obtain the matching {@link Response} for the given request. For example: - * - *

-     *   RequestResponseConverter converter = ...
-     *   CronetRequestAndOkHttpResponse reqResp = converter.convert(okHttpRequest);
-     *   reqResp.getRequest.start();
-     *
-     *   // Will block until status code, headers... are available
-     *   Response okHttpResponse = reqResp.getResponse();
-     *
-     *   // use OkHttp Response as usual
-     * 
- */ - CronetRequestAndOkHttpResponse convert( - Request okHttpRequest, int readTimeoutMillis, int writeTimeoutMillis) throws IOException { - - OkHttpBridgeRequestCallback callback = - new OkHttpBridgeRequestCallback(readTimeoutMillis, redirectStrategy); - - // The OkHttp request callback methods are lightweight, the heavy lifting is done by OkHttp / - // app owned threads. Use a direct executor to avoid extra thread hops. - UrlRequest.Builder builder = - cronetEngine - .newUrlRequestBuilder( - okHttpRequest.url().toString(), callback, MoreExecutors.directExecutor()) - .allowDirectExecutor(); - - builder.setHttpMethod(okHttpRequest.method()); - - for (int i = 0; i < okHttpRequest.headers().size(); i++) { - builder.addHeader(okHttpRequest.headers().name(i), okHttpRequest.headers().value(i)); - } - - RequestBody body = okHttpRequest.body(); - - if (body != null) { - if (okHttpRequest.header(CONTENT_LENGTH_HEADER_NAME) == null && body.contentLength() != -1) { - builder.addHeader(CONTENT_LENGTH_HEADER_NAME, String.valueOf(body.contentLength())); - } - - if (body.contentLength() != 0) { - if (body.contentType() != null) { - builder.addHeader(CONTENT_TYPE_HEADER_NAME, body.contentType().toString()); - } else if (okHttpRequest.header(CONTENT_TYPE_HEADER_NAME) == null) { - // Cronet always requires content-type to be present when a body is present. Use a generic - // value if one isn't provided. - builder.addHeader(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_DEFAULT_VALUE); - } // else use the header - - builder.setUploadDataProvider( - requestBodyConverter.convertRequestBody(body, writeTimeoutMillis), - uploadDataProviderExecutor); - } - } - - return new CronetRequestAndOkHttpResponse( - builder.build(), createResponseSupplier(okHttpRequest, callback)); - } - - private ResponseSupplier createResponseSupplier( - Request request, OkHttpBridgeRequestCallback callback) { - return new ResponseSupplier() { - @Override - public Response getResponse() throws IOException { - return responseConverter.toResponse(request, callback); - } - - @Override - public ListenableFuture getResponseFuture() { - return responseConverter.toResponseAsync(request, callback); - } - }; - } - - /** A {@link Future} like holder for OkHttp's {@link Response}. */ - private interface ResponseSupplier { - Response getResponse() throws IOException; - - ListenableFuture getResponseFuture(); - } - - /** A simple data class for bundling Cronet request and OkHttp response. */ - static final class CronetRequestAndOkHttpResponse { - private final UrlRequest request; - private final ResponseSupplier responseSupplier; - - CronetRequestAndOkHttpResponse(UrlRequest request, ResponseSupplier responseSupplier) { - this.request = request; - this.responseSupplier = responseSupplier; - } - - public UrlRequest getRequest() { - return request; - } - - public Response getResponse() throws IOException { - return responseSupplier.getResponse(); - } - - public ListenableFuture getResponseAsync() { - return responseSupplier.getResponseFuture(); - } - } -} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverterBasedBuilder.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverterBasedBuilder.java deleted file mode 100644 index fbdaccf3f..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/RequestResponseConverterBasedBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.tonapps.wallet.api.cronet; - - -import static com.google.android.gms.common.internal.Preconditions.checkArgument; -import static com.google.android.gms.common.internal.Preconditions.checkNotNull; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import org.chromium.net.CronetEngine; -import org.chromium.net.UploadDataProvider; - -abstract class RequestResponseConverterBasedBuilder< - SubBuilderT extends RequestResponseConverterBasedBuilder, - ObjectBeingBuiltT> { - private static final int DEFAULT_THREAD_POOL_SIZE = 4; - - private final CronetEngine cronetEngine; - private int uploadDataProviderExecutorSize = DEFAULT_THREAD_POOL_SIZE; - // Not setting the default straight away to lazy initialize the object if it ends up not being - // used. - private RedirectStrategy redirectStrategy = null; - private final SubBuilderT castedThis; - - @SuppressWarnings("unchecked") // checked as a precondition - RequestResponseConverterBasedBuilder(CronetEngine cronetEngine, Class clazz) { - this.cronetEngine = checkNotNull(cronetEngine); - checkArgument(this.getClass().equals(clazz)); - castedThis = (SubBuilderT) this; - } - - /** - * Sets the size of upload data provider executor. The same executor is used for all upload data - * providers within the interceptor. - * - * @see org.chromium.net.UrlRequest.Builder#setUploadDataProvider(UploadDataProvider, Executor) - */ - public final SubBuilderT setUploadDataProviderExecutorSize(int size) { - checkArgument(size > 0, "The number of threads must be positive!"); - uploadDataProviderExecutorSize = size; - return castedThis; - } - - /** - * Sets the strategy for following redirects. - * - *

Note that the Cronet (i.e. Chromium) wide safeguards will still apply if one attempts to - * follow redirects too many times. - */ - public final SubBuilderT setRedirectStrategy(RedirectStrategy redirectStrategy) { - checkNotNull(redirectStrategy); - this.redirectStrategy = redirectStrategy; - return castedThis; - } - - abstract ObjectBeingBuiltT build(RequestResponseConverter converter); - - public final ObjectBeingBuiltT build() { - if (redirectStrategy == null) { - redirectStrategy = RedirectStrategy.defaultStrategy(); - } - - RequestResponseConverter converter = - new RequestResponseConverter( - cronetEngine, - Executors.newFixedThreadPool(uploadDataProviderExecutorSize), - // There must always be enough executors to blocking-read the OkHttp request bodies - // otherwise deadlocks can occur. - RequestBodyConverterImpl.create(Executors.newCachedThreadPool()), - new ResponseConverter(), - redirectStrategy); - - return build(converter); - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/ResponseConverter.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/ResponseConverter.java deleted file mode 100644 index 42f26fb65..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/ResponseConverter.java +++ /dev/null @@ -1,249 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static com.google.firebase.components.Preconditions.checkArgument; -import static com.google.firebase.components.Preconditions.checkNotNull; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.common.base.Ascii; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.Uninterruptibles; - -import java.io.IOException; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.Okio; -import okio.Source; -import org.chromium.net.UrlResponseInfo; - -/** - * Converts Cronet's responses (or, more precisely, its chunks as they come from Cronet's {@link - * org.chromium.net.UrlRequest.Callback}), to OkHttp's {@link Response}. - */ -final class ResponseConverter { - private static final String CONTENT_LENGTH_HEADER_NAME = "Content-Length"; - private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type"; - private static final String CONTENT_ENCODING_HEADER_NAME = "Content-Encoding"; - - // https://source.chromium.org/search?q=symbol:FilterSourceStream::ParseEncodingType%20f:cc - private static final ImmutableSet ENCODINGS_HANDLED_BY_CRONET = - ImmutableSet.of("br", "deflate", "gzip", "x-gzip"); - - private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); - - /** - * Creates an OkHttp's Response from the OkHttp-Cronet bridging callback. - * - *

As long as the callback's {@code UrlResponseInfo} is available this method is non-blocking. - * However, this method doesn't fetch the entire body response. As a result, subsequent calls to - * the result's {@link Response#body()} methods might block further. - */ - Response toResponse(Request request, OkHttpBridgeRequestCallback callback) throws IOException { - UrlResponseInfo cronetResponseInfo = getFutureValue(callback.getUrlResponseInfo()); - Response.Builder responseBuilder = - createResponse(request, cronetResponseInfo, getFutureValue(callback.getBodySource())); - - List redirectResponseInfos = callback.getUrlResponseInfoChain(); - List urlChain = cronetResponseInfo.getUrlChain(); - - if (!redirectResponseInfos.isEmpty()) { - checkArgument( - urlChain.size() == redirectResponseInfos.size() + 1, - "The number of redirects should be consistent across URLs and headers!"); - - Response priorResponse = null; - for (int i = 0; i < redirectResponseInfos.size(); i++) { - Request redirectedRequest = request.newBuilder().url(urlChain.get(i)).build(); - priorResponse = - createResponse(redirectedRequest, redirectResponseInfos.get(i), null) - .priorResponse(priorResponse) - .build(); - } - - responseBuilder - .request(request.newBuilder().url(Iterables.getLast(urlChain)).build()) - .priorResponse(priorResponse); - } - - return responseBuilder.build(); - } - - ListenableFuture toResponseAsync( - Request request, OkHttpBridgeRequestCallback callback) { - return Futures.whenAllComplete(callback.getUrlResponseInfo(), callback.getBodySource()) - .call(() -> toResponse(request, callback), MoreExecutors.directExecutor()); - } - - private static Response.Builder createResponse( - Request request, UrlResponseInfo cronetResponseInfo, @Nullable Source bodySource) - throws IOException { - - Response.Builder responseBuilder = new Response.Builder(); - - @Nullable String contentType = getLastHeaderValue(CONTENT_TYPE_HEADER_NAME, cronetResponseInfo); - - // If all content encodings are those known to Cronet natively, Cronet decodes the body stream. - // Otherwise, it's sent to the callbacks verbatim. For consistency with OkHttp, we only leave - // the Content-Encoding headers if Cronet didn't decode the request. Similarly, for consistency, - // we strip the Content-Length header of decoded responses. - - @Nullable String contentLengthString = null; - - // Theoretically, the content encodings can be scattered across multiple comma separated - // Content-Encoding headers. This list contains individual encodings. - List contentEncodingItems = new ArrayList<>(); - - for (String contentEncodingHeaderValue : - getOrDefault( - cronetResponseInfo.getAllHeaders(), - CONTENT_ENCODING_HEADER_NAME, - Collections.emptyList())) { - Iterables.addAll(contentEncodingItems, COMMA_SPLITTER.split(contentEncodingHeaderValue)); - } - - boolean keepEncodingAffectedHeaders = - contentEncodingItems.isEmpty() - || !ENCODINGS_HANDLED_BY_CRONET.containsAll(contentEncodingItems); - - if (keepEncodingAffectedHeaders) { - contentLengthString = getLastHeaderValue(CONTENT_LENGTH_HEADER_NAME, cronetResponseInfo); - } - - ResponseBody responseBody = null; - if (bodySource != null) { - responseBody = - createResponseBody( - request, - cronetResponseInfo.getHttpStatusCode(), - contentType, - contentLengthString, - bodySource); - } - - responseBuilder - .request(request) - .code(cronetResponseInfo.getHttpStatusCode()) - .message(cronetResponseInfo.getHttpStatusText()) - .protocol(convertProtocol(cronetResponseInfo.getNegotiatedProtocol())) - .body(responseBody); - - for (Map.Entry header : cronetResponseInfo.getAllHeadersAsList()) { - boolean copyHeader = true; - if (!keepEncodingAffectedHeaders) { - if (Ascii.equalsIgnoreCase(header.getKey(), CONTENT_LENGTH_HEADER_NAME) - || Ascii.equalsIgnoreCase(header.getKey(), CONTENT_ENCODING_HEADER_NAME)) { - copyHeader = false; - } - } - if (copyHeader) { - responseBuilder.addHeader(header.getKey(), header.getValue()); - } - } - - return responseBuilder; - } - - /** - * Creates an OkHttp's ResponseBody from the OkHttp-Cronet bridging callback. - * - *

As long as the callback's {@code UrlResponseInfo} is available this method is non-blocking. - * However, this method doesn't fetch the entire body response. As a result, subsequent calls to - * {@link ResponseBody} methods might block further to fetch parts of the body. - */ - private static ResponseBody createResponseBody( - Request request, - int httpStatusCode, - @Nullable String contentType, - @Nullable String contentLengthString, - Source bodySource) - throws IOException { - - long contentLength; - - // Ignore content-length header for HEAD requests (consistency with OkHttp) - if (request.method().equals("HEAD")) { - contentLength = 0; - } else { - try { - contentLength = contentLengthString != null ? Long.parseLong(contentLengthString) : -1; - } catch (NumberFormatException e) { - // TODO(danstahr): add logging - contentLength = -1; - } - } - - // Check for absence of body in No Content / Reset Content responses (OkHttp consistency) - if ((httpStatusCode == 204 || httpStatusCode == 205) && contentLength > 0) { - throw new ProtocolException( - "HTTP " + httpStatusCode + " had non-zero Content-Length: " + contentLengthString); - } - - return ResponseBody.create( - contentType != null ? MediaType.parse(contentType) : null, - contentLength, - Okio.buffer(bodySource)); - } - - /** Converts Cronet's negotiated protocol string to OkHttp's {@link Protocol}. */ - private static Protocol convertProtocol(String negotiatedProtocol) { - // See - // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - if (negotiatedProtocol.contains("quic")) { - return Protocol.QUIC; - } else if (negotiatedProtocol.contains("h3")) { - // TODO(danstahr): Should be h3 for newer OkHttp - return Protocol.QUIC; - } else if (negotiatedProtocol.contains("spdy")) { - return Protocol.HTTP_2; - } else if (negotiatedProtocol.contains("h2")) { - return Protocol.HTTP_2; - } else if (negotiatedProtocol.contains("http/1.1")) { - return Protocol.HTTP_1_1; - } - - return Protocol.HTTP_1_0; - } - - /** Returns the last header value for the given name, or null if the header isn't present. */ - @Nullable - private static String getLastHeaderValue(String name, UrlResponseInfo responseInfo) { - List headers = responseInfo.getAllHeaders().get(name); - if (headers == null || headers.isEmpty()) { - return null; - } - return Iterables.getLast(headers); - } - - private static T getFutureValue(Future future) throws IOException { - try { - return Uninterruptibles.getUninterruptibly(future); - } catch (ExecutionException e) { - throw new IOException(e); - } - } - - private static V getOrDefault(Map map, K key, @NonNull V defaultValue) { - V value = map.get(key); - if (value == null) { - return checkNotNull(defaultValue); - } else { - return value; - } - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/UploadBodyDataBroker.java b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/UploadBodyDataBroker.java deleted file mode 100644 index 6229071d4..000000000 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/cronet/UploadBodyDataBroker.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.tonapps.wallet.api.cronet; - -import static com.google.android.gms.common.internal.Preconditions.checkState; - -import android.util.Pair; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.SettableFuture; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import okio.Buffer; -import okio.Sink; -import okio.Timeout; -import org.chromium.net.UploadDataSink; - -final class UploadBodyDataBroker implements Sink { - - /** - * The read request calls to {@link org.chromium.net.UploadDataProvider#read(UploadDataSink, - * ByteBuffer)} associated with this broker that we haven't started handling. - * - *

We don't expect more than one parallel read call for a single request body provider. - */ - private final BlockingQueue>> pendingRead = - new ArrayBlockingQueue<>(1); - - /** - * Whether the sink has been closed. - * - *

Calling close() has no practical use but we check that nobody tries to write to the sink - * after closing it, which is an indication of misuse. - */ - private final AtomicBoolean isClosed = new AtomicBoolean(); - - /** - * The exception thrown by the body reading background thread, if any. The exception will be - * rethrown every time someone attempts to continue reading the body. - */ - private final AtomicReference backgroundReadThrowable = new AtomicReference<>(); - - /** - * Indicates that Cronet is ready to receive another body part. - * - *

This method is executed by Cronet's upload data provider. - */ - Future enqueueBodyRead(ByteBuffer readBuffer) { - Throwable backgroundThrowable = backgroundReadThrowable.get(); - if (backgroundThrowable != null) { - return Futures.immediateFailedFuture(backgroundThrowable); - } - SettableFuture future = SettableFuture.create(); - pendingRead.add(Pair.create(readBuffer, future)); - - // Properly handle interleaving handleBackgroundReadError / enqueueBodyRead calls. - if ((backgroundThrowable = backgroundReadThrowable.get()) != null) { - future.setException(backgroundThrowable); - } - return future; - } - - /** - * Signals that reading the OkHttp body failed with the given throwable. - * - *

This method is executed by the background OkHttp body reading thread. - */ - void setBackgroundReadError(Throwable t) { - backgroundReadThrowable.set(t); - Pair> read = pendingRead.poll(); - if (read != null) { - read.second.setException(t); - } - } - - /** - * Signals that reading the body has ended and no future bytes will be sent. - * - *

This method is executed by the background OkHttp body reading thread. - */ - void handleEndOfStreamSignal() throws IOException { - if (isClosed.getAndSet(true)) { - throw new IllegalStateException("Already closed"); - } - - getPendingCronetRead().second.set(ReadResult.END_OF_BODY); - } - - /** - * {@inheritDoc} - * - *

This method is executed by the background OkHttp body reading thread. - */ - @Override - public void write(Buffer source, long byteCount) throws IOException { - // This is just a safeguard, close() is a no-op if the body length contract is honored. - checkState(!isClosed.get()); - - long bytesRemaining = byteCount; - - while (bytesRemaining != 0) { - Pair> payload = getPendingCronetRead(); - - ByteBuffer readBuffer = payload.first; - SettableFuture future = payload.second; - - int originalBufferLimit = readBuffer.limit(); - int bytesToDrain = (int) Math.min(originalBufferLimit, bytesRemaining); - - readBuffer.limit(bytesToDrain); - - try { - long bytesRead = source.read(readBuffer); - if (bytesRead == -1) { - IOException e = new IOException("The source has been exhausted but we expected more!"); - future.setException(e); - throw e; - } - bytesRemaining -= bytesRead; - readBuffer.limit(originalBufferLimit); - future.set(ReadResult.SUCCESS); - } catch (IOException e) { - future.setException(e); - throw e; - } - } - } - - private Pair> getPendingCronetRead() throws IOException { - try { - return pendingRead.take(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted while waiting for a read to finish!"); - } - } - - @Override - public void close() { - isClosed.set(true); - } - - @Override - public void flush() { - // Not necessary, we "flush" by sending the data to Cronet straight away when write() is called. - // Note that this class is wrapped with a okio buffer so writes to the outer layer won't be - // seen by this class immediately. - } - - @Override - public Timeout timeout() { - return Timeout.NONE; - } - - enum ReadResult { - SUCCESS, - END_OF_BODY - } -} \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountDetailsEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountDetailsEntity.kt index 2e1a7c20f..e8b7911f1 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountDetailsEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountDetailsEntity.kt @@ -1,11 +1,9 @@ package com.tonapps.wallet.api.entity import android.os.Parcelable -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.contract.BaseWalletContract import com.tonapps.blockchain.ton.contract.WalletVersion -import com.tonapps.blockchain.ton.extensions.isValidTonAddress -import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.blockchain.ton.extensions.toUserFriendly import io.tonapi.models.Account import io.tonapi.models.AccountStatus @@ -21,7 +19,7 @@ data class AccountDetailsEntity( val balance: Long, val new: Boolean = false, val initialized: Boolean, - val testnet: Boolean, + val network: TonNetwork, ): Parcelable { val address: String @@ -32,60 +30,60 @@ data class AccountDetailsEntity( constructor( contract: BaseWalletContract, - testnet: Boolean, + network: TonNetwork, new: Boolean = false, initialized: Boolean, ) : this( query = "", - preview = AccountEntity(contract.address, testnet), + preview = AccountEntity(contract.address, network), active = true, walletVersion = contract.getWalletVersion(), balance = 0, new = new, initialized = initialized, - testnet = testnet, + network = network, ) constructor( query: String, account: Account, - testnet: Boolean, + network: TonNetwork, new: Boolean = false ) : this( query = query, - preview = AccountEntity(account, testnet), + preview = AccountEntity(account, network), active = account.status == AccountStatus.active, - walletVersion = resolveVersion(testnet, account.interfaces, account.address), + walletVersion = resolveVersion(network, account.interfaces, account.address), balance = account.balance, new = new, initialized = account.status == AccountStatus.active || account.status == AccountStatus.frozen, - testnet = testnet, + network = network, ) constructor( query: String, wallet: Wallet, - testnet: Boolean, + network: TonNetwork, new: Boolean = false ) : this( query = query, - preview = AccountEntity(wallet, testnet), + preview = AccountEntity(wallet, network), active = wallet.status == AccountStatus.active, - walletVersion = resolveVersion(testnet, wallet.interfaces, wallet.address), + walletVersion = resolveVersion(network, wallet.interfaces, wallet.address), balance = wallet.balance, new = new, initialized = wallet.status == AccountStatus.active || wallet.status == AccountStatus.frozen, - testnet = testnet, + network = network, ) private companion object { - private fun resolveVersion(testnet: Boolean, interfaces: List?, address: String): WalletVersion { + private fun resolveVersion(network: TonNetwork, interfaces: List?, address: String): WalletVersion { val version = resolveVersionByInterface(interfaces) if (version == WalletVersion.UNKNOWN) { return resolveVersionByAddress(address.toUserFriendly( wallet = true, - testnet = testnet, + testnet = network.isTestnet, )) } return version @@ -118,4 +116,4 @@ data class AccountDetailsEntity( } } -} \ No newline at end of file +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountEntity.kt index 9d563eab0..485c8e09e 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AccountEntity.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.api.entity import android.net.Uri import android.os.Parcelable +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.blockchain.ton.extensions.toUserFriendly @@ -32,8 +33,8 @@ data class AccountEntity( return name } - constructor(address: AddrStd, testnet: Boolean) : this( - address = address.toWalletAddress(testnet), + constructor(address: AddrStd, network: TonNetwork) : this( + address = address.toWalletAddress(network.isTestnet), accountId = address.toAccountId(), name = null, iconUri = null, @@ -41,8 +42,8 @@ data class AccountEntity( isScam = false, ) - constructor(model: AccountAddress, testnet: Boolean): this( - address = model.address.toUserFriendly(model.isWallet, testnet), + constructor(model: AccountAddress, network: TonNetwork): this( + address = model.address.toUserFriendly(model.isWallet, network.isTestnet), accountId = model.address.toRawAddress(), name = model.name, iconUri = model.icon?.toUri(), @@ -50,8 +51,8 @@ data class AccountEntity( isScam = model.isScam ) - constructor(account: Account, testnet: Boolean) : this( - address = account.address.toUserFriendly(account.isWallet, testnet), + constructor(account: Account, network: TonNetwork) : this( + address = account.address.toUserFriendly(account.isWallet, network.isTestnet), accountId = account.address.toRawAddress(), name = account.name, iconUri = account.icon?.toUri(), @@ -59,8 +60,8 @@ data class AccountEntity( isScam = account.isScam ?: false ) - constructor(wallet: Wallet, testnet: Boolean) : this( - address = wallet.address.toUserFriendly(wallet.isWallet, testnet), + constructor(wallet: Wallet, network: TonNetwork) : this( + address = wallet.address.toUserFriendly(wallet.isWallet, network.isTestnet), accountId = wallet.address.toRawAddress(), name = wallet.name, iconUri = wallet.icon?.toUri(), diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AppVersion.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AppVersion.kt index 310a203c2..159214f2a 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AppVersion.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/AppVersion.kt @@ -1,7 +1,6 @@ package com.tonapps.wallet.api.entity import android.os.Parcelable -import android.util.Log import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/BalanceEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/BalanceEntity.kt index e2f61d979..343141d8d 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/BalanceEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/BalanceEntity.kt @@ -18,7 +18,9 @@ data class BalanceEntity( val isRequestMinting: Boolean = false, val isTransferable: Boolean = true, val lastActivity: Long = -1, -): Parcelable { + val numerator: BigDecimal? = null, + val denominator: BigDecimal? = null, +) : Parcelable { companion object { @@ -58,13 +60,41 @@ data class BalanceEntity( val blockchain: Blockchain get() = token.blockchain + val uiBalance: Coins + get() { + if (numerator == null || denominator == null) { + return value + } + + return Coins.of( + value.value.multiply(numerator).divide(denominator, 18, BigDecimal.ROUND_HALF_UP), + decimals + ) + } + + fun fromUIBalance(amount: Coins): Coins { + if (numerator == null || denominator == null) { + return amount + } + + return Coins.of( + amount.value.multiply(denominator).divide(numerator, 18, BigDecimal.ROUND_HALF_UP), + decimals + ) + } + constructor(jettonBalance: JettonBalance) : this( token = TokenEntity(jettonBalance.jetton, jettonBalance.extensions, jettonBalance.lock), - value = Coins.of(BigDecimal(jettonBalance.balance).movePointLeft(jettonBalance.jetton.decimals), jettonBalance.jetton.decimals), + value = Coins.of( + BigDecimal(jettonBalance.balance).movePointLeft(jettonBalance.jetton.decimals), + jettonBalance.jetton.decimals + ), walletAddress = jettonBalance.walletAddress.address, initializedAccount = true, isRequestMinting = jettonBalance.extensions?.contains(TokenEntity.Extension.CustomPayload.value) == true, - isTransferable = jettonBalance.extensions?.contains(TokenEntity.Extension.NonTransferable.value) != true + isTransferable = jettonBalance.extensions?.contains(TokenEntity.Extension.NonTransferable.value) != true, + numerator = jettonBalance.jetton.scaledUi?.numerator?.toBigDecimal(), + denominator = jettonBalance.jetton.scaledUi?.denominator?.toBigDecimal() ) { rates = jettonBalance.price } diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigEntity.kt index cbaacb6d1..13af8948a 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigEntity.kt @@ -2,7 +2,7 @@ package com.tonapps.wallet.api.entity import android.net.Uri import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.toStringList import com.tonapps.icu.Coins import kotlinx.parcelize.IgnoredOnParcel @@ -65,6 +65,7 @@ data class ConfigEntity( val privacyPolicyUrl: String, val termsOfUseUrl: String, val webSwapsUrl: String, + val tronFeeFaqUrl: String, ): Parcelable { @IgnoredOnParcel @@ -145,7 +146,8 @@ data class ConfigEntity( // tronApiKey = json.optString("tron_api_key"), privacyPolicyUrl = json.getString("privacy_policy"), termsOfUseUrl = json.getString("terms_of_use"), - webSwapsUrl = json.optString("web_swaps_url", Constants.SWAP_PREFIX) + webSwapsUrl = json.optString("web_swaps_url", Constants.SWAP_PREFIX), + tronFeeFaqUrl = json.getString("faq_tron_fee_url"), ) constructor() : this( @@ -164,7 +166,7 @@ data class ConfigEntity( tonkeeperNewsUrl = "https://t.me/tonkeeper_new", tonCommunityUrl = "https://t.me/toncoin", tonCommunityChatUrl = "https://t.me/toncoin_chat", - tonApiV2Key = "", + tonApiV2Key = "AF77F5JNEUSNXPQAAAAMDXXG7RBQ3IRP6PC2HTHL4KYRWMZYOUQGDEKYFDKBETZ6FDVZJBI", featuredPlayInterval = 3000, flags = FlagsEntity(), faqUrl = "https://tonkeeper.helpscoutdocs.com/", @@ -199,7 +201,8 @@ data class ConfigEntity( tronSwapTitle = "LetsExchange", privacyPolicyUrl = "https://tonkeeper.com/privacy", termsOfUseUrl = "https://tonkeeper.com/terms", - webSwapsUrl = Constants.SWAP_PREFIX + webSwapsUrl = Constants.SWAP_PREFIX, + tronFeeFaqUrl = "https://tonkeeper.helpscoutdocs.com/article/137-multichain" ) fun formatTransactionExplorer(testnet: Boolean, tron: Boolean, hash: String): String { diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigResponseEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigResponseEntity.kt new file mode 100644 index 000000000..b1a9da2de --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/ConfigResponseEntity.kt @@ -0,0 +1,20 @@ +package com.tonapps.wallet.api.entity + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.json.JSONObject + +@Parcelize +data class ConfigResponseEntity( + val mainnet: ConfigEntity, + val testnet: ConfigEntity, + val tetra: ConfigEntity, +): Parcelable { + constructor(json: JSONObject, debug: Boolean) : this( + mainnet = ConfigEntity(json.getJSONObject("mainnet"), debug), + testnet = ConfigEntity(json.getJSONObject("testnet"), debug), + tetra = ConfigEntity(json.getJSONObject("tetra"), debug) + ) +} + + diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EmulateWithBatteryResult.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EmulateWithBatteryResult.kt new file mode 100644 index 000000000..c84911039 --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EmulateWithBatteryResult.kt @@ -0,0 +1,9 @@ +package com.tonapps.wallet.api.entity + +import io.tonapi.models.MessageConsequences + +data class EmulateWithBatteryResult( + val consequences: MessageConsequences, + val withBattery: Boolean, + val excess: Long?, +) \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/FlagsEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/FlagsEntity.kt index 46dfe74f0..4628fbf26 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/FlagsEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/FlagsEntity.kt @@ -18,7 +18,8 @@ data class FlagsEntity( val disableUsde: Boolean, val disableNativeSwap: Boolean, val disableOnboardingStory: Boolean, - val disableNfts: Boolean + val disableNfts: Boolean, + val disableWalletKit: Boolean ) : Parcelable { constructor(json: JSONObject) : this( @@ -34,7 +35,8 @@ data class FlagsEntity( disableUsde = json.optBoolean("disable_usde", false), disableNativeSwap = json.optBoolean("disable_native_swap", false), disableOnboardingStory = json.optBoolean("disable_onboarding_story", false), - disableNfts = json.optBoolean("disable_nfts", false) + disableNfts = json.optBoolean("disable_nfts", false), + disableWalletKit = json.optBoolean("disable_wallet_kit", true) ) constructor() : this( @@ -50,6 +52,7 @@ data class FlagsEntity( disableUsde = false, disableNativeSwap = false, disableOnboardingStory = false, - disableNfts = false + disableNfts = false, + disableWalletKit = true, ) } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/QRScannerExtendsEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/QRScannerExtendsEntity.kt index 3ff6fadcd..4fc801862 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/QRScannerExtendsEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/QRScannerExtendsEntity.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.api.entity import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.json.JSONArray diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/TokenEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/TokenEntity.kt index d5ed94e17..538909d72 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/TokenEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/TokenEntity.kt @@ -5,7 +5,7 @@ import android.os.Parcelable import com.tonapps.blockchain.ton.extensions.cellFromHex import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.toRawAddress -import com.tonapps.wallet.api.R +import com.tonapps.icu.Coins import com.tonapps.wallet.api.entity.value.Blockchain import io.tonapi.models.JettonBalanceLock import io.tonapi.models.JettonInfo @@ -18,6 +18,8 @@ import org.ton.block.StateInit import org.ton.cell.Cell import org.ton.tlb.CellRef import org.ton.tlb.asRef +import java.math.BigDecimal +import com.tonapps.apps.wallet.api.R @Parcelize data class TokenEntity( @@ -31,7 +33,9 @@ data class TokenEntity( val isRequestMinting: Boolean, val isTransferable: Boolean, val lock: Lock? = null, - val customPayloadApiUri: String? + val customPayloadApiUri: String?, + val numerator: BigDecimal? = null, + val denominator: BigDecimal? = null, ): Parcelable { val isTsTON: Boolean @@ -93,6 +97,7 @@ data class TokenEntity( val USDT_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_usdt_with_bg.toString()).build() val USDE_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_udse_ethena_with_bg.toString()).build() val TS_USDE_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_tsusde_with_bg.toString()).build() + val TRX_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_trx.toString()).build() const val TRC20_USDT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" const val TON_USDT = "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" @@ -139,6 +144,19 @@ data class TokenEntity( customPayloadApiUri = null ) + val TRX = TokenEntity( + blockchain = Blockchain.TRON, + address = "TRX", + name = "Tron TRX", + symbol = "TRX", + imageUri = TRX_ICON_URI, + decimals = 6, + verification = Verification.whitelist, + isRequestMinting = false, + isTransferable = true, + customPayloadApiUri = null + ) + val USDE = TokenEntity( blockchain = Blockchain.TON, address = TON_USDE, @@ -187,12 +205,39 @@ data class TokenEntity( address == TRC20_USDT } + @IgnoredOnParcel + val isTrx: Boolean by lazy { + address == TRX.address + } + + val tokenType: String by lazy { // TODO remove after deposit merge + when { + isTrc20 -> "trc20" + else -> "ton" + } + } + + val plainSymbol: String by lazy { // TODO remove after deposit merge + return@lazy symbol.replace("₮", "T") + } + val verified: Boolean get() = verification == Verification.whitelist val blacklist: Boolean get() = verification == TokenEntity.Verification.blacklist + fun toUIAmount(amount: Coins): Coins { + if (numerator == null || denominator == null) { + return amount + } + + return Coins.of( + amount.value * numerator / denominator, + decimals + ) + } + constructor( jetton: JettonPreview, extensions: List? = null, @@ -208,7 +253,9 @@ data class TokenEntity( isRequestMinting = extensions?.contains(Extension.CustomPayload.value) == true, isTransferable = extensions?.contains(Extension.NonTransferable.value) != true, lock = lock?.let { Lock(it) }, - customPayloadApiUri = jetton.customPayloadApiUri + customPayloadApiUri = jetton.customPayloadApiUri, + numerator = jetton.scaledUi?.numerator?.toBigDecimal(), + denominator = jetton.scaledUi?.denominator?.toBigDecimal() ) constructor( diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/BlockchainAddress.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/BlockchainAddress.kt index 3ff6a6180..b4cbd38a1 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/BlockchainAddress.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/BlockchainAddress.kt @@ -1,22 +1,23 @@ package com.tonapps.wallet.api.entity.value import android.os.Parcelable +import com.tonapps.blockchain.ton.TonNetwork import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize data class BlockchainAddress( val value: String, - val testnet: Boolean, + val network: TonNetwork, val blockchain: Blockchain, ): Parcelable { @IgnoredOnParcel val key: String by lazy { - if (testnet) { - "${blockchain.id}:$value:testnet" - } else { - "${blockchain.id}:$value" + when (network) { + TonNetwork.TESTNET -> "${blockchain.id}:$value:testnet" + TonNetwork.TETRA -> "${blockchain.id}:$value:tetra" + TonNetwork.MAINNET -> "${blockchain.id}:$value" } } @@ -27,13 +28,17 @@ data class BlockchainAddress( return if (split.size == 2) { BlockchainAddress( value = split[1], - testnet = false, + network = TonNetwork.MAINNET, blockchain = Blockchain.valueOf(split[0]) ) } else { BlockchainAddress( value = split[2], - testnet = split[1] == "testnet", + network = when (split[1]) { + "testnet" -> TonNetwork.TESTNET + "tetra" -> TonNetwork.TETRA + else -> TonNetwork.MAINNET + }, blockchain = Blockchain.valueOf(split[0]) ) } diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/LoggingInterceptor.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/LoggingInterceptor.kt new file mode 100644 index 000000000..6bc8b15fa --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/LoggingInterceptor.kt @@ -0,0 +1,166 @@ +package com.tonapps.wallet.api.interceptors + +import android.os.SystemClock +import com.tonapps.log.L +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import okhttp3.internal.http.promisesBody +import okio.Buffer +import java.io.PrintWriter +import java.io.StringWriter +import java.util.concurrent.atomic.AtomicInteger + +class LoggingInterceptor : Interceptor { + + private val requestIdGenerator = AtomicInteger(1) + + private val prefixer = LoggingPrefixer() + private val prefix = ThreadLocal() + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + return runBlocking { + prefix.set(prefixer.getPrefix()) + val requestId = requestIdGenerator.getAndIncrement() + val response = interceptWithDetailedLog(requestId, request, chain) + + response + } + } + + private fun interceptWithDetailedLog(requestId: Int, request: Request, chain: Interceptor.Chain): Response { + val requestLog = mutableListOf() + requestLog.add("----> [$requestId] =============== Request ===============") + requestLog.add("${request.method} ${request.url}") + + if (request.headers.size > 0) { + request.headers.forEach { (header, values) -> + if (header == "Authorization") { + requestLog.add("Authorization: ") + } else { + requestLog.add("$header: $values") + } + } + } + + when (val requestBody = request.body) { + null -> requestLog.add("") + else -> { + requestLog.add("") + requestLog.add("Request body:") + + val buffer = Buffer() + requestBody.writeTo(buffer) + requestLog.add(buffer.readUtf8()) + } + } + + requestLog.add("----> [$requestId] End of request") + netLog(requestLog) + + try { + val timeStartMs = SystemClock.elapsedRealtime() + val response = chain.proceed(request) + val timeEndMs = SystemClock.elapsedRealtime() + val duration = timeEndMs - timeStartMs + + val responseLog = mutableListOf() + responseLog.add("<---- [$requestId] =============== Response ===============") + responseLog.add("${response.code} ${response.message} ${request.url} (${duration}ms)") + + if (response.headers.size > 0) { + response.headers.forEach { (header, values) -> + responseLog.add("$header: $values") + } + } + + val responseBody = response.body + + responseLog.add("") + responseLog.add("Response body:") + + val isGzip = "gzip".equals(response.header("content-encoding"), ignoreCase = true) + val isStreaming = "text/event-stream".equals( + response.header("content-type")?.substringBefore(';'), + ignoreCase = true + ) + if (response.promisesBody() && !isGzip && !isStreaming) { + val source = responseBody.source() + source.request(Long.MAX_VALUE) // Buffer the entire body. + val buffer = source.buffer + + val response = buffer.clone().readString(Charsets.UTF_8) + responseLog.add(response) + } else if (isStreaming) { + responseLog.add("") + } else { + responseLog.add("") + } + + responseLog.add("<---- [$requestId] End of Response") + netLog(responseLog) + + return response + } catch (th: Throwable) { + logError(requestId, request, th) + throw th + } + } + + private fun logError(requestId: Int, request: Request, th: Throwable) { + val responseLog = mutableListOf().apply { + add("<---- [$requestId] Response") + add(request.url.toString()) + addAll(th.getStackTraceString().lines()) + add("<---- [$requestId] End of Response") + } + netErr(responseLog) + } + + private fun netLog(lines: List) { + val log = lines.joinToString("\n") + if (log.isNotBlank()) L.d("NetLog", "${prefix.get()} ${log.trimEnd()}") + } + + private fun netErr(lines: List) { + val log = lines.joinToString("\n") + if (log.isNotBlank()) L.e("NetLog", "${prefix.get()} ${log.trimEnd()}") + } + + private fun Throwable?.getStackTraceString(): String { + if (this == null) return "" + val sw = StringWriter() + val pw = PrintWriter(sw) + try { + this.printStackTrace(pw) + pw.flush() + return sw.toString() + } finally { + pw.close() + sw.close() + } + } + + private class LoggingPrefixer { + + private val startEmoji: Int = 129292 // 🤌 + private val endEmoji: Int = 129535 // 🧾 + private var lastUsedEmoji = startEmoji + + private val mutex = Mutex() + + suspend fun getPrefix(): String { + mutex.withLock { + if (lastUsedEmoji > endEmoji) { + lastUsedEmoji = startEmoji + } + return String(Character.toChars(lastUsedEmoji++)) + } + } + } +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/RateLimitInterceptor.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/RateLimitInterceptor.kt new file mode 100644 index 000000000..6aad7e036 --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/interceptors/RateLimitInterceptor.kt @@ -0,0 +1,60 @@ +package com.tonapps.wallet.api.interceptors + +import okhttp3.Interceptor +import okhttp3.Response +import java.util.ArrayDeque +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * OkHttp interceptor that implements rate limiting and automatic retry for 429 responses. + * + * Uses a sliding window algorithm with ArrayDeque to track requests per second. + * Implements non-blocking approach where possible using atomic operations. + * + * @param requestsPerSecondLimit Maximum number of requests allowed per second + */ +class RateLimitInterceptor( + private val requestsPerSecondLimit: Int = 10, +) : Interceptor { + + private val requestTimestamps = ArrayDeque(requestsPerSecondLimit) + private val lock = ReentrantLock() + + init { + require(requestsPerSecondLimit > 0) { "requestsPerSecondLimit must be positive" } + } + + companion object { + private const val WINDOW_MS = 1000L + } + + override fun intercept(chain: Interceptor.Chain): Response { + waitForRateLimit() + return chain.proceed(chain.request()) + } + + /** + * Waits if necessary to ensure we don't exceed the rate limit. + * Fixed-size ArrayDeque acts as a circular buffer of [requestsPerSecondLimit] elements. + * When full, checks if oldest request was within 1 second — if so, waits the remainder. + */ + private fun waitForRateLimit() { + lock.withLock { + val now = System.currentTimeMillis() + + if (requestTimestamps.size >= requestsPerSecondLimit) { + val oldest = requestTimestamps.first() + val delta = now - oldest + + if (delta < WINDOW_MS) { + Thread.sleep(WINDOW_MS - delta) + } + + requestTimestamps.removeFirst() + } + + requestTimestamps.addLast(now) + } + } +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/ConfigRepository.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/ConfigRepository.kt index 21ba64b93..3afeff37c 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/ConfigRepository.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/ConfigRepository.kt @@ -1,10 +1,12 @@ package com.tonapps.wallet.api.internal import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.file import com.tonapps.extensions.toByteArray import com.tonapps.extensions.toParcel import com.tonapps.wallet.api.entity.ConfigEntity +import com.tonapps.wallet.api.entity.ConfigResponseEntity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -18,18 +20,23 @@ internal class ConfigRepository( private val internalApi: InternalApi, ) { - private val configFile = context.cacheDir.file("config") + private val configFile = context.cacheDir.file("config_all") private val _stream = MutableStateFlow(ConfigEntity.default) val stream = _stream.asStateFlow() - var configEntity: ConfigEntity = ConfigEntity.default - private set (value) { + var configMainnetEntity: ConfigEntity = ConfigEntity.default + private set(value) { field = value - _stream.value = value.copy() internalApi.setApiUrl(value.tonkeeperApiUrl) } + var configTestnetEntity: ConfigEntity = ConfigEntity.default + private set + + var configTetraEntity: ConfigEntity = ConfigEntity.default + private set + init { scope.launch(Dispatchers.IO) { val cached = readCache() @@ -41,32 +48,43 @@ internal class ConfigRepository( } } - private suspend fun setConfig(config: ConfigEntity) = withContext(Dispatchers.Main) { - configEntity = config + private suspend fun setConfig(config: ConfigResponseEntity) = withContext(Dispatchers.Main) { + configMainnetEntity = config.mainnet + configTestnetEntity = config.testnet + configTetraEntity = config.tetra + _stream.value = configMainnetEntity } - private fun readCache(): ConfigEntity? { + private fun readCache(): ConfigResponseEntity? { if (configFile.exists() && configFile.length() > 0) { return configFile.readBytes().toParcel() } return null } - private suspend fun remote(testnet: Boolean): ConfigEntity? = withContext(Dispatchers.IO) { - val config = internalApi.downloadConfig(testnet) ?: return@withContext null - configFile.writeBytes(config.toByteArray()) - config + private suspend fun remote(): ConfigResponseEntity? = withContext(Dispatchers.IO) { + val response = internalApi.downloadConfig() ?: return@withContext null + configFile.writeBytes(response.toByteArray()) + response } - suspend fun refresh(testnet: Boolean) { - val config = remote(testnet) ?: return + suspend fun refresh() { + val config = remote() ?: return setConfig(config) } suspend fun initConfig() { - remote(false)?.let { + remote()?.let { setConfig(it) } } + fun getConfig(network: TonNetwork): ConfigEntity { + return when (network) { + TonNetwork.MAINNET -> configMainnetEntity + TonNetwork.TESTNET -> configTestnetEntity + TonNetwork.TETRA -> configTetraEntity + } + } + } \ No newline at end of file diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt index 380086a4b..9e4f1e652 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt @@ -1,20 +1,21 @@ package com.tonapps.wallet.api.internal import android.content.Context -import android.util.Log import androidx.collection.ArrayMap import androidx.core.net.toUri import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.isDebug import com.tonapps.extensions.locale import com.tonapps.extensions.map import com.tonapps.network.get import com.tonapps.network.postJSON -import com.tonapps.wallet.api.entity.ConfigEntity +import com.tonapps.wallet.api.entity.ConfigResponseEntity import com.tonapps.wallet.api.entity.EthenaEntity import com.tonapps.wallet.api.entity.NotificationEntity import com.tonapps.wallet.api.entity.OnRampArgsEntity import com.tonapps.wallet.api.entity.StoryEntity +import com.tonapps.wallet.api.readBody import com.tonapps.wallet.api.withRetry import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -47,7 +48,7 @@ internal class InternalApi( private fun endpoint( path: String, - testnet: Boolean, + network: TonNetwork, platform: String, build: String, boot: Boolean = false, @@ -61,12 +62,17 @@ internal class InternalApi( } else { _apiEndpoint.buildUpon() } + val chainName = when (network) { + TonNetwork.TESTNET -> "testnet" + TonNetwork.MAINNET -> "mainnet" + TonNetwork.TETRA -> "mainnet" + } builder .appendEncodedPath(path) .appendQueryParameter("lang", context.locale.language) .appendQueryParameter("build", build) .appendQueryParameter("platform", platform) - .appendQueryParameter("chainName", if (testnet) "testnet" else "mainnet") + .appendQueryParameter("chainName", chainName) .appendQueryParameter("bundle_id", context.packageName) _storeCountry?.let { @@ -85,7 +91,7 @@ internal class InternalApi( private fun request( path: String, - testnet: Boolean, + network: TonNetwork, platform: String = "android", build: String = appVersionName, locale: Locale, @@ -93,7 +99,7 @@ internal class InternalApi( queryParams: Map = emptyMap(), bootFallback: Boolean = false, ): JSONObject { - val url = endpoint(path, testnet, platform, build, boot, queryParams, bootFallback) + val url = endpoint(path, network, platform, build, boot, queryParams, bootFallback) val headers = ArrayMap() headers["Accept-Language"] = locale.toString() val body = withRetry { @@ -134,12 +140,17 @@ internal class InternalApi( okHttpClient.postJSON( swapEndpoint(prefix, "v2/onramp/calculate"), json.toString() - ).body.string() + ).readBody() } } fun getNotifications(): List { - val json = request("notifications", false, locale = context.locale) + val json = request( + path = "notifications", + network = TonNetwork.MAINNET, + locale = context.locale, + boot = false + ) val array = json.getJSONArray("notifications") val list = mutableListOf() for (i in 0 until array.length()) { @@ -173,29 +184,29 @@ internal class InternalApi( return (maskDomains + cleanDomains + telegramBots).toTypedArray() } - fun getBrowserApps(testnet: Boolean, locale: Locale): JSONObject { - val data = request("apps/popular", testnet, locale = locale) + fun getBrowserApps(network: TonNetwork, locale: Locale): JSONObject { + val data = request("apps/popular", network, locale = locale) return data.getJSONObject("data") } - fun getFiatMethods(testnet: Boolean = false, locale: Locale): JSONObject { - val data = request("fiat/methods", testnet, locale = locale) + fun getFiatMethods(network: TonNetwork = TonNetwork.MAINNET, locale: Locale): JSONObject { + val data = request("fiat/methods", network, locale = locale) return data.getJSONObject("data") } - fun downloadConfig(testnet: Boolean, fallback: Boolean = false): ConfigEntity? { + fun downloadConfig(fallback: Boolean = false): ConfigResponseEntity? { return try { val json = request( - "keys", - testnet, + "keys/all", + network = TonNetwork.MAINNET, locale = context.locale, boot = true, bootFallback = fallback ) - ConfigEntity(json, context.isDebug) + ConfigResponseEntity(json, context.isDebug) } catch (e: Throwable) { if (!fallback) { - downloadConfig(testnet, true) + downloadConfig(true) } else { FirebaseCrashlytics.getInstance().recordException(e) null @@ -205,7 +216,12 @@ internal class InternalApi( fun getStories(id: String): StoryEntity.Stories? { return try { - val json = request("stories/$id", false, locale = context.locale) + val json = request( + path = "stories/$id", + network = TonNetwork.MAINNET, + locale = context.locale, + boot = false + ) val pages = json.getJSONArray("pages") val list = mutableListOf() for (i in 0 until pages.length()) { @@ -224,7 +240,12 @@ internal class InternalApi( suspend fun resolveCountry(): String? = withContext(Dispatchers.IO) { try { - val json = request("my/ip", false, locale = context.locale) + val json = request( + path = "my/ip", + network = TonNetwork.MAINNET, + locale = context.locale, + boot = false + ) val country = json.getString("country") if (country.isNullOrBlank()) { null @@ -239,9 +260,10 @@ internal class InternalApi( fun getEthena(accountId: String): EthenaEntity? = withRetry { val json = request( - "staking/ethena", - false, + path = "staking/ethena", + network = TonNetwork.MAINNET, locale = context.locale, + boot = false, queryParams = mapOf("address" to accountId) ) EthenaEntity(json) diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/TronApi.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/TronApi.kt index 322f1258e..ae657b502 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/TronApi.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/TronApi.kt @@ -3,30 +3,46 @@ package com.tonapps.wallet.api.tron import android.net.Uri import androidx.collection.arrayMapOf import androidx.core.net.toUri +import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.blockchain.tron.TronTransaction import com.tonapps.blockchain.tron.TronTransfer import com.tonapps.blockchain.tron.encodeTronAddress import com.tonapps.blockchain.tron.tronHex +import com.tonapps.extensions.TimedCacheMemory +import com.tonapps.extensions.fromHex +import com.tonapps.extensions.getOrLoad import com.tonapps.extensions.map import com.tonapps.icu.Coins +import com.tonapps.log.L import com.tonapps.network.get import com.tonapps.network.postJSON +import com.tonapps.wallet.api.backoff.ExponentialBackoff import com.tonapps.wallet.api.entity.BalanceEntity import com.tonapps.wallet.api.entity.ConfigEntity import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.api.entity.value.Timestamp +import com.tonapps.wallet.api.readBody import com.tonapps.wallet.api.tron.entity.TronEstimationEntity import com.tonapps.wallet.api.tron.entity.TronEventEntity +import com.tonapps.wallet.api.tron.entity.TronResourcePrices import com.tonapps.wallet.api.tron.entity.TronResourcesEntity import com.tonapps.wallet.api.withRetry import io.batteryapi.apis.DefaultApi +import io.batteryapi.models.EstimatedTronTx +import io.batteryapi.models.EstimatedTronTxInstantFeeAcceptedAssetsInner import io.batteryapi.models.TronSendRequest import io.ktor.util.encodeBase64 +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.put import okhttp3.OkHttpClient import org.json.JSONObject +import org.ton.cell.Cell import java.math.BigInteger class TronApi( @@ -39,9 +55,16 @@ class TronApi( private const val DATA_HEX_PROTOBUF_EXTRA = 9 private const val MAX_RESULT_SIZE_IN_TX = 64 private const val A_SIGNATURE = 67 + private const val RESOURCE_PRICES_KEY = "resource_prices" } + val transferDefaultResources = TronResourcesEntity( + energy = 64285, + bandwidth = 345, + ) + private var safetyMargin: Double? = null + private val resourcePricesCache = TimedCacheMemory() private val tronApiKey: String? get() = config.tronApiKey?.ifBlank { null } @@ -79,7 +102,7 @@ class TronApi( } val response = post(url, requestBody) - val body = response.body.string() + val body = response.readBody() val json = JSONObject(body) val constantResultArray = json.optJSONArray("constant_result") @@ -91,7 +114,7 @@ class TronApi( value = Coins.of(balance.toLong(), TokenEntity.TRON_USDT.decimals), walletAddress = tronAddress, ) - } catch (e: Throwable) { + } catch (_: Throwable) { return BalanceEntity( token = TokenEntity.TRON_USDT, value = Coins.of(BigInteger.ZERO, TokenEntity.TRON_USDT.decimals), @@ -100,6 +123,33 @@ class TronApi( } } + fun getTrxBalance( + tronAddress: String, + ): BalanceEntity { + return try { + val builder = config.tronApiUrl.toUri().buildUpon() + .appendEncodedPath("v1/accounts/$tronAddress") + val url = builder.build() + + val body = get(url) + val json = JSONObject(body).getJSONArray("data") + + val balanceSun = json.optJSONObject(0)?.optLong("balance") ?: 0L + + return BalanceEntity( + token = TokenEntity.TRX, + value = Coins.of(balanceSun, TokenEntity.TRX.decimals), + walletAddress = tronAddress, + ) + } catch (_: Throwable) { + BalanceEntity( + token = TokenEntity.TRX, + value = Coins.of(BigInteger.ZERO, TokenEntity.TRX.decimals), + walletAddress = tronAddress, + ) + } + } + private fun getTronBlockchainHistory( tronAddress: String, limit: Int, @@ -118,7 +168,15 @@ class TronApi( val body = get(builder.build()) val json = JSONObject(body).getJSONArray("data") - return json.map { TronEventEntity(it) } + return json.map { + if (it.getString("type") == "Transfer" && it.getJSONObject("token_info") + .getString("address") == TokenEntity.TRC20_USDT + ) { + TronEventEntity(it) + } else { + null + } + }.filterNotNull() } private fun getBatteryTransfersHistory( @@ -134,21 +192,37 @@ class TronApi( return response.transactions.filter { it.txid.isNotEmpty() }.map { TronEventEntity(it) } } - fun getTronHistory( + suspend fun getTronHistory( tronAddress: String, tonProofToken: String, limit: Int, beforeTimestamp: Timestamp?, afterTimestamp: Timestamp? = null, ): List { - val blockchainEvents = getTronBlockchainHistory(tronAddress, limit, beforeTimestamp, afterTimestamp) - val batteryEvents = getBatteryTransfersHistory(tonProofToken, limit, beforeTimestamp) + val (blockchainEvents, batteryEvents) = coroutineScope { + val blockchainEventsDeferred = async { + getTronBlockchainHistory( + tronAddress, + limit, + beforeTimestamp, + afterTimestamp + ) + } + val batteryEventsDeferred = + async { getBatteryTransfersHistory(tonProofToken, limit, beforeTimestamp) } + Pair(blockchainEventsDeferred.await(), batteryEventsDeferred.await()) + } return (batteryEvents + blockchainEvents) .distinctBy { it.transactionHash } .sortedByDescending { it.timestamp } } + private fun estimateBandwidth(rawHex: String): Int { + return rawHex.fromHex().size + + DATA_HEX_PROTOBUF_EXTRA + MAX_RESULT_SIZE_IN_TX + A_SIGNATURE + } + private fun estimateResources(transfer: TronTransfer): TronResourcesEntity { val builder = config.tronApiUrl.toUri().buildUpon() @@ -164,7 +238,7 @@ class TronApi( } val response = post(url, requestBody) - val body = response.body.string() + val body = response.readBody() val json = JSONObject(body) val resultObj = json.optJSONObject("result") @@ -181,12 +255,64 @@ class TronApi( val rawHex = transaction?.optString("raw_data_hex") ?: throw Exception("Transaction data missing in response") - val bandwidth = (rawHex.length / 2) + - DATA_HEX_PROTOBUF_EXTRA + MAX_RESULT_SIZE_IN_TX + A_SIGNATURE + val bandwidth = estimateBandwidth(rawHex) return TronResourcesEntity(energy = energy, bandwidth = bandwidth) } + private suspend fun getResourcePrices(): TronResourcePrices { + return resourcePricesCache.getOrLoad(RESOURCE_PRICES_KEY) { + val builder = config.tronApiUrl.toUri().buildUpon() + .appendEncodedPath("wallet/getchainparameters") + val url = builder.build() + + val requestBody = buildJsonObject { } + + val response = post(url, requestBody) + val body = response.readBody() + val json = JSONObject(body) + + val paramsArray = json.optJSONArray("chainParameter") + ?: throw Exception("Missing chainParameter array in response") + + fun getValue(key: String): Long? { + for (i in 0 until paramsArray.length()) { + val obj = paramsArray.optJSONObject(i) ?: continue + if (obj.optString("key") == key) { + val value = obj.opt("value") + if (value is Number) { + return value.toLong() + } + } + } + return null + } + + val energySun = getValue("getEnergyFee") + val bandwidthSun = getValue("getTransactionFee") + + if (energySun == null || bandwidthSun == null) { + throw Exception("Missing or invalid energy or bandwidth price in chain parameters") + } + + TronResourcePrices( + energy = Coins.of(energySun, TokenEntity.TRX.decimals), + bandwidth = Coins.of(bandwidthSun, TokenEntity.TRX.decimals) + ) + } + } + + suspend fun getBurnTrxAmountForResources(resources: TronResourcesEntity): Coins { + val prices = getResourcePrices() + val burnTrxForEnergy = prices.energy.value * resources.energy.toBigDecimal() + val burnTrxForBandwidth = prices.bandwidth.value * resources.bandwidth.toBigDecimal() + + return Coins.of( + burnTrxForEnergy + burnTrxForBandwidth, + TokenEntity.TRX.decimals + ) + } + private fun applyResourcesSafetyMargin(resources: TronResourcesEntity): TronResourcesEntity { val margin = safetyMargin ?: run { val tronConfig = batteryApi.getTronConfig() @@ -199,30 +325,72 @@ class TronApi( val energy = kotlin.math.ceil(resources.energy * (1 + margin)).toInt() val bandwidth = kotlin.math.ceil(resources.bandwidth * (1 + margin)).toInt() - return TronResourcesEntity(energy = energy, bandwidth = bandwidth) + return resources.copy(energy = energy, bandwidth = bandwidth) } private fun getAccountBandwidth(tronAddress: String): Int { - val builder = - config.tronApiUrl.toUri().buildUpon().appendEncodedPath("v1/accounts/$tronAddress") + val url = config.tronApiUrl.toUri() + .buildUpon() + .appendEncodedPath("wallet/getaccountnet") + .build() - val body = get(builder.build()) + val requestBody = buildJsonObject { + put("address", tronAddress) + put("visible", true) + } + + val response = post(url, requestBody) + val body = response.readBody() val json = JSONObject(body) - val dataArray = json.optJSONArray("data") + val freeNetLimit = json.optLong("freeNetLimit", 0L) + if (freeNetLimit <= 0) return 0 + + val freeNetUsed = json.optLong("freeNetUsed", 0L) - val info = dataArray.optJSONObject(0) - return info?.optInt("free_net_usage", 0) ?: 0 + val available = freeNetLimit - freeNetUsed + + return if (available > 0) available.toInt() else 0 } - fun estimateBatteryCharges(transfer: TronTransfer): TronEstimationEntity { + private fun broadcastSignedTransaction( + transaction: TronTransaction, + ) { + val builder = config.tronApiUrl.toUri().buildUpon() + .appendEncodedPath("wallet/broadcasttransaction") + val url = builder.build() + + val jsonBody = Json.parseToJsonElement(transaction.json.toString()).jsonObject + val response = post(url, jsonBody) + val body = response.readBody() + val json = JSONObject(body) + + val ok = json.optBoolean("result") + if (!ok) { + val message = json.optString("message", "Broadcast failed") + throw Exception("Broadcast failed: $message") + } + } + + + fun estimateTransferResources(transfer: TronTransfer): TronResourcesEntity { var resources = applyResourcesSafetyMargin(estimateResources(transfer)) val bandwidthAvailable = getAccountBandwidth(transfer.from) - resources = resources.copy( - bandwidth = kotlin.math.max(0, resources.bandwidth - bandwidthAvailable) - ) - val estimation = withRetry { + if (bandwidthAvailable > resources.bandwidth) { + resources = resources.copy( + bandwidth = 0 + ) + } + + return resources + } + + fun estimateBatteryCharges( + transfer: TronTransfer, + resources: TronResourcesEntity + ): TronEstimationEntity.Charges { + val estimated = withRetry { batteryApi.tronEstimate( wallet = transfer.from, energy = resources.energy, @@ -230,13 +398,61 @@ class TronApi( ) } ?: throw Exception("tron api failed") - return TronEstimationEntity( - charges = estimation.totalCharges, - resources = resources, + return TronEstimationEntity.Charges( + charges = estimated.totalCharges, + estimated = estimated, ) } - fun sendTransaction( + suspend fun estimateTrxFee(resources: TronResourcesEntity): TronEstimationEntity.TrxFee { + val fee = getBurnTrxAmountForResources(resources) + return TronEstimationEntity.TrxFee(fee = fee) + } + + fun estimateTonFee( + batteryEstimated: EstimatedTronTx, + ): TronEstimationEntity.TonFee { + val tonInstantFee = batteryEstimated.instantFee.acceptedAssets.find { + it.type == EstimatedTronTxInstantFeeAcceptedAssetsInner.Type.ton + } ?: throw Exception("Instant fee for ton not allowed") + + return TronEstimationEntity.TonFee( + fee = Coins.ofNano(tonInstantFee.amountNano, TokenEntity.TON.decimals), + sendToAddress = batteryEstimated.instantFee.feeAddress, + ) + } + + fun sendWithTon( + transaction: TronTransaction, + instantFeeTx: Cell, + resources: TronResourcesEntity, + tronAddress: String, + userPublicKey: String, + ) { + val base64 = transaction.json.toString().encodeBase64() + val request = TronSendRequest( + wallet = tronAddress, + tx = base64, + energy = resources.energy, + bandwidth = resources.bandwidth, + instantFeeTx = instantFeeTx.base64() + ) + + batteryApi.tronSend( + request, + userPublicKey = userPublicKey + ) + } + + fun sendWithTrx( + transaction: TronTransaction, + resources: TronResourcesEntity, + tronAddress: String, + ) { + broadcastSignedTransaction(transaction) + } + + fun sendWithBattery( transaction: TronTransaction, resources: TronResourcesEntity, tronAddress: String, @@ -268,23 +484,15 @@ class TronApi( } val response = post(url, requestBody) - val body = response.body.string() + val body = response.readBody() val json = JSONObject(body) return TronTransaction(json = json.getJSONObject("transaction")) } private fun tronRetry(retryBlock: () -> R) = withRetry( - delay = (1000L..3000L).random() + backoff = ExponentialBackoff(minDelayMs = 1000, maxDelayMs = 32000) ) { retryBlock() } - - fun activateWallet( - tronAddress: String, - tonProofToken: String, - ) { - batteryApi.tronSend(TronSendRequest(wallet = tronAddress, tx = ""), tonProofToken) - } - -} \ No newline at end of file +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEstimationEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEstimationEntity.kt index dce320aec..2bebbcb05 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEstimationEntity.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEstimationEntity.kt @@ -1,3 +1,10 @@ package com.tonapps.wallet.api.tron.entity -data class TronEstimationEntity(val charges: Int, val resources: TronResourcesEntity) +import com.tonapps.icu.Coins +import io.batteryapi.models.EstimatedTronTx + +sealed class TronEstimationEntity { + data class Charges(val charges: Int, val estimated: EstimatedTronTx) + data class TrxFee(val fee: Coins) + data class TonFee(val fee: Coins, val sendToAddress: String) +} diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronResourcePrices.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronResourcePrices.kt new file mode 100644 index 000000000..5aaf08577 --- /dev/null +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronResourcePrices.kt @@ -0,0 +1,8 @@ +package com.tonapps.wallet.api.tron.entity + +import com.tonapps.icu.Coins + +data class TronResourcePrices( + val energy: Coins, + val bandwidth: Coins +) diff --git a/apps/wallet/api/src/main/res/drawable/ic_trx.png b/apps/wallet/api/src/main/res/drawable/ic_trx.png new file mode 100644 index 0000000000000000000000000000000000000000..50ab0d9ff31f4725c29da9d1bdbc050ef8264bb9 GIT binary patch literal 10892 zcmdUVWmg3PE+?~Ol5Q1B9hs%5K zuecwoYjv;g>QiSQtG!ONnyTD8EHW$v1cY}A^3ob_^Y8x+479f^8N2P)+l2W|Ue6T) zflcXu1F=LT^~c*oL{|+tNrdW2ii5Ws6e|f82?T_?cA z(c;OM7pw$P*8Ydh@%=x`iWdHB*hhbB zu*iaonC%5E#7;MO7KOEu{e6KAo-7*G`DVupDqOg!iHt|IGAv>)`>+0{>8CTVC>Vfd z)`Z73Y>cNzt3>7TRA%K&1KSFIN#Dq@)`*DO_U2wF1^wCl2Z`P9FZBn}6zm!O`)NpF zwvstKNZvx)h5TH2l%dAI2vr?kn07I z>g_ojZuw|$R&4jl#lp1fazJ!Lrj~&h`(PO*lpY+zhcX1(hyvc>r>QfV%PoThKD8_G z4P{7O$_%~J6ZXo_>uFEN(yL+7VM7KGvp(@OdbGy5YXb$sl>wq6m{C^+GLCp8sXg1g z+g<7Ofi^V>Tgi2de>1cxdN=iPc3u^kSiOou8>i;wi>5+iI)(6gLpG$^ZAO1Gi4)h^=p^yrd}Oin#vd3WJ33 z3iY8Gp#)fu9X^CB#XfwN2{cc?c%=6f4QaDnq)8CK>QqnqEoO%JWR-*R%Ho=@%p5|1 z)nhl#Tq1Wv@jW6wDs|;2@ASv!K6^o=qsb&GCx*ybdK>vyVw!%g2CR(DE~<`nvvoVm6nP#6s;us&^D=fDx?z1MD#_zNz{bgB@t+DiE=k&-~>c?a7MMg({*63!E!{Vy)|Wf$DZ% z9dt-dFLR&@tu|>}D&aEN-`gFgt#n$kzne0Y?={45zxY%h+WU`!%rOf+UVQT+@ypib z!sE1JbN45G%&~ufgG4TWi*G(3MsP3?CFj||klByb5a>;fii3BdcV zdD!>X@+0itD5laplSuCPzFEz8%SygtcK=>PI6Ovn(Ghv19ucJf)9fymS%0#K_V~x8 zb$~Z0`MfB9cdy5_|1i-%GT(`{Za4L;GQ2ak(6UNbL~>8|Q=Ot{nH+(IDF%KM@G_K| z=JF+r5?4RY>PtFKN@3mbYTMzqK<;cp<9zn=muNDODN$nrN6YvJIRx%3BG*w_T{uXZ zKR)r_=WNu?KZILi=CppUr4p3fS}z>N<_mL)TbgLk>v;0XJT+_by~1j$dx|(cEE;Gr zm02X0ENIX-D@mFU>wSpf>uD_8Neh(jd$&pVYQ~I(>ljH#7k2>SwCA;whw{pR%_yp0 zma-L4t0M4Zl=_H5h+uFKromsa&ch_QQAdh6F zZ$4s{Q{pN|XJ1upGBg$%;2B<6LR6|7oQUfFkNK`_s!`f|cSNo1bC{qD#xF^t;>BE7 z)|^3LPPz_qEFyzAK*aCTiq>;|(y?3I4B#v2z*gDQtSN zdj_&p&bkR8-r1GIL1MYX9na`f2^pE}L81*2``%bqLST4cdKe5 zub%|}(>(AiwJx3>A~i+Q%Fk+7*Y!tHG~+afeaK|Og*oW4GO=@KBMkHxeg=Q>RLPo*$>XXFxacg*s4crr+X z=a)Hm@R;99;=-k-e)Qo;l0w+Fd>Ha73*|SYWQa0f2DkhB>S#~(*X0I#mYtS}mCYiJ z{_@C!_ySWW?qu5VQw3jV2`v7QmkMB@FjNFR{~^o}r?P4;J=ZA;t3Zx1;n&AX_yIg}rn??Ex2j6yxQN%92WAb-w5_QiA=xnnBmQ|%> z0R9Ql<~X!E^=Nfz#u6uuCZU~mEp~qh%e8IgF!u`c9}se~LW#baBlB5)qPdMwOMzQ= zI=4D#H;^MFcqM1Ep^zAfN77LT8VScoDD{e$3s{QkpFTQ`B|E!bNz<7uCSDF|4!1pv zx_pUUSIO=~%HhUK%6g+~6kl?5h8ZQMz2!gl}>?YWley_bGih*#sa<*_YeYv?C&Di#ln z4|%|1MwM6|W6D{L{E&s%E|O8MZxiruaUt|({$tZ8NwHtSxRlNTh-Ah;54rLSvAzWN zxHXH}zAMOPH9N*uq~ya0uj^BjC7GVZEs??0D@!=~VMk5tHh-JYyygC}@X^rFH1V;R zN98JdPtBELhdeZ?K!-|t;`hjcfO`c2L5&>JJ8E`@J$Igi^Zg*eR`)t}!fBiAv#50i ze@hnkVjwc??-;v2rc~q%&QB2O7a97$wr*qD&aJDl=6@q1#`tOP!efVnm|dwez5n~f z^np>N?S1QKk=V+iQ<~s|^N%6J*0ybb80So0sQ*R7>dR$GbVb!DWxjYMGKlj7R@6bS zMUDSdGOX9{(1I|~uh4QtOE40%PTh#}c`#k@g|B-*XK|~_yF$opCqDrbce?617@HMI zA0@}U&$%sL4{wRo6lXq%&Dck)|NSV{;zbf-^2~2%4g7W|GEi<%th+BVHz`V(C?=|W z-=RR=6?jlOqr#dR{4<*_{m)T#pSwZW>SpL`n77N)Dw8axh^c8NS5;JTt$IX=AjljB zru=B$KYG5RsaiaD6*g%SUl>|&BKxnNgm&+`Opx2E(tM?huh0XI=>}IV#uj+DFy}vG zzic9jd9PLmiCvz9W^T>yn`Dv~(WoZR?mtmT_G~FKhn|EFtMZGrH`HupJJCF>Y}#~+ ztmwxs;{m4u(`{-X_{Hy}>OSSy5!c^I5M`uN%!zod%}>&scTBdj6AilxE=xNu%_O@x z9RWQ6*q;GdFd624~~cwH5jPZUm+`c(rm?5S$8Aa(;}P%X7FC_PS0 zUHJJswy-a&7O;bGZ9iFqOY~J&>m50wxz)f>mG}z%ZswJ%i?I(y!N)F6JC{d4s`+3< zPH{la)e1;p&duO9t?sYEg6*|5;lNBbCVV}B^ygq)$XgA4I|>XwLE1gh4gD>X2Di4; zIr}mxUms4VXHV|(dX??aWSx`6o|d$HF`=ZO3~tYblB|?^f1RJhbO{fuz&MdlzmD7) zZac?qlSsZ10>BW=vbdGbTtu12)1N3%R6j*ZFQvS2CXi(6hUxoGew7Zm_;~aOzWuka zpYk;$GU4%&DL;`$g9;rvKi++(|O>iy#R6|Q`j%nC7RaEa&*3uHu9jNY zD}z(o3AaHF7qo(ZAMDXu?aVr`vs1jKSNQi%NRQzR&4k#0MD})!{Y>9CT7ekUH1!N% z#QKup@lU?JNiEA^CO+gZm&EOPR%SB*o0q=KAl6cd+p7N}ulKMBraB{T?*M@Nf2E&CF@2pg7UA~TdLwasnzNk72 zv~RjI8YiCMGsvZ>w_LCQ88E*uy8ll7pF)^ujJ3N-2R?i#uP%VPte}eR8$W)_mEqs6 zh8eQbTW8N?)TJ2AF{obiz&cdVhgYPk`q4f6PwzZS$X%=&ucdGUiH^SKlP=Tlc&aC$ z?U=QV(eq|J3_pcElgl7iW}(PaS43s|rj53KC#w*)qfr54Ncp(r09Y`&rjA;BFkM}% zLH1a82#Tmcwc{_dqLm`~E%2L3osMTt_?>{$OZ<`Mp=t|0+Qd}9ek&F2uD1oV#q)36 ziZ6xhSBn5&V^aS^?>YZxNgHD~1so5{Slwl6j%BpX2Fz1ec4uDFg}RmP$*@jKk##QF zTVwi?Aj!lNdJAJ0URYkh(zAD!KSUZku@!tQ3idzKfFK4C=m9n3LLQ2=EtO#t={Lip z87?Tqxxs6Zk2(Zf_%{o-_J|i;z0=K&g+GN9^GPy!8Rs@DkwcWk zrb0Lr(l^gtl7o00o}a9*#ePguVwbRtElVG;YFx<`;Eu|5;z#KoZZjqDfp`MuSP0V{j`!d^u4GGGfc;j2zpg?fZl`j?k`(Xfi9Rz3<_V;7Rl zv-Z5)%wmV(ewjnNMECDo-`Ul+-vLmo$Op#7$X6n6b0VYOLUi!>>k~JOs*|^%DW=!{ zN7qvO>#{fY%*T6#IS=hcVP~)yM?(T+iCon?&80=Q+_ zWq3ECdJC!cvAgP8S&;WeWo?})FKDpYl{w6-$EkhIdkNxv!d~e8n%=PYwew=x+=Kwh z;I2m70n>6YA#VnEjNu}i)WVsgQu(^fvD2$GJEnFzxOAD0oHAOTY}20Gp?)Az5M-zraf}3JoTW*vFFci$*p<{_wZ@$ zLNkZe+uvP`wSz+jix!CIaL1Cra(0D2Tu!OR0cWXfCbj6k{rNF$WPY~rfkLC~I8wwD ztmsi>JE(kjAxGnvzob8sFbtwIYvM^*F4QQVDdKt`V+;9@tzC1DEyi|xD~tVxM|eaZ z2(m&;?)~+-t9mTeiDf9@Y+j1xL1*@?0{wBVw2)1lgblol2r}~wsdz6n3DrK=t6tT@O0i!QucCRqzBKi62>zDE?W54Y(H68n`>4DhvX(C#fwPQus$`rd=`?j#CUUc$PQ_Vco>k?1cA(^trv__mYgzN@O?~9e z!dC4eK_>fInGeV0PVE`0LrbFVrfI1AvUCvTFYG@faFjaVvK7t#LKVJMfP9S?#E4`aa-u3-J9rY3CLUfc75VrE?{2J zv8u7jJ_jNUn+%zXR2d{%K9*5)Rt<8{L>w#Oe#Y9{_-KhB2}p)QI{~$ zaB22D<7vDm(kOA8GP#TF!KEa8rir`Cya_<@;BP{%_3INEM0BI`&=~*eMf-@G)F@l> zg0!@5dr`EHKQRj|XXTK7VCs<=#DtK65?x?9vy6odUxPFl!K%TXMFcS!t4u0sH?;^t zQij-4^}{Qhza0mk79FC5pK2RbtN9alp|bFAco&N`WoE0&#W;X@>tQSVl2)<|`oh~E z+P{FZ?ycXHX>O`@{^En?I*{C3X_`s5c(dN9Exx~$RnIJr?8Gfsg3>QHAJ*+;iwC`L z5Qt5!8Zx;Vgnnm%3bv6r%myi{Wan$Qnt-#hWl0no?z=pH9Rh$O;?lhSw^XEdC*?PY zGL4Ecwg7mK@-NO9TPzt~u)+^af#Qw-j!ndDPvmzRcCw6cnYDWtP0ru@I{o)DTUh(r zn4!o=bnQ3#P1^AR74~|*8aE^}m0l2vpYA~hrSyeoDY3mP!US5eOQz9o_vU9s)D@)f z+K~U%cF^aZn76flv1}4=u+>VCqSo_p8+-L?NvnK}O*c70#}|IBNWEKACW(ny*`@r@ z$*E^Y@F+nZ^SKwm1?V3rJZ@w)@3%!|0`Io+tG$+B%Lkal4M>;bv*oDe{uz*X3R{~@ zL(B=Uj8eF?u?rGeU>=lL{?v(+{RA}UqAo1mf2Vyh3KtV5@-y?}Sfm4%Wp7=C9@_Sc zjdI>cu%?k+_foW{ZDFKiz&Zri{qmmBLv&0Iy@bDQSq0&M@W0xgrE`BLE}u18)2YjR zj>&>Q4Vg%KwI)Q!H&M>_tHEgQ)!Egxk^_l-$N2-klwnuQ!z=AD`y{)G7)@oXBqE9%TG!*h?i_H>;O>Ex}>$@;L?d3QM z5RU0qu%fx#AZHI9w#-OY_eS8&Ghq`dzP<+4OPeBkpe}1qiywcU z62b$oj;B`|mn*{y;#^4bohxHPJg{ewMs--X4F7iCN*c8H;VwO^+YL_9omnb&De<@T zARW{7LZjDQ;;*~sp{)^xrtdVOt-ium2=I*i;7}PL0P$~WZ(xdEW35;B~sQ-!;_5GEu zot$CAfDzaN*Iznv1yIZMM_^Y+7#rgbj2|wRD*5UL8OfxE;!0U8KTlG^Mxg3LP-)XA z1$l?0QD#5Qbsf{F|G+YnQ();}cYCIbmw!#{t@F|8x*0X*a4hrHt7DCXr&M7nDM8+bl>!j z$5H1E3+979SJ1SKPJ*^wqFeu@0x(?d!o@J}D}{jg)FlmiIT@F~dh2Qp)LOL)tlysT zU$)uKLqjmhy}lWu2MNf$Fy-h^IyVna$0gbW6DkC1BhEtlm&%Bqor^5)I^e&u^xK0H zjI(7(jj?A7;*urA9eWZjvrzl-5%CSIcC|Up0Z>WS`Zy`-;T;?!&N%3=&S3vwL%J;V?6r=z3IRS$5oH%$wKG0VZSzWKrv>##yO%ae|dlh)q8y zK$Niy-PBt%xlKf;K<0iULi1>u`Ko!p1D=Yo9@dl`(8CW3sb&8)xZ=0%tlU{b+F8|a zOX{nhnb7jL4^XgcBjv%61IqJ>bWS6MQWMZbI)-0;SsT^+XM_HqHyhnoGWpX9;JqBH zwXyNzXL4J{iPD43Im9C`!8dbfDaC`sc)4zylStsvI_Cq4jzgGPT*kDeV_&UDcNC_GjL6PrjXX3r@y!nivhfI2tJ2dP*8-2!P2Gw2@DBza`k;MZCgDqL_|8Wx=4Rrt4#hNzS+B3dOn*H3SBl zu}8z&ytUf1O`Fhrke-{6m4H}Ww8f=)Oz4@a^0v;6q zcfmRO9(kSDTsn_rv%%{NmnF|Tm9Hp7kNo`}w6_~#-GMU{w;*s=%zmD+_+=h7&5$cm zS_T6KHrGh`s5K8~JGdH{KVYPo+2&FjmQRZCESV}d;e5Ubzjd1M>fima}a)OKg z28sxDbaoSQfRB)V@k_%1H|=$V;gPtKlp6b_>`(_GbzWb_r_o{#yjkT<^)T`qNlby* zN=U!(Z#8obvWrnreWEvpj-$t>@Bjz6Vw5?5m_K6nolzLQDXbt?ovq$R@1@q)a=elS z11EQ*N-M{H@Q6;-`J8+{GN~SSn(6eNJ4TulOob#Zys>cH{)&zHr9k-j7>%u)D4#WS z`f16jYJb;7JtDw|p7PLEQL>04-te76833x8Ai4qs{ zb-_NcqYI&h`VCN2NjH_D{Emm}%k1bl^qMw|!g{?$4FbVAZf6uPU7qcet*5b#F50v3 z=KMH9)Xh_DJ9@Df27T=gMJHzrq1^Q_)O0#6cbrT|eMb>4A&TMx3*wNx_T=hS$cLC{ z#=Aef+w7ZiE(bk%TBn<8(Uo>iGQXd|Cm?XzGx>%O^*b>W$&njp;j3drw(ewrxZ5Qb z=@;HfcyMQ``;^B1*G9h(5py=gHvaAT=40_@l0T!*pMLme0G~H@dy2}R z$LX+AXVLm!-u-wJhqV}}8I)=exV-^}O!C}em%5o{5KJJJwd^5WpIF{d1+9x8Zzg0# zuerZMO(3%H8lNjSyBd!-bh+PDO1Cz>J46MeYQdQW1wPOVy5UEQ*G2AH(i_E-yU;rF zfBy#?x97aL^)!ZP3wZ}OR!{v_VgmltQ)Vaxm9}E7h;65H>jVM|M>OalP+bf1X5bkW z9J@632;l@&|L@_Q8cdSI5|Zg>ygjfe$+<4mCC8D)p%8+a{#7e{Jl)c>m4XPO3<-hY z$aEdkUfetG5A}y^a$rnzj*crP-adBq@;jAw1WE}Zu3+00#XrAGw4!RfaWb7{u&G7R z{O^yP{WdvQ#gXAe`nXWq5=8meV!gtj-#oA6Sj!K8* zBdq$oX9t6`RNMsSiCyBki5$6uhyxcf9LCmK1Yg82l+e;mQEL%vF~9mf!-5i}{b{qs zMXmwCZm$X&)*FTmsLtsozC^&m!{pewta9ifCoGkmTjA|MLyiG1-9l(cvCBzgfuBRq zj2ZdY-3&@Uq|pxOMcEOZl}=$+hFGt675Le$9$w?VrNC?KsZhO5;orAT@(gU<#*!SG zZRdNFeabN*ijI6BViR%A-l(yU(!aCbGhu#aiBi2qPJZOKmwU;oD2QQD`JYd81_*q$ z+e~q`Kmi!tVvT1Cexz%XJEnKOov;`@b|(4}%{+(c%=ee=v=~@pUuY1V zU2@wUxS();>*Qs_kV(}N9+7LlbguknMH_{0<=P(i-mnx&m#*{a6#0>tp*??9)OENc zr-`5AGUtNwyWAKgFLYb3Wrlp8Hb=QWqtTM zX!xq4=k|PnVJXf|acVnFLl&-1zd|dBp?2j4kEdDFhU|V2Rk?Bdk;66g(*^GR8nN;A z;aGXhlFGs49c0|*WSn#$$Z1Y@agir3<~<2+3eQ{0bOOpfP3kEo?r#;| zXs+?`f4&Fm^2upr3O`kYFuE}kFH|o>AC5SgW-aOXwJs2d^U&jCT?0E74)&l~gYo^@-PW9S#cDahn!uip5loOzgj($=}+W_ECAp zXOCD5I$jILoRNY2hBosaiV}m?7=kO;J68^Q!-x-V=>f>z20D5+VdB}(DONb2u_Oz6iJ2Tzwx|7Ja3TW%D zklMvsQJ%cjejPnV!0FepfTx_dVozvP>H~ub1j}c%uyx_OwzMP}44pJLYmU3`%W; z`i660G)OTQ9gTLckxA&yPa!Mk7Eu?yD{djE``n_IL>b(kZBB;*_FN$9yNsjTBk~w^ zwHLE&5ZKr3WNAD94$ZkxWa9UF;;)F^z&7=`-Uo9v7)WRmeLzk9?|C-y6xoevsOm&A zN|hzV${!W}zGjTlZ`n%RAP7lGbOlhDtBv zNq#-Q19Z7=H*pNkDd`rU$07A9rX_&7?=A8in<(I=8>iY$LGu$*DRM9L$18cxK6T)Sl>C{#>WboLSlJQ>#VqV@`;PKQAItY=x>|V@^Se{$+ zFM~CX+Yiz)t3S*hKJBS^-f<>#$iq;#{vZU6TlK5@_R}OwG;ZF};h_X&0&B5o*2SD_h zJs4?eCHxG9UU{o+FY*x5?un{75Q{qQc)VdLoO8MHc^^lQ5>sztISy zEfTdhDX6$_x4xE}d5#kobP!?q9w2@N?{cRTwUaA~(Cosf|4AG}g))0VdvGrotv)}ls706sXa)yVrbAP5svr5SYpP^tJCv$&Io2jBr+Sf; z<>W0~GG<_pl<^slctyL}12-@4?Axv^m%egwW=^Ki&RsV^z8pc*%*5AHC>>7!&%g;$v za^Iv%0+7x`vL`XXyTin!JbSao`q9IepVeUgbZ^0w{tZweTb#r3w?g_CIxkl?IqUVC zuO7suzXDaR(y;W%e{wxz@tMs~3M`tRU%yqE|NnI}UIgOP7*kZ~L`lCjk0L0@s7hB$ Hnuh!zq2kf+ literal 0 HcmV?d00001 diff --git a/apps/wallet/data/account/build.gradle.kts b/apps/wallet/data/account/build.gradle.kts index 38b476884..08bb4a926 100644 --- a/apps/wallet/data/account/build.gradle.kts +++ b/apps/wallet/data/account/build.gradle.kts @@ -1,16 +1,12 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") kotlin("plugin.serialization") } -android { - namespace = Build.namespacePrefix("wallet.data.account") -} - dependencies { - implementation(libs.kotlinX.serialization.json) - implementation(libs.kotlinX.coroutines.android) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.android) implementation(libs.koin.core) implementation(libs.ton.tvm) implementation(libs.ton.crypto) @@ -18,15 +14,16 @@ dependencies { implementation(libs.ton.blockTlb) implementation(libs.ton.tonapiTl) implementation(libs.ton.contract) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.rn)) - implementation(project(ProjectModules.Wallet.Data.rates)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Lib.ledger)) + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.rn) + implementation(projects.apps.wallet.data.rates) + implementation(projects.apps.wallet.api) + implementation(projects.lib.security) + implementation(projects.lib.network) + implementation(projects.lib.extensions) + implementation(projects.lib.blockchain) + implementation(projects.lib.sqlite) + implementation(projects.lib.ledger) + implementation(projects.kmp.async) } 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 b31d52ee2..552b8d99e 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 @@ -2,7 +2,9 @@ package com.tonapps.wallet.data.account import android.app.KeyguardManager import android.content.Context +import com.tonapps.async.Async import com.tonapps.blockchain.MnemonicHelper +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.contract.BaseWalletContract import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.blockchain.ton.contract.walletVersion @@ -25,9 +27,7 @@ import com.tonapps.wallet.data.rn.RNLegacy import com.tonapps.wallet.data.rn.data.RNKeystone import com.tonapps.wallet.data.rn.data.RNLedger import com.tonapps.wallet.data.rn.data.RNWallet -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged @@ -51,12 +51,6 @@ class AccountRepository( private val rnLegacy: RNLegacy, ) { - companion object { - fun newWalletId(): String { - return UUID.randomUUID().toString() - } - } - sealed class SelectedState { data object Initialization : SelectedState() data object Empty : SelectedState() @@ -67,7 +61,7 @@ class AccountRepository( context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager } - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = Async.ioScope() private val database = DatabaseSource(context, scope) private val storageSource: StorageSource by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { StorageSource(context) } private val vaultSource: VaultSource by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { VaultSource(context) } @@ -229,7 +223,7 @@ class AccountRepository( private suspend fun saveTonProof(wallet: WalletEntity, token: String) = withContext(Dispatchers.IO) { storageSource.setTonProofToken(wallet.publicKey, token) - val wallets = getWalletByPublicKey(wallet.publicKey, wallet.testnet) + val wallets = getWalletByPublicKey(wallet.publicKey, wallet.network) for (w in wallets) { rnLegacy.setTonProof(w.id, token) } @@ -279,7 +273,7 @@ class AccountRepository( val address = getTronAddress(id) ?: return null return BlockchainAddress( value = address, - testnet = false, + network = TonNetwork.MAINNET, blockchain = Blockchain.TRON ) } @@ -353,11 +347,10 @@ class AccountRepository( label: Wallet.NewLabel, mnemonic: List, versions: List, - testnet: Boolean, - initialized: List + type: Wallet.Type, + initialized: List, ): List { val publicKey = vaultSource.addMnemonic(mnemonic) - val type = if (testnet) Wallet.Type.Testnet else Wallet.Type.Default return addWallet(ids, label, publicKey, versions, type, initialized = initialized) } @@ -439,7 +432,7 @@ class AccountRepository( val payload = api.tonconnectPayload() ?: return null return try { val publicKey = wallet.publicKey - val contract = BaseWalletContract.create(publicKey, WalletVersion.V4R2.title, wallet.testnet) + val contract = BaseWalletContract.create(publicKey, WalletVersion.V4R2.title, wallet.network) val secretKey = vaultSource.getPrivateKey(publicKey) ?: throw Exception("private key not found") val address = contract.address val proof = WalletProof.signTonkeeper( @@ -485,20 +478,20 @@ class AccountRepository( setSelectedWallet(null) } - suspend fun getWalletsByAccountId(accountId: String, testnet: Boolean): List { + suspend fun getWalletsByAccountId(accountId: String, network: TonNetwork): List { return database.getAccounts().filter { - it.accountId.equalsAddress(accountId) && it.testnet == testnet + it.accountId.equalsAddress(accountId) && it.network == network } } - suspend fun getWalletByPublicKey(publicKey: PublicKeyEd25519, testnet: Boolean): List { + suspend fun getWalletByPublicKey(publicKey: PublicKeyEd25519, network: TonNetwork): List { return database.getAccounts().filter { - it.publicKey == publicKey && it.testnet == testnet + it.publicKey == publicKey && it.network == network } } - suspend fun getWalletByAccountId(accountId: String, testnet: Boolean = false): WalletEntity? { - val wallets = getWalletsByAccountId(accountId, testnet) + suspend fun getWalletByAccountId(accountId: String, network: TonNetwork = TonNetwork.MAINNET): WalletEntity? { + val wallets = getWalletsByAccountId(accountId, network) if (wallets.isEmpty()) { return null } @@ -523,13 +516,13 @@ class AccountRepository( suspend fun getSeqno( wallet: WalletEntity ): Int = withContext(Dispatchers.IO) { - api.getAccountSeqno(wallet.accountId, wallet.testnet) + api.getAccountSeqno(wallet.accountId, wallet.network) } suspend fun getValidUntil( - testnet: Boolean + network: TonNetwork ): Long = withContext(Dispatchers.IO) { - val seconds = api.getServerTime(testnet) + val seconds = api.getServerTime(network) seconds + (5 * 30L) // 5 minutes } @@ -551,8 +544,14 @@ class AccountRepository( return messageBody( wallet = wallet, seqNo = seqNo, - validUntil = if (validUntil > 0) validUntil else getValidUntil(wallet.testnet), + validUntil = if (validUntil > 0) validUntil else getValidUntil(wallet.network), transfers = transfers ) } -} \ No newline at end of file + + companion object { + fun newWalletId(): String { + return UUID.randomUUID().toString() + } + } +} diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/Wallet.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/Wallet.kt index 7ce2decbf..a3365d933 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/Wallet.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/Wallet.kt @@ -15,7 +15,7 @@ sealed class Wallet { } enum class Type(val id: Int) { - Default(0), Watch(1), Testnet(2), Signer(3), Lockup(4), Ledger(5), SignerQR(6), Keystone(7) + Default(0), Watch(1), Testnet(2), Signer(3), Lockup(4), Ledger(5), SignerQR(6), Keystone(7), Tetra(8); } @Parcelize diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/entities/WalletEntity.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/entities/WalletEntity.kt index a16f59a0c..522ab7902 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/entities/WalletEntity.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/entities/WalletEntity.kt @@ -72,9 +72,9 @@ data class WalletEntity( ) : Parcelable val contract: BaseWalletContract by lazy { - val network = if (testnet) TonNetwork.TESTNET.value else TonNetwork.MAINNET.value - - BaseWalletContract.create(publicKey, version.title, network) + val contractNetwork = if (tetra) TonNetwork.MAINNET else network + val signatureNetwork = if (tetra) network else null + BaseWalletContract.create(publicKey, version.title, contractNetwork, signatureNetwork) } val maxMessages: Int @@ -83,11 +83,21 @@ data class WalletEntity( val testnet: Boolean get() = type == Wallet.Type.Testnet + val tetra: Boolean + get() = type == Wallet.Type.Tetra + + val network: TonNetwork + get() = when (type) { + Wallet.Type.Testnet -> TonNetwork.TESTNET + Wallet.Type.Tetra -> TonNetwork.TETRA + else -> TonNetwork.MAINNET + } + val signer: Boolean get() = type == Wallet.Type.Signer || type == Wallet.Type.SignerQR val hasPrivateKey: Boolean - get() = type == Wallet.Type.Default || type == Wallet.Type.Testnet || type == Wallet.Type.Lockup + get() = type == Wallet.Type.Default || type == Wallet.Type.Tetra || type == Wallet.Type.Testnet || type == Wallet.Type.Lockup val accountId: String = contract.address.toAccountId() @@ -96,7 +106,7 @@ data class WalletEntity( val blockchainAddress: BlockchainAddress get() = BlockchainAddress( value = address, - testnet = testnet, + network = network, blockchain = Blockchain.TON ) diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/DatabaseSource.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/DatabaseSource.kt index 7f86e70e9..26db88380 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/DatabaseSource.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/DatabaseSource.kt @@ -5,7 +5,7 @@ import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper -import android.util.Log +import com.tonapps.log.L import androidx.core.database.getStringOrNull import com.tonapps.blockchain.ton.contract.walletVersion import com.tonapps.extensions.closeSafe diff --git a/apps/wallet/data/backup/build.gradle.kts b/apps/wallet/data/backup/build.gradle.kts index f9429764e..848f079ba 100644 --- a/apps/wallet/data/backup/build.gradle.kts +++ b/apps/wallet/data/backup/build.gradle.kts @@ -1,15 +1,13 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.backup") -} - dependencies { - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Wallet.Data.rn)) + implementation(libs.koin.core) + + implementation(projects.lib.sqlite) + implementation(projects.lib.extensions) + implementation(projects.apps.wallet.data.rn) } diff --git a/apps/wallet/data/backup/src/main/java/com/tonapps/wallet/data/backup/source/LocalDataSource.kt b/apps/wallet/data/backup/src/main/java/com/tonapps/wallet/data/backup/source/LocalDataSource.kt index a274ec8ad..17c91ae46 100644 --- a/apps/wallet/data/backup/src/main/java/com/tonapps/wallet/data/backup/source/LocalDataSource.kt +++ b/apps/wallet/data/backup/src/main/java/com/tonapps/wallet/data/backup/source/LocalDataSource.kt @@ -3,7 +3,7 @@ package com.tonapps.wallet.data.backup.source import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase -import android.util.Log +import com.tonapps.log.L import com.tonapps.sqlite.SQLiteHelper import com.tonapps.wallet.data.backup.entities.BackupEntity diff --git a/apps/wallet/data/battery/build.gradle.kts b/apps/wallet/data/battery/build.gradle.kts index 631851974..1a82f8eb3 100644 --- a/apps/wallet/data/battery/build.gradle.kts +++ b/apps/wallet/data/battery/build.gradle.kts @@ -1,21 +1,18 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.battery") -} - dependencies { implementation(libs.okhttp) + implementation(libs.koin.core) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.icu)) - implementation(project(ProjectModules.Lib.security)) + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.api) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.network) + implementation(projects.lib.icu) + implementation(projects.lib.security) } diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryMapper.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryMapper.kt index 52ef57364..7a0c67605 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryMapper.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryMapper.kt @@ -21,6 +21,15 @@ object BatteryMapper { return balance.value.divide(meanFeesBigDecimal, 0, RoundingMode.UP).toInt() } + fun convertFromCharges( + charges: Int, + meanFees: String + ): Coins { + val meanFeesBigDecimal = BigDecimal(meanFees) + val amountBigDecimal = meanFeesBigDecimal.multiply(BigDecimal(charges)) + return Coins(amountBigDecimal) + } + fun calculateChargesAmount( transactionCostBigDecimal: BigDecimal, meanFees: String diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryRepository.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryRepository.kt index 8f6f6bb1c..9b36e7bc9 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryRepository.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/BatteryRepository.kt @@ -1,33 +1,20 @@ package com.tonapps.wallet.data.battery import android.content.Context -import android.util.Log -import androidx.collection.ArrayMap +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.extensions.MutableEffectFlow -import com.tonapps.extensions.filterList -import com.tonapps.icu.CurrencyFormatter import com.tonapps.wallet.api.API -import com.tonapps.wallet.data.battery.entity.BatteryConfigEntity +import com.tonapps.wallet.api.entity.EmulateWithBatteryResult import com.tonapps.wallet.data.battery.entity.BatteryBalanceEntity +import com.tonapps.wallet.data.battery.entity.BatteryConfigEntity import com.tonapps.wallet.data.battery.entity.RechargeMethodEntity import com.tonapps.wallet.data.battery.source.LocalDataSource import com.tonapps.wallet.data.battery.source.RemoteDataSource -import io.tonapi.models.MessageConsequences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.cancellable -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.ton.api.pub.PublicKeyEd25519 @@ -47,15 +34,15 @@ class BatteryRepository( init { _balanceUpdatedFlow.tryEmit(Unit) scope.launch(Dispatchers.IO) { - getConfig(false, ignoreCache = true) + getConfig(TonNetwork.MAINNET, ignoreCache = true) } } suspend fun getRechargeMethodByJetton( - testnet: Boolean, + network: TonNetwork, jetton: String ): RechargeMethodEntity? { - val rechargeMethods = getConfig(testnet).rechargeMethods.filter { it.supportRecharge } + val rechargeMethods = getConfig(network).rechargeMethods.filter { it.supportRecharge } if (rechargeMethods.isEmpty()) { return null } @@ -68,35 +55,39 @@ class BatteryRepository( } suspend fun getConfig( - testnet: Boolean, + network: TonNetwork, ignoreCache: Boolean = false ): BatteryConfigEntity = withContext(Dispatchers.IO) { if (ignoreCache) { - fetchConfig(testnet) + fetchConfig(network) } else { - localDataSource.getConfig(testnet) ?: fetchConfig(testnet) + localDataSource.getConfig(network) ?: fetchConfig(network) } } - private suspend fun fetchConfig(testnet: Boolean): BatteryConfigEntity { - val config = remoteDataSource.fetchConfig(testnet) ?: return BatteryConfigEntity.Empty - localDataSource.setConfig(testnet, config) + private suspend fun fetchConfig(network: TonNetwork): BatteryConfigEntity { + val config = remoteDataSource.fetchConfig(network) ?: return BatteryConfigEntity.Empty + localDataSource.setConfig(network, config) return config } suspend fun getBalance( tonProofToken: String, publicKey: PublicKeyEd25519, - testnet: Boolean, + network: TonNetwork, ignoreCache: Boolean = false, ): BatteryBalanceEntity = withContext(Dispatchers.IO) { + if (network.isTetra) { + return@withContext BatteryBalanceEntity.Empty + } + val balance = if (ignoreCache) { - fetchBalance(publicKey, tonProofToken, testnet) + fetchBalance(publicKey, tonProofToken, network) } else { - localDataSource.getBalance(publicKey, testnet) ?: fetchBalance( + localDataSource.getBalance(publicKey, network) ?: fetchBalance( publicKey, tonProofToken, - testnet + network ) } balance @@ -105,11 +96,11 @@ class BatteryRepository( suspend fun getCharges( tonProofToken: String, publicKey: PublicKeyEd25519, - testnet: Boolean, + network: TonNetwork, ignoreCache: Boolean = false, ): Int = withContext(Dispatchers.IO) { - val balance = getBalance(tonProofToken, publicKey, testnet, ignoreCache) - val config = getConfig(testnet, ignoreCache) + val balance = getBalance(tonProofToken, publicKey, network, ignoreCache) + val config = getConfig(network, ignoreCache) val charges = BatteryMapper.convertToCharges(balance.balance, config.chargeCost) charges } @@ -117,11 +108,15 @@ class BatteryRepository( private suspend fun fetchBalance( publicKey: PublicKeyEd25519, tonProofToken: String, - testnet: Boolean + network: TonNetwork ): BatteryBalanceEntity { - val balance = remoteDataSource.fetchBalance(tonProofToken, testnet) + if (network.isTetra) { + return BatteryBalanceEntity.Empty + } + + val balance = remoteDataSource.fetchBalance(tonProofToken, network) ?: return BatteryBalanceEntity.Empty - localDataSource.setBalance(publicKey, testnet, balance) + localDataSource.setBalance(publicKey, network, balance) _balanceUpdatedFlow.emit(Unit) return balance } @@ -129,47 +124,47 @@ class BatteryRepository( fun refreshBalanceDelay( publicKey: PublicKeyEd25519, tonProofToken: String, - testnet: Boolean + network: TonNetwork ) { scope.launch(Dispatchers.IO) { delay(10000) - fetchBalance(publicKey, tonProofToken, testnet) + fetchBalance(publicKey, tonProofToken, network) } } suspend fun emulate( tonProofToken: String, publicKey: PublicKeyEd25519, - testnet: Boolean, + network: TonNetwork, boc: Cell, forceRelayer: Boolean = false, safeModeEnabled: Boolean, - ): Pair? = withContext(Dispatchers.IO) { + ): EmulateWithBatteryResult? = withContext(Dispatchers.IO) { val balance = getBalance( tonProofToken = tonProofToken, publicKey = publicKey, - testnet = testnet + network = network ).balance if (!forceRelayer && !balance.isPositive) { throw IllegalStateException("Zero balance") } - api.emulateWithBattery(tonProofToken, boc, testnet, safeModeEnabled) + api.emulateWithBattery(tonProofToken, boc, network, safeModeEnabled) } suspend fun getAppliedPromo( - testnet: Boolean, + network: TonNetwork, ): String? = withContext(Dispatchers.IO) { - localDataSource.getAppliedPromo(testnet) + localDataSource.getAppliedPromo(network) } suspend fun setAppliedPromo( - testnet: Boolean, + network: TonNetwork, promo: String?, ) = withContext(Dispatchers.IO) { - localDataSource.setAppliedPromo(testnet, promo) + localDataSource.setAppliedPromo(network, promo) } } \ No newline at end of file diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/BatteryConfigEntity.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/BatteryConfigEntity.kt index 0174189e4..7f43b5083 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/BatteryConfigEntity.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/BatteryConfigEntity.kt @@ -23,7 +23,8 @@ data class BatteryConfigEntity( val batteryMeanPriceSwap: Int, val batteryMeanPriceJetton: Int, val batteryMeanPriceNft: Int, - val batteryMeanPriceTronUsdt: Int? = null, + val batteryMeanPriceTronUsdt: Int, + val tonMeanPriceTronUsdt: Float ) : Parcelable @IgnoredOnParcel @@ -37,7 +38,7 @@ data class BatteryConfigEntity( fundReceiver = null, rechargeMethods = emptyList(), gasProxy = emptyList(), - meanPrices = MeanPrices(0, 0, 0, null), + meanPrices = MeanPrices(0, 0, 0, 0, 0f), chargeCost = "0", reservedAmount = "0", ) diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/RechargeMethodEntity.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/RechargeMethodEntity.kt index 4a05cfb8b..96339220d 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/RechargeMethodEntity.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/entity/RechargeMethodEntity.kt @@ -20,17 +20,6 @@ data class RechargeMethodEntity( val minBootstrapValue: String? = null ) : Parcelable { - private companion object { - - private fun RechargeMethodsMethodsInner.Type.toRechargeMethodType(): RechargeMethodType { - return when (this) { - RechargeMethodsMethodsInner.Type.jetton -> RechargeMethodType.JETTON - RechargeMethodsMethodsInner.Type.ton -> RechargeMethodType.TON - RechargeMethodsMethodsInner.Type.unknown -> RechargeMethodType.TON - } - } - } - constructor(method: RechargeMethodsMethodsInner) : this( type = method.type.toRechargeMethodType(), rate = method.rate, @@ -49,4 +38,14 @@ data class RechargeMethodEntity( } fun fromTon(amount: String) = fromTon(amount.toBigDecimal()) -} \ No newline at end of file + + private companion object { + + private fun RechargeMethodsMethodsInner.Type.toRechargeMethodType(): RechargeMethodType { + return when (this) { + RechargeMethodsMethodsInner.Type.jetton -> RechargeMethodType.JETTON + RechargeMethodsMethodsInner.Type.ton -> RechargeMethodType.TON + } + } + } +} diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/LocalDataSource.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/LocalDataSource.kt index d50f4e333..3f4c9db43 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/LocalDataSource.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/LocalDataSource.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.battery.source import android.content.Context import androidx.core.content.edit +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.hex import com.tonapps.extensions.prefs import com.tonapps.security.Security @@ -23,43 +24,42 @@ internal class LocalDataSource( private val prefs = context.prefs(NAME) - fun setConfig(testnet: Boolean, entity: BatteryConfigEntity) { - configStore.setCache(configCacheKey(testnet), entity) + fun setConfig(network: TonNetwork, entity: BatteryConfigEntity) { + configStore.setCache(configCacheKey(network), entity) } - fun getConfig(testnet: Boolean): BatteryConfigEntity? { - return configStore.getCache(configCacheKey(testnet)) + fun getConfig(network: TonNetwork): BatteryConfigEntity? { + return configStore.getCache(configCacheKey(network)) } - private fun configCacheKey(testnet: Boolean): String { - return if (testnet) "testnet" else "mainnet" + private fun configCacheKey(network: TonNetwork): String { + return network.name.lowercase() } - fun setBalance(publicKey: PublicKeyEd25519, testnet: Boolean, entity: BatteryBalanceEntity) { - balance.setCache(balanceCacheKey(publicKey, testnet), entity) + fun setBalance(publicKey: PublicKeyEd25519, network: TonNetwork, entity: BatteryBalanceEntity) { + balance.setCache(balanceCacheKey(publicKey, network), entity) } - fun getBalance(publicKey: PublicKeyEd25519, testnet: Boolean): BatteryBalanceEntity? { - return balance.getCache(balanceCacheKey(publicKey, testnet)) + fun getBalance(publicKey: PublicKeyEd25519, network: TonNetwork): BatteryBalanceEntity? { + return balance.getCache(balanceCacheKey(publicKey, network)) } - private fun balanceCacheKey(publicKey: PublicKeyEd25519, testnet: Boolean): String { - val prefix = if (testnet) "testnet" else "mainnet" - return "$prefix:${publicKey.hex()}" + private fun balanceCacheKey(publicKey: PublicKeyEd25519, network: TonNetwork): String { + return "${network.name.lowercase()}:${publicKey.hex()}" } - fun getAppliedPromo(testnet: Boolean): String? { - return prefs.getString(promoKey(testnet), null) + fun getAppliedPromo(network: TonNetwork): String? { + return prefs.getString(promoKey(network), null) } - fun setAppliedPromo(testnet: Boolean, promo: String?) { + fun setAppliedPromo(network: TonNetwork, promo: String?) { prefs.edit { - putString(promoKey(testnet), promo) + putString(promoKey(network), promo) } } - private fun promoKey(testnet: Boolean): String { - return "promo_${if (testnet) "testnet" else "mainnet"}" + private fun promoKey(network: TonNetwork): String { + return "promo_${network.name.lowercase()}" } } diff --git a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/RemoteDataSource.kt b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/RemoteDataSource.kt index 91ce2aeed..b1d1ee205 100644 --- a/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/RemoteDataSource.kt +++ b/apps/wallet/data/battery/src/main/java/com/tonapps/wallet/data/battery/source/RemoteDataSource.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.battery.source -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork +import com.tonapps.log.L import com.tonapps.icu.Coins import com.tonapps.wallet.api.API import com.tonapps.wallet.data.battery.entity.BatteryBalanceEntity @@ -16,9 +17,9 @@ internal class RemoteDataSource( suspend fun fetchBalance( tonProofToken: String, - testnet: Boolean + network: TonNetwork ): BatteryBalanceEntity? = withContext(Dispatchers.IO) { - val response = api.getBatteryBalance(tonProofToken, testnet) ?: return@withContext null + val response = api.getBatteryBalance(tonProofToken, network) ?: return@withContext null BatteryBalanceEntity( balance = Coins.of(response.balance.toBigDecimal(), 20), @@ -27,10 +28,10 @@ internal class RemoteDataSource( } suspend fun fetchConfig( - testnet: Boolean + network: TonNetwork ): BatteryConfigEntity? = withContext(Dispatchers.IO) { - val configDeferred = async { api.getBatteryConfig(testnet) } - val rechargeMethodsDeferred = async { api.getBatteryRechargeMethods(testnet) } + val configDeferred = async { api.getBatteryConfig(network) } + val rechargeMethodsDeferred = async { api.getBatteryRechargeMethods(network) } val config = configDeferred.await() ?: return@withContext null val rechargeMethods = rechargeMethodsDeferred.await() ?: return@withContext null @@ -44,7 +45,8 @@ internal class RemoteDataSource( batteryMeanPriceSwap = config.meanPrices.batteryMeanPriceSwap, batteryMeanPriceJetton = config.meanPrices.batteryMeanPriceJetton, batteryMeanPriceNft = config.meanPrices.batteryMeanPriceNft, - batteryMeanPriceTronUsdt = config.meanPrices.batteryMeanPriceTronUsdt + batteryMeanPriceTronUsdt = config.meanPrices.batteryMeanPriceTronUsdt ?: 0, + tonMeanPriceTronUsdt = config.meanPrices.tonMeanPriceTronUsdt ?: 0f ), chargeCost = config.chargeCost, reservedAmount = config.batteryReservedAmount diff --git a/apps/wallet/data/browser/build.gradle.kts b/apps/wallet/data/browser/build.gradle.kts index d4430de92..dcb62984a 100644 --- a/apps/wallet/data/browser/build.gradle.kts +++ b/apps/wallet/data/browser/build.gradle.kts @@ -1,20 +1,18 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.browser") -} - dependencies { implementation(libs.okhttp) + implementation(libs.koin.core) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.account)) + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.account) - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.extensions)) + implementation(projects.lib.blockchain) + implementation(projects.lib.network) + implementation(projects.lib.extensions) } diff --git a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/BrowserRepository.kt b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/BrowserRepository.kt index 861c54f8a..c9a73f27e 100644 --- a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/BrowserRepository.kt +++ b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/BrowserRepository.kt @@ -2,7 +2,8 @@ package com.tonapps.wallet.data.browser import android.content.Context import android.net.Uri -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork +import com.tonapps.log.L import com.tonapps.extensions.toUriOrNull import com.tonapps.wallet.api.API import com.tonapps.wallet.data.browser.entities.BrowserAppEntity @@ -28,10 +29,10 @@ class BrowserRepository(context: Context, api: API) { suspend fun search( country: String, query: String, - testnet: Boolean = false, + network: TonNetwork = TonNetwork.MAINNET, locale: Locale ): List { - val data = load(country, testnet, locale) ?: return emptyList() + val data = load(country, network, locale) ?: return emptyList() val all = data.categories.map { it.apps }.flatten() return all.filter { it.name.contains(query, ignoreCase = true) || @@ -40,12 +41,12 @@ class BrowserRepository(context: Context, api: API) { }.distinctBy { it.url } } - suspend fun isTrustedApp(country: String, testnet: Boolean, locale: Locale, deeplink: Uri): Boolean { + suspend fun isTrustedApp(country: String, network: TonNetwork, locale: Locale, deeplink: Uri): Boolean { if (deeplink.host == "dapp.aeon.xyz" || deeplink.host == "tonkeeper.com" || deeplink.host?.endsWith(".tonkeeper.com") == true) { return true } val host = deeplink.host ?: return false - val apps = getApps(country, testnet, locale) + val apps = getApps(country, network, locale) for (app in apps) { if (app.useTG) { continue @@ -56,17 +57,17 @@ class BrowserRepository(context: Context, api: API) { return false } - suspend fun getApps(country: String, testnet: Boolean, locale: Locale): List { - return load(country, testnet, locale)?.categories?.map { it.apps }?.flatten() ?: emptyList() + suspend fun getApps(country: String, network: TonNetwork, locale: Locale): List { + return load(country, network, locale)?.categories?.map { it.apps }?.flatten() ?: emptyList() } - suspend fun getApp(country: String, testnet: Boolean, locale: Locale, uri: Uri): BrowserAppEntity? { + suspend fun getApp(country: String, network: TonNetwork, locale: Locale, uri: Uri): BrowserAppEntity? { val host = uri.host ?: return null val browserApp = appCacheByHost[host] if (browserApp != null) { return browserApp } - val apps = getApps(country, testnet, locale) + val apps = getApps(country, network, locale) for (app in apps) { if (app.useTG) { continue @@ -80,23 +81,23 @@ class BrowserRepository(context: Context, api: API) { fun dataFlow( country: String, - testnet: Boolean, + network: TonNetwork, locale: Locale ) = flow { loadLocal(country, locale)?.let { emit(it) } - loadRemote(country, testnet, locale)?.let { emit(it) } + loadRemote(country, network, locale)?.let { emit(it) } } - suspend fun load(country: String, testnet: Boolean, locale: Locale): BrowserDataEntity? = withContext(Dispatchers.IO) { - loadLocal(country, locale) ?: loadRemote(country, testnet, locale) + suspend fun load(country: String, network: TonNetwork, locale: Locale): BrowserDataEntity? = withContext(Dispatchers.IO) { + loadLocal(country, locale) ?: loadRemote(country, network, locale) } suspend fun loadCategories( country: String, - testnet: Boolean, + network: TonNetwork, locale: Locale ): List { - return load(country, testnet, locale)?.categories?.map { it.id } ?: emptyList() + return load(country, network, locale)?.categories?.map { it.id } ?: emptyList() } private fun loadLocal(country: String, locale: Locale): BrowserDataEntity? { @@ -106,8 +107,8 @@ class BrowserRepository(context: Context, api: API) { private fun cacheKey(country: String, locale: Locale) = "browser_data_${country}_${locale.language}" - suspend fun loadRemote(country: String, testnet: Boolean, locale: Locale): BrowserDataEntity? { - val data = remoteDataSource.load(testnet, locale) ?: return null + suspend fun loadRemote(country: String, network: TonNetwork, locale: Locale): BrowserDataEntity? { + val data = remoteDataSource.load(network, locale) ?: return null val key = cacheKey(country, locale) localDataSource.setCache(key, data) return data diff --git a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserAppEntity.kt b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserAppEntity.kt index fd5eca11f..13a5bdbcd 100644 --- a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserAppEntity.kt +++ b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserAppEntity.kt @@ -3,7 +3,7 @@ package com.tonapps.wallet.data.browser.entities import android.graphics.Color import android.net.Uri import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import androidx.core.net.toUri import com.tonapps.extensions.toUriOrNull import kotlinx.parcelize.IgnoredOnParcel diff --git a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserDataEntity.kt b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserDataEntity.kt index 71577a4dc..90a84eb71 100644 --- a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserDataEntity.kt +++ b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/entities/BrowserDataEntity.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.browser.entities import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import kotlinx.parcelize.Parcelize import org.json.JSONObject diff --git a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/source/RemoteDataSource.kt b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/source/RemoteDataSource.kt index 3d09b8b9c..f52624a5c 100644 --- a/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/source/RemoteDataSource.kt +++ b/apps/wallet/data/browser/src/main/java/com/tonapps/wallet/data/browser/source/RemoteDataSource.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.browser.source -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork +import com.tonapps.log.L import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.wallet.api.API import com.tonapps.wallet.data.browser.entities.BrowserDataEntity @@ -12,9 +13,9 @@ internal class RemoteDataSource( private val api: API ) { - suspend fun load(testnet: Boolean, locale: Locale): BrowserDataEntity? = withContext(Dispatchers.IO) { + suspend fun load(network: TonNetwork, locale: Locale): BrowserDataEntity? = withContext(Dispatchers.IO) { try { - BrowserDataEntity(api.getBrowserApps(testnet, locale)) + BrowserDataEntity(api.getBrowserApps(network, locale)) } catch (e: Throwable) { FirebaseCrashlytics.getInstance().recordException(e) null diff --git a/apps/wallet/data/collectibles/build.gradle.kts b/apps/wallet/data/collectibles/build.gradle.kts index aa2a5a682..65ea2c33f 100644 --- a/apps/wallet/data/collectibles/build.gradle.kts +++ b/apps/wallet/data/collectibles/build.gradle.kts @@ -1,17 +1,15 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.collectibles") -} - dependencies { - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.sqlite)) + implementation(libs.koin.core) + + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.api) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.sqlite) } diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt index bb9c01368..a3bfc936d 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt @@ -1,21 +1,18 @@ package com.tonapps.wallet.data.collectibles import android.content.Context -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.wallet.api.API -import com.tonapps.wallet.api.withRetry import com.tonapps.wallet.data.collectibles.entities.DnsExpiringEntity import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.data.collectibles.entities.NftListResult import com.tonapps.wallet.data.collectibles.source.LocalDataSource import io.extensions.renderType import io.tonapi.models.TrustType -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.withContext class CollectiblesRepository( private val context: Context, @@ -26,49 +23,51 @@ class CollectiblesRepository( LocalDataSource(context) } - suspend fun getDnsExpiring(accountId: String, testnet: Boolean, period: Int) = api.getDnsExpiring(accountId, testnet, period).map { model -> - DnsExpiringEntity( - expiringAt = model.expiringAt, - name = model.name, - dnsItem = model.dnsItem?.let { NftEntity(it, testnet) } - ) - }.sortedBy { it.daysUntilExpiration } + suspend fun getDnsExpiring(accountId: String, network: TonNetwork, period: Int) = run { + api.getDnsExpiring(accountId, network, period).map { model -> + DnsExpiringEntity( + expiringAt = model.expiringAt, + name = model.name, + dnsItem = model.dnsItem?.let { NftEntity(it, network) } + ) + }.sortedBy { it.daysUntilExpiration } + } - suspend fun getDnsSoonExpiring(accountId: String, testnet: Boolean, period: Int = 30) = getDnsExpiring(accountId, testnet, period) + suspend fun getDnsSoonExpiring(accountId: String, network: TonNetwork, period: Int = 30) = getDnsExpiring(accountId, network, period) suspend fun getDnsNftExpiring( accountId: String, - testnet: Boolean, + network: TonNetwork, nftAddress: String - ) = getDnsExpiring(accountId, testnet, 366).firstOrNull { + ) = getDnsExpiring(accountId, network, 366).firstOrNull { it.dnsItem?.address?.equalsAddress(nftAddress) == true } - fun getNft(accountId: String, testnet: Boolean, address: String): NftEntity? { - val nft = localDataSource.getSingle(accountId, testnet, address) + fun getNft(accountId: String, network: TonNetwork, address: String): NftEntity? { + val nft = localDataSource.getSingle(accountId, network.isTestnet, address) if (nft != null) { return nft } - return api.getNft(address, testnet)?.let { NftEntity(it, testnet) } + return api.getNft(address, network)?.let { NftEntity(it, network) } } - fun get(address: String, testnet: Boolean): List? { - val local = localDataSource.get(address, testnet) + fun get(address: String, network: TonNetwork): List? { + val local = localDataSource.get(address, network.isTestnet) if (local.isEmpty()) { - return getRemoteNftItems(address, testnet) + return getRemoteNftItems(address, network) } return local } - fun getFlow(address: String, testnet: Boolean, isOnline: Boolean) = flow { + fun getFlow(address: String, network: TonNetwork, isOnline: Boolean) = flow { try { - val local = getLocalNftItems(address, testnet) + val local = getLocalNftItems(address, network) if (local.isNotEmpty()) { emit(NftListResult(cache = true, list = local)) } if (isOnline) { - val remote = getRemoteNftItems(address, testnet) ?: return@flow + val remote = getRemoteNftItems(address, network) ?: return@flow emit(NftListResult(cache = false, list = remote)) } } catch (e: Throwable) { @@ -78,21 +77,21 @@ class CollectiblesRepository( private fun getLocalNftItems( address: String, - testnet: Boolean + network: TonNetwork ): List { - return localDataSource.get(address, testnet) + return localDataSource.get(address, network.isTestnet) } private fun getRemoteNftItems( address: String, - testnet: Boolean + network: TonNetwork ): List? { - val nftItems = api.getNftItems(address, testnet) ?: return null + val nftItems = api.getNftItems(address, network) ?: return null val items = nftItems.filter { it.trust != TrustType.blacklist && it.renderType != "hidden" - }.map { NftEntity(it, testnet) } + }.map { NftEntity(it, network) } - localDataSource.save(address, testnet, items.toList()) + localDataSource.save(address, network.isTestnet, items.toList()) return items } -} \ No newline at end of file +} diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftEntity.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftEntity.kt index 6e3362af0..9e01bfd40 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftEntity.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftEntity.kt @@ -3,6 +3,7 @@ package com.tonapps.wallet.data.collectibles.entities import android.net.Uri import android.os.Parcelable import androidx.core.net.toUri +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.toUserFriendly import com.tonapps.wallet.api.entity.AccountEntity @@ -18,7 +19,7 @@ data class NftEntity( val collection: NftCollectionEntity?, val metadata: NftMetadataEntity, val previews: List, - val testnet: Boolean, + val network: TonNetwork, val verified: Boolean, val inSale: Boolean, val dns: String?, @@ -29,10 +30,10 @@ data class NftEntity( get() = collection?.address ?: address val id: String - get() = if (testnet) "testnet:$address" else address + get() = if (network.isTestnet) "testnet:$address" else address val userFriendlyAddress: String - get() = address.toUserFriendly(wallet = false, testnet = testnet) + get() = address.toUserFriendly(wallet = false, testnet = network.isTestnet) val name: String get() = metadata.name ?: "" @@ -93,6 +94,19 @@ data class NftEntity( return address.equalsAddress("0:80d78a35f955a14b679faa887ff4cd5bfc0f43b4a4eea2a7e6927f3701b273c2") } + constructor(item: NftItem, network: TonNetwork) : this( + address = item.address, + owner = item.owner?.let { AccountEntity(it, network) }, + collection = item.collection?.let { NftCollectionEntity(it) }, + metadata = NftMetadataEntity(item.metadata), + previews = item.previews?.map { NftPreviewEntity(it) } ?: emptyList(), + network = network, + verified = item.approvedBy.isNotEmpty(), + inSale = item.sale != null, + dns = item.dns, + trust = Trust(item.trust.value), + ) + private fun getImage(minSize: Int, maxSize: Int): NftPreviewEntity? { return previews.find { if (minSize > it.width || minSize > it.height) { @@ -108,17 +122,4 @@ data class NftEntity( private fun getImageUri(minSize: Int, maxSize: Int): Uri? { return getImage(minSize, maxSize)?.url?.let { Uri.parse(it) } } - - constructor(item: NftItem, testnet: Boolean) : this( - address = item.address, - owner = item.owner?.let { AccountEntity(it, testnet) }, - collection = item.collection?.let { NftCollectionEntity(it) }, - metadata = NftMetadataEntity(item.metadata), - previews = item.previews?.map { NftPreviewEntity(it) } ?: emptyList(), - testnet = testnet, - verified = item.approvedBy.isNotEmpty(), - inSale = item.sale != null, - dns = item.dns, - trust = Trust(item.trust.value), - ) -} \ No newline at end of file +} diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftMetadataEntity.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftMetadataEntity.kt index 6e3eaff71..093e2e78a 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftMetadataEntity.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftMetadataEntity.kt @@ -2,7 +2,7 @@ package com.tonapps.wallet.data.collectibles.entities import android.os.Parcelable import android.util.Base64 -import android.util.Log +import com.tonapps.log.L import io.JsonAny import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftPreviewEntity.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftPreviewEntity.kt index 5f92abbf3..6f05a3ebf 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftPreviewEntity.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/NftPreviewEntity.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.collectibles.entities import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import io.tonapi.models.ImagePreview import kotlinx.parcelize.Parcelize diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/source/LocalDataSource.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/source/LocalDataSource.kt index 54a1bdc68..57bff2061 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/source/LocalDataSource.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/source/LocalDataSource.kt @@ -3,7 +3,7 @@ package com.tonapps.wallet.data.collectibles.source import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.toByteArray import com.tonapps.extensions.toParcel import com.tonapps.sqlite.SQLiteHelper diff --git a/apps/wallet/data/contacts/build.gradle.kts b/apps/wallet/data/contacts/build.gradle.kts index 976af010e..699bad4ec 100644 --- a/apps/wallet/data/contacts/build.gradle.kts +++ b/apps/wallet/data/contacts/build.gradle.kts @@ -1,15 +1,14 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.contacts") -} - dependencies { - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Wallet.Data.rn)) + implementation(libs.koin.core) + + implementation(projects.apps.wallet.data.core) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.sqlite) + implementation(projects.apps.wallet.data.rn) } diff --git a/apps/wallet/data/contacts/src/main/java/com/tonapps/wallet/data/contacts/ContactsRepository.kt b/apps/wallet/data/contacts/src/main/java/com/tonapps/wallet/data/contacts/ContactsRepository.kt index 78b3a9c16..0d8a4e93d 100644 --- a/apps/wallet/data/contacts/src/main/java/com/tonapps/wallet/data/contacts/ContactsRepository.kt +++ b/apps/wallet/data/contacts/src/main/java/com/tonapps/wallet/data/contacts/ContactsRepository.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.contacts import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.MutableEffectFlow import com.tonapps.wallet.data.contacts.entities.ContactEntity import com.tonapps.wallet.data.contacts.source.DatabaseSource @@ -11,7 +12,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -51,15 +51,16 @@ class ContactsRepository( } } - fun isHidden(accountId: String, testnet: Boolean) = database.isHidden(accountId, testnet) + fun isHidden(accountId: String, network: TonNetwork) = + database.isHidden(accountId, network.isTestnet) - fun hide(accountId: String, testnet: Boolean) { - database.setHidden(accountId, testnet, true) + fun hide(accountId: String, network: TonNetwork) { + database.setHidden(accountId, network.isTestnet, true) _hiddenFlow.tryEmit(Unit) } - suspend fun add(name: String, address: String, testnet: Boolean): ContactEntity = withContext(Dispatchers.IO) { - val contact = database.addContact(name, address, testnet) + suspend fun add(name: String, address: String, network: TonNetwork): ContactEntity = withContext(Dispatchers.IO) { + val contact = database.addContact(name, address, network.isTestnet) _contactsFlow.value = _contactsFlow.value.orEmpty().toMutableList().apply { add(contact) } @@ -81,4 +82,4 @@ class ContactsRepository( oldContacts[index] = oldContacts[index].copy(name = name) _contactsFlow.value = oldContacts } -} \ No newline at end of file +} diff --git a/apps/wallet/data/core/build.gradle.kts b/apps/wallet/data/core/build.gradle.kts index 778852732..7111e9596 100644 --- a/apps/wallet/data/core/build.gradle.kts +++ b/apps/wallet/data/core/build.gradle.kts @@ -1,21 +1,8 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.core") - compileSdk = Build.compileSdkVersion - - defaultConfig { - minSdk = Build.minSdkVersion - consumerProguardFiles("consumer-rules.pro") - } -} - dependencies { api(platform(libs.firebase.bom)) api(libs.firebase.crashlytics) @@ -27,14 +14,11 @@ dependencies { implementation(libs.ton.tonapiTl) implementation(libs.ton.contract) implementation(libs.koin.core) - implementation(libs.androidX.biometric) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.UIKit.flag)) + implementation(libs.androidx.biometric) + implementation(projects.apps.wallet.api) + implementation(projects.lib.extensions) + implementation(projects.lib.blockchain) + implementation(projects.lib.sqlite) + implementation(projects.tonapi.legacy) + implementation(projects.ui.uikit.flag) } - - - diff --git a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt index 340954125..270847836 100644 --- a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt +++ b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt @@ -3,7 +3,7 @@ package com.tonapps.wallet.data.core import android.content.Context import android.os.Parcel import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.cacheFolder import com.tonapps.extensions.file diff --git a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/currency/WalletCurrency.kt b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/currency/WalletCurrency.kt index 9c0f194fe..594aa293a 100644 --- a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/currency/WalletCurrency.kt +++ b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/currency/WalletCurrency.kt @@ -2,14 +2,14 @@ package com.tonapps.wallet.data.core.currency import android.net.Uri import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import androidx.annotation.DrawableRes import com.tonapps.extensions.toUriOrNull import com.tonapps.uikit.flag.getFlagDrawable -import com.tonapps.wallet.api.R import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import androidx.core.net.toUri +import com.tonapps.apps.wallet.api.R @Parcelize data class WalletCurrency( 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 ac2f81349..86bd450df 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 @@ -2,21 +2,15 @@ package com.tonapps.wallet.data.core.entity import android.net.Uri import android.os.Parcelable -import android.util.Log import com.tonapps.blockchain.ton.TonNetwork -import com.tonapps.blockchain.ton.extensions.isValidTonAddress import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.extensions.currentTimeSeconds import com.tonapps.extensions.optStringCompatJS -import kotlinx.datetime.Clock import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.json.JSONArray import org.json.JSONObject import org.ton.block.AddrStd -import org.ton.block.StateInit -import org.ton.tlb.CellRef -import kotlin.time.Duration.Companion.seconds @Parcelize data class SignRequestEntity( @@ -42,7 +36,7 @@ data class SignRequestEntity( get() = messagesVariants?.battery.isNullOrEmpty().not() val isTestnet: Boolean - get() = network == TonNetwork.TESTNET + get() = network.isTestnet val targetAddressValue: String get() = messages.first().addressValue @@ -155,4 +149,4 @@ data class SignRequestEntity( } } } -} \ No newline at end of file +} diff --git a/apps/wallet/data/dapps/build.gradle.kts b/apps/wallet/data/dapps/build.gradle.kts index cd0ae5059..e7cb95893 100644 --- a/apps/wallet/data/dapps/build.gradle.kts +++ b/apps/wallet/data/dapps/build.gradle.kts @@ -1,13 +1,8 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.dapps") -} - - dependencies { implementation(libs.ton.tvm) implementation(libs.ton.crypto) @@ -15,13 +10,15 @@ dependencies { implementation(libs.ton.blockTlb) implementation(libs.ton.tonapiTl) implementation(libs.ton.contract) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.rn)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.base64)) + implementation(libs.koin.core) + + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.rn) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.sqlite) + implementation(projects.lib.security) + implementation(projects.lib.network) + implementation(projects.lib.base64) } \ No newline at end of file diff --git a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/DAppsRepository.kt b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/DAppsRepository.kt index 13b7d7eac..de2b22351 100644 --- a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/DAppsRepository.kt +++ b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/DAppsRepository.kt @@ -2,7 +2,7 @@ package com.tonapps.wallet.data.dapps import android.content.Context import android.net.Uri -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork import androidx.collection.ArrayMap import androidx.core.net.toUri import com.google.firebase.crashlytics.FirebaseCrashlytics @@ -22,16 +22,13 @@ import com.tonapps.wallet.data.rn.data.RNTC import com.tonapps.wallet.data.rn.data.RNTCApp import com.tonapps.wallet.data.rn.data.RNTCApps import com.tonapps.wallet.data.rn.data.RNTCConnection -import com.tonapps.wallet.data.rn.data.RNTCKeyPair import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -39,7 +36,6 @@ import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.json.JSONObject class DAppsRepository( context: Context, @@ -159,10 +155,10 @@ class DAppsRepository( suspend fun getConnections( accountId: String, - testnet: Boolean + network: TonNetwork ): ArrayMap> { val connections = (_connectionsFlow.value ?: emptyList()).filter { - it.accountId == accountId && it.testnet == testnet + it.accountId == accountId && it.network == network } val map = connections.groupBy { it.appUrl.withoutQuery } val apps = getApps(map.keys.toList()) @@ -185,16 +181,16 @@ class DAppsRepository( database.setLastAppRequestId(clientId, requestId) } - fun isPushEnabled(accountId: String, testnet: Boolean, appUrl: Uri): Boolean { - return database.isPushEnabled(accountId, testnet, appUrl.withoutQuery) + fun isPushEnabled(accountId: String, network: TonNetwork, appUrl: Uri): Boolean { + return database.isPushEnabled(accountId, network, appUrl.withoutQuery) } - fun setPushEnabled(accountId: String, testnet: Boolean, appUrl: Uri, enabled: Boolean): List { + fun setPushEnabled(accountId: String, network: TonNetwork, appUrl: Uri, enabled: Boolean): List { val otherConnections = mutableListOf() val accountConnections = mutableListOf() for (connection in (_connectionsFlow.value ?: return emptyList())) { - if (connection.accountId == accountId && connection.testnet == testnet && connection.appUrl.withoutQuery == appUrl.withoutQuery) { + if (connection.accountId == accountId && connection.network == network && connection.appUrl.withoutQuery == appUrl.withoutQuery) { accountConnections.add(connection.copy(pushEnabled = enabled)) } else { otherConnections.add(connection.copy()) @@ -205,7 +201,7 @@ class DAppsRepository( return emptyList() } - database.setPushEnabled(accountId, testnet, appUrl, enabled) + database.setPushEnabled(accountId, network, appUrl, enabled) _connectionsFlow.value = otherConnections + accountConnections return accountConnections } @@ -237,19 +233,19 @@ class DAppsRepository( suspend fun deleteApp( accountId: String, - testnet: Boolean, + network: TonNetwork, appUrl: Uri, type: AppConnectEntity.Type? = null ): List { if (type == null) { val predicate: (AppConnectEntity) -> Boolean = { - it.accountId == accountId && it.testnet == testnet && it.appUrl.withoutQuery == appUrl.withoutQuery + it.accountId == accountId && it.network == network && it.appUrl.withoutQuery == appUrl.withoutQuery } return deleteConnections(predicate) } else { val predicate: (AppConnectEntity) -> Boolean = { - it.accountId == accountId && it.testnet == testnet && it.appUrl.withoutQuery == appUrl.withoutQuery && it.type == type + it.accountId == accountId && it.network == network && it.appUrl.withoutQuery == appUrl.withoutQuery && it.type == type } return deleteConnections(predicate) @@ -258,10 +254,10 @@ class DAppsRepository( suspend fun deleteApps( accountId: String, - testnet: Boolean + network: TonNetwork ): List { val predicate: (AppConnectEntity) -> Boolean = { - it.accountId == accountId && it.testnet == testnet + it.accountId == accountId && it.network == network } return deleteConnections(predicate) } @@ -317,17 +313,17 @@ class DAppsRepository( try { val tcApps = rnLegacy.getTCApps() for (app in tcApps.mainnet) { - migrationFromLegacy(app, false) + migrationFromLegacy(app, TonNetwork.MAINNET) } for (apps in tcApps.testnet) { - migrationFromLegacy(apps, true) + migrationFromLegacy(apps, TonNetwork.TESTNET) } } catch (e: Throwable) { recordException(e) } } - suspend fun migrationFromLegacy(connections: RNTCApps, testnet: Boolean) { + suspend fun migrationFromLegacy(connections: RNTCApps, network: TonNetwork) { val accountId = connections.address.toRawAddress() for (legacyApp in connections.apps) { val newApp = AppEntity( @@ -340,7 +336,7 @@ class DAppsRepository( for (legacyConnections in legacyApp.connections) { val newConnection = AppConnectEntity( accountId = accountId, - testnet = testnet, + network = network, clientId = legacyConnections.clientId, type = if (legacyConnections.type == "remote") AppConnectEntity.Type.External else AppConnectEntity.Type.Internal, appUrl = newApp.url, @@ -361,18 +357,16 @@ class DAppsRepository( val (mainnetConnections, testnetConnections) = LegacyHelper.sortByNetworkAndAccount(connections) - addToLegacyCreateApps(testnetConnections, true, appsMap) - val data = RNTC( - mainnet = addToLegacyCreateApps(mainnetConnections, false, appsMap), - testnet = addToLegacyCreateApps(testnetConnections, true, appsMap) + mainnet = addToLegacyCreateApps(mainnetConnections, TonNetwork.MAINNET, appsMap), + testnet = addToLegacyCreateApps(testnetConnections, TonNetwork.TESTNET, appsMap) ) rnLegacy.setTCApps(data) } private fun addToLegacyCreateApps( connectionsMap: ArrayMap>, - testnet: Boolean, + network: TonNetwork, appsMap: Map ): List { val legacyApps = mutableListOf() @@ -383,7 +377,11 @@ class DAppsRepository( for ((appUrl, appUrlConnections) in connectionsByAppUrls) { val app = appsMap[appUrl] ?: continue - val notificationsEnabled = isPushEnabled(accountId, testnet, appUrl) + val notificationsEnabled = isPushEnabled( + accountId = accountId, + network = network, + appUrl = appUrl + ) val legacyConnections = mutableListOf() for (appUrlConnection in appUrlConnections) { legacyConnections.add(LegacyHelper.createConnection(appUrlConnection)) @@ -402,7 +400,7 @@ class DAppsRepository( } legacyApps.add(RNTCApps( - address = accountId.toUserFriendly(wallet = true, bounceable = true, testnet = testnet), + address = accountId.toUserFriendly(wallet = true, bounceable = true, testnet = network.isTestnet), apps = legacyAccountApps )) } @@ -438,6 +436,12 @@ class DAppsRepository( companion object { + private val manifestPaths = arrayOf( + "tonconnect-manifest.json", + "manifest.json", + "tcm.json" + ) + fun fixAppTitle(value: String): String { var name = value.trim() if (name.contains(":")) { @@ -454,12 +458,6 @@ class DAppsRepository( } return name } - - private val manifestPaths = arrayOf( - "tonconnect-manifest.json", - "manifest.json", - "tcm.json" - ) } -} \ No newline at end of file +} diff --git a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/LegacyHelper.kt b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/LegacyHelper.kt index 69f93331c..44611a60e 100644 --- a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/LegacyHelper.kt +++ b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/LegacyHelper.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.dapps import android.net.Uri import androidx.collection.ArrayMap +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.data.dapps.entities.AppConnectEntity import com.tonapps.wallet.data.rn.data.RNTCConnection import com.tonapps.wallet.data.rn.data.RNTCKeyPair @@ -22,7 +23,8 @@ internal object LegacyHelper { val mainnetConnectionsMap = ArrayMap>() val testnetConnectionsMap = ArrayMap>() for (connection in connections) { - val list = (if (connection.testnet) { + val isTestnet = connection.network == TonNetwork.TESTNET + val list = (if (isTestnet) { testnetConnectionsMap[connection.accountId] } else { mainnetConnectionsMap[connection.accountId] @@ -30,7 +32,7 @@ internal object LegacyHelper { list.add(connection) - if (connection.testnet) { + if (isTestnet) { testnetConnectionsMap[connection.accountId] = list } else { mainnetConnectionsMap[connection.accountId] = list diff --git a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/entities/AppConnectEntity.kt b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/entities/AppConnectEntity.kt index b5a2090c8..f7eb19b5c 100644 --- a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/entities/AppConnectEntity.kt +++ b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/entities/AppConnectEntity.kt @@ -3,6 +3,7 @@ package com.tonapps.wallet.data.dapps.entities import android.net.Uri import android.os.Parcelable import android.util.Base64 +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.asJSON import com.tonapps.security.CryptoBox import com.tonapps.security.Sodium @@ -14,7 +15,7 @@ import org.json.JSONObject @Parcelize data class AppConnectEntity( val accountId: String, - val testnet: Boolean, + val network: TonNetwork, val clientId: String, val type: Type, val appUrl: Uri, diff --git a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/source/DatabaseSource.kt b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/source/DatabaseSource.kt index c82d6a6a2..bf1624b7f 100644 --- a/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/source/DatabaseSource.kt +++ b/apps/wallet/data/dapps/src/main/java/com/tonapps/wallet/data/dapps/source/DatabaseSource.kt @@ -6,11 +6,12 @@ import android.content.SharedPreferences import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.net.Uri -import android.util.Log +import com.tonapps.log.L import androidx.core.content.edit import androidx.core.database.sqlite.transaction import androidx.core.net.toUri import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.extensions.getParcelable import com.tonapps.extensions.prefs @@ -39,7 +40,7 @@ internal class DatabaseSource( private companion object { private const val DATABASE_NAME = "dapps" - private const val DATABASE_VERSION = 2 + private const val DATABASE_VERSION = 3 private const val KEY_ALIAS = "_com_tonapps_dapps_master_key_" @@ -55,6 +56,7 @@ internal class DatabaseSource( private const val CONNECT_TABLE_APP_URL_COLUMN = "app_url" private const val CONNECT_TABLE_ACCOUNT_ID_COLUMN = "account_id" private const val CONNECT_TABLE_TESTNET_COLUMN = "testnet" + private const val CONNECT_TABLE_NETWORK_COLUMN = "network" private const val CONNECT_TABLE_CLIENT_ID_COLUMN = "client_id" private const val CONNECT_TABLE_TYPE_COLUMN = "type" private const val CONNECT_TABLE_TIMESTAMP_COLUMN = "timestamp" @@ -74,7 +76,7 @@ internal class DatabaseSource( private val connectFields = arrayOf( CONNECT_TABLE_APP_URL_COLUMN, CONNECT_TABLE_ACCOUNT_ID_COLUMN, - CONNECT_TABLE_TESTNET_COLUMN, + CONNECT_TABLE_NETWORK_COLUMN, CONNECT_TABLE_CLIENT_ID_COLUMN, CONNECT_TABLE_TYPE_COLUMN, CONNECT_TABLE_TIMESTAMP_COLUMN @@ -108,7 +110,7 @@ internal class DatabaseSource( db.execSQL("CREATE TABLE $CONNECT_TABLE_NAME (" + "$CONNECT_TABLE_CLIENT_ID_COLUMN TEXT PRIMARY KEY," + "$CONNECT_TABLE_ACCOUNT_ID_COLUMN TEXT," + - "$CONNECT_TABLE_TESTNET_COLUMN INTEGER," + + "$CONNECT_TABLE_NETWORK_COLUMN INTEGER," + "$CONNECT_TABLE_TYPE_COLUMN INTEGER," + "$CONNECT_TABLE_APP_URL_COLUMN TEXT," + "$CONNECT_TABLE_TIMESTAMP_COLUMN INTEGER" + @@ -116,7 +118,7 @@ internal class DatabaseSource( val connectIndexPrefix = "idx_$CONNECT_TABLE_NAME" db.execSQL("CREATE UNIQUE INDEX ${connectIndexPrefix}_client_id ON $CONNECT_TABLE_NAME ($CONNECT_TABLE_CLIENT_ID_COLUMN)") - db.execSQL("CREATE INDEX ${connectIndexPrefix}_account_id_testnet ON $CONNECT_TABLE_NAME ($CONNECT_TABLE_ACCOUNT_ID_COLUMN, $CONNECT_TABLE_TESTNET_COLUMN)") + db.execSQL("CREATE INDEX ${connectIndexPrefix}_account_id_network ON $CONNECT_TABLE_NAME ($CONNECT_TABLE_ACCOUNT_ID_COLUMN, $CONNECT_TABLE_NETWORK_COLUMN)") db.execSQL("CREATE INDEX ${connectIndexPrefix}_app_url ON $CONNECT_TABLE_NAME ($CONNECT_TABLE_TYPE_COLUMN, $CONNECT_TABLE_APP_URL_COLUMN)") } @@ -144,6 +146,20 @@ internal class DatabaseSource( if (oldVersion < 2) { createNotificationsTable(db) } + if (oldVersion < 3) { + db.beginTransaction() + try { + db.execSQL("ALTER TABLE $CONNECT_TABLE_NAME ADD COLUMN $CONNECT_TABLE_NETWORK_COLUMN INTEGER DEFAULT ${TonNetwork.MAINNET.value}") + db.execSQL( + "UPDATE $CONNECT_TABLE_NAME SET $CONNECT_TABLE_NETWORK_COLUMN = " + + "CASE WHEN $CONNECT_TABLE_TESTNET_COLUMN = 1 THEN ${TonNetwork.TESTNET.value} " + + "ELSE ${TonNetwork.MAINNET.value} END" + ) + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } + } } suspend fun insertNotifications(accountId: String, list: List) = withContext(coroutineContext) { @@ -220,7 +236,7 @@ internal class DatabaseSource( suspend fun insertConnection(connection: AppConnectEntity) = withContext(coroutineContext) { try { - val prefix = prefixAccount(connection.accountId, connection.testnet) + val prefix = prefixAccount(connection.accountId, connection.network) writableDatabase.delete(CONNECT_TABLE_NAME, "$CONNECT_TABLE_CLIENT_ID_COLUMN = ?", arrayOf(connection.clientId)) encryptedPrefs.edit { @@ -232,7 +248,7 @@ internal class DatabaseSource( val values = ContentValues() values.put(CONNECT_TABLE_APP_URL_COLUMN, connection.appUrl.withoutQuery.toString().removeSuffix("/")) values.put(CONNECT_TABLE_ACCOUNT_ID_COLUMN, connection.accountId) - values.put(CONNECT_TABLE_TESTNET_COLUMN, if (connection.testnet) 1 else 0) + values.put(CONNECT_TABLE_NETWORK_COLUMN, connection.network.value) values.put(CONNECT_TABLE_CLIENT_ID_COLUMN, connection.clientId) values.put(CONNECT_TABLE_TYPE_COLUMN, connection.type.value) values.put(CONNECT_TABLE_TIMESTAMP_COLUMN, connection.timestamp) @@ -254,10 +270,14 @@ internal class DatabaseSource( suspend fun deleteConnect(connection: AppConnectEntity): Boolean = withContext(coroutineContext) { val count = writableDatabase.delete(CONNECT_TABLE_NAME, "$CONNECT_TABLE_CLIENT_ID_COLUMN = ?", arrayOf(connection.clientId)) encryptedPrefs.edit { - val prefix = prefixAccount(connection.accountId, connection.testnet) + val prefix = prefixAccount(connection.accountId, connection.network) + val legacyPrefix = legacyPrefixAccount(connection.accountId, connection.network) remove(prefixKeyPair(prefix, connection.clientId)) remove(prefixProofSignature(prefix, connection.appUrl)) remove(prefixProofPayload(prefix, connection.appUrl)) + remove(prefixKeyPair(legacyPrefix, connection.clientId)) + remove(prefixProofSignature(legacyPrefix, connection.appUrl)) + remove(prefixProofPayload(legacyPrefix, connection.appUrl)) } count > 0 } @@ -273,7 +293,7 @@ internal class DatabaseSource( private fun readConnections(cursor: Cursor): List { val appUrlIndex = cursor.getColumnIndex(CONNECT_TABLE_APP_URL_COLUMN) val accountIdIndex = cursor.getColumnIndex(CONNECT_TABLE_ACCOUNT_ID_COLUMN) - val testnetIndex = cursor.getColumnIndex(CONNECT_TABLE_TESTNET_COLUMN) + val networkIndex = cursor.getColumnIndex(CONNECT_TABLE_NETWORK_COLUMN) val clientIdIndex = cursor.getColumnIndex(CONNECT_TABLE_CLIENT_ID_COLUMN) val typeIndex = cursor.getColumnIndex(CONNECT_TABLE_TYPE_COLUMN) val timestampIndex = cursor.getColumnIndex(CONNECT_TABLE_TIMESTAMP_COLUMN) @@ -281,28 +301,60 @@ internal class DatabaseSource( val connections = mutableListOf() while (cursor.moveToNext()) { val accountId = cursor.getString(accountIdIndex) - val testnet = cursor.getInt(testnetIndex) == 1 + val networkValue = cursor.getInt(networkIndex) + val network = TonNetwork.entries.firstOrNull { it.value == networkValue } ?: continue val clientId = cursor.getString(clientIdIndex) val appUrl = Uri.parse(cursor.getString(appUrlIndex)).withoutQuery - val prefix = prefixAccount(accountId, testnet) - val connectionEncrypted = getConnectionEncrypted(prefix, clientId, appUrl) ?: continue + + val prefix = prefixAccount(accountId, network) + var connectionEncrypted = getConnectionEncrypted(prefix, clientId, appUrl) + if (connectionEncrypted == null) { + val legacyPrefix = legacyPrefixAccount(accountId, network) + connectionEncrypted = getConnectionEncrypted(legacyPrefix, clientId, appUrl) + if (connectionEncrypted != null) { + migrateEncryptedData(legacyPrefix, prefix, clientId, appUrl, connectionEncrypted) + } + } + connectionEncrypted ?: continue connections.add(AppConnectEntity( appUrl = appUrl, accountId = accountId, - testnet = testnet, - clientId = cursor.getString(clientIdIndex), + network = network, + clientId = clientId, type = AppConnectEntity.Type.entries.first { it.value == cursor.getInt(typeIndex) }, keyPair = connectionEncrypted.keyPair, proofSignature = connectionEncrypted.proofSignature, proofPayload = connectionEncrypted.proofPayload, timestamp = cursor.getLong(timestampIndex), - pushEnabled = isPushEnabled(accountId, testnet, appUrl) + pushEnabled = isPushEnabled(accountId, network, appUrl) )) } return connections } + // TODO TK-125 + private fun migrateEncryptedData( + oldPrefix: String, + newPrefix: String, + clientId: String, + appUrl: Uri, + encrypted: ConnectionEncryptedEntity + ) { + encryptedPrefs.edit { + remove(prefixKeyPair(oldPrefix, clientId)) + remove(prefixProofSignature(oldPrefix, appUrl)) + remove(prefixProofPayload(oldPrefix, appUrl)) + } + encryptedPrefs.putParcelable(prefixKeyPair(newPrefix, clientId), encrypted.keyPair) + if (encrypted.proofSignature != null) { + encryptedPrefs.putString(prefixProofSignature(newPrefix, appUrl), encrypted.proofSignature) + } + if (encrypted.proofPayload != null) { + encryptedPrefs.putString(prefixProofPayload(newPrefix, appUrl), encrypted.proofPayload) + } + } + private fun getConnectionEncrypted( prefix: String, clientId: String, @@ -349,11 +401,12 @@ internal class DatabaseSource( return "push_${prefix}_${appUrl}" } - private fun prefixAccount( - accountId: String, - testnet: Boolean - ): String { - return "account_${accountId}:${if (testnet) "1" else "0"}" + private fun prefixAccount(accountId: String, network: TonNetwork): String { + return "account_${accountId}:${network.value}" + } + + private fun legacyPrefixAccount(accountId: String, network: TonNetwork): String { + return "account_${accountId}:${if (network.isTestnet) "1" else "0"}" } internal fun getLastEventId(): Long { @@ -376,13 +429,17 @@ internal class DatabaseSource( } } - internal fun isPushEnabled(accountId: String, testnet: Boolean, appUrl: Uri): Boolean { - return prefs.getBoolean(prefixPush(prefixAccount(accountId, testnet), appUrl), false) + internal fun isPushEnabled(accountId: String, network: TonNetwork, appUrl: Uri): Boolean { + val key = prefixPush(prefixAccount(accountId, network), appUrl) + if (prefs.contains(key)) { + return prefs.getBoolean(key, false) + } + return prefs.getBoolean(prefixPush(legacyPrefixAccount(accountId, network), appUrl), false) } - internal fun setPushEnabled(accountId: String, testnet: Boolean, appUrl: Uri, enabled: Boolean) { + internal fun setPushEnabled(accountId: String, network: TonNetwork, appUrl: Uri, enabled: Boolean) { prefs.edit { - putBoolean(prefixPush(prefixAccount(accountId, testnet), appUrl), enabled) + putBoolean(prefixPush(prefixAccount(accountId, network), appUrl), enabled) } } diff --git a/apps/wallet/data/events/build.gradle.kts b/apps/wallet/data/events/build.gradle.kts index 26fd17466..56efde99c 100644 --- a/apps/wallet/data/events/build.gradle.kts +++ b/apps/wallet/data/events/build.gradle.kts @@ -1,30 +1,26 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") id("com.google.devtools.ksp") kotlin("plugin.serialization") } -android { - namespace = Build.namespacePrefix("wallet.data.events") -} - dependencies { - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.rates)) - implementation(project(ProjectModules.Wallet.Data.collectibles)) - implementation(project(ProjectModules.Wallet.Data.staking)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.sqlite)) + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.rates) + implementation(projects.apps.wallet.data.collectibles) + implementation(projects.apps.wallet.data.staking) + implementation(projects.apps.wallet.api) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) + implementation(projects.lib.security) + implementation(projects.lib.sqlite) - implementation(libs.compose.paging) + implementation(libs.koin.core) - implementation(libs.androidX.room.runtime) - implementation(libs.androidX.room.ktx) - ksp(libs.androidX.room.compiler) + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) } diff --git a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt index 221bb5fe9..8a0123392 100644 --- a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt +++ b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.events import android.content.Context -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.api.entity.value.BlockchainAddress @@ -59,10 +59,6 @@ class EventsRepository( localDataSource.saveDecryptedComment(txId, comment) } - fun clearTxEvents(account: BlockchainAddress) { - localDataSource.clearTxEvents(account) - } - suspend fun fetch(query: TxFetchQuery): TxPage { val events = remoteDataSource.events(query) return TxPage( @@ -86,34 +82,34 @@ class EventsRepository( return sentTransactions.take(6) } - fun latestRecipientsFlow(accountId: String, testnet: Boolean) = flow { - localDataSource.getLatestRecipients(cacheLatestRecipientsKey(accountId, testnet))?.let { + fun latestRecipientsFlow(accountId: String, network: TonNetwork) = flow { + localDataSource.getLatestRecipients(cacheLatestRecipientsKey(accountId, network))?.let { emit(it) } - val remote = loadLatestRecipients(accountId, testnet) + val remote = loadLatestRecipients(accountId, network) emit(remote) }.flowOn(Dispatchers.IO) - private fun loadLatestRecipients(accountId: String, testnet: Boolean): List { - val list = remoteDataSource.getLatestRecipients(accountId, testnet) - localDataSource.setLatestRecipients(cacheLatestRecipientsKey(accountId, testnet), list) + private fun loadLatestRecipients(accountId: String, network: TonNetwork): List { + val list = remoteDataSource.getLatestRecipients(accountId, network) + localDataSource.setLatestRecipients(cacheLatestRecipientsKey(accountId, network), list) return list } - suspend fun getSingle(eventId: String, testnet: Boolean) = remoteDataSource.getSingle(eventId, testnet) + suspend fun getSingle(eventId: String, network: TonNetwork) = remoteDataSource.getSingle(eventId, network) suspend fun loadForToken( tokenAddress: String, accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null ): AccountEvents? = withContext(Dispatchers.IO) { if (tokenAddress == TokenEntity.TON.address) { - getRemote(accountId, testnet, beforeLt) + getRemote(accountId, network, beforeLt) } else { try { - api.getTokenEvents(tokenAddress, accountId, testnet, beforeLt) + api.getTokenEvents(tokenAddress, accountId, network, beforeLt) } catch (e: Throwable) { null } @@ -141,26 +137,26 @@ class EventsRepository( suspend fun get( accountId: String, - testnet: Boolean - ) = getLocal(accountId, testnet) ?: getRemote(accountId, testnet) + network: TonNetwork + ) = getLocal(accountId, network) ?: getRemote(accountId, network) suspend fun getRemote( accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null, limit: Int = 10 ): AccountEvents? = withContext(Dispatchers.IO) { try { val accountEvents = if (beforeLt != null) { - remoteDataSource.get(accountId, testnet, beforeLt, limit) + remoteDataSource.get(accountId, network, beforeLt, limit) } else { - val events = remoteDataSource.get(accountId, testnet, null, limit)?.also { - localDataSource.setEvents(cacheEventsKey(accountId, testnet), it) + val events = remoteDataSource.get(accountId, network, null, limit)?.also { + localDataSource.setEvents(cacheEventsKey(accountId, network), it) } events } ?: return@withContext null - localDataSource.addSpam(accountId, testnet, accountEvents.events.filter { + localDataSource.addSpam(accountId, network.isTestnet, accountEvents.events.filter { it.isScam }) @@ -170,17 +166,17 @@ class EventsRepository( } } - suspend fun getLocalSpam(accountId: String, testnet: Boolean) = withContext(Dispatchers.IO) { - localDataSource.getSpam(accountId, testnet) + suspend fun getLocalSpam(accountId: String, network: TonNetwork) = withContext(Dispatchers.IO) { + localDataSource.getSpam(accountId, network.isTestnet) } suspend fun markAsSpam( accountId: String, - testnet: Boolean, + network: TonNetwork, eventId: String, ) = withContext(Dispatchers.IO) { - val events = getSingle(eventId, testnet) ?: return@withContext - localDataSource.addSpam(accountId, testnet, events) + val events = getSingle(eventId, network) ?: return@withContext + localDataSource.addSpam(accountId, network.isTestnet, events) _hiddenTxIdsFlow.update { it.plus(eventId) } @@ -188,10 +184,10 @@ class EventsRepository( suspend fun removeSpam( accountId: String, - testnet: Boolean, + network: TonNetwork, eventId: String, ) = withContext(Dispatchers.IO) { - localDataSource.removeSpam(accountId, testnet, eventId) + localDataSource.removeSpam(accountId, network.isTestnet, eventId) _hiddenTxIdsFlow.update { it.minus(eventId) } @@ -199,7 +195,7 @@ class EventsRepository( suspend fun getRemoteSpam( accountId: String, - testnet: Boolean, + network: TonNetwork, startBeforeLt: Long? = null ) = withContext(Dispatchers.IO) { val list = mutableListOf() @@ -207,7 +203,7 @@ class EventsRepository( for (i in 0 until 10) { val events = remoteDataSource.get( accountId = accountId, - testnet = testnet, + network = network, beforeLt = beforeLt, limit = 50 )?.events ?: emptyList() @@ -220,29 +216,23 @@ class EventsRepository( beforeLt = events.lastOrNull()?.lt ?: break } val spamList = list.filter { it.isScam } - localDataSource.addSpam(accountId, testnet, spamList) + localDataSource.addSpam(accountId, network.isTestnet, spamList) spamList } suspend fun getLocal( accountId: String, - testnet: Boolean + network: TonNetwork ): AccountEvents? = withContext(Dispatchers.IO) { - localDataSource.getEvents(cacheEventsKey(accountId, testnet)) + localDataSource.getEvents(cacheEventsKey(accountId, network)) } - private fun cacheEventsKey(accountId: String, testnet: Boolean): String { - if (!testnet) { - return accountId - } - return "${accountId}_testnet" + private fun cacheEventsKey(accountId: String, network: TonNetwork): String { + return "${accountId}_${network.name.lowercase()}" } - private fun cacheLatestRecipientsKey(accountId: String, testnet: Boolean): String { - if (!testnet) { - return accountId - } - return "${accountId}_testnet" + private fun cacheLatestRecipientsKey(accountId: String, network: TonNetwork): String { + return "${accountId}_${network.name.lowercase()}" } -} \ No newline at end of file +} diff --git a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/Extensions.kt b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/Extensions.kt index 643412384..dfeeb32df 100644 --- a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/Extensions.kt +++ b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/Extensions.kt @@ -1,5 +1,6 @@ package com.tonapps.wallet.data.events +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.icu.Coins import com.tonapps.wallet.data.core.currency.WalletCurrency @@ -12,12 +13,12 @@ import io.tonapi.models.JettonTransferAction val JettonTransferAction.amountCoins: Coins get() = Coins.ofNano(amount, jetton.decimals) -suspend fun Action.getTonAmountRaw(ratesRepository: RatesRepository): Coins { +suspend fun Action.getTonAmountRaw(network: TonNetwork, ratesRepository: RatesRepository): Coins { val tonAmount = tonTransfer?.let { Coins.of(it.amount) } val jettonAmountInTON = jettonTransfer?.let { val amountCoins = it.amountCoins val jettonAddress = it.jetton.address - val rates = ratesRepository.getRates(WalletCurrency.TON, jettonAddress) + val rates = ratesRepository.getRates(TonNetwork.MAINNET, WalletCurrency.TON, jettonAddress) rates.convert(jettonAddress, amountCoins) } return tonAmount ?: jettonAmountInTON ?: Coins.ZERO diff --git a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/source/RemoteDataSource.kt b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/source/RemoteDataSource.kt index 050ba4db9..3e4998860 100644 --- a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/source/RemoteDataSource.kt +++ b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/source/RemoteDataSource.kt @@ -1,5 +1,6 @@ package com.tonapps.wallet.data.events.source +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.value.BlockchainAddress @@ -22,7 +23,13 @@ internal class RemoteDataSource( suspend fun events(query: TxFetchQuery): List = coroutineScope { val fetchLimit = query.limit val tonDeferred = async { - tonEvents(query.tonAddress, query.beforeTimestamp, query.afterTimestamp, fetchLimit) + tonEvents( + address = query.tonAddress, + network = query.tonAddress.network, + beforeTimestamp = query.beforeTimestamp, + afterTimestamp = query.afterTimestamp, + limit = fetchLimit + ) } val tronDeferred = async { @@ -38,13 +45,14 @@ internal class RemoteDataSource( suspend fun tonEvents( address: BlockchainAddress, + network: TonNetwork, beforeTimestamp: Timestamp?, afterTimestamp: Timestamp?, limit: Int, ): List { val events = api.fetchTonEvents( accountId = address.value, - testnet = address.testnet, + network = network, beforeTimestamp = beforeTimestamp, afterTimestamp = afterTimestamp, limit = limit @@ -52,7 +60,7 @@ internal class RemoteDataSource( return mapper.events(address, events) } - fun tronEvents( + suspend fun tronEvents( address: BlockchainAddress, tonProofToken: String, beforeTimestamp: Timestamp?, @@ -71,17 +79,17 @@ internal class RemoteDataSource( fun get( accountId: String, - testnet: Boolean, + network: TonNetwork, beforeLt: Long? = null, limit: Int = 12 - ): AccountEvents? = api.getEvents(accountId, testnet, beforeLt, limit) + ): AccountEvents? = api.getEvents(accountId, network, beforeLt, limit) - suspend fun getSingle(eventId: String, testnet: Boolean) = api.getSingleEvent(eventId, testnet) + suspend fun getSingle(eventId: String, network: TonNetwork) = api.getSingleEvent(eventId, network) - fun getLatestRecipients(accountId: String, testnet: Boolean): List { + fun getLatestRecipients(accountId: String, network: TonNetwork): List { val events = api.getEvents( accountId = accountId, - testnet = testnet, + network = network, limit = 100 )?.events ?: return emptyList() diff --git a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/tx/TxActionMapper.kt b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/tx/TxActionMapper.kt index dab86f3e8..e5504bf93 100644 --- a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/tx/TxActionMapper.kt +++ b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/tx/TxActionMapper.kt @@ -1,9 +1,10 @@ package com.tonapps.wallet.data.events.tx -import android.util.Log +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.icu.Coins import com.tonapps.wallet.api.API +import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.value.BlockchainAddress import com.tonapps.wallet.api.entity.value.Timestamp @@ -72,13 +73,14 @@ internal class TxActionMapper( val currency = WalletCurrency.USDT_TRON val isOutgoing = event.from == address.value val builder = TxActionBody.Builder(if (isOutgoing) ActionType.Send else ActionType.Received) + val isTestnet = address.network.isTestnet builder.setRecipient(TxActionBody.Account( address = event.to, - testnet = address.testnet + testnet = isTestnet )) builder.setSender(TxActionBody.Account( address = event.from, - testnet = address.testnet + testnet = isTestnet )) val isScam = event.from != address.value && event.amount < Coins.of(0.1, currency.decimals) if (isOutgoing) { @@ -153,7 +155,7 @@ internal class TxActionMapper( private fun fetchNft(address: BlockchainAddress, nftAddress: String) = collectiblesRepository.getNft( accountId = address.value, - testnet = address.testnet, + network = address.network, address = nftAddress ) @@ -165,7 +167,7 @@ internal class TxActionMapper( imageUrl = product.thumbUri.toString() ) - private fun product(nft: NftItem, testnet: Boolean) = product(NftEntity(nft, testnet)) + private fun product(nft: NftItem, network: TonNetwork) = product(NftEntity(nft, network)) private fun text(comment: String?, encryptedComment: EncryptedComment?): TxActionBody.Text? { if (comment != null) { @@ -217,8 +219,8 @@ internal class TxActionMapper( private suspend fun isMaybeSpam(address: BlockchainAddress, action: Action): Boolean { val isTransfer = action.type == Action.Type.TonTransfer || action.type == Action.Type.JettonTransfer if (isTransfer && !action.isOutTransfer(address.value)) { - val total = action.getTonAmountRaw(ratesRepository) - return total < api.config.reportAmount + val total = action.getTonAmountRaw(address.network, ratesRepository) + return total < api.getConfig(address.network).reportAmount } else { return false } @@ -314,28 +316,28 @@ internal class TxActionMapper( private fun removeExtension(address: BlockchainAddress, action: RemoveExtensionAction): TxActionBody { val builder = TxActionBody.Builder(ActionType.RemoveExtension) builder.setSubtitle(action.extension) - builder.setSender(account(action.wallet, address.testnet)) + builder.setSender(account(action.wallet, address.network.isTestnet)) return builder.build() } private fun addExtension(address: BlockchainAddress, action: AddExtensionAction): TxActionBody { val builder = TxActionBody.Builder(ActionType.AddExtension) builder.setSubtitle(action.extension) - builder.setSender(account(action.wallet, address.testnet)) + builder.setSender(account(action.wallet, address.network.isTestnet)) return builder.build() } private fun setSignatureAllowed(address: BlockchainAddress, action: SetSignatureAllowedAction): TxActionBody { val type = if (action.allowed) ActionType.SetSignatureAllowed else ActionType.SetSignatureNotAllowed val builder = TxActionBody.Builder(type) - builder.setSender(account(action.wallet, address.testnet)) + builder.setSender(account(action.wallet, address.network.isTestnet)) return builder.build() } private fun subscribe(address: BlockchainAddress, action: SubscriptionAction): TxActionBody { val amount = Coins.ofNano(action.price.value, action.price.decimals) val builder = TxActionBody.Builder(ActionType.Subscribe) - builder.setRecipient(account(action.beneficiary, address.testnet)) + builder.setRecipient(account(action.beneficiary, address.network.isTestnet)) builder.setSubtitle(action.subscription) builder.setOutgoingAmount(amount) builder.setImageUrl(action.beneficiary.icon) @@ -344,7 +346,7 @@ internal class TxActionMapper( private fun unSubscribe(address: BlockchainAddress, action: UnSubscriptionAction): TxActionBody { val builder = TxActionBody.Builder(ActionType.UnSubscribe) - builder.setRecipient(account(action.beneficiary, address.testnet)) + builder.setRecipient(account(action.beneficiary, address.network.isTestnet)) builder.setSubtitle(action.subscription) builder.setImageUrl(action.beneficiary.icon) return builder.build() @@ -364,8 +366,8 @@ internal class TxActionMapper( private fun nftPurchase(address: BlockchainAddress, action: NftPurchaseAction): TxActionBody { val currency = currency(action.amount) val amount = Coins.ofNano(action.amount.value, currency.decimals) - val recipient = account(action.seller, address.testnet) - val product = product(action.nft, address.testnet) + val recipient = account(action.seller, address.network.isTestnet) + val product = product(action.nft, address.network) val builder = TxActionBody.Builder(ActionType.NftPurchase) builder.setRecipient(recipient) @@ -381,7 +383,7 @@ internal class TxActionMapper( private fun withdrawStake(address: BlockchainAddress, action: WithdrawStakeAction): TxActionBody { val amount = Coins.of(action.amount) - val recipient = account(action.pool, address.testnet) + val recipient = account(action.pool, address.network.isTestnet) val builder = TxActionBody.Builder(ActionType.WithdrawStake) builder.setRecipient(recipient) builder.setOutgoingAmount(amount) @@ -392,9 +394,9 @@ internal class TxActionMapper( val currency = currency(action.amount) val amount = Coins.ofNano(action.amount.value, currency.decimals) val product = action.nft?.let { - product(it, address.testnet) + product(it, address.network) } - val recipient = account(action.auction, address.testnet) + val recipient = account(action.auction, address.network.isTestnet) val builder = TxActionBody.Builder(ActionType.AuctionBid) builder.setRecipient(recipient) builder.setOutgoingAmount(amount, currency) @@ -415,7 +417,7 @@ internal class TxActionMapper( private fun withdrawStakeRequest(address: BlockchainAddress, action: WithdrawStakeRequestAction): TxActionBody { val amount = Coins.of(action.amount ?: 0L) - val recipient = account(action.pool, address.testnet) + val recipient = account(action.pool, address.network.isTestnet) val builder = TxActionBody.Builder(ActionType.WithdrawStakeRequest) builder.setRecipient(recipient) builder.setOutgoingAmount(amount) @@ -425,7 +427,7 @@ internal class TxActionMapper( private fun jettonMint(address: BlockchainAddress, action: JettonMintAction): TxActionBody { val amount = Coins.ofNano(action.amount, action.jetton.decimals) val currency = currency(action.jetton) - val recipient = account(action.recipient, address.testnet) + val recipient = account(action.recipient, address.network.isTestnet) val builder = TxActionBody.Builder(ActionType.JettonMint) builder.setRecipient(recipient) builder.setOutgoingAmount(amount, currency) @@ -471,7 +473,7 @@ internal class TxActionMapper( val iconUrl = "android.resource://com.ton_keeper/${StakingPool.getIcon(implementation)}" val amount = Coins.of(action.amount) val builder = TxActionBody.Builder(ActionType.DepositStake) - builder.setRecipient(account(action.pool, address.testnet)) + builder.setRecipient(account(action.pool, address.network.isTestnet)) builder.setOutgoingAmount(amount) builder.setImageUrl(iconUrl) return builder.build() @@ -482,8 +484,8 @@ internal class TxActionMapper( private fun nftItemTransfer(address: BlockchainAddress, action: NftItemTransferAction): TxActionBody { val nft = fetchNft(address, action.nft) val product = nft?.let(::product) - val sender = action.sender?.let { account(it, address.testnet) } - val recipient = action.recipient?.let { account(it, address.testnet) } + val sender = action.sender?.let { account(it, address.network.isTestnet) } + val recipient = action.recipient?.let { account(it, address.network.isTestnet) } val isOutgoing = sender?.address?.equalsAddress(address.value) == true val builder = TxActionBody.Builder(if (isOutgoing) ActionType.NftSend else ActionType.NftReceived) sender?.let(builder::setSender) @@ -504,7 +506,7 @@ internal class TxActionMapper( private fun smartContract(address: BlockchainAddress, action: SmartContractAction): TxActionBody { val amount = Coins.of(action.tonAttached) val builder = TxActionBody.Builder(ActionType.CallContract) - builder.setSender(account(action.executor, address.testnet)) + builder.setSender(account(action.executor, address.network.isTestnet)) builder.setSubtitle(action.payload ?: action.operation) builder.setOutgoingAmount(amount) return builder.build() @@ -513,8 +515,8 @@ internal class TxActionMapper( private fun tonTransfer(address: BlockchainAddress, action: TonTransferAction): TxActionBody { val amount = Coins.of(action.amount) val currency = WalletCurrency.TON - val sender = account(action.sender, address.testnet) - val recipient = account(action.recipient, address.testnet) + val sender = account(action.sender, address.network.isTestnet) + val recipient = account(action.recipient, address.network.isTestnet) val isOutgoing = sender.address.equalsAddress(address.value) val builder = TxActionBody.Builder(if (isOutgoing) ActionType.Send else ActionType.Received) builder.setSender(sender) @@ -530,10 +532,11 @@ internal class TxActionMapper( } private fun jettonTransfer(address: BlockchainAddress, action: JettonTransferAction): TxActionBody { - val amount = Coins.ofNano(action.amount, action.jetton.decimals) + val jetton = TokenEntity(action.jetton) + val amount = jetton.toUIAmount(Coins.ofNano(action.amount, jetton.decimals)) val currency = currency(action.jetton) - val sender = action.sender?.let { account(it, address.testnet) } - val recipient = action.recipient?.let { account(it, address.testnet) } + val sender = action.sender?.let { account(it, address.network.isTestnet) } + val recipient = action.recipient?.let { account(it, address.network.isTestnet) } val isOutgoing = recipient?.let { !it.address.equalsAddress(address.value) } ?: false @@ -558,23 +561,25 @@ internal class TxActionMapper( val incomingAmount = if (action.tonIn != null) { TxActionBody.Value(Coins.of(action.tonIn!!), WalletCurrency.TON) } else { + val jetton = TokenEntity(action.jettonMasterIn!!) val currency = currency(action.jettonMasterIn!!) - TxActionBody.Value(Coins.ofNano(action.amountIn, currency.decimals), currency) + TxActionBody.Value(jetton.toUIAmount(Coins.ofNano(action.amountIn, jetton.decimals)), currency) } val outgoingAmount = if (action.tonOut != null) { TxActionBody.Value(Coins.of(action.tonOut!!), WalletCurrency.TON) } else { + val jetton = TokenEntity(action.jettonMasterOut!!) val currency = currency(action.jettonMasterOut!!) - TxActionBody.Value(Coins.ofNano(action.amountOut, currency.decimals), currency) + TxActionBody.Value(jetton.toUIAmount(Coins.ofNano(action.amountOut, jetton.decimals)), currency) } val builder = TxActionBody.Builder(ActionType.Swap) builder.setSubtitle(action.dex) builder.setIncomingAmount(outgoingAmount) builder.setOutgoingAmount(incomingAmount) - builder.setRecipient(account(action.userWallet, address.testnet)) - builder.setSender(account(action.router, address.testnet)) + builder.setRecipient(account(action.userWallet, address.network.isTestnet)) + builder.setSender(account(action.router, address.network.isTestnet)) action.jettonMasterOut?.verification?.let { verification -> if (verification != JettonVerificationType.whitelist) { builder.addFlag(TxFlag.UnverifiedToken) @@ -592,7 +597,7 @@ internal class TxActionMapper( val amount = action.amount val coins = Coins.ofNano(amount.value, amount.decimals) val builder = TxActionBody.Builder(ActionType.Purchase) - builder.setRecipient(account(action.destination, address.testnet)) + builder.setRecipient(account(action.destination, address.network.isTestnet)) builder.setOutgoingAmount(coins, currency(amount)) if (amount.verification != TrustType.whitelist) { builder.addFlag(TxFlag.UnverifiedToken) @@ -603,7 +608,7 @@ internal class TxActionMapper( private fun gasRelay(address: BlockchainAddress, action: GasRelayAction): TxActionBody { val coins = Coins.of(action.amount) val builder = TxActionBody.Builder(ActionType.GasRelay) - builder.setRecipient(account(action.target, address.testnet)) + builder.setRecipient(account(action.target, address.network.isTestnet)) builder.setIncomingAmount(coins, WalletCurrency.TON) return builder.build() } @@ -615,11 +620,11 @@ internal class TxActionMapper( val sender = simplePreview.accounts.firstOrNull { it.address.equalsAddress(address.value) - }?.let { account(it, address.testnet) } + }?.let { account(it, address.network.isTestnet) } val recipient = simplePreview.accounts.firstOrNull { !it.address.equalsAddress(address.value) - }?.let { account(it, address.testnet) } + }?.let { account(it, address.network.isTestnet) } val builder = TxActionBody.Builder(ActionType.Unknown) builder.setTitle(simplePreview.name) @@ -631,4 +636,4 @@ internal class TxActionMapper( builder.setOutgoingAmount(Coins.ZERO, unknown) return builder.build() } -} \ No newline at end of file +} diff --git a/apps/wallet/data/passcode/build.gradle.kts b/apps/wallet/data/passcode/build.gradle.kts index 5a397012c..5bf9945b8 100644 --- a/apps/wallet/data/passcode/build.gradle.kts +++ b/apps/wallet/data/passcode/build.gradle.kts @@ -1,21 +1,16 @@ plugins { - id("com.tonapps.wallet.data") -} - -android { - namespace = Build.namespacePrefix("wallet.data.passcode") + id("target.android.library") } dependencies { + implementation(libs.androidx.biometric) + implementation(libs.koin.core) - implementation(libs.androidX.biometric) - - implementation(project(ProjectModules.UIKit.core)) - - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.account)) - implementation(project(ProjectModules.Wallet.Data.settings)) - implementation(project(ProjectModules.Wallet.Data.rn)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.security)) + implementation(projects.ui.uikit.core) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.account) + implementation(projects.apps.wallet.data.settings) + implementation(projects.apps.wallet.data.rn) + implementation(projects.lib.extensions) + implementation(projects.lib.security) } diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/LockScreen.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/LockScreen.kt index def785160..a32f4a02e 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/LockScreen.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/LockScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.passcode import android.content.Context -import android.util.Log +import com.tonapps.log.L import androidx.biometric.BiometricPrompt import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeHelper.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeHelper.kt index 6616f5f63..773b83aac 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeHelper.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeHelper.kt @@ -2,7 +2,7 @@ package com.tonapps.wallet.data.passcode import android.content.Context import android.content.res.Configuration -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.passcode.source.PasscodeStore import com.tonapps.wallet.data.settings.SettingsRepository diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt index 0ba3b75a9..582504fff 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.passcode import android.content.Context -import android.util.Log +import com.tonapps.log.L import androidx.biometric.BiometricPrompt import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.logError diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/dialog/PasscodeDialog.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/dialog/PasscodeDialog.kt index 3a2f7181a..e8a631ba3 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/dialog/PasscodeDialog.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/dialog/PasscodeDialog.kt @@ -6,7 +6,7 @@ import android.view.View import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope import com.tonapps.wallet.data.passcode.PasscodeHelper -import com.tonapps.wallet.data.passcode.R +import com.tonapps.apps.wallet.data.passcode.R import com.tonapps.wallet.data.passcode.ui.PasscodeView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/source/PasscodeStore.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/source/PasscodeStore.kt index fc9bbbc58..d118c314a 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/source/PasscodeStore.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/source/PasscodeStore.kt @@ -2,7 +2,7 @@ package com.tonapps.wallet.data.passcode.source import android.content.Context import android.content.SharedPreferences -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.clear import com.tonapps.extensions.putString import com.tonapps.extensions.remove diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/ui/PasscodeView.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/ui/PasscodeView.kt index 9d40a3df1..70b98c901 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/ui/PasscodeView.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/ui/PasscodeView.kt @@ -3,7 +3,7 @@ package com.tonapps.wallet.data.passcode.ui import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView -import com.tonapps.wallet.data.passcode.R +import com.tonapps.apps.wallet.data.passcode.R import uikit.widget.ColumnLayout import uikit.widget.NumPadView import uikit.widget.PinInputView diff --git a/apps/wallet/data/plugins/build.gradle.kts b/apps/wallet/data/plugins/build.gradle.kts index 037632319..aa14b23f3 100644 --- a/apps/wallet/data/plugins/build.gradle.kts +++ b/apps/wallet/data/plugins/build.gradle.kts @@ -1,16 +1,14 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } - -android { - namespace = Build.namespacePrefix("wallet.data.plugins") -} - dependencies { - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) + implementation(libs.koin.core) + + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.lib.blockchain) } diff --git a/apps/wallet/data/plugins/src/main/java/com/tonapps/wallet/data/plugins/PluginsRepository.kt b/apps/wallet/data/plugins/src/main/java/com/tonapps/wallet/data/plugins/PluginsRepository.kt index c979a7ab9..d84b7dd55 100644 --- a/apps/wallet/data/plugins/src/main/java/com/tonapps/wallet/data/plugins/PluginsRepository.kt +++ b/apps/wallet/data/plugins/src/main/java/com/tonapps/wallet/data/plugins/PluginsRepository.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.plugins import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.data.core.BlobDataSource import io.Serializer @@ -30,10 +31,10 @@ class PluginsRepository( suspend fun getPlugins( accountId: String, - testnet: Boolean, + network: TonNetwork, refresh: Boolean = false, ): List = withContext(Dispatchers.IO) { - val key = cacheKey(accountId, testnet) + val key = cacheKey(accountId, network) if (!refresh) { val cached = getCache(key) if (cached != null) { @@ -41,7 +42,7 @@ class PluginsRepository( } } try { - val wallet = api.wallet(testnet).getWalletInfo(accountId) + val wallet = api.wallet(network).getWalletInfo(accountId) val plugins = wallet.plugins setCache(key, plugins) _updatedFlow.emit(Unit) @@ -55,8 +56,8 @@ class PluginsRepository( } } - private fun cacheKey(accountId: String, testnet: Boolean): String { - return if (testnet) "${accountId}_testnet" else accountId + private fun cacheKey(accountId: String, network: TonNetwork): String { + return "${accountId}_${network.name.lowercase()}" } override fun onMarshall(data: List) = Serializer.toJSON(data).toByteArray() diff --git a/apps/wallet/data/purchase/build.gradle.kts b/apps/wallet/data/purchase/build.gradle.kts index 1dd88c190..ab87107d4 100644 --- a/apps/wallet/data/purchase/build.gradle.kts +++ b/apps/wallet/data/purchase/build.gradle.kts @@ -1,18 +1,15 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") kotlin("plugin.serialization") } -android { - namespace = Build.namespacePrefix("wallet.data.purchase") -} - dependencies { - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Module.tonApi)) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.api) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.tonapi.legacy) api(libs.ton.tvm) api(libs.ton.crypto) @@ -20,4 +17,5 @@ dependencies { api(libs.ton.blockTlb) api(libs.ton.tonapiTl) api(libs.ton.contract) + implementation(libs.koin.core) } diff --git a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/PurchaseRepository.kt b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/PurchaseRepository.kt index 3ed17d589..583b63a8c 100644 --- a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/PurchaseRepository.kt +++ b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/PurchaseRepository.kt @@ -1,16 +1,12 @@ package com.tonapps.wallet.data.purchase import android.content.Context -import android.util.Log -import com.google.firebase.BuildConfig -import com.tonapps.extensions.getParcelable -import com.tonapps.extensions.prefs -import com.tonapps.extensions.putParcelable +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.toByteArray import com.tonapps.extensions.toParcel +import com.tonapps.log.L import com.tonapps.wallet.api.API import com.tonapps.wallet.data.core.BlobDataSource -import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.purchase.entity.MerchantEntity import com.tonapps.wallet.data.purchase.entity.OnRamp import com.tonapps.wallet.data.purchase.entity.PurchaseCategoryEntity @@ -19,15 +15,10 @@ import com.tonapps.wallet.data.purchase.entity.PurchaseMethodEntity import io.Serializer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext -import org.ton.crypto.digest.sha512 -import org.ton.crypto.hex import java.util.Locale -import java.util.UUID import java.util.concurrent.TimeUnit class PurchaseRepository( @@ -74,10 +65,10 @@ class PurchaseRepository( suspend fun getPaymentMethods(currency: String): List = withContext(Dispatchers.IO) { try { val data = api.getOnRampPaymentMethods(currency) ?: throw Exception("No payment methods found for country: ${api.country}") - Log.d("PurchaseRepositoryLog", "getPaymentMethods: $data") + L.d("PurchaseRepositoryLog", "getPaymentMethods: $data") Serializer.JSON.decodeFromString>(data) } catch (e: Throwable) { - Log.e("PurchaseRepositoryLog", "error", e) + L.e("PurchaseRepositoryLog", "error", e) emptyList() } } @@ -105,11 +96,11 @@ class PurchaseRepository( fun get( - testnet: Boolean, + network: TonNetwork, country: String, locale: Locale, ): Pair, List>? { - val data = get(testnet, locale) ?: return null + val data = get(network, locale) ?: return null val methods = data.getCountry(country).methods return filterMethods(data.buy, methods) to filterMethods(data.sell, methods) } @@ -141,50 +132,29 @@ class PurchaseRepository( return list } - fun getMethod(id: String, testnet: Boolean, locale: Locale): PurchaseMethodEntity? { - val data = get(testnet, locale) ?: return null + fun getMethod(id: String, network: TonNetwork, locale: Locale): PurchaseMethodEntity? { + val data = get(network, locale) ?: return null val methods = (data.buy + data.sell).map { it.items }.flatten() return methods.find { it.id == id } } - private fun get(testnet: Boolean, locale: Locale): PurchaseDataEntity? { - val key = cacheKey(testnet, locale) + private fun get(network: TonNetwork, locale: Locale): PurchaseDataEntity? { + val key = cacheKey(network, locale) var data = getCache(key) if (data == null) { - data = load(testnet, locale) ?: return null + data = load(network, locale) ?: return null setCache(key, data) } return data } - private fun load(testnet: Boolean, locale: Locale): PurchaseDataEntity? { - val json = api.getFiatMethods(testnet, locale) ?: return null + private fun load(network: TonNetwork, locale: Locale): PurchaseDataEntity? { + val json = api.getFiatMethods(network, locale) ?: return null return PurchaseDataEntity(json) } - private fun cacheKey(testnet: Boolean, locale: Locale): String { - val prefix = if (testnet) "testnet" else "mainnet" - return "$prefix-${locale.language}" - } - - fun replaceUrl( - url: String, - address: String, - currency: String - ): String { - var replacedUrl = url.replace("{ADDRESS}", address) - replacedUrl = replacedUrl.replace("{CUR_FROM}", currency) - replacedUrl = replacedUrl.replace("{CUR_TO}", "TON") - - if (replacedUrl.contains("TX_ID")) { - val mercuryoSecret = api.config.mercuryoSecret - val signature = hex(sha512((address+mercuryoSecret).toByteArray())) - val tx = "mercuryo_" + UUID.randomUUID().toString() - replacedUrl = replacedUrl.replace("{TX_ID}", tx) - replacedUrl = replacedUrl.replace("=TON&", "=TONCOIN&") - replacedUrl += "&signature=$signature" - } - return replacedUrl + private fun cacheKey(network: TonNetwork, locale: Locale): String { + return "${network.name.lowercase()}-${locale.language}" } override fun onMarshall(data: PurchaseDataEntity) = data.toByteArray() diff --git a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRamp.kt b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRamp.kt index e4a258d40..951232528 100644 --- a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRamp.kt +++ b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRamp.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.purchase.entity import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.purchase.OnRampUtils import kotlinx.coroutines.flow.map diff --git a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRampCurrencies.kt b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRampCurrencies.kt index b204f582e..2683cb768 100644 --- a/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRampCurrencies.kt +++ b/apps/wallet/data/purchase/src/main/java/com/tonapps/wallet/data/purchase/entity/OnRampCurrencies.kt @@ -1,6 +1,6 @@ package com.tonapps.wallet.data.purchase.entity -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.data.purchase.entity.OnRamp.Asset data class OnRampCurrencies( diff --git a/apps/wallet/data/rates/build.gradle.kts b/apps/wallet/data/rates/build.gradle.kts index 9a821a184..2c96d764a 100644 --- a/apps/wallet/data/rates/build.gradle.kts +++ b/apps/wallet/data/rates/build.gradle.kts @@ -1,18 +1,16 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.rates") -} - dependencies { - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) + implementation(libs.koin.core) + + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.tonapi.legacy) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) } diff --git a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/RatesRepository.kt b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/RatesRepository.kt index 050fd6eb2..45c3805dc 100644 --- a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/RatesRepository.kt +++ b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/RatesRepository.kt @@ -1,9 +1,8 @@ package com.tonapps.wallet.data.rates import android.content.Context -import android.util.Log -import com.google.firebase.annotations.concurrent.Background import com.tonapps.icu.Coins +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.core.currency.WalletCurrency @@ -25,6 +24,7 @@ class RatesRepository( private val manager = RateManager() suspend fun getRate( + network: TonNetwork, from: WalletCurrency, to: WalletCurrency, baseCurrency: WalletCurrency = WalletCurrency.USD @@ -33,7 +33,7 @@ class RatesRepository( val all = listOf(from, to, baseCurrency) val currency = all.first { it.fiat } val tokens = all.filter { !it.fiat } - val response = fetchRates(currency.code, tokens.map { it.tokenQuery }) + val response = fetchRates(network, currency.code, tokens.map { it.tokenQuery }) for (token in tokens) { val tokenRates = response[token.tokenQuery] ?: continue val price = tokenRates.prices?.get(currency.code)?.let { @@ -48,31 +48,32 @@ class RatesRepository( } suspend fun convert( + network: TonNetwork, amount: Coins, from: WalletCurrency, to: WalletCurrency ): Coins { - getRate(from, to) + getRate(network, from, to) return manager.convert(amount, from, to) ?: Coins.ZERO } - suspend fun updateAll(currency: WalletCurrency, tokens: List) = withContext(Dispatchers.IO) { - load(currency, tokens.take(100).toMutableList()) + suspend fun updateAll(network: TonNetwork, currency: WalletCurrency, tokens: List) = withContext(Dispatchers.IO) { + load(network, currency, tokens.take(100).toMutableList()) } - suspend fun updateAll(currency: WalletCurrency) = withContext(Dispatchers.IO) { - updateAll(currency, localDataSource.get(currency).tokens) + suspend fun updateAll(network: TonNetwork, currency: WalletCurrency) = withContext(Dispatchers.IO) { + updateAll(network, currency, localDataSource.get(network, currency).tokens) } - fun cache(currency: WalletCurrency, tokens: List): RatesEntity { - return localDataSource.get(currency).filter(tokens) + fun cache(network: TonNetwork, currency: WalletCurrency, tokens: List): RatesEntity { + return localDataSource.get(network, currency).filter(tokens) } - fun load(currency: WalletCurrency, token: String) { - load(currency, mutableListOf(token)) + fun load(network: TonNetwork, currency: WalletCurrency, token: String) { + load(network, currency, mutableListOf(token)) } - fun load(currency: WalletCurrency, tokens: MutableList) { + fun load(network: TonNetwork, currency: WalletCurrency, tokens: MutableList) { if (!tokens.contains(TokenEntity.TON.address)) { tokens.add(TokenEntity.TON.address) } @@ -81,23 +82,23 @@ class RatesRepository( } val rates = mutableMapOf() for (chunk in tokens.chunked(100)) { - runCatching { fetchRates(currency.code, chunk) }.onSuccess(rates::putAll) + runCatching { fetchRates(network, currency.code, chunk) }.onSuccess(rates::putAll) } val usdtRate = rates[TokenEntity.USDT.address] usdtRate?.let { rates.put(TokenEntity.TRON_USDT.address, usdtRate) } - insertRates(currency, rates) + insertRates(network, currency, rates) } - private fun fetchRates(code: String, tokens: List): Map { + private fun fetchRates(network: TonNetwork, code: String, tokens: List): Map { if (tokens.size > 100) { throw IllegalArgumentException("Too many tokens requested: ${tokens.size}") } - return api.getRates(code, tokens) ?: throw IllegalStateException("Failed to fetch rates for $code with tokens: $tokens") + return api.getRates(network, code, tokens) ?: throw IllegalStateException("Failed to fetch rates for $code with tokens: $tokens") } - fun insertRates(currency: WalletCurrency, rates: Map) { + fun insertRates(network: TonNetwork, currency: WalletCurrency, rates: Map) { if (rates.isEmpty()) { return } @@ -114,31 +115,32 @@ class RatesRepository( diff = RateDiffEntity(currency, value), )) } - localDataSource.add(currency, entities) + localDataSource.add(network, currency, entities) } - private fun getCachedRates(currency: WalletCurrency, tokens: List): RatesEntity { - return localDataSource.get(currency).filter(tokens) + private fun getCachedRates(network: TonNetwork, currency: WalletCurrency, tokens: List): RatesEntity { + return localDataSource.get(network, currency).filter(tokens) } - suspend fun getRates(currency: WalletCurrency, token: String): RatesEntity { - return getRates(currency, listOf(token)) + suspend fun getRates(network: TonNetwork, currency: WalletCurrency, token: String): RatesEntity { + return getRates(network, currency, listOf(token)) } - suspend fun getTONRates(currency: WalletCurrency): RatesEntity { - return getRates(currency, TokenEntity.TON.address) + suspend fun getTONRates(network: TonNetwork, currency: WalletCurrency): RatesEntity { + return getRates(network, currency, TokenEntity.TON.address) } suspend fun getRates( + network: TonNetwork, currency: WalletCurrency, tokens: List ): RatesEntity = withContext(Dispatchers.IO) { - val rates = getCachedRates(currency, tokens) + val rates = getCachedRates(network, currency, tokens) if (rates.hasTokens(tokens)) { rates } else { - load(currency, tokens.toMutableList()) - getCachedRates(currency, tokens) + load(network, currency, tokens.toMutableList()) + getCachedRates(network, currency, tokens) } } -} \ No newline at end of file +} diff --git a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/entity/RatesEntity.kt b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/entity/RatesEntity.kt index 50fb78f7f..1f9f408a1 100644 --- a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/entity/RatesEntity.kt +++ b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/entity/RatesEntity.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.rates.entity import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.icu.Coins import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.core.currency.WalletCurrency diff --git a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/source/BlobDataSource.kt b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/source/BlobDataSource.kt index b1473dbc8..a5b3710a2 100644 --- a/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/source/BlobDataSource.kt +++ b/apps/wallet/data/rates/src/main/java/com/tonapps/wallet/data/rates/source/BlobDataSource.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.rates.source import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.toByteArray import com.tonapps.extensions.toParcel import com.tonapps.wallet.data.core.BlobDataSource @@ -19,21 +20,26 @@ internal class BlobDataSource(context: Context): BlobDataSource( override fun onMarshall(data: RatesEntity) = data.toByteArray() - fun get(currency: WalletCurrency): RatesEntity { - val rates = getCache(currency.code) ?: RatesEntity.empty(currency) + private fun cacheKey(network: TonNetwork, currency: WalletCurrency): String { + return "${network.value}_${currency.code}" + } + + fun get(network: TonNetwork, currency: WalletCurrency): RatesEntity { + val key = cacheKey(network, currency) + val rates = getCache(key) ?: RatesEntity.empty(currency) if (rates.isEmpty) { - clearCache(currency.code) + clearCache(key) return rates } return rates.copy() } - fun add(currency: WalletCurrency, list: List) { + fun add(network: TonNetwork, currency: WalletCurrency, list: List) { if (list.isEmpty()) { return } - val rates = get(currency).merge(list) - setCache(currency.code, rates) + val rates = get(network, currency).merge(list) + setCache(cacheKey(network, currency), rates) } } \ No newline at end of file diff --git a/apps/wallet/data/rn/build.gradle.kts b/apps/wallet/data/rn/build.gradle.kts index 4bef1d33e..fafa0b7af 100644 --- a/apps/wallet/data/rn/build.gradle.kts +++ b/apps/wallet/data/rn/build.gradle.kts @@ -1,22 +1,19 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.rn") -} - dependencies { api(platform(libs.firebase.bom)) api(libs.firebase.crashlytics) - implementation(libs.androidX.biometric) + implementation(libs.androidx.biometric) implementation(libs.ton.crypto) + implementation(libs.koin.core) - implementation(project(ProjectModules.Lib.sqlite)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.extensions)) + implementation(projects.lib.sqlite) + implementation(projects.lib.security) + implementation(projects.lib.extensions) } diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt index caa9fa2cd..982103127 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.rn import android.content.Context -import android.util.Log +import com.tonapps.log.L import androidx.fragment.app.FragmentActivity import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.bestMessage diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNSeedStorage.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNSeedStorage.kt index 7dee88552..b25e04b9f 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNSeedStorage.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNSeedStorage.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.rn import android.content.Context -import android.util.Log +import com.tonapps.log.L import androidx.fragment.app.FragmentActivity import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.asJSON diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNWallet.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNWallet.kt index 4537b1537..cc353d63d 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNWallet.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNWallet.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.rn.data import android.util.ArrayMap -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.color import org.json.JSONArray import org.json.JSONObject diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt index 70c660caf..4c7db5644 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.SharedPreferences import android.preference.PreferenceManager import android.security.keystore.KeyPermanentlyInvalidatedException -import android.util.Log +import com.tonapps.log.L import androidx.fragment.app.FragmentActivity import com.tonapps.extensions.asJSON import com.tonapps.wallet.data.rn.expo.encryptors.AESEncryptor @@ -100,7 +100,7 @@ internal class SecureStoreModule( } } } catch (e: KeyPermanentlyInvalidatedException) { - Log.w(TAG, "The requested key has been permanently invalidated. Returning null") + L.w(TAG, "The requested key has been permanently invalidated. Returning null") return null } catch (e: BadPaddingException) { throw (DecryptException("Could not decrypt the value with provided keychain $legacyReadFailedWarning", key, options.keychainService, e)) @@ -149,7 +149,7 @@ internal class SecureStoreModule( } } catch (e: KeyPermanentlyInvalidatedException) { if (!keyIsInvalidated) { - Log.w(TAG, "Key has been invalidated, retrying with the key deleted") + L.w(TAG, "Key has been invalidated, retrying with the key deleted") return setItemImpl(key, value, options, true) } throw EncryptException("Encryption Failed. The key $key has been permanently invalidated and cannot be reinitialized", key, options.keychainService, e) @@ -223,7 +223,7 @@ internal class SecureStoreModule( // so we shouldn't delete them. if (requireAuthentication && keychainService == entryKeychainService) { sharedPreferences.edit().remove(key).apply() - Log.w(TAG, "Removing entry: $key due to the encryption key being deleted") + L.w(TAG, "Removing entry: $key due to the encryption key being deleted") } } } diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/encryptors/AESEncryptor.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/encryptors/AESEncryptor.kt index 443000df8..f790e077d 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/encryptors/AESEncryptor.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/encryptors/AESEncryptor.kt @@ -4,7 +4,6 @@ import android.annotation.TargetApi import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 -import android.util.Log import com.tonapps.wallet.data.rn.expo.AuthenticationHelper import com.tonapps.wallet.data.rn.expo.DecryptException import com.tonapps.wallet.data.rn.expo.SecureStoreModule diff --git a/apps/wallet/data/settings/build.gradle.kts b/apps/wallet/data/settings/build.gradle.kts index 2111fb392..db0b2a6f1 100644 --- a/apps/wallet/data/settings/build.gradle.kts +++ b/apps/wallet/data/settings/build.gradle.kts @@ -1,14 +1,12 @@ plugins { - id("com.tonapps.wallet.data") -} - -android { - namespace = Build.namespacePrefix("wallet.data.settings") + id("target.android.library") } dependencies { - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.rn)) - implementation(project(ProjectModules.Wallet.localization)) + implementation(libs.koin.core) + + implementation(projects.lib.extensions) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.rn) + implementation(projects.apps.wallet.localization) } diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt index da8726b73..8f9eb2be2 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt @@ -18,6 +18,7 @@ import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.core.isAvailableBiometric import com.tonapps.wallet.data.rn.RNLegacy import com.tonapps.wallet.data.settings.entities.PreferredFeeMethod +import com.tonapps.wallet.data.settings.entities.PreferredTronFeeMethod import com.tonapps.wallet.data.settings.entities.TokenPrefsEntity import com.tonapps.wallet.data.settings.folder.TokenPrefsFolder import com.tonapps.wallet.data.settings.folder.WalletPrefsFolder @@ -478,6 +479,12 @@ class SettingsRepository( fun setPreferredFeeMethod(walletId: String, method: PreferredFeeMethod) = walletPrefsFolder.setPreferredFeeMethod(walletId, method) + fun getPreferredTronFeeMethod(walletId: String) = + walletPrefsFolder.getPreferredTronFeeMethod(walletId) + + fun setPreferredTronFeeMethod(walletId: String, method: PreferredTronFeeMethod) = + walletPrefsFolder.setPreferredTronFeeMethod(walletId, method) + suspend fun getTokenPrefs( walletId: String, tokenAddress: String, diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/PreferredTronFeeMethod.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/PreferredTronFeeMethod.kt new file mode 100644 index 000000000..3f0ac61dc --- /dev/null +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/PreferredTronFeeMethod.kt @@ -0,0 +1,16 @@ +package com.tonapps.wallet.data.settings.entities + +enum class PreferredTronFeeMethod(val id: Int) { + UNSPECIFIED(0), + TRX(1), + TON(2), + BATTERY(3); + + companion object { + fun fromId(id: Int): PreferredTronFeeMethod { + return entries.find { it.id == id } + ?: throw IllegalArgumentException("Invalid PreferredTronFeeMethod id: $id") + } + } +} + diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt index f374c59d0..3cc62e4d8 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt @@ -42,7 +42,7 @@ internal class TokenPrefsFolder(context: Context, scope: CoroutineScope) : fun getHidden(walletId: String, tokenAddress: String): Boolean { // trc20 usdt - val defValue = tokenAddress == "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + val defValue = tokenAddress == "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" || tokenAddress == "TRX" return getBoolean(keyHidden(walletId, tokenAddress), defValue) } diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt index 295fe4fb5..cf69fb41b 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt @@ -2,12 +2,13 @@ package com.tonapps.wallet.data.settings.folder import android.content.Context import android.os.SystemClock -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.data.settings.BatteryTransaction import com.tonapps.wallet.data.settings.BatteryTransaction.Companion.toIntArray import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.settings.SpamTransactionState import com.tonapps.wallet.data.settings.entities.PreferredFeeMethod +import com.tonapps.wallet.data.settings.entities.PreferredTronFeeMethod import com.tonapps.wallet.data.settings.entities.WalletPrefsEntity import kotlinx.coroutines.CoroutineScope @@ -25,6 +26,7 @@ internal class WalletPrefsFolder(context: Context, scope: CoroutineScope): BaseS private const val USDT_W5_PREFIX = "usdt_w5_" private const val DAPP_CONFIRM_PREFIX = "dapp_confirm_" private const val PREFERRED_FEE_PREFIX = "preferred_fee_" + private const val PREFERRED_TRON_FEE_PREFIX = "preferred_tron_fee_" } fun isUSDTW5(walletId: String): Boolean { @@ -138,10 +140,24 @@ internal class WalletPrefsFolder(context: Context, scope: CoroutineScope): BaseS putInt(key, method.id) } + fun getPreferredTronFeeMethod(walletId: String): PreferredTronFeeMethod { + val value = getInt(keyPreferredTronFeeMethod(walletId), PreferredTronFeeMethod.UNSPECIFIED.id) + return PreferredTronFeeMethod.fromId(value) + } + + fun setPreferredTronFeeMethod(walletId: String, method: PreferredTronFeeMethod) { + val key = keyPreferredTronFeeMethod(walletId) + putInt(key, method.id) + } + private fun keyPreferredFeeMethod(walletId: String): String { return key(PREFERRED_FEE_PREFIX, walletId) } + private fun keyPreferredTronFeeMethod(walletId: String): String { + return key(PREFERRED_TRON_FEE_PREFIX, walletId) + } + private fun keyBatteryTxEnabled(accountId: String): String { return key(BATTERY_TX_ENABLED_PREFIX, accountId) } diff --git a/apps/wallet/data/staking/build.gradle.kts b/apps/wallet/data/staking/build.gradle.kts index c9dfa38b4..269ac3123 100644 --- a/apps/wallet/data/staking/build.gradle.kts +++ b/apps/wallet/data/staking/build.gradle.kts @@ -1,20 +1,17 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.staking") -} - dependencies { - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) + implementation(libs.koin.core) - implementation(project(ProjectModules.Module.tonApi)) + implementation(projects.tonapi.legacy) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.tokens)) + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.tokens) } diff --git a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingPool.kt b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingPool.kt index 8de75053a..ebafce0cf 100644 --- a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingPool.kt +++ b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingPool.kt @@ -1,5 +1,6 @@ package com.tonapps.wallet.data.staking +import com.tonapps.apps.wallet.data.staking.R import com.tonapps.icu.Coins import io.tonapi.models.PoolImplementationType diff --git a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingRepository.kt b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingRepository.kt index 86217deff..527c1b9a0 100644 --- a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingRepository.kt +++ b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/StakingRepository.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.staking import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.data.staking.entities.PoolInfoEntity import com.tonapps.wallet.data.staking.entities.StakingEntity @@ -17,24 +18,21 @@ class StakingRepository(context: Context, api: API) { suspend fun get( accountId: String, - testnet: Boolean, + network: TonNetwork, ignoreCache: Boolean = false, initializedAccount: Boolean = true ): StakingEntity = withContext(Dispatchers.IO) { - val cacheKey = cacheKey(accountId, testnet) + val cacheKey = cacheKey(accountId, network) val local: StakingEntity? = if (ignoreCache) null else localDataSource.getCache(cacheKey) if (local == null) { - val remote = remoteDataSource.load(accountId, testnet, initializedAccount) + val remote = remoteDataSource.load(accountId, network, initializedAccount) localDataSource.setCache(cacheKey, remote) return@withContext remote } return@withContext local } - private fun cacheKey(accountId: String, testnet: Boolean): String { - if (!testnet) { - return accountId - } - return "${accountId}_testnet_2" + private fun cacheKey(accountId: String, network: TonNetwork): String { + return "${accountId}_${network.name.lowercase()}_2" } } \ No newline at end of file diff --git a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/entities/StakingInfoEntity.kt b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/entities/StakingInfoEntity.kt index d2049c608..da63408d9 100644 --- a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/entities/StakingInfoEntity.kt +++ b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/entities/StakingInfoEntity.kt @@ -1,7 +1,7 @@ package com.tonapps.wallet.data.staking.entities import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.icu.Coins import com.tonapps.wallet.data.staking.StakingPool import io.tonapi.models.AccountStakingInfo diff --git a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/source/RemoteDataSource.kt b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/source/RemoteDataSource.kt index a9686cdd5..e2f9613de 100644 --- a/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/source/RemoteDataSource.kt +++ b/apps/wallet/data/staking/src/main/java/com/tonapps/wallet/data/staking/source/RemoteDataSource.kt @@ -1,5 +1,6 @@ package com.tonapps.wallet.data.staking.source +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.api.withRetry import com.tonapps.wallet.data.staking.StakingPool @@ -17,12 +18,12 @@ internal class RemoteDataSource( ) { suspend fun load( - accountId: String, testnet: Boolean, initializedAccount: Boolean + accountId: String, network: TonNetwork, initializedAccount: Boolean ): StakingEntity = withContext(Dispatchers.IO) { - val poolsDeferred = async { loadPools(accountId, testnet) } + val poolsDeferred = async { loadPools(accountId, network) } val infoDeferred = async { if (initializedAccount) { - loadInfo(accountId, testnet) + loadInfo(accountId, network) } else { emptyList() } @@ -38,19 +39,19 @@ internal class RemoteDataSource( } private fun loadInfo( - accountId: String, testnet: Boolean + accountId: String, network: TonNetwork ): List { val list = withRetry { - api.staking(testnet).getAccountNominatorsPools(accountId).pools + api.staking(network).getAccountNominatorsPools(accountId).pools } ?: return emptyList() return list.map { StakingInfoEntity(it) } } private fun loadPools( - accountId: String, testnet: Boolean + accountId: String, network: TonNetwork ): List { val response = withRetry { - api.staking(testnet).getStakingPools(accountId, includeUnverified = false) + api.staking(network).getStakingPools(accountId, includeUnverified = false) } ?: return emptyList() val maxApyImplementation = response.pools.maxByOrNull { it.apy }?.implementation diff --git a/apps/wallet/data/swap/build.gradle.kts b/apps/wallet/data/swap/build.gradle.kts index d7b4b01bb..60aa7e1ab 100644 --- a/apps/wallet/data/swap/build.gradle.kts +++ b/apps/wallet/data/swap/build.gradle.kts @@ -1,17 +1,13 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.swap") -} - dependencies { implementation(libs.koin.core) - implementation(libs.kotlinX.coroutines.guava) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) + implementation(libs.kotlinx.coroutines.core) + implementation(projects.apps.wallet.api) + implementation(projects.apps.wallet.data.core) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) } diff --git a/apps/wallet/data/swap/src/main/java/com/tonapps/wallet/data/swap/SwapRepository.kt b/apps/wallet/data/swap/src/main/java/com/tonapps/wallet/data/swap/SwapRepository.kt index 696682432..5f323ed53 100644 --- a/apps/wallet/data/swap/src/main/java/com/tonapps/wallet/data/swap/SwapRepository.kt +++ b/apps/wallet/data/swap/src/main/java/com/tonapps/wallet/data/swap/SwapRepository.kt @@ -35,10 +35,16 @@ class SwapRepository( val assetsFlow = flow { emit(getAssets()) + emit(getAssets(true)) }.mapList { it.currency }.stateIn(scope, SharingStarted.Lazily, null).filterNotNull() - suspend fun getAssets(): List = withContext(Dispatchers.IO) { - getCache(ASSETS_KEY) ?: loadAssets() + suspend fun getAssets(ignoreCache: Boolean = false): List = withContext(Dispatchers.IO) { + val cached = if (ignoreCache) { + null + } else { + getCache(ASSETS_KEY) + } + cached ?: loadAssets() } private fun loadAssets(): List { diff --git a/apps/wallet/data/tokens/build.gradle.kts b/apps/wallet/data/tokens/build.gradle.kts index d42770fb3..8c40a0c27 100644 --- a/apps/wallet/data/tokens/build.gradle.kts +++ b/apps/wallet/data/tokens/build.gradle.kts @@ -1,18 +1,16 @@ plugins { - id("com.tonapps.wallet.data") + id("target.android.library") id("kotlin-parcelize") } -android { - namespace = Build.namespacePrefix("wallet.data.tokens") -} - dependencies { - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.rates)) - implementation(project(ProjectModules.Wallet.api)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.icu)) + implementation(libs.koin.core) + + implementation(projects.lib.blockchain) + implementation(projects.tonapi.legacy) + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.rates) + implementation(projects.apps.wallet.api) + implementation(projects.lib.extensions) + implementation(projects.lib.icu) } diff --git a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/TokenRepository.kt b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/TokenRepository.kt index e8798803c..f4006ab2f 100644 --- a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/TokenRepository.kt +++ b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/TokenRepository.kt @@ -1,8 +1,8 @@ package com.tonapps.wallet.data.token import android.content.Context -import android.util.Log import androidx.collection.ArrayMap +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.icu.Coins import com.tonapps.wallet.api.API @@ -21,7 +21,6 @@ import io.tonapi.models.TokenRates import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap @@ -38,142 +37,159 @@ class TokenRepository( private val ethenaCache = BlobDataSource.simple(context, "ethena") - fun getToken(accountId: String, testnet: Boolean): TokenEntity? { + fun getToken(accountId: String, network: TonNetwork): TokenEntity? { if (accountId.equals("TON", ignoreCase = true)) { return TokenEntity.TON } - return remoteDataSource.getJetton(accountId, testnet) + return remoteDataSource.getJetton(accountId, network) } - suspend fun getTokens(testnet: Boolean, accountIds: List): List = + suspend fun getTokens(network: TonNetwork, accountIds: List): List = withContext(Dispatchers.IO) { if (accountIds.isEmpty()) { return@withContext emptyList() } val deferredTokens = accountIds.map { accountId -> - async { getToken(accountId, testnet) } + async { getToken(accountId, network) } } deferredTokens.mapNotNull { it.await() } } fun getToken(accountId: String): TokenEntity? { - return getToken(accountId, false) ?: getToken(accountId, true) + return getToken(accountId, TonNetwork.MAINNET) ?: getToken(accountId, TonNetwork.TESTNET) } - suspend fun getToken(accountId: String, testnet: Boolean, tokenAddress: String): TokenEntity? { + suspend fun getToken(accountId: String, network: TonNetwork, tokenAddress: String): TokenEntity? { if (accountId.equals("TON", ignoreCase = true)) { return TokenEntity.TON } - val token = get(WalletCurrency.USD, accountId, testnet)?.firstOrNull { token -> + val token = get(WalletCurrency.USD, accountId, network)?.firstOrNull { token -> token.balance.token.address.equalsAddress(tokenAddress) }?.balance?.token - return token ?: getToken(tokenAddress, testnet) + return token ?: getToken(tokenAddress, network) } suspend fun getTON( currency: WalletCurrency, accountId: String, - testnet: Boolean, + network: TonNetwork, refresh: Boolean = false, ): AccountTokenEntity? { - val tokens = get(currency, accountId, testnet, refresh) ?: return null + val tokens = get(currency, accountId, network, refresh) ?: return null return tokens.firstOrNull { it.isTon } } suspend fun getTonBalance( currency: WalletCurrency, accountId: String, - testnet: Boolean + network: TonNetwork ): Coins? { - val token = getTON(currency, accountId, testnet, false) ?: return null + val token = getTON(currency, accountId, network, false) ?: return null return token.balance.value } suspend fun refreshTron( - accountId: String, testnet: Boolean, tronAddress: String + accountId: String, network: TonNetwork, tronAddress: String ) { val tronUsdtBalance = remoteDataSource.loadTronUsdt(tronAddress) + val tronTrxBalance = remoteDataSource.loadTronTrx(tronAddress) - val cached = localDataSource.getCache(cacheKey(accountId, false)) ?: return + val cached = localDataSource.getCache(cacheKey(accountId, network)) ?: return val entities = cached.toMutableList() - val index = - entities.indexOfFirst { it.token.address.equalsAddress(TokenEntity.TRON_USDT.address) } - if (index != -1) { - entities[index] = tronUsdtBalance - } else if (!api.config.flags.disableTron) { - entities.add(tronUsdtBalance) + run { + val index = entities.indexOfFirst { + it.token.address.equalsAddress(TokenEntity.TRON_USDT.address) + } + + if (index != -1) { + entities[index] = tronUsdtBalance + } else if (!api.getConfig(network).flags.disableTron) { + entities.add(tronUsdtBalance) + } + } + + run { + val index = entities.indexOfFirst { + it.token.address.equalsAddress(TokenEntity.TRX.address) + } + + if (index != -1) { + entities[index] = tronTrxBalance + } else if (!api.getConfig(network).flags.disableTron) { + entities.add(tronTrxBalance) + } } - localDataSource.setCache(cacheKey(accountId, testnet), entities) + localDataSource.setCache(cacheKey(accountId, network), entities) } suspend fun get( currency: WalletCurrency, accountId: String, - testnet: Boolean, + network: TonNetwork, refresh: Boolean = false, tronAddress: String? = null, ): List? { if (refresh) { - return getRemote(currency, accountId, tronAddress, testnet) + return getRemote(currency, accountId, tronAddress, network) } - val tokens = getLocal(currency, accountId, testnet) + val tokens = getLocal(currency, accountId, network) if (tokens.isNotEmpty()) { return tokens } - return getRemote(currency, accountId, tronAddress, testnet) + return getRemote(currency, accountId, tronAddress, network) } suspend fun mustGet( currency: WalletCurrency, accountId: String, - testnet: Boolean, + network: TonNetwork, refresh: Boolean = false, ): List { - return get(currency, accountId, testnet, refresh) ?: emptyList() + return get(currency, accountId, network, refresh) ?: emptyList() } private suspend fun getRemote( - currency: WalletCurrency, accountId: String, tronAddress: String?, testnet: Boolean + currency: WalletCurrency, accountId: String, tronAddress: String?, network: TonNetwork ): List? = withContext(Dispatchers.IO) { - val balances = load(currency, accountId, tronAddress, testnet) ?: return@withContext null - if (testnet) { - return@withContext buildTokens( + val balances = load(currency, accountId, tronAddress, network) ?: return@withContext null + if (network.isTestnet) { + return@withContext buildTokens( currency = currency, balances = balances, fiatRates = RatesEntity.empty(currency), - testnet = true + network = network ) } - val fiatRates = ratesRepository.getRates(currency, balances.map { it.token.address }) + val fiatRates = ratesRepository.getRates(network, currency, balances.map { it.token.address }) buildTokens( - currency = currency, balances = balances, fiatRates = fiatRates, testnet = false + currency = currency, balances = balances, fiatRates = fiatRates, network = network ) } suspend fun getLocal( - currency: WalletCurrency, accountId: String, testnet: Boolean + currency: WalletCurrency, accountId: String, network: TonNetwork ): List = withContext(Dispatchers.IO) { - val balances = cache(accountId, testnet) ?: return@withContext emptyList() - if (testnet) { - return@withContext buildTokens( + val balances = cache(accountId, network) ?: return@withContext emptyList() + if (network.isTestnet) { + return@withContext buildTokens( currency = currency, balances = balances, fiatRates = RatesEntity.empty(currency), - testnet = true + network = network ) } - val fiatRates = ratesRepository.cache(currency, balances.map { it.token.address }) + val fiatRates = ratesRepository.cache(network, currency, balances.map { it.token.address }) if (fiatRates.isEmpty) { emptyList() } else { buildTokens( - currency = currency, balances = balances, fiatRates = fiatRates, testnet = false + currency = currency, balances = balances, fiatRates = fiatRates, network = network ) } } @@ -182,7 +198,7 @@ class TokenRepository( currency: WalletCurrency, balances: List, fiatRates: RatesEntity, - testnet: Boolean + network: TonNetwork ): List { val verified = mutableListOf() val unverified = mutableListOf() @@ -203,7 +219,7 @@ class TokenRepository( unverified.add(token) } } - if (testnet) { + if (network.isTestnet) { return sortTestnet(verified + unverified) } return sort(verified) + sort(unverified) @@ -232,34 +248,46 @@ class TokenRepository( } private fun cache( - accountId: String, testnet: Boolean + accountId: String, network: TonNetwork ): List? { - val key = cacheKey(accountId, testnet) + val key = cacheKey(accountId, network) return localDataSource.getCache(key) } - private fun updateRates(currency: WalletCurrency, tokens: List) { - ratesRepository.load(currency, tokens.toMutableList()) + private fun updateRates(network: TonNetwork, currency: WalletCurrency, tokens: List) { + ratesRepository.load(network, currency, tokens.toMutableList()) } private suspend fun load( - currency: WalletCurrency, accountId: String, tronAddress: String?, testnet: Boolean + currency: WalletCurrency, + accountId: String, + tronAddress: String?, + network: TonNetwork ): List? = withContext(Dispatchers.IO) { - val tonBalanceDeferred = async { remoteDataSource.loadTON(currency, accountId, testnet) } - val jettonsDeferred = async { remoteDataSource.loadJettons(currency, accountId, testnet) } + val tonBalanceDeferred = async { remoteDataSource.loadTON(currency, accountId, network) } + val jettonsDeferred = async { remoteDataSource.loadJettons(currency, accountId, network) } + val tronUsdtDeferred = async { - if (tronAddress != null && !testnet) { + if (tronAddress != null && network.isMainnet) { remoteDataSource.loadTronUsdt(tronAddress) } else { null } } - val tonBalance = tonBalanceDeferred.await() ?: return@withContext null + val tronTrxDeferred = async { + if (tronAddress != null && network.isMainnet) { + remoteDataSource.loadTronTrx(tronAddress) + } else { + null + } + } + val tonBalance = tonBalanceDeferred.await() ?: return@withContext null val jettons = jettonsDeferred.await()?.toMutableList() ?: mutableListOf() val tronUsdt = tronUsdtDeferred.await() + val tronTrx = tronTrxDeferred.await() val usdtIndex = jettons.indexOfFirst { it.token.address == TokenEntity.USDT.address @@ -269,14 +297,22 @@ class TokenRepository( it.token.address == TokenEntity.USDE.address } + val tsUsdeIndex = jettons.indexOfFirst { + it.token.address == TokenEntity.TS_USDE.address + } + val entities = mutableListOf() entities.add(tonBalance) - if (tronUsdt != null && (!api.config.flags.disableTron || tronUsdt.value.isPositive)) { + if (tronTrx != null && (!api.getConfig(network).flags.disableTron || tronTrx.value.isPositive)) { + entities.add(tronTrx) + } + + if (tronUsdt != null && (!api.getConfig(network).flags.disableTron || tronUsdt.value.isPositive)) { entities.add(tronUsdt) } - if (usdtIndex == -1 && !testnet) { + if (usdtIndex == -1 && !network.isTestnet) { entities.add( BalanceEntity( token = TokenEntity.USDT, @@ -293,7 +329,9 @@ class TokenRepository( ) } - if (usdeIndex == -1 && !testnet && !api.config.flags.disableUsde) { + val shouldAddUsde = usdeIndex == -1 && (!api.getConfig(network).flags.disableUsde || tsUsdeIndex != -1) + + if (shouldAddUsde && !network.isTestnet) { entities.add( BalanceEntity( token = TokenEntity.USDE, @@ -312,28 +350,26 @@ class TokenRepository( entities.addAll(jettons) - updateRates(currency, listOf(TokenEntity.TON.symbol)) - bindRates(currency, entities) - localDataSource.setCache(cacheKey(accountId, testnet), entities) - totalBalanceCache.remove(cacheKey(accountId, testnet)) + updateRates(network, currency, listOf(TokenEntity.TON.symbol)) + bindRates(network, currency, entities) + localDataSource.setCache(cacheKey(accountId, network), entities) + totalBalanceCache.remove(cacheKey(accountId, network)) entities.toList() } - private fun cacheKey(accountId: String, testnet: Boolean): String { - if (!testnet) { - return accountId - } - return "${accountId}_testnet" + + private fun cacheKey(accountId: String, network: TonNetwork): String { + return "${accountId}_${network.name.lowercase()}" } - private suspend fun bindRates(currency: WalletCurrency, list: List) { + private suspend fun bindRates(network: TonNetwork, currency: WalletCurrency, list: List) { val rates = ArrayMap() for (balance in list) { balance.rates?.let { rates[balance.token.address] = it } } - ratesRepository.insertRates(currency, rates) + ratesRepository.insertRates(network, currency, rates) } suspend fun getEthena(accountId: String, refresh: Boolean = false): EthenaEntity? { @@ -351,4 +387,4 @@ class TokenRepository( } data } -} \ No newline at end of file +} diff --git a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt index c0de65871..f36bf0377 100644 --- a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt +++ b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt @@ -61,6 +61,9 @@ data class AccountTokenEntity( val isTon: Boolean get() = address == TokenEntity.TON.address + val isTrx: Boolean + get() = address == TokenEntity.TRX.address + val isLiquid: Boolean get() = balance.token.isLiquid diff --git a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt index ca0f72f52..3951c8102 100644 --- a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt +++ b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt @@ -1,5 +1,6 @@ package com.tonapps.wallet.data.token.source +import com.tonapps.blockchain.ton.TonNetwork import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.BalanceEntity @@ -12,25 +13,25 @@ internal class RemoteDataSource( private val api: API ) { - fun getJetton(accountId: String, testnet: Boolean) = api.getJetton(accountId, testnet) + fun getJetton(accountId: String, network: TonNetwork) = api.getJetton(accountId, network) suspend fun loadTON( currency: WalletCurrency, accountId: String, - testnet: Boolean + network: TonNetwork ): BalanceEntity? = withContext(Dispatchers.IO) { - api.getTonBalance(accountId, testnet, currency.code) + api.getTonBalance(accountId, network, currency.code) } suspend fun loadJettons( currency: WalletCurrency, accountId: String, - testnet: Boolean + network: TonNetwork ): List? = withContext(Dispatchers.IO) { try { api.getJettonsBalances( accountId = accountId, - testnet = testnet, + network = network, currency = currency.code, extensions = listOf( TokenEntity.Extension.CustomPayload.value, @@ -49,4 +50,10 @@ internal class RemoteDataSource( api.tron.getTronUsdtBalance(tronAddress) } + suspend fun loadTronTrx( + tronAddress: String, + ): BalanceEntity = withContext(Dispatchers.IO) { + api.tron.getTrxBalance(tronAddress) + } + } \ No newline at end of file diff --git a/apps/wallet/features/sample/build.gradle.kts b/apps/wallet/features/sample/build.gradle.kts new file mode 100644 index 000000000..a377404f0 --- /dev/null +++ b/apps/wallet/features/sample/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("target.android.compose") + alias(libs.plugins.kotlin.compose) +} + +dependencies { + implementation(platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.bundles.nav3) + implementation(libs.kotlinx.coroutines.android) + + implementation(projects.kmp.core) + implementation(projects.kmp.ui) + implementation(projects.kmp.mvi) +} diff --git a/apps/wallet/instance/app/.gitignore b/apps/wallet/instance/app/.gitignore index 987c4867b..ec227929c 100644 --- a/apps/wallet/instance/app/.gitignore +++ b/apps/wallet/instance/app/.gitignore @@ -2,4 +2,4 @@ /release /benchmarkRelease /src/release -/src/main/genereated \ No newline at end of file +/src/main/generated \ No newline at end of file diff --git a/apps/wallet/instance/app/build.gradle.kts b/apps/wallet/instance/app/build.gradle.kts index deb75d07f..2e2820629 100644 --- a/apps/wallet/instance/app/build.gradle.kts +++ b/apps/wallet/instance/app/build.gradle.kts @@ -1,31 +1,23 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.android.kotlin) + id("target.android.compose") alias(libs.plugins.kotlin.compose) id("kotlin-parcelize") id("kotlinx-serialization") } android { - namespace = Build.namespacePrefix("tonkeeperx") - compileSdk = Build.compileSdkVersion - ndkVersion = Build.ndkVersion - - defaultConfig { - minSdk = Build.minSdkVersion - } + namespace = "com.tonapps.tonkeeperx" buildFeatures { buildConfig = true - compose = true } } dependencies { implementation(libs.koin.core) implementation(libs.koin.workmanager) - implementation(libs.kotlinX.datetime) - implementation(libs.kotlinX.collections.immutable) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.collections.immutable) implementation(libs.j2objc) implementation(libs.cbor) implementation(libs.ton.tvm) @@ -35,47 +27,50 @@ dependencies { implementation(libs.ton.tonapiTl) implementation(libs.ton.contract) - implementation(project(ProjectModules.KMP.ui)) - - implementation(project(ProjectModules.Wallet.localization)) - implementation(project(ProjectModules.Wallet.api)) - - implementation(project(ProjectModules.Wallet.Data.core)) - implementation(project(ProjectModules.Wallet.Data.tokens)) - implementation(project(ProjectModules.Wallet.Data.account)) - implementation(project(ProjectModules.Wallet.Data.settings)) - implementation(project(ProjectModules.Wallet.Data.rates)) - implementation(project(ProjectModules.Wallet.Data.collectibles)) - implementation(project(ProjectModules.Wallet.Data.events)) - implementation(project(ProjectModules.Wallet.Data.browser)) - implementation(project(ProjectModules.Wallet.Data.backup)) - implementation(project(ProjectModules.Wallet.Data.rn)) - implementation(project(ProjectModules.Wallet.Data.passcode)) - implementation(project(ProjectModules.Wallet.Data.staking)) - implementation(project(ProjectModules.Wallet.Data.purchase)) - implementation(project(ProjectModules.Wallet.Data.battery)) - implementation(project(ProjectModules.Wallet.Data.dapps)) - implementation(project(ProjectModules.Wallet.Data.contacts)) - implementation(project(ProjectModules.Wallet.Data.swap)) - implementation(project(ProjectModules.Wallet.Data.plugins)) - - implementation(project(ProjectModules.UIKit.core)) - implementation(project(ProjectModules.UIKit.flag)) - - implementation(libs.androidX.core) - implementation(libs.androidX.shortcuts) - implementation(libs.androidX.appCompat) - implementation(libs.androidX.activity) - implementation(libs.androidX.fragment) - implementation(libs.androidX.recyclerView) - implementation(libs.androidX.viewPager2) - implementation(libs.androidX.workManager) - implementation(libs.androidX.biometric) - implementation(libs.androidX.swiperefreshlayout) - implementation(libs.androidX.lifecycle) - implementation(libs.androidX.webkit) - implementation(libs.androidX.browser) - + implementation(projects.kmp.ui) + implementation(projects.kmp.mvi) + implementation(projects.kmp.async) + + implementation(projects.lib.bus) + + implementation(projects.apps.wallet.localization) + implementation(projects.apps.wallet.api) + + implementation(projects.apps.wallet.data.core) + implementation(projects.apps.wallet.data.tokens) + implementation(projects.apps.wallet.data.account) + implementation(projects.apps.wallet.data.settings) + implementation(projects.apps.wallet.data.rates) + implementation(projects.apps.wallet.data.collectibles) + implementation(projects.apps.wallet.data.events) + implementation(projects.apps.wallet.data.browser) + implementation(projects.apps.wallet.data.backup) + implementation(projects.apps.wallet.data.rn) + implementation(projects.apps.wallet.data.passcode) + implementation(projects.apps.wallet.data.staking) + implementation(projects.apps.wallet.data.purchase) + implementation(projects.apps.wallet.data.battery) + implementation(projects.apps.wallet.data.dapps) + implementation(projects.apps.wallet.data.contacts) + implementation(projects.apps.wallet.data.swap) + implementation(projects.apps.wallet.data.plugins) + + implementation(projects.ui.uikit.core) + implementation(projects.ui.uikit.flag) + + implementation(libs.androidx.core) + implementation(libs.androidx.shortcuts) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity) + implementation(libs.androidx.fragment) + implementation(libs.androidx.recyclerView) + implementation(libs.androidx.viewPager2) + implementation(libs.androidx.workManager) + implementation(libs.androidx.biometric) + implementation(libs.androidx.swiperefreshlayout) + implementation(libs.androidx.lifecycle) + implementation(libs.androidx.webkit) + implementation(libs.androidx.browser) implementation(libs.material) implementation(libs.flexbox) @@ -87,24 +82,25 @@ dependencies { implementation(libs.firebase.messaging) implementation(libs.firebase.performance) - implementation(project(ProjectModules.Module.tonApi)) - implementation(project(ProjectModules.Module.blur)) - - implementation(project(ProjectModules.Lib.network)) - implementation(project(ProjectModules.Lib.icu)) - implementation(project(ProjectModules.Lib.qr)) - implementation(project(ProjectModules.Lib.emoji)) - implementation(project(ProjectModules.Lib.security)) - implementation(project(ProjectModules.Lib.blockchain)) - implementation(project(ProjectModules.Lib.extensions)) - implementation(project(ProjectModules.Lib.ledger)) - implementation(project(ProjectModules.Lib.ur)) - implementation(project(ProjectModules.Lib.base64)) - - implementation(libs.cameraX.base) - implementation(libs.cameraX.core) - implementation(libs.cameraX.lifecycle) - implementation(libs.cameraX.view) + implementation(projects.tonapi.legacy) + implementation(projects.ui.blur) + + implementation(projects.lib.network) + implementation(projects.lib.icu) + implementation(projects.lib.qr) + implementation(projects.lib.log) + implementation(projects.lib.emoji) + implementation(projects.lib.security) + implementation(projects.lib.blockchain) + implementation(projects.lib.extensions) + implementation(projects.lib.ledger) + implementation(projects.lib.ur) + implementation(projects.lib.base64) + + implementation(libs.camerax.base) + implementation(libs.camerax.core) + implementation(libs.camerax.lifecycle) + implementation(libs.camerax.view) implementation(libs.google.play.review) implementation(libs.google.play.billing) @@ -139,4 +135,4 @@ tasks.withType().configureEach "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${buildDir}/compose_reports" ) } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/AndroidManifest.xml b/apps/wallet/instance/app/src/main/AndroidManifest.xml index 7ac4c8aed..19cf1b2a2 100644 --- a/apps/wallet/instance/app/src/main/AndroidManifest.xml +++ b/apps/wallet/instance/app/src/main/AndroidManifest.xml @@ -141,7 +141,8 @@ android:hardwareAccelerated="true" android:theme="@style/Theme.Wallet.SplashScreen" android:screenOrientation="portrait" - android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"> + android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" + android:taskAffinity=""> @@ -171,8 +172,10 @@ android:exported="false" android:enabled="true" android:fitsSystemWindows="true" - android:theme="@style/Theme.Hidden"> + android:theme="@style/Theme.Hidden" + android:taskAffinity=""> + + + diff --git a/apps/wallet/instance/app/src/main/assets/key b/apps/wallet/instance/app/src/main/assets/key new file mode 100644 index 0000000000000000000000000000000000000000..8b1f765535e6253787da21eb48173609a36f04e6 GIT binary patch literal 294 zcmV+>0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>l+-oRX5~0j1SCG-S zXq)2OPg%40iotzuEfA#jSdah=%{2^Y$}k)EL45eJe})Yg$vij=B!~Z)ciri7)I~@b zFyjLruZ=g%uA8ipOOF10qU+WsqbsN+7UF9)NwGp=&Huw#gaTjkCzGZW*@*(f;)BT4 zM!@XaNjK?YR--6y_v>@a2Kq9W*bN9X-7e*5lH4p|Z3rNeB{d<$OT!h-Xv$3LbyuZ{W%4D! s8t7|We1hBG8+O#{JdFkUMTjwTk9kB)EeTBXwQYp@47!h<0s{d60Ua2IH2?qr literal 0 HcmV?d00001 diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/App.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/App.kt index a2977c654..c09ec1d03 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/App.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/App.kt @@ -1,47 +1,56 @@ package com.tonapps.tonkeeper import android.app.Application +import android.content.Context import android.content.res.Configuration -import android.graphics.Bitmap import android.os.Build -import android.os.StrictMode import android.util.Log import androidx.camera.camera2.Camera2Config import androidx.camera.core.CameraXConfig import com.google.firebase.FirebaseApp +import com.tonapps.extensions.cacheFolder +import com.tonapps.extensions.cacheSharedFolder +import com.tonapps.extensions.pubKey import com.tonapps.extensions.setLocales import com.tonapps.icu.CurrencyFormatter +import com.tonapps.log.L +import com.tonapps.log.LoggerConfig +import com.tonapps.mvi.Mvi +import com.tonapps.tonkeeper.core.DevSettings import com.tonapps.tonkeeper.koin.koinModel import com.tonapps.tonkeeper.koin.viewModelWalletModule import com.tonapps.tonkeeper.koin.workerModule import com.tonapps.tonkeeperx.BuildConfig import com.tonapps.wallet.api.apiModule import com.tonapps.wallet.data.account.accountModule -import com.tonapps.wallet.data.rates.ratesModule -import com.tonapps.wallet.data.token.tokenModule -import com.tonapps.wallet.data.swap.swapModule -import org.koin.android.ext.koin.androidContext -import org.koin.core.context.startKoin import com.tonapps.wallet.data.backup.backupModule import com.tonapps.wallet.data.battery.batteryModule import com.tonapps.wallet.data.browser.browserModule import com.tonapps.wallet.data.collectibles.collectiblesModule -import com.tonapps.wallet.data.plugins.pluginsModule import com.tonapps.wallet.data.contacts.contactsModule import com.tonapps.wallet.data.core.Theme import com.tonapps.wallet.data.core.dataModule import com.tonapps.wallet.data.dapps.dAppsModule import com.tonapps.wallet.data.events.eventsModule import com.tonapps.wallet.data.passcode.passcodeModule +import com.tonapps.wallet.data.plugins.pluginsModule import com.tonapps.wallet.data.purchase.purchaseModule +import com.tonapps.wallet.data.rates.ratesModule import com.tonapps.wallet.data.rn.rnLegacyModule import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.staking.stakingModule +import com.tonapps.wallet.data.swap.swapModule +import com.tonapps.wallet.data.token.tokenModule import com.tonapps.wallet.localization.Localization -import org.koin.core.component.KoinComponent +import kotlinx.coroutines.GlobalScope import org.koin.android.ext.android.inject +import org.koin.android.ext.koin.androidContext import org.koin.androidx.workmanager.koin.workManagerFactory -import java.util.concurrent.Executors +import org.koin.core.component.KoinComponent +import org.koin.core.context.startKoin +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit class App: Application(), CameraXConfig.Provider, KoinComponent { @@ -58,21 +67,25 @@ class App: Application(), CameraXConfig.Provider, KoinComponent { override fun onCreate() { if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() - .penaltyLog() - .detectAll() - .penaltyListener(Executors.newSingleThreadExecutor()) { - Log.e("TonkeeperStrictModeLog", "StrictMode.VmPolicy: $it", it.cause) - }.build()) +// StrictMode.setVmPolicy( +// StrictMode.VmPolicy.Builder() +// .detectLeakedClosableObjects() +// .detectLeakedRegistrationObjects() +// .detectActivityLeaks() +// .penaltyLog() +// .penaltyListener(Executors.newSingleThreadExecutor()) { +// L.e( it.cause ?: IllegalStateException("Unknown StrictMode error"), "StrictMode.VmPolicy: $it") +// }.build()) } + instance = this + initLogger(this) + Mvi.init(Mvi.Config(useThreadCheck = BuildConfig.DEBUG, isFastFail = BuildConfig.DEBUG)) super.onCreate() updateThemes() FirebaseApp.initializeApp(this) - instance = this - startKoin { androidContext(this@App) modules(koinModel, contactsModule, workerModule, dAppsModule, viewModelWalletModule, purchaseModule, batteryModule, stakingModule, passcodeModule, rnLegacyModule, swapModule, backupModule, dataModule, browserModule, apiModule, accountModule, ratesModule, tokenModule, eventsModule, collectiblesModule, pluginsModule) @@ -81,6 +94,26 @@ class App: Application(), CameraXConfig.Provider, KoinComponent { setLocales(settingsRepository.localeList) } + private fun initLogger(context: Context) { + L.initialize( + config = LoggerConfig( + logsDir = context.cacheFolder("log"), + sharedDir = context.cacheSharedFolder("log"), + executor = ThreadPoolExecutor( + // TODO Move to ExecutorsFactory + 0, + 1, + 200L, + TimeUnit.MILLISECONDS, + LinkedBlockingQueue(), + ), + scope = GlobalScope, // TODO replace on Async + pubKeyProvider = { pubKey(context) }, // TODO provide aync encryption key + ), + targets = L.defaultTargets(context, DevSettings.isLogsEnabled), + ) + } + fun updateThemes() { Theme.clear() Theme.add("blue", uikit.R.style.Theme_App_Blue, title = getString(Localization.theme_deep_blue)) 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 964541a6f..9787de9e9 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 @@ -100,7 +100,7 @@ class Environment( val installerSource: AppInstall.Source by lazy { AppInstall.request(context) } val isFromGooglePlay: Boolean by lazy { - installerSource == AppInstall.Source.GOOGLE_PLAY + installerSource == AppInstall.Source.GOOGLE_PLAY || installerSource == AppInstall.Source.AURORA_STORE } val isGooglePlayServicesAvailable: Boolean by lazy { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/RemoteConfig.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/RemoteConfig.kt index 6fc1a0cef..7976adb13 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/RemoteConfig.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/RemoteConfig.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper import android.content.Context -import android.util.Log +import com.tonapps.log.L import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings import com.tonapps.wallet.api.API @@ -10,7 +10,6 @@ class RemoteConfig(context: Context, private val api: API) { private val remoteConfig: FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance() private enum class FeatureFlag(val key: String) { - IS_DAPPS_DISABLE("isDappsDisable"), HARDCODED_COUNTRY_CODE("hardcodedCountryCode"), IN_APP_UPDATE_AVAILABLE("inAppUpdateAvailable"), IS_COUNTRY_PICKER_DISABLE("isCountryPickerDisable"), @@ -26,7 +25,6 @@ class RemoteConfig(context: Context, private val api: API) { remoteConfig.setConfigSettingsAsync(configSettings) val defaults = mapOf( - FeatureFlag.IS_DAPPS_DISABLE.key to true, FeatureFlag.ONBOARDING_STORIES_ENABLED.key to true ) @@ -36,9 +34,9 @@ class RemoteConfig(context: Context, private val api: API) { fun fetchAndActivate() { remoteConfig.fetchAndActivate().addOnCompleteListener { task -> if (task.isSuccessful) { - Log.d("RemoteConfig", "Fetched and activated successfully") + L.d("RemoteConfig", "Fetched and activated successfully") } else { - Log.e("RemoteConfig", "Fetch failed, using defaults") + L.e("RemoteConfig", "Fetch failed, using defaults") } } } @@ -55,9 +53,6 @@ class RemoteConfig(context: Context, private val api: API) { val hardcodedCountryCode: String? get() = remoteConfig.getString(FeatureFlag.HARDCODED_COUNTRY_CODE.key).takeIf { it.isNotEmpty() } - val isDappsDisable: Boolean - get() = remoteConfig.getBoolean(FeatureFlag.IS_DAPPS_DISABLE.key) - val isOnboardingStoriesEnabled: Boolean get() = remoteConfig.getBoolean(FeatureFlag.ONBOARDING_STORIES_ENABLED.key) } \ 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 862d7fca4..2f03e6d63 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 @@ -2,7 +2,6 @@ package com.tonapps.tonkeeper.billing import android.app.Activity import android.content.Context -import android.util.Log import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClient.ProductType import com.android.billingclient.api.BillingFlowParams @@ -19,6 +18,7 @@ import com.android.billingclient.api.consumePurchase import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.filterList +import com.tonapps.log.L import com.tonapps.tonkeeper.Environment import com.tonapps.wallet.data.account.entities.WalletEntity import kotlinx.coroutines.CoroutineScope @@ -27,14 +27,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.mapNotNull 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 @@ -48,7 +46,13 @@ class BillingManager( private var billingClient: BillingClient = BillingClient.newBuilder(context) .setListener(this) - .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().enablePrepaidPlans().build()) + .enablePendingPurchases( + PendingPurchasesParams + .newBuilder() + .enableOneTimeProducts() + .enablePrepaidPlans() + .build() + ) .build() private val _productsFlow = MutableStateFlow?>(null) @@ -80,6 +84,7 @@ class BillingManager( try { getProducts(productIds, productType) } catch (e: Throwable) { + L.e(e) null } } ?: emptyList() @@ -108,6 +113,7 @@ class BillingManager( if (productIds.isEmpty()) { return@ready emptyList() } + try { val productList = productIds.map { productId -> QueryProductDetailsParams.Product.newBuilder() @@ -130,10 +136,10 @@ class BillingManager( private fun log(msg: String, e: Throwable? = null) { if (e == null) { - Log.d("BillingManager", msg) + L.d("BillingManager", msg) } else { - Log.e("BillingManager", e.message ?: msg) - Log.e("BillingManager", msg, e) + L.e("BillingManager", e.message ?: msg) + L.e("BillingManager", msg, e) FirebaseCrashlytics.getInstance().recordException(e) } } @@ -194,9 +200,11 @@ class BillingManager( } @OptIn(FlowPreview::class) - fun productFlow(productId: String) = productsFlow.take(1).filterList { product -> - product.productId == productId - }.mapNotNull { it.firstOrNull() }.timeout(5.seconds) + fun productFlow(productId: String) = productsFlow + .take(1) + .filterList { product -> product.productId == productId } + .mapNotNull { it.firstOrNull() } + .timeout(5.seconds) suspend fun restorePurchases(): List = billingClient.ready { getPendingPurchases(it) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt index 6e3b9d888..40e332594 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.client.safemode import android.content.Context import android.net.Uri -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.MutableEffectFlow import com.tonapps.icu.Coins import com.tonapps.tonkeeper.client.safemode.BadDomainsEntity.Companion.isNullOrEmpty diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/Amount.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/Amount.kt index 8badcc3d8..6c34058b0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/Amount.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/Amount.kt @@ -11,6 +11,9 @@ data class Amount( val isTon: Boolean get() = token.isTon + val isTrx: Boolean + get() = token.isTrx + val symbol: String get() = token.symbol diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/DevSettings.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/DevSettings.kt index ffb1607d8..d64a2553a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/DevSettings.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/DevSettings.kt @@ -1,16 +1,24 @@ package com.tonapps.tonkeeper.core -import android.util.Log +import com.tonapps.log.L import com.tonapps.tonkeeper.App -import androidx.core.content.edit import com.tonapps.extensions.putBoolean import com.tonapps.extensions.putLong import com.tonapps.extensions.putString +import com.tonapps.tonkeeperx.BuildConfig object DevSettings { private val prefs = App.instance.getSharedPreferences("dev_settings", 0) + var tetraEnabled: Boolean = prefs.getBoolean("tetra_enabled", false) + set(value) { + if (field != value) { + field = value + prefs.putBoolean("tetra_enabled", value) + } + } + var country: String? = prefs.getString("country", null) set(value) { if (field != value) { @@ -59,6 +67,14 @@ object DevSettings { } } + var isLogsEnabled: Boolean = prefs.getBoolean("is_logs_enabled", false) + set(value) { + if (field != value) { + field = value + prefs.putBoolean("is_logs_enabled", value) + } + } + var ignoreSystemFontSize: Boolean = prefs.getBoolean("ignore_system_font_size", false) set(value) { if (field != value) { @@ -69,11 +85,11 @@ object DevSettings { fun tonConnectLog(message: String, error: Boolean = false) { - if (tonConnectLogs) { + if (tonConnectLogs || BuildConfig.DEBUG) { if (error) { - Log.e("TonConnect", message) + L.e("TonConnect", message) } else { - Log.d("TonConnect", message) + L.d("TonConnect", message) } } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt index 07cbf161e..1bb28cf22 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt @@ -1,6 +1,8 @@ package com.tonapps.tonkeeper.core.entities import com.tonapps.icu.Coins +import com.tonapps.icu.Coins.Companion.sumOf +import com.tonapps.tonkeeper.core.entities.AssetsEntity.Token import com.tonapps.wallet.api.entity.BalanceEntity import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.TokenEntity @@ -15,7 +17,6 @@ sealed class AssetsEntity( ) { companion object { - suspend fun List.sort( wallet: WalletEntity, settingsRepository: SettingsRepository @@ -27,7 +28,20 @@ sealed class AssetsEntity( TokenPrefsEntity() } AssetsExtendedEntity(asset, pref, wallet.accountId) - }.filter { !it.hidden }.sortedWith(AssetsExtendedEntity.comparator).map { it.raw } + } + .filter { !it.hidden } + .sortedWith(AssetsExtendedEntity.comparator) + .map { it.raw } + } + + fun List.sumOfVerifiedFiat(): Coins { + return sumOf { + if (it !is Token || it.token.verified) { + it.fiat + } else { + Coins.ZERO + } + } } } @@ -71,4 +85,4 @@ sealed class AssetsEntity( val currency: WalletCurrency, val coins: Coins = Coins.ZERO ): AssetsEntity(coins) -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt index b579793ec..f19c7806e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt @@ -95,4 +95,7 @@ data class AssetsExtendedEntity( val isTon: Boolean get() = (raw as? AssetsEntity.Token)?.token?.isTon ?: false + + val isTrx: Boolean + get() = token.isTrx } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/StakedEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/StakedEntity.kt index 59969a5da..b8734a499 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/StakedEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/StakedEntity.kt @@ -41,7 +41,7 @@ data class StakedEntity( ratesRepository: RatesRepository, api: API, ): List { - val fiatRates = ratesRepository.getTONRates(currency) + val fiatRates = ratesRepository.getTONRates(wallet.network, currency) val list = mutableListOf() val activePools = getActivePools(staking, tokens) for (pool in activePools) { @@ -53,7 +53,7 @@ data class StakedEntity( val liquidJettonMaster = pool.liquidJettonMaster ?: continue val token = tokens.find { it.address.equalsAddress(liquidJettonMaster) } ?: continue - val rates = ratesRepository.getRates(WalletCurrency.TON, token.address) + val rates = ratesRepository.getRates(wallet.network, WalletCurrency.TON, token.address) val balance = rates.convert(token.address, token.balance.value) val readyWithdraw = rates.convert(token.address, staking.getReadyWithdraw(pool)) val pendingDeposit = rates.convert(token.address, staking.getPendingDeposit(pool)) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt index 0ca85518f..3ccffcc27 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt @@ -292,6 +292,21 @@ data class TransferEntity( ) } + fun sign( + privateKey: PrivateKeyEd25519, + jettonTransferAmount: Coins + ): Cell { + return contract.createTransferMessageCell( + address = contract.address, + privateKey = privateKey, + seqNo = seqno, + unsignedBody = getUnsignedBody( + privateKey = fakePrivateKey, + jettonTransferAmount = jettonTransferAmount, + ), + ) + } + fun gaslessInternalGift( jettonAmount: Coins, batteryAddress: AddrStd ): WalletTransfer { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/WalletPurchaseMethodEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/WalletPurchaseMethodEntity.kt index 19c8c6e98..0c4cc3a96 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/WalletPurchaseMethodEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/WalletPurchaseMethodEntity.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.core.entities import android.net.Uri import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.api.entity.ConfigEntity import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.purchase.entity.PurchaseMethodEntity diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/Extensions.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/Extensions.kt index 506d38bef..8d8cd16d7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/Extensions.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/Extensions.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.core.history import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.icu.Coins import com.tonapps.wallet.localization.Localization import com.tonapps.tonkeeperx.R @@ -14,12 +15,12 @@ import io.tonapi.models.Action import io.tonapi.models.JettonSwapAction import io.tonapi.models.JettonTransferAction -suspend fun Action.getTonAmountRaw(ratesRepository: RatesRepository): Coins { +suspend fun Action.getTonAmountRaw(network: TonNetwork, ratesRepository: RatesRepository): Coins { val tonAmount = tonTransfer?.let { Coins.of(it.amount) } val jettonAmountInTON = jettonTransfer?.let { val amountCoins = it.amountCoins val jettonAddress = it.jetton.address - val rates = ratesRepository.getRates(WalletCurrency.TON, jettonAddress) + val rates = ratesRepository.getRates(network, WalletCurrency.TON, jettonAddress) rates.convert(jettonAddress, amountCoins) } return tonAmount ?: jettonAmountInTON ?: Coins.ZERO 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 bff4c09dc..1808ddb50 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 @@ -1,7 +1,6 @@ package com.tonapps.tonkeeper.core.history import android.content.Context -import android.util.Log import androidx.collection.arrayMapOf import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.icu.Coins @@ -85,10 +84,11 @@ class HistoryHelper( private fun getGroupKey(timestamp: Long): String { try { val calendar = Calendar.getInstance() - calendar.timeInMillis = timestamp * 1000 + calendar.timeInMillis = timestamp val now = Calendar.getInstance() val yearDiff = now.get(Calendar.YEAR) - calendar.get(Calendar.YEAR) - val monthDiff = yearDiff * 12 + now.get(Calendar.MONTH) - calendar.get(Calendar.MONTH) + val monthDiff = + yearDiff * 12 + now.get(Calendar.MONTH) - calendar.get(Calendar.MONTH) return if (monthDiff < 1) { dayMonthFormatter.format(calendar.time) @@ -130,9 +130,7 @@ class HistoryHelper( return list .filter { it is HistoryItem.Event || it is HistoryItem.App } .distinctBy { it.uniqueId } - .sortedWith { a, b -> - (b.timestampForSort - a.timestampForSort).toInt() - } + .sortedByDescending { it.timestampForSort } } fun groupByDate(items: List): List { @@ -156,9 +154,9 @@ class HistoryHelper( section.events.add(event) output[groupKey] = section } - return output.map { it.value.get() }.flatten().sortedWith { a, b -> - (b.timestampForSort - a.timestampForSort).toInt() - }.distinctBy { it.uniqueId }.toList() + + return output.map { it.value.get() }.flatten().distinctBy { it.uniqueId } + .sortedByDescending { it.timestampForSort }.toList() } fun requestDecryptComment( @@ -178,7 +176,8 @@ class HistoryHelper( } it }.map { wallet -> - val privateKey = accountRepository.getPrivateKey(wallet.id) ?: throw Exception("Private key not found") + val privateKey = + accountRepository.getPrivateKey(wallet.id) ?: throw Exception("Private key not found") val decrypted = CommentEncryption.decryptComment( wallet.publicKey, privateKey, @@ -236,7 +235,8 @@ class HistoryHelper( } val feeFormat = "≈ " + CurrencyFormatter.format("TON", emulated.extra.value) - val feeFiatFormat = CurrencyFormatter.formatFiat(emulated.currency.code, emulated.extra.fiat) + val feeFiatFormat = + CurrencyFormatter.formatFiat(emulated.currency.code, emulated.extra.fiat) val value = if (fee is SendFee.Battery) { "≈ " + context.resources.getQuantityString( @@ -341,7 +341,8 @@ class HistoryHelper( options: ActionOptions, ): List { if (event == null) { - val position = if (options.positionExtra == 0) ListCell.Position.SINGLE else ListCell.Position.FIRST + val position = + if (options.positionExtra == 0) ListCell.Position.SINGLE else ListCell.Position.FIRST return listOf(createFakeUnknown(position)) } return mapping(wallet, listOf(event), options) @@ -352,7 +353,7 @@ class HistoryHelper( eventId: String, options: ActionOptions, ): List { - val events = eventsRepository.getSingle(eventId, wallet.testnet) ?: return emptyList() + val events = eventsRepository.getSingle(eventId, wallet.network) ?: return emptyList() return mapping( wallet = wallet, events = events, @@ -395,7 +396,8 @@ class HistoryHelper( amount.withPlus } - val amountFull = CurrencyFormatter.formatFull(token.symbol, event.amount, token.decimals) + val amountFull = + CurrencyFormatter.formatFull(token.symbol, event.amount, token.decimals) val valueFullFormatted = if (event.from == tronAddress) { amountFull.withMinus } else { @@ -474,7 +476,7 @@ class HistoryHelper( val currency = settingsRepository.currency - val rates = ratesRepository.getRates(currency, TokenEntity.TON.symbol) + val rates = ratesRepository.getRates(wallet.network, currency, TokenEntity.TON.symbol) val feeInCurrency = rates.convert(TokenEntity.TON.symbol, fee) val refundInCurrency = rates.convert(TokenEntity.TON.symbol, refund) @@ -484,7 +486,8 @@ class HistoryHelper( var actionOutStatusAny = 0 for ((actionIndex, action) in actions.withIndex()) { - val isScam = event.isScam || settingsRepository.isSpamTransaction(wallet.id, event.eventId) + val isScam = + event.isScam || settingsRepository.isSpamTransaction(wallet.id, event.eventId) if (options.spamFilter == ActionOptions.SpamFilter.SPAM && !isScam) { continue @@ -508,13 +511,16 @@ class HistoryHelper( ActionOutStatus.Any -> actionOutStatusAny++ ActionOutStatus.Received -> actionOutStatusReceived++ ActionOutStatus.Send -> actionOutStatusSend++ - ActionOutStatus.App, ActionOutStatus.dApps -> { } + ActionOutStatus.App, ActionOutStatus.dApps -> {} } chunkItems.add( item.copy( pending = pending, - position = ListCell.getPosition(actions.size + options.positionExtra, actionIndex), + position = ListCell.getPosition( + actions.size + options.positionExtra, + actionIndex + ), fee = if (fee.isPositive) CurrencyFormatter.format( TokenEntity.TON.symbol, fee, @@ -576,7 +582,8 @@ class HistoryHelper( ): HistoryItem.Event? { val simplePreview = action.simplePreview val date = DateHelper.formatTransactionTime(timestamp, settingsRepository.getLocale()) - val dateDetails = DateHelper.formatTransactionDetailsTime(timestamp, settingsRepository.getLocale()) + val dateDetails = + DateHelper.formatTransactionDetailsTime(timestamp, settingsRepository.getLocale()) // actionArgs.isTon && !actionArgs.isOut && !actionArgs.isScam && actionArgs.comment != null if (action.withdrawTokenStakeRequest != null) { @@ -590,8 +597,9 @@ class HistoryHelper( Coins.ofNano(it.value, it.decimals) } ?: Coins.ZERO val value = CurrencyFormatter.format(jettonSymbol, coins).withPlus - val valueFullFormatted = CurrencyFormatter.formatFull(jettonSymbol, coins, jettonDecimals) - val rates = ratesRepository.getRates(currency, jettonAddress) + val valueFullFormatted = + CurrencyFormatter.formatFull(jettonSymbol, coins, jettonDecimals) + val rates = ratesRepository.getRates(wallet.network, currency, jettonAddress) val inCurrency = rates.convert(jettonAddress, coins) return HistoryItem.Event( @@ -627,8 +635,9 @@ class HistoryHelper( Coins.ofNano(it.value, it.decimals) } ?: Coins.ZERO val value = CurrencyFormatter.format(jettonSymbol, coins).withMinus - val valueFullFormatted = CurrencyFormatter.formatFull(jettonSymbol, coins, jettonDecimals) - val rates = ratesRepository.getRates(currency, jettonAddress) + val valueFullFormatted = + CurrencyFormatter.formatFull(jettonSymbol, coins, jettonDecimals) + val rates = ratesRepository.getRates(wallet.network, currency, jettonAddress) val inCurrency = rates.convert(jettonAddress, coins) return HistoryItem.Event( @@ -665,7 +674,7 @@ class HistoryHelper( val valueFullFormatted = CurrencyFormatter.formatFull(tokenCode, amount, token.decimals) val isOut = wallet.isMyAddress(purchase.destination.address) - val rates = ratesRepository.getRates(currency, token.address) + val rates = ratesRepository.getRates(wallet.network, currency, token.address) val inCurrency = rates.convert(token.address, amount) return HistoryItem.Event( @@ -702,13 +711,13 @@ class HistoryHelper( val amountIn = jettonSwap.amountCoinsIn val amountOut = jettonSwap.amountCoinsOut - val value = CurrencyFormatter.format(tokenOut.symbol, amountOut).withPlus - val value2 = CurrencyFormatter.format(tokenIn.symbol, amountIn).withMinus + val value = CurrencyFormatter.format(tokenOut.symbol, tokenIn.toUIAmount(amountOut)).withPlus + val value2 = CurrencyFormatter.format(tokenIn.symbol, tokenOut.toUIAmount(amountIn)).withMinus - val valueFullFormatted = CurrencyFormatter.formatFull(tokenOut.symbol, amountOut, tokenOut.decimals).withPlus - val valueFullFormatted2 = CurrencyFormatter.formatFull(tokenIn.symbol, amountIn, tokenIn.decimals).withMinus + val valueFullFormatted = CurrencyFormatter.formatFull(tokenOut.symbol, tokenOut.toUIAmount(amountOut), tokenOut.decimals).withPlus + val valueFullFormatted2 = CurrencyFormatter.formatFull(tokenIn.symbol, tokenOut.toUIAmount(amountIn), tokenIn.decimals).withMinus - val rates = ratesRepository.getRates(currency, tokenIn.address) + val rates = ratesRepository.getRates(wallet.network, currency, tokenIn.address) val inCurrency = rates.convert(tokenIn.address, amountIn) return HistoryItem.Event( @@ -739,7 +748,7 @@ class HistoryHelper( ) } else if (action.jettonTransfer != null) { val jettonTransfer = action.jettonTransfer!! - val token = jettonTransfer.jetton.address + val token = TokenEntity(jettonTransfer.jetton) if (options.safeMode && jettonTransfer.jetton.verification != JettonVerificationType.whitelist) { return null @@ -753,8 +762,8 @@ class HistoryHelper( } ?: false val amount = Coins.ofNano(jettonTransfer.amount, jettonTransfer.jetton.decimals) - var value = CurrencyFormatter.format(symbol, amount) - var valueFullFormatted = CurrencyFormatter.formatFull(symbol, amount, jettonTransfer.jetton.decimals) + var value = CurrencyFormatter.format(symbol, token.toUIAmount(amount)) + var valueFullFormatted = CurrencyFormatter.formatFull(symbol, token.toUIAmount(amount), jettonTransfer.jetton.decimals) val itemAction: ActionType val accountAddress: AccountAddress? @@ -776,8 +785,8 @@ class HistoryHelper( valueFullFormatted = valueFullFormatted.withPlus } - val rates = ratesRepository.getRates(currency, token) - val inCurrency = rates.convert(token, amount) + val rates = ratesRepository.getRates(wallet.network, currency, token.address) + val inCurrency = rates.convert(token.address, amount) val isEncryptedComment = jettonTransfer.encryptedComment != null val comment = HistoryItem.Event.Comment.create( @@ -800,7 +809,7 @@ class HistoryHelper( comment = comment, value = value, valueFullFormatted = valueFullFormatted, - tokenAddress = token, + tokenAddress = token.address, tokenCode = "", coinIconUrl = jettonTransfer.jetton.image, timestamp = timestamp, @@ -814,13 +823,13 @@ class HistoryHelper( unverifiedToken = jettonTransfer.jetton.verification != JettonVerificationType.whitelist, isScam = isScam, wallet = wallet, - isMaybeSpam = action.getTonAmountRaw(ratesRepository) < api.config.reportAmount, + isMaybeSpam = action.getTonAmountRaw(wallet.network, ratesRepository) < api.getConfig(wallet.network).reportAmount, spamState = settingsRepository.getSpamStateTransaction(wallet.id, txId), actionOutStatus = if (isOut) ActionOutStatus.Send else ActionOutStatus.Received ) } else if (action.tonTransfer != null) { val tonTransfer = action.tonTransfer!! - val batteryConfig = batteryRepository.getConfig(wallet.testnet) + val batteryConfig = batteryRepository.getConfig(wallet.network) val isOut = !wallet.isMyAddress(tonTransfer.recipient.address) @@ -846,7 +855,7 @@ class HistoryHelper( isFromBattery = batteryConfig.gasProxy.contains(accountAddress.address) } - val rates = ratesRepository.getRates(currency, TokenEntity.TON.symbol) + val rates = ratesRepository.getRates(wallet.network, currency, TokenEntity.TON.symbol) val inCurrency = rates.convert(TokenEntity.TON.symbol, amount) val isEncryptedComment = tonTransfer.encryptedComment != null @@ -878,7 +887,7 @@ class HistoryHelper( failed = action.status == Action.Status.failed, isScam = isScam, wallet = wallet, - isMaybeSpam = action.getTonAmountRaw(ratesRepository) < api.config.reportAmount, + isMaybeSpam = action.getTonAmountRaw(wallet.network, ratesRepository) < api.getConfig(wallet.network).reportAmount, spamState = settingsRepository.getSpamStateTransaction(wallet.id, txId), actionOutStatus = if (isOut || isFromBattery || wallet.isMyAddress(tonTransfer.sender.address)) ActionOutStatus.Send else ActionOutStatus.Received ) @@ -939,7 +948,7 @@ class HistoryHelper( var nftItem = if (isScam) null else collectiblesRepository.getNft( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, address = nftItemTransfer.nft )?.let { val pref = settingsRepository.getTokenPrefs(wallet.id, it.address) @@ -1047,7 +1056,11 @@ class HistoryHelper( val amount = jettonMint.parsedAmount val value = CurrencyFormatter.format(jettonMint.jetton.symbol, amount) - val valueFullFormatted = CurrencyFormatter.formatFull(jettonMint.jetton.symbol, amount, jettonMint.jetton.decimals) + val valueFullFormatted = CurrencyFormatter.formatFull( + jettonMint.jetton.symbol, + amount, + jettonMint.jetton.decimals + ) return HistoryItem.Event( index = index, @@ -1134,7 +1147,8 @@ class HistoryHelper( val tokenCode = auctionBid.amount.tokenName val value = CurrencyFormatter.format(auctionBid.amount.tokenName, amount) - val valueFullFormatted = CurrencyFormatter.formatFull(auctionBid.amount.tokenName, amount, 9) + val valueFullFormatted = + CurrencyFormatter.formatFull(auctionBid.amount.tokenName, amount, 9) return HistoryItem.Event( index = index, @@ -1206,11 +1220,12 @@ class HistoryHelper( val amount = Coins.of(nftPurchase.amount.value.toLong()) val value = CurrencyFormatter.format(nftPurchase.amount.tokenName, amount) - val valueFullFormatted = CurrencyFormatter.formatFull(nftPurchase.amount.tokenName, amount, 9) + val valueFullFormatted = + CurrencyFormatter.formatFull(nftPurchase.amount.tokenName, amount, 9) val nftItem = if (isScam) null else collectiblesRepository.getNft( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, address = nftPurchase.nft.address )?.let { val pref = settingsRepository.getTokenPrefs(wallet.id, it.address) @@ -1247,7 +1262,11 @@ class HistoryHelper( val amount = jettonBurn.parsedAmount val value = CurrencyFormatter.format(jettonBurn.jetton.symbol, amount) - val valueFullFormatted = CurrencyFormatter.formatFull(jettonBurn.jetton.symbol, amount, jettonBurn.jetton.decimals) + val valueFullFormatted = CurrencyFormatter.formatFull( + jettonBurn.jetton.symbol, + amount, + jettonBurn.jetton.decimals + ) return HistoryItem.Event( index = index, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/HistoryItemDecoration.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/HistoryItemDecoration.kt index 49b99f176..51aca5a07 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/HistoryItemDecoration.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/HistoryItemDecoration.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.core.history.list import android.graphics.Rect -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.core.view.doOnLayout import androidx.recyclerview.widget.RecyclerView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/signer/SingerResultContract.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/signer/SingerResultContract.kt index ea6b5b79a..b1cc20d13 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/signer/SingerResultContract.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/signer/SingerResultContract.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.core.signer import android.app.Activity import android.content.Context import android.content.Intent -import android.util.Log +import com.tonapps.log.L import androidx.activity.result.contract.ActivityResultContract import org.ton.api.pub.PublicKeyEd25519 import org.ton.bitstring.BitString 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 6d7498db0..aada93776 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 @@ -2,13 +2,36 @@ package com.tonapps.tonkeeper.deeplink import android.net.Uri import androidx.core.net.toUri +import com.tonapps.bus.generated.Events +import com.tonapps.tonkeeper.manager.tonconnect.TonConnectManager data class DeepLink( val route: DeepLinkRoute, val fromQR: Boolean, val referrer: Uri?, + val isTonConnect: Boolean = false, ) { + enum class Source { + Deeplink, QR, TonConnect; // TODO refactor + + val analytic: Events.SendNative.SendNativeFrom get() { + return when (this) { + Deeplink -> Events.SendNative.SendNativeFrom.DeepLink + QR -> Events.SendNative.SendNativeFrom.QrCode + TonConnect -> Events.SendNative.SendNativeFrom.TonconnectLocal + } + } + } + + val source: Source get() { + return when { + isTonConnect -> Source.TonConnect + fromQR -> Source.QR + else -> Source.Deeplink + } + } + companion object { fun fixBadUri(uri: Uri): Uri { @@ -29,7 +52,7 @@ data class DeepLink( ): this( route = DeepLinkRoute.resolve(uri), fromQR = fromQR, - referrer = referrer + referrer = referrer, + isTonConnect = TonConnectManager.isTonConnectDeepLink(uri) // TODO remove from here ) - -} \ No newline at end of file +} 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 0bb3ca0ec..512e409dd 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,7 +1,7 @@ package com.tonapps.tonkeeper.deeplink import android.net.Uri -import android.util.Log +import com.tonapps.log.L import androidx.core.net.toUri import com.tonapps.blockchain.ton.extensions.cellFromBase64 import com.tonapps.blockchain.ton.extensions.isValidTonAddress diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Context.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Context.kt index 17c7490d3..511686dda 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Context.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Context.kt @@ -15,7 +15,7 @@ import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan -import android.util.Log +import com.tonapps.log.L import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -196,6 +196,7 @@ fun Context.getWalletBadges( Wallet.Type.Signer, Wallet.Type.SignerQR -> Localization.signer Wallet.Type.Ledger -> Localization.ledger Wallet.Type.Keystone -> Localization.keystone + Wallet.Type.Tetra -> Localization.tetra else -> throw IllegalArgumentException("Unknown wallet type: $type") } builder = builder.badgeDefault(this, resId) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/DAppsRepository.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/DAppsRepository.kt index e019ce1d0..28528be54 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/DAppsRepository.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/DAppsRepository.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.extensions import android.net.Uri -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.filterList import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.entities.WalletEntity @@ -53,7 +53,7 @@ suspend fun DAppsRepository.getAppFixIcon( var app = getApp(url) val browserApp = browserRepository.getApp( country = settingsRepository.country, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale(), uri = url ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/PurchaseRepository.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/PurchaseRepository.kt index 8e43959e8..54c59dcd3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/PurchaseRepository.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/PurchaseRepository.kt @@ -26,7 +26,7 @@ suspend fun PurchaseRepository.getProvidersByCountry( settingsRepository: SettingsRepository, country: String ): List = withContext(Dispatchers.IO) { - val methods = get(wallet.testnet, country, settingsRepository.getLocale()) ?: return@withContext emptyList() + val methods = get(wallet.network, country, settingsRepository.getLocale()) ?: return@withContext emptyList() val all = methods.first + methods.second all.map { it.items }.flatten().distinctBy { it.title } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SendFee.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SendFee.kt index c5e219396..5f3a047cd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SendFee.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SendFee.kt @@ -4,6 +4,7 @@ import android.content.Context import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee import com.tonapps.wallet.data.settings.entities.PreferredFeeMethod +import com.tonapps.wallet.data.settings.entities.PreferredTronFeeMethod import com.tonapps.wallet.localization.Plurals val SendFee.id: String @@ -30,9 +31,18 @@ fun SendFee.Battery.formattedCharges(context: Context): CharSequence { ) } -val SendFee.method: PreferredFeeMethod +val SendFee.method: PreferredFeeMethod? get() = when (this) { is SendFee.Gasless -> PreferredFeeMethod.GASLESS is SendFee.Battery -> PreferredFeeMethod.BATTERY is SendFee.Ton -> PreferredFeeMethod.TON + else -> null + } + +val SendFee.tronMethod: PreferredTronFeeMethod? + get() = when (this) { + is SendFee.Battery -> PreferredTronFeeMethod.BATTERY + is SendFee.TronTrx -> PreferredTronFeeMethod.TRX + is SendFee.TronTon -> PreferredTronFeeMethod.TON + else -> null } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SettingsRepository.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SettingsRepository.kt index ed9f11d20..c9c4e888a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SettingsRepository.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SettingsRepository.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.extensions import com.tonapps.wallet.api.API +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.data.settings.SafeModeState import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers @@ -9,10 +10,10 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import java.util.Locale -fun SettingsRepository.isSafeModeEnabled(api: API): Boolean { +fun SettingsRepository.isSafeModeEnabled(api: API, network: TonNetwork): Boolean { val state = getSafeModeState() if (state == SafeModeState.Default) { - return api.config.flags.safeModeEnabled + return api.getConfig(network).flags.safeModeEnabled } return state == SafeModeState.Enabled } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SignRequestEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SignRequestEntity.kt index 330e7f662..07ff6a3a9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SignRequestEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/SignRequestEntity.kt @@ -22,7 +22,7 @@ suspend fun SignRequestEntity.getTransfers( val transferMessages = getTransferMessages(batteryEnabled) val transfers = mutableListOf() for (message in transferMessages) { - val sendMode = if (tonBalance != null && message.coins == tonBalance) { + val sendMode = if (tonBalance != null && message.amount == tonBalance.toBigInteger()) { TonSendMode.CARRY_ALL_REMAINING_BALANCE.value + TonSendMode.IGNORE_ERRORS.value } else { TonSendMode.PAY_GAS_SEPARATELY.value + TonSendMode.IGNORE_ERRORS.value @@ -35,7 +35,7 @@ suspend fun SignRequestEntity.getTransfers( it.balance.walletAddress.equalsAddress(message.addressValue) } val jettonCustomPayload = jetton?.let { - api.getJettonCustomPayload(wallet.accountId, wallet.testnet, it.address) + api.getJettonCustomPayload(wallet.accountId, wallet.network, it.address) } val transfer = message.getWalletTransfer( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Wallet.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Wallet.kt index 482d50eb0..dff780b9f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Wallet.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/Wallet.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.extensions import android.content.Context import android.text.SpannableString -import android.util.Log +import com.tonapps.log.L import androidx.appcompat.widget.AppCompatTextView import com.tonapps.emoji.Emoji import com.tonapps.uikit.color.textPrimaryColor diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/WalletCurrency.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/WalletCurrency.kt index 88489a05d..eb9d7f48e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/WalletCurrency.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/extensions/WalletCurrency.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.extensions import android.content.Context import android.text.SpannableStringBuilder -import com.tonapps.wallet.api.R +import com.tonapps.apps.wallet.api.R import com.tonapps.wallet.data.core.currency.WalletCurrency import uikit.extensions.badgeBlue import uikit.extensions.badgeDefault diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BatteryHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BatteryHelper.kt index 523ac45cb..c9e823ff1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BatteryHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BatteryHelper.kt @@ -22,7 +22,7 @@ object BatteryHelper { batteryRepository: BatteryRepository ): Int = withContext(Dispatchers.IO) { accountRepository.requestTonProofToken(wallet)?.let { - batteryRepository.getCharges(it, wallet.publicKey, wallet.testnet, true) + batteryRepository.getCharges(it, wallet.publicKey, wallet.network, true) } ?: 0 } @@ -35,7 +35,7 @@ object BatteryHelper { val entity = batteryRepository.getBalance( tonProofToken = tonProof, publicKey = wallet.publicKey, - testnet = wallet.testnet, + network = wallet.network, ignoreCache = true ) return entity.balance @@ -51,7 +51,7 @@ object BatteryHelper { params: Boolean ): Emulated? { val chargesBalance = getBatteryCharges(wallet, accountRepository, batteryRepository) - val batteryConfig = batteryRepository.getConfig(wallet.testnet) + val batteryConfig = batteryRepository.getConfig(wallet.network) val emulated = emulationUseCase( message = message, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BrowserHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BrowserHelper.kt index e8698979c..0a3a98cec 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BrowserHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/BrowserHelper.kt @@ -1,22 +1,17 @@ package com.tonapps.tonkeeper.helper import android.app.Activity -import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.provider.Browser -import android.util.Log import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import com.tonapps.extensions.activity import com.tonapps.extensions.locale import com.tonapps.extensions.toUriOrNull -import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.extensions.showToast -import com.tonapps.tonkeeper.koin.installId import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppScreen import com.tonapps.uikit.color.backgroundPageColor import com.tonapps.uikit.color.textPrimaryColor @@ -38,7 +33,7 @@ object BrowserHelper { } else if (useTG) { openTG(context, url) } - context.analytics?.trackEventClickDApp( + context.analytics?.dappClick( url = url.toString(), name = name, source = source, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/NotificationsHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/NotificationsHelper.kt index e3fd45c28..17601a053 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/NotificationsHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/NotificationsHelper.kt @@ -4,7 +4,7 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.net.Uri -import android.util.Log +import com.tonapps.log.L import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationManagerCompat import androidx.core.net.toUri diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/TwinInput.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/TwinInput.kt index 272b0d46c..57ab42de2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/TwinInput.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/TwinInput.kt @@ -1,6 +1,6 @@ package com.tonapps.tonkeeper.helper -import android.util.Log +import com.tonapps.log.L import com.tonapps.icu.Coins import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.rates.RateData diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/Extension.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/Extension.kt index 906d3a41d..ad664273c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/Extension.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/Extension.kt @@ -7,13 +7,14 @@ import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.viewmodel.CreationExtras import androidx.recyclerview.widget.RecyclerView import com.tonapps.tonkeeper.RemoteConfig -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.history.HistoryHelper import com.tonapps.tonkeeper.manager.apk.APKManager import com.tonapps.tonkeeper.manager.push.PushManager import com.tonapps.tonkeeper.ui.base.BaseWalletScreen import com.tonapps.tonkeeper.ui.base.ScreenContext import com.tonapps.tonkeeper.ui.base.compose.ComposeWalletScreen +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.ConfigEntity import com.tonapps.wallet.api.entity.FlagsEntity @@ -85,10 +86,10 @@ val Context.remoteConfig: RemoteConfig? get() = koin?.get() val Context.serverConfig: ConfigEntity? - get() = api?.config + get() = api?.getConfig(TonNetwork.MAINNET) val Context.serverFlags: FlagsEntity? - get() = api?.config?.flags + get() = api?.getConfig(TonNetwork.MAINNET)?.flags val Context.settingsRepository: SettingsRepository? get() = koin?.get() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt index c0eddb901..933030de1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt @@ -1,11 +1,12 @@ package com.tonapps.tonkeeper.koin +import com.tonapps.async.Async import com.tonapps.network.NetworkMonitor import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.RemoteConfig import com.tonapps.tonkeeper.billing.BillingManager import com.tonapps.tonkeeper.client.safemode.SafeModeClient -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.manager.assets.AssetsManager import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.core.history.HistoryHelper @@ -24,8 +25,8 @@ import com.tonapps.tonkeeper.ui.screen.browser.main.BrowserMainViewModel import com.tonapps.tonkeeper.ui.screen.browser.search.BrowserSearchViewModel import com.tonapps.tonkeeper.ui.screen.country.CountryPickerViewModel import com.tonapps.tonkeeper.ui.screen.dev.DevViewModel -import com.tonapps.tonkeeper.ui.screen.settings.currency.CurrencyViewModel import com.tonapps.tonkeeper.ui.screen.init.InitViewModel +import com.tonapps.tonkeeper.ui.screen.settings.currency.CurrencyViewModel import com.tonapps.tonkeeper.ui.screen.ledger.steps.LedgerConnectionViewModel import com.tonapps.tonkeeper.ui.screen.migration.MigrationViewModel import com.tonapps.tonkeeper.ui.screen.settings.language.LanguageViewModel @@ -39,9 +40,7 @@ import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectViewModel import com.tonapps.tonkeeper.usecase.emulation.EmulationUseCase import com.tonapps.tonkeeper.usecase.sign.SignUseCase import com.tonapps.wallet.data.settings.SettingsRepository -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.viewModelOf @@ -50,7 +49,7 @@ import org.koin.dsl.module val koinModel = module { factory { Dispatchers.Default } - single(createdAtStart = true) { CoroutineScope(Dispatchers.IO + SupervisorJob()) } + single(createdAtStart = true) { Async.ioScope() } singleOf(::Environment) singleOf(::RemoteConfig) @@ -66,7 +65,7 @@ val koinModel = module { singleOf(::APKManager) singleOf(::CacheHelper) singleOf(::ReferrerClientHelper) - singleOf(::AnalyticsHelper) + singleOf(AnalyticsHelper::Default) factoryOf(::SignUseCase) factoryOf(::EmulationUseCase) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt index 9ca742dd7..acaf7602f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt @@ -46,6 +46,7 @@ import com.tonapps.tonkeeper.ui.screen.staking.withdraw.StakeWithdrawViewModel import com.tonapps.tonkeeper.ui.screen.swap.omniston.OmnistonViewModel import com.tonapps.tonkeeper.ui.screen.swap.picker.SwapPickerViewModel import com.tonapps.tonkeeper.ui.screen.transaction.TransactionViewModel +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesViewModel import org.koin.core.module.dsl.viewModelOf val viewModelWalletModule = module { @@ -94,4 +95,5 @@ val viewModelWalletModule = module { viewModelOf(::DNSRenewViewModel) viewModelOf(::TxEventsViewModel) viewModelOf(::TxDetailsViewModel) + viewModelOf(::TronFeesViewModel) } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/apk/APKManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/apk/APKManager.kt index 85f9a6c35..c4ec3d0f3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/apk/APKManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/apk/APKManager.kt @@ -7,14 +7,11 @@ import android.os.Build import android.os.Environment import android.os.Parcelable import android.provider.Settings -import android.util.Log import androidx.core.content.FileProvider import androidx.core.content.edit import androidx.core.net.toUri import com.tonapps.extensions.appVersionName import com.tonapps.extensions.file -import com.tonapps.extensions.getParcelable -import com.tonapps.extensions.putParcelable import com.tonapps.tonkeeper.RemoteConfig import com.tonapps.tonkeeper.extensions.safeCanRequestPackageInstalls import com.tonapps.tonkeeper.worker.ApkDownloadWorker diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt index 76f02cb82..18fa710f9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt @@ -3,9 +3,9 @@ package com.tonapps.tonkeeper.manager.assets import android.content.Context import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.icu.Coins -import com.tonapps.icu.Coins.Companion.sumOf import com.tonapps.tonkeeper.core.entities.AssetsEntity import com.tonapps.tonkeeper.core.entities.AssetsEntity.Companion.sort +import com.tonapps.tonkeeper.core.entities.AssetsEntity.Companion.sumOfVerifiedFiat import com.tonapps.tonkeeper.core.entities.StakedEntity import com.tonapps.tonkeeper.extensions.isSafeModeEnabled import com.tonapps.wallet.api.API @@ -15,7 +15,6 @@ import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.rates.RatesRepository import com.tonapps.wallet.data.settings.SettingsRepository -import com.tonapps.wallet.data.staking.StakingPool import com.tonapps.wallet.data.staking.StakingRepository import com.tonapps.wallet.data.staking.entities.StakingEntity import com.tonapps.wallet.data.token.TokenRepository @@ -65,6 +64,7 @@ class AssetsManager( tokenUsde?.let { if (tokenTsUsde != null) { val rates = ratesRepository.getRates( + wallet.network, currency, listOf(it.address, tokenTsUsde.address) ) @@ -134,13 +134,13 @@ class AssetsManager( currency: WalletCurrency = settingsRepository.currency, refresh: Boolean, ): List { - val safeMode = settingsRepository.isSafeModeEnabled(api) + val safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network) val tronAddress = if (wallet.hasPrivateKey && !wallet.testnet) { accountRepository.getTronAddress(wallet.id) } else null val tokens = - tokenRepository.get(currency, wallet.accountId, wallet.testnet, refresh, tronAddress) + tokenRepository.get(currency, wallet.accountId, wallet.network, refresh, tronAddress) ?: return emptyList() tokens.firstOrNull()?.let { if (wallet.initialized != it.balance.initializedAccount) { @@ -169,7 +169,7 @@ class AssetsManager( wallet: WalletEntity, refresh: Boolean ): StakingEntity { return stakingRepository.get( - accountId = wallet.accountId, testnet = wallet.testnet, ignoreCache = refresh + accountId = wallet.accountId, network = wallet.network, ignoreCache = refresh ) } @@ -212,7 +212,6 @@ class AssetsManager( if (sorted) { assets = assets.sort(wallet, settingsRepository) } - return assets.map { it.fiat }.sumOf { it } + return assets.sumOfVerifiedFiat() } - } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/FirebasePush.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/FirebasePush.kt index d4c82538f..6900381ad 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/FirebasePush.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/FirebasePush.kt @@ -54,4 +54,4 @@ class FirebasePush: FirebaseMessagingService() { } } } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/PushManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/PushManager.kt index d6d987b50..d0b1fc1b4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/PushManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/push/PushManager.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.manager.push import android.content.Context -import android.util.Log +import com.tonapps.log.L import androidx.core.app.NotificationManagerCompat import com.tonapps.extensions.locale import com.tonapps.wallet.api.API @@ -82,12 +82,12 @@ class PushManager( throw IllegalStateException("Failed to subscribe") } for (wallet in wallets) { - val apps = dAppsRepository.getConnections(wallet.accountId, wallet.testnet) + val apps = dAppsRepository.getConnections(wallet.accountId, wallet.network) for ((app, connections) in apps) { dAppPush( wallet = wallet, connections = connections, - commercial = dAppsRepository.isPushEnabled(wallet.accountId, wallet.testnet, app.url), + commercial = dAppsRepository.isPushEnabled(wallet.accountId, wallet.network, app.url), silent = true ) } @@ -119,7 +119,7 @@ class PushManager( for (wallet in wallets) { settingsRepository.setPushWallet(wallet.id, false) if (!delete) { - val apps = dAppsRepository.getConnections(wallet.accountId, wallet.testnet) + val apps = dAppsRepository.getConnections(wallet.accountId, wallet.network) for ((_, connections) in apps) { dAppPush( wallet = wallet, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/theme/MainContextWrapper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/theme/MainContextWrapper.kt index 43ff45b79..44309c8c6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/theme/MainContextWrapper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/theme/MainContextWrapper.kt @@ -2,11 +2,11 @@ package com.tonapps.tonkeeper.manager.theme import android.content.Context import android.content.ContextWrapper -import android.util.Log +import com.tonapps.log.L class MainContextWrapper(base: Context): ContextWrapper(base) { init { - Log.d("RootActivityLog", "ThemeContextWrapper init") + L.d("RootActivityLog", "ThemeContextWrapper init") } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnect.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnect.kt index df2771ba2..84bdd75d5 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnect.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnect.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.manager.tonconnect import android.net.Uri import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import androidx.core.net.toUri import com.tonapps.extensions.getMultipleQuery import com.tonapps.security.Security diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt index dd17ed508..7ba2076f4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt @@ -2,12 +2,12 @@ package com.tonapps.tonkeeper.manager.tonconnect import android.content.Context import android.net.Uri -import android.util.Log import androidx.collection.ArrayMap import androidx.core.net.toUri import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.connect.TONProof +import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.extensions.appVersionName import com.tonapps.extensions.bestMessage import com.tonapps.extensions.filterList @@ -35,6 +35,7 @@ import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectSafeModeDialog import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectScreen import com.tonapps.tonkeeper.worker.DAppPushToggleWorker import com.tonapps.wallet.api.API +import com.tonapps.wallet.api.readBody import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.dapps.DAppsRepository @@ -51,12 +52,9 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONObject @@ -65,8 +63,8 @@ import uikit.extensions.addForResult import uikit.navigation.NavigationActivity import java.util.concurrent.CancellationException import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock class TonConnectManager( private val scope: CoroutineScope, @@ -79,7 +77,6 @@ class TonConnectManager( ) { private val bridge: Bridge = Bridge(api) - private val bridgeConnected = AtomicBoolean(false) private var bridgeJob: Job? = null private val recentlyConnectedClients = ConcurrentHashMap() @@ -116,48 +113,55 @@ class TonConnectManager( .map { it } .shareIn(scope, SharingStarted.Eagerly, 0) + private val mutex = ReentrantLock() + fun connectBridge() { - if (bridgeConnected.get()) { - return + mutex.withLock { + if (bridgeJob?.isActive == true) { + return + } + + startBridge() + } + } + + fun disconnectBridge() { + mutex.withLock { + bridgeJob?.cancel() + bridgeJob = null + } + } + + private fun reconnectBridge() { + mutex.withLock { + if (bridgeJob != null) { + bridgeJob?.cancel() + startBridge() + } } - bridgeJob?.cancel() - bridgeConnected.set(true) + } + private fun startBridge() { bridgeJob = scope.launch(Dispatchers.IO) { val connections = dAppsRepository.getConnections().chunked(50) if (connections.isEmpty()) { return@launch } + val flow = connections.map { bridge.eventsFlow(it, dAppsRepository.lastEventId) }.flatter() + flow.collect { - if (bridgeConnected.get()) { - _eventsFlow.emit(it) - } + _eventsFlow.emit(it) } } } - fun disconnectBridge() { - if (!bridgeConnected.get()) { - return - } - bridgeJob?.cancel() - bridgeConnected.set(false) - } - - private fun reconnectBridge() { - if (bridgeConnected.get()) { - disconnectBridge() - connectBridge() - } - } - - fun walletConnectionsFlow(wallet: WalletEntity) = accountConnectionsFlow(wallet.accountId, wallet.testnet) + fun walletConnectionsFlow(wallet: WalletEntity) = accountConnectionsFlow(wallet.accountId, wallet.network) - fun accountConnectionsFlow(accountId: String, testnet: Boolean = false) = dAppsRepository.connectionsFlow.filterList { connection -> - connection.testnet == testnet && connection.accountId.equalsAddress(accountId) + fun accountConnectionsFlow(accountId: String, network: TonNetwork = TonNetwork.MAINNET) = dAppsRepository.connectionsFlow.filterList { connection -> + connection.network == network && connection.accountId.equalsAddress(accountId) } fun setLastAppRequestId(clientId: String, messageId: Long) { @@ -176,18 +180,18 @@ class TonConnectManager( bridge.sendDisconnectResponseSuccess(connection, messageId) } - accountRepository.getWalletByAccountId(connection.accountId, connection.testnet)?.let { + accountRepository.getWalletByAccountId(connection.accountId, connection.network)?.let { pushManager.dAppUnsubscribe(it, listOf(connection)) } } suspend fun getConnection( accountId: String, - testnet: Boolean, + network: TonNetwork, appUrl: Uri, type: AppConnectEntity.Type ): AppConnectEntity? { - val apps = dAppsRepository.getConnections(accountId, testnet) + val apps = dAppsRepository.getConnections(accountId, network) if (apps.isEmpty()) { return null } @@ -204,7 +208,7 @@ class TonConnectManager( fun disconnect(wallet: WalletEntity, appUrl: Uri, type: AppConnectEntity.Type? = null) { scope.launch(Dispatchers.IO) { - val connections = dAppsRepository.deleteApp(wallet.accountId, wallet.testnet, appUrl, type) + val connections = dAppsRepository.deleteApp(wallet.accountId, wallet.network, appUrl, type) if (connections.isNotEmpty()) { for (connection in connections) { bridge.sendDisconnect(connection) @@ -218,7 +222,7 @@ class TonConnectManager( } suspend fun clear(wallet: WalletEntity) = withContext(Dispatchers.IO) { - val connections = dAppsRepository.deleteApps(wallet.accountId, wallet.testnet) + val connections = dAppsRepository.deleteApps(wallet.accountId, wallet.network) for (connection in connections) { bridge.sendDisconnect(connection) } @@ -241,7 +245,7 @@ class TonConnectManager( } fun isPushEnabled(wallet: WalletEntity, appUrl: Uri): Boolean { - return dAppsRepository.isPushEnabled(wallet.accountId, wallet.testnet, appUrl) + return dAppsRepository.isPushEnabled(wallet.accountId, wallet.network, appUrl) } private suspend fun newConnect( @@ -256,7 +260,7 @@ class TonConnectManager( val timestamp = proof?.timestamp ?: (System.currentTimeMillis() / 1000L) val connection = AppConnectEntity( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, clientId = clientId, type = type, appUrl = appUrl, @@ -352,7 +356,7 @@ class TonConnectManager( screen.contract.parseResult(bundle) } - val connect = newConnect( + newConnect( wallet = response.wallet, keyPair = keyPair, clientId = clientId, @@ -397,9 +401,9 @@ class TonConnectManager( } suspend fun showLogoutAppBar(wallet: WalletEntity, context: Context, url: Uri) = withContext(Dispatchers.Main.immediate) { - val connections = dAppsRepository.getConnections(wallet.accountId, wallet.testnet).flatMap { + val connections = dAppsRepository.getConnections(wallet.accountId, wallet.network).flatMap { it.value - }.filter { it.accountId.equalsAddress(wallet.accountId) && it.testnet == wallet.testnet } + }.filter { it.accountId.equalsAddress(wallet.accountId) && it.network == wallet.network } if (connections.isNotEmpty()) { val text = context.getString(Localization.disconnect_dapp_confirm, url.host) @@ -408,7 +412,7 @@ class TonConnectManager( } suspend fun isScam(context: Context, wallet: WalletEntity, vararg uris: Uri): Boolean { - if (settingsRepository.isSafeModeEnabled(api) && safeModeClient.isHasScamUris(*uris)) { + if (settingsRepository.isSafeModeEnabled(api, wallet.network) && safeModeClient.isHasScamUris(*uris)) { withContext(Dispatchers.Main) { TonConnectSafeModeDialog(context).show(wallet) } @@ -429,7 +433,7 @@ class TonConnectManager( if (response.code != 200) { throw ManifestException.NotFound(response.code) } - val body = response.body.string() + val body = response.readBody() try { val app = AppEntity(body) dAppsRepository.insertApp(app) @@ -463,4 +467,4 @@ class TonConnectManager( } } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/Bridge.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/Bridge.kt index 55c8a4624..7c9f996d3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/Bridge.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/Bridge.kt @@ -1,6 +1,6 @@ package com.tonapps.tonkeeper.manager.tonconnect.bridge -import android.util.Log +import com.tonapps.log.L import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.base64.encodeBase64 import com.tonapps.blockchain.ton.connect.TONProof diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/JsonBuilder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/JsonBuilder.kt index cd710d41a..36037bb17 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/JsonBuilder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/JsonBuilder.kt @@ -146,7 +146,7 @@ internal object JsonBuilder { val json = JSONObject() json.put("name", "ton_addr") json.put("address", wallet.accountId) - json.put("network", (if (wallet.testnet) -3 else -239).toString()) + json.put("network", wallet.network.value.toString()) json.put("publicKey", wallet.publicKey.hex()) json.put("walletStateInit", stateInit) return json diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/BridgeEvent.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/BridgeEvent.kt index c0cf7aa6c..fca2e8e2c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/BridgeEvent.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/BridgeEvent.kt @@ -12,14 +12,40 @@ data class BridgeEvent( val connection: AppConnectEntity, ) { + sealed interface Event + val method: BridgeMethod get() = message.method + + companion object { + fun parse(array: JSONArray): List { + val messages = mutableListOf() + for (i in 0 until array.length()) { + val obj = array.getJSONObject(i) + when { +// obj.has("traceId") -> messages.add(Trace(obj)) + obj.has("method") -> messages.add(Message(obj)) + } + } + + return messages + } + } + + data class Trace( + val traceId: String, + ) : Event { + constructor(json: JSONObject) : this( + json.getString("traceId"), + ) + } + data class Message( val method: BridgeMethod, val params: List, val id: Long, - ) { + ) : Event { constructor(json: JSONObject) : this( BridgeMethod.of(json.getString("method")), @@ -36,14 +62,6 @@ data class BridgeEvent( else -> listOf(params.toString()) } } - - fun parse(array: JSONArray): List { - val messages = mutableListOf() - for (i in 0 until array.length()) { - messages.add(Message(array.getJSONObject(i))) - } - return messages - } } } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/SignDataRequestPayload.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/SignDataRequestPayload.kt index 941d2256b..6f992db64 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/SignDataRequestPayload.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/bridge/model/SignDataRequestPayload.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.manager.tonconnect.bridge.model import android.os.Parcelable -import android.util.Log +import com.tonapps.log.L import com.tonapps.base64.decodeBase64 import com.tonapps.base64.encodeBase64 import com.tonapps.blockchain.ton.extensions.cellFromBase64 @@ -25,9 +25,7 @@ abstract class SignDataRequestPayload(val type: String): Parcelable { return try { parse(JSONObject(value)) } catch (e: Throwable) { - if (DevSettings.tonConnectLogs) { - Log.d("TonConnect", "Failed to parse SignDataRequestPayload: $value", e) - } + L.e("TonConnect", "Failed to parse SignDataRequestPayload: $value", e) null } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/BaseTransactionManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/BaseTransactionManager.kt index 939cb9c15..d94f9ed2f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/BaseTransactionManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/BaseTransactionManager.kt @@ -1,6 +1,8 @@ package com.tonapps.tonkeeper.manager.tx +import com.tonapps.async.Async import com.tonapps.extensions.MutableEffectFlow +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.tonkeeper.manager.tx.model.PendingHash import com.tonapps.tonkeeper.manager.tx.model.PendingWrapEvent import com.tonapps.wallet.api.API @@ -16,30 +18,30 @@ open class BaseTransactionManager( private val api: API ) { - val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val scope = Async.ioScope() private val _pendingHashFlow = MutableEffectFlow() private val pendingTxFlow = _pendingHashFlow.mapNotNull(::fetchPendingTx).flowOn(Dispatchers.IO) - fun addPendingHash(accountId: String, testnet: Boolean, hash: String) { - _pendingHashFlow.tryEmit(PendingHash(accountId, testnet, hash)) + fun addPendingHash(accountId: String, network: TonNetwork, hash: String) { + _pendingHashFlow.tryEmit(PendingHash(accountId, network, hash)) } private suspend fun fetchTx( accountId: String, - testnet: Boolean, + network: TonNetwork, hash: String ) = withContext(Dispatchers.IO) { withRetry { - api.accounts(testnet).getAccountEvent(accountId, hash) + api.accounts(network).getAccountEvent(accountId, hash) } } private suspend fun fetchPendingTx(hash: PendingHash): PendingWrapEvent? { val event = fetchTx( accountId = hash.accountId, - testnet = hash.testnet, + network = hash.network, hash = hash.hash ) ?: return null return PendingWrapEvent(hash, event) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/TransactionManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/TransactionManager.kt index f75f09d9f..fc092f892 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/TransactionManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/TransactionManager.kt @@ -1,6 +1,5 @@ package com.tonapps.tonkeeper.manager.tx -import android.util.Log import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.extensions.MutableEffectFlow import com.tonapps.tonkeeper.App @@ -14,11 +13,9 @@ import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.battery.BatteryRepository import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.token.TokenRepository -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -58,6 +55,7 @@ class TransactionManager( ) private val transactionFlow = _transactionFlow.asSharedFlow() + // TODO private val _tronUpdatedFlow = MutableEffectFlow() val tronUpdatedFlow = _tronUpdatedFlow.asSharedFlow() @@ -91,7 +89,7 @@ class TransactionManager( ) { wallet, _, _ -> val tronEnabled = settingsRepository.getTronUsdtEnabled(wallet.id) val tronAddress = accountRepository.getTronAddress(wallet.id) - if (tronEnabled && tronAddress != null && wallet.hasPrivateKey && !wallet.testnet && !api.config.flags.disableBattery) { + if (tronEnabled && tronAddress != null && wallet.hasPrivateKey && !wallet.testnet && !api.getConfig(wallet.network).flags.disableBattery) { Pair(wallet, tronAddress) } else { null @@ -99,8 +97,8 @@ class TransactionManager( }.filterNotNull().onEach { (wallet, tronAddress) -> tronRefreshJob?.cancel() tronRefreshJob = scope.launch { - delay(30.seconds) - tokenRepository.refreshTron(wallet.accountId, wallet.testnet, tronAddress) + delay(60.seconds) + tokenRepository.refreshTron(wallet.accountId, wallet.network, tronAddress) _tronUpdatedFlow.tryEmit(Unit) } @@ -113,7 +111,7 @@ class TransactionManager( private fun realtime(config: ConfigEntity, wallet: WalletEntity) = api.realtime( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, config = config, onFailure = null ).map { it.data }.map { getTransaction(wallet, it) } @@ -122,7 +120,7 @@ class TransactionManager( wallet: WalletEntity, hash: String ): AccountEventEntity? = withContext(Dispatchers.IO) { - api.getTransactionByHash(wallet.accountId, wallet.testnet, hash) + api.getTransactionByHash(wallet.accountId, wallet.network, hash) } private suspend fun sendWithBattery( @@ -136,7 +134,7 @@ class TransactionManager( val state = api.sendToBlockchainWithBattery( boc = boc, tonProofToken = tonProofToken, - testnet = wallet.testnet, + network = wallet.network, source = source, confirmationTime = confirmationTime ) @@ -144,7 +142,7 @@ class TransactionManager( batteryRepository.refreshBalanceDelay( publicKey = wallet.publicKey, tonProofToken = tonProofToken, - testnet = wallet.testnet, + network = wallet.network, ) } return state @@ -171,7 +169,7 @@ class TransactionManager( val state = if (withBattery) { sendWithBattery(wallet, boc, source, confirmationTime) } else { - api.sendToBlockchain(boc, wallet.testnet, source, confirmationTime) + api.sendToBlockchain(boc, wallet.network, source, confirmationTime) } if (state == SendBlockchainState.SUCCESS) { // addPendingHash(wallet.accountId, wallet.testnet, normalizedHash.toHex()) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/model/PendingHash.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/model/PendingHash.kt index 3c7fdf137..5de868ac0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/model/PendingHash.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tx/model/PendingHash.kt @@ -1,7 +1,9 @@ package com.tonapps.tonkeeper.manager.tx.model +import com.tonapps.blockchain.ton.TonNetwork + data class PendingHash( val accountId: String, - val testnet: Boolean, + val network: TonNetwork, val hash: String ) \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetManager.kt index ad7fd1af7..cfd4ad7fc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetManager.kt @@ -9,7 +9,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.whileTimeoutOrNull diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetSettings.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetSettings.kt index 30a148501..e7a91506c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetSettings.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/widget/WidgetSettings.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.manager.widget import android.content.Context import android.content.SharedPreferences -import android.util.Log +import com.tonapps.log.L import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.extensions.constructor import com.tonapps.extensions.string diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AndroidCurrency.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AndroidCurrency.kt index 500677334..f802667ce 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AndroidCurrency.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AndroidCurrency.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.os import android.icu.util.Currency -import com.tonapps.uikit.flag.R +import com.tonapps.ui.uikit.flag.R import com.tonapps.uikit.flag.getFlagDrawable import java.util.Locale diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AppInstall.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AppInstall.kt index b735917d3..86bfe9bfd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AppInstall.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/os/AppInstall.kt @@ -7,6 +7,7 @@ object AppInstall { enum class Source(val packageName: String, val title: String) { GOOGLE_PLAY("com.android.vending", "GooglePlay"), + AURORA_STORE("com.aurora.store", "AuroraStore"), UNKNOWN("unknown", "APK") } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseListWalletScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseListWalletScreen.kt index 12965080e..829a74bae 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseListWalletScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseListWalletScreen.kt @@ -1,7 +1,6 @@ package com.tonapps.tonkeeper.ui.base import android.os.Bundle -import android.util.Log import android.view.View import android.widget.FrameLayout import androidx.annotation.DrawableRes @@ -20,7 +19,6 @@ import com.tonapps.uikit.icon.UIKitIcon import uikit.R import uikit.extensions.applyNavBottomPadding import uikit.extensions.collectFlow -import uikit.extensions.getDimensionPixelSize import uikit.extensions.topScrolled import uikit.widget.HeaderView import uikit.widget.SimpleRecyclerView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletScreen.kt index 8a6fc05c2..9333221f2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletScreen.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.core.view.WindowInsetsControllerCompat import com.tonapps.tonkeeper.RemoteConfig -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.koin.analytics import com.tonapps.tonkeeper.koin.remoteConfig import com.tonapps.tonkeeper.koin.serverConfig diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletVM.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletVM.kt index 1a2711e7b..5f6a65f0c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletVM.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/BaseWalletVM.kt @@ -2,37 +2,21 @@ package com.tonapps.tonkeeper.ui.base import android.app.Application import android.content.Context -import android.os.Handler -import android.util.Log import androidx.annotation.StringRes import androidx.annotation.UiThread -import androidx.core.content.ContextCompat -import androidx.fragment.app.FragmentActivity import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tonapps.extensions.bestMessage -import com.tonapps.extensions.isUIThread import com.tonapps.tonkeeper.extensions.loading import com.tonapps.tonkeeper.extensions.showToast -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.observeOn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import uikit.base.BaseFragment import uikit.navigation.Navigation -import uikit.navigation.Navigation.Companion.navigation import java.lang.ref.WeakReference -import java.util.concurrent.Executor abstract class BaseWalletVM( app: Application diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/InjectedTonConnectScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/InjectedTonConnectScreen.kt index 6daa80aba..e3a83a44c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/InjectedTonConnectScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/InjectedTonConnectScreen.kt @@ -6,6 +6,7 @@ import android.webkit.WebResourceRequest import androidx.annotation.LayoutRes import androidx.core.net.toUri import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.bus.generated.Events import com.tonapps.extensions.appVersionName import com.tonapps.extensions.bestMessage import com.tonapps.extensions.filterList @@ -35,7 +36,6 @@ import com.tonapps.wallet.data.dapps.entities.AppConnectEntity import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map -import org.json.JSONArray import org.json.JSONObject import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.activityViewModel @@ -111,7 +111,7 @@ abstract class InjectedTonConnectScreen(@LayoutRes layoutId: Int, wallet: Wallet suspend fun tonapiFetch( url: String, options: String - ) = api.tonapiFetch(url, options) + ) = api.tonapiFetch(url, options, wallet.network) suspend fun tonconnect( version: Int, @@ -148,43 +148,41 @@ abstract class InjectedTonConnectScreen(@LayoutRes layoutId: Int, wallet: Wallet } } - suspend fun tonconnectSend(array: JSONArray, showLogout: Boolean = true): JSONObject { + suspend fun tonconnectSend(tx: JSONObject, showLogout: Boolean = true): JSONObject { var id = 0L try { - val messages = BridgeEvent.Message.parse(array) - if (messages.size == 1) { - val message = messages.first() - id = message.id - if (wallet.isWatchOnly) { - navigation?.add(WatchInfoScreen.newInstance(wallet)) - return JsonBuilder.responseError(id, BridgeError.userDeclinedTransaction()) - } - if (message.method == BridgeMethod.SIGN_DATA) { - return tonconnectSignData(message) - } else if (message.method != BridgeMethod.SEND_TRANSACTION) { - return JsonBuilder.responseError(id, BridgeError.methodNotSupported("Method \"${message.method}\" not supported.")) - } - val signRequests = message.params.map { SignRequestEntity(it, uri) } - if (signRequests.size != 1) { - return JsonBuilder.responseError(id, BridgeError.badRequest("Request contains excess transactions. Required: 1, Provided: ${signRequests.size}")) - } - val signRequest = signRequests.first() - return try { - val boc = SendTransactionScreen.run(requireContext(), wallet, signRequest) - JsonBuilder.responseSendTransaction(id, boc) - } catch (e: CancellationException) { - if (showLogout) { - context?.let { tonConnectManager.showLogoutAppBar(wallet, it, uri) } - } - JsonBuilder.responseError(id, BridgeError.userDeclinedTransaction()) - } catch (e: BridgeException) { - JsonBuilder.responseError(id, BridgeError.badRequest(e.bestMessage)) - } catch (e: Throwable) { - FirebaseCrashlytics.getInstance().recordException(e) - JsonBuilder.responseError(id, BridgeError.unknown(e.bestMessage)) + val message = BridgeEvent.Message(tx) + id = message.id + if (wallet.isWatchOnly) { + navigation?.add(WatchInfoScreen.newInstance(wallet)) + return JsonBuilder.responseError(id, BridgeError.userDeclinedTransaction()) + } + if (message.method == BridgeMethod.SIGN_DATA) { + return tonconnectSignData(message) + } else if (message.method != BridgeMethod.SEND_TRANSACTION) { + return JsonBuilder.responseError(id, BridgeError.methodNotSupported("Method \"${message.method}\" not supported.")) + } + val signRequests = message.params.map { SignRequestEntity(it, uri) } + if (signRequests.size != 1) { + return JsonBuilder.responseError(id, BridgeError.badRequest("Request contains excess transactions. Required: 1, Provided: ${signRequests.size}")) + } + val signRequest = signRequests.first() + return try { + val boc = SendTransactionScreen.run( + requireContext(), wallet, signRequest, + sendNativeFrom = Events.SendNative.SendNativeFrom.TonconnectLocal + ) + JsonBuilder.responseSendTransaction(id, boc) + } catch (e: CancellationException) { + if (showLogout) { + context?.let { tonConnectManager.showLogoutAppBar(wallet, it, uri) } } - } else { - return JsonBuilder.responseError(id, BridgeError.badRequest("Request contains excess messages. Required: 1, Provided: ${messages.size}")) + JsonBuilder.responseError(id, BridgeError.userDeclinedTransaction()) + } catch (e: BridgeException) { + JsonBuilder.responseError(id, BridgeError.badRequest(e.bestMessage)) + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + JsonBuilder.responseError(id, BridgeError.unknown(e.bestMessage)) } } catch (e: Throwable) { navigation?.toast(e.bestMessage) @@ -222,12 +220,12 @@ abstract class InjectedTonConnectScreen(@LayoutRes layoutId: Int, wallet: Wallet private suspend fun loadConnection(attempt: Int = 0, currentUri: Uri?): AppConnectEntity? { if (attempt > 3) { - val firstApp = tonConnectManager.getConnection(wallet.accountId, wallet.testnet, url, AppConnectEntity.Type.Internal) + val firstApp = tonConnectManager.getConnection(wallet.accountId, wallet.network, url, AppConnectEntity.Type.Internal) if (firstApp != null) { return firstApp } if (currentUri != null) { - return tonConnectManager.getConnection(wallet.accountId, wallet.testnet, currentUri, AppConnectEntity.Type.Internal) + return tonConnectManager.getConnection(wallet.accountId, wallet.network, currentUri, AppConnectEntity.Type.Internal) } return null } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/QRCameraScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/QRCameraScreen.kt index 0a933c76b..cb70c00a4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/QRCameraScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/QRCameraScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.base import android.content.Context import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.annotation.LayoutRes import androidx.annotation.OptIn diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/compose/ComposeScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/compose/ComposeScreen.kt index 154242e4d..86729a8a4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/compose/ComposeScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/base/compose/ComposeScreen.kt @@ -1,9 +1,7 @@ package com.tonapps.tonkeeper.ui.base.compose import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.NonRestartableComposable import androidx.compose.ui.platform.ComposeView @@ -13,7 +11,7 @@ import com.tonapps.tonkeeper.ui.base.BaseWalletScreen import com.tonapps.tonkeeper.ui.base.ScreenContext import com.tonapps.tonkeeperx.R import org.koin.android.ext.android.inject -import ui.theme.UIKit +import ui.theme.LunaTheme abstract class ComposeScreen(screenContext: C) : BaseWalletScreen(R.layout.fragment_compose_host, screenContext) { @@ -25,7 +23,7 @@ abstract class ComposeScreen(screenContext: C) : BaseWalletScr view.findViewById(R.id.compose_view).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - UIKit(colorScheme = theme) { + LunaTheme(colorScheme = theme) { ScreenContent() } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/SnackBarView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/SnackBarView.kt index 1eb5a4625..9f5b111a7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/SnackBarView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/SnackBarView.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.component import android.content.Context import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.ViewGroup import android.widget.Button import android.widget.FrameLayout diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/TonConnectWebView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/TonConnectWebView.kt index b1c3f60ce..43c2da6ce 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/TonConnectWebView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/TonConnectWebView.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.ui.component import android.content.Context import android.net.Uri import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.toUriOrNull import uikit.widget.webview.bridge.BridgeWebView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/UpdateAvailableDialog.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/UpdateAvailableDialog.kt index f973380e0..9a252c7bb 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/UpdateAvailableDialog.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/UpdateAvailableDialog.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.component import android.content.Context import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import com.tonapps.tonkeeper.manager.apk.APKManager import com.tonapps.tonkeeperx.R diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/WordEditText.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/WordEditText.kt index 98f1c3731..ab1bd4088 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/WordEditText.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/WordEditText.kt @@ -6,7 +6,7 @@ import android.graphics.Paint import android.graphics.Rect import android.text.Editable import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.util.TypedValue import android.view.KeyEvent import android.view.View diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartDrawable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartDrawable.kt index 846d3d6a1..d933e2d4e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartDrawable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartDrawable.kt @@ -9,7 +9,7 @@ import android.graphics.Path import android.graphics.PointF import android.graphics.Rect import android.graphics.Shader -import android.util.Log +import com.tonapps.log.L import androidx.core.graphics.withTranslation import com.tonapps.wallet.api.entity.ChartEntity import uikit.extensions.withAlpha diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartView.kt index 4ef164069..50201de3a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/chart/ChartView.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.MotionEvent import android.view.View import com.tonapps.wallet.api.entity.ChartEntity diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt index 10c82bbbc..466969a61 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.Paint import android.text.TextPaint import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.core.content.res.ResourcesCompat import androidx.core.widget.doAfterTextChanged diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/format/CoinFormattingTextWatcher.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/format/CoinFormattingTextWatcher.kt index 8169b3afb..9123c2405 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/format/CoinFormattingTextWatcher.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/format/CoinFormattingTextWatcher.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.component.coin.format import android.text.Editable import android.text.TextWatcher -import android.util.Log +import com.tonapps.log.L import com.tonapps.icu.CurrencyFormatter import uikit.extensions.deleteLast import uikit.extensions.replaceAll diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/label/LabelEditorView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/label/LabelEditorView.kt index 18c66b618..ac1b765c6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/label/LabelEditorView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/label/LabelEditorView.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.Color import android.graphics.Rect import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.ViewGroup import android.view.WindowInsets diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/token/TokenPickerView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/token/TokenPickerView.kt index 204c61b66..c314d0d43 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/token/TokenPickerView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/token/TokenPickerView.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.ui.component.token import android.content.Context import android.os.Bundle import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.getParcelableCompat import com.tonapps.tonkeeper.ui.screen.token.picker.TokenPickerScreen import com.tonapps.wallet.api.entity.TokenEntity diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/wallet/WalletHeaderView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/wallet/WalletHeaderView.kt index 684812b06..7cdd10af8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/wallet/WalletHeaderView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/wallet/WalletHeaderView.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.GestureDetector import android.view.MotionEvent import android.view.View diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletScreen.kt index e793dcd8d..6afc9da3a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletScreen.kt @@ -40,6 +40,7 @@ class AddWalletScreen: BaseListWalletScreen(ScreenContext.No Item.SIGNER_WALLET_ID -> openScreen(SignerAddScreen.newInstance()) Item.LEDGER_WALLET_ID -> openScreen(PairLedgerScreen.newInstance()) Item.KEYSTONE_WALLET_ID -> openScreen(KeystoneAddScreen.newInstance()) + Item.TETRA_WALLET_ID -> openScreen(InitScreen.newInstance(InitArgs.Type.Tetra)) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletViewModel.kt index 3c9025ed2..9be8de5b6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/AddWalletViewModel.kt @@ -1,6 +1,8 @@ package com.tonapps.tonkeeper.ui.screen.add import android.app.Application +import com.tonapps.blockchain.ton.TonNetwork +import com.tonapps.tonkeeper.core.DevSettings import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.add.list.Item import com.tonapps.wallet.api.API @@ -22,13 +24,19 @@ class AddWalletViewModel( uiItems.add(Item.header(Localization.import_wallet, Localization.import_wallet_subtitle)) } uiItems.add(Item.import) - uiItems.add(Item.watch) - uiItems.add(Item.testnet) - if (!api.config.flags.disableSigner) { + if (!api.getConfig(TonNetwork.MAINNET).flags.disableSigner) { uiItems.add(Item.signer) } uiItems.add(Item.keystone) uiItems.add(Item.ledger) + uiItems.add(Item.otherOptionsTitle) + uiItems.add(Item.watch) + uiItems.add(Item.forDevelopersTitle) + uiItems.add(Item.testnet) + if (DevSettings.tetraEnabled) { + uiItems.add(Item.tetra) + } + uiItems.toList() } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Adapter.kt index a073a797f..48645259e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Adapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Adapter.kt @@ -3,6 +3,7 @@ package com.tonapps.tonkeeper.ui.screen.add.list import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.tonapps.tonkeeper.ui.screen.add.list.holder.HeaderHolder +import com.tonapps.tonkeeper.ui.screen.add.list.holder.SectionTitleHolder import com.tonapps.tonkeeper.ui.screen.add.list.holder.WalletHolder import com.tonapps.uikit.list.BaseListAdapter import com.tonapps.uikit.list.BaseListHolder @@ -15,6 +16,7 @@ class Adapter( override fun createHolder(parent: ViewGroup, viewType: Int): BaseListHolder { return when(viewType) { Item.TYPE_HEADER -> HeaderHolder(parent) + Item.TYPE_SECTION_TITLE -> SectionTitleHolder(parent) Item.TYPE_WALLET -> WalletHolder(parent, onClick) else -> throw IllegalArgumentException("Unknown viewType: $viewType") } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Item.kt index bb700cf3d..fb63df25d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/Item.kt @@ -12,6 +12,7 @@ sealed class Item(type: Int): BaseListItem(type) { companion object { const val TYPE_WALLET = -10 const val TYPE_HEADER = -11 + const val TYPE_SECTION_TITLE = -12 const val IMPORT_WALLET_ID = 1 const val WATCH_WALLET_ID = 2 @@ -20,11 +21,16 @@ sealed class Item(type: Int): BaseListItem(type) { const val LEDGER_WALLET_ID = 5 const val KEYSTONE_WALLET_ID = 6 const val NEW_WALLET_ID = 7 + const val TETRA_WALLET_ID = 8 fun header(title: Int, subtitle: Int): Header { return Header(title, subtitle) } + val forDevelopersTitle = SectionTitle(titleResId = Localization.for_developers) + + val otherOptionsTitle = SectionTitle(titleResId = Localization.other_options) + val new = Wallet( id = NEW_WALLET_ID, iconResId = R.drawable.ic_plus_circle_28, @@ -73,6 +79,13 @@ sealed class Item(type: Int): BaseListItem(type) { titleResId = Localization.ledger_title, subtitleResId = Localization.ledger_subtitle ) + + val tetra = Wallet( + id = TETRA_WALLET_ID, + iconResId = R.drawable.ic_tetra_24, + titleResId = Localization.tetra_title, + subtitleResId = Localization.tetra_subtitle + ) } data class Wallet( @@ -87,4 +100,8 @@ sealed class Item(type: Int): BaseListItem(type) { @StringRes val subtitleResId: Int ): Item(TYPE_HEADER) + data class SectionTitle( + @StringRes val titleResId: Int, + ): Item(TYPE_SECTION_TITLE) + } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/holder/SectionTitleHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/holder/SectionTitleHolder.kt new file mode 100644 index 000000000..71fa50c98 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/add/list/holder/SectionTitleHolder.kt @@ -0,0 +1,21 @@ +package com.tonapps.tonkeeper.ui.screen.add.list.holder + +import android.view.View +import android.view.ViewGroup +import androidx.core.view.marginTop +import com.tonapps.tonkeeper.ui.screen.add.list.Item +import uikit.widget.TextHeaderView + +class SectionTitleHolder(parent: ViewGroup): Holder(TextHeaderView(parent.context)) { + + private val itemActionView = itemView as TextHeaderView + + override fun onBind(item: Item.SectionTitle) { + itemActionView.titleView.visibility = View.GONE + (itemActionView.descriptionView.layoutParams as ViewGroup.MarginLayoutParams) + .apply { topMargin = 0 } + .also { itemActionView.descriptionView.layoutParams = it } + itemActionView.desciption = getString(item.titleResId) + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/check/BackupCheckScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/check/BackupCheckScreen.kt index e2628bd54..17d688138 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/check/BackupCheckScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/check/BackupCheckScreen.kt @@ -1,29 +1,18 @@ package com.tonapps.tonkeeper.ui.screen.backup.check import android.os.Bundle -import android.util.Log import android.view.View import android.widget.Button import androidx.core.view.updatePadding import androidx.core.widget.NestedScrollView -import androidx.lifecycle.lifecycleScope import com.tonapps.tonkeeper.koin.walletViewModel -import com.tonapps.tonkeeper.ui.base.BaseWalletScreen -import com.tonapps.tonkeeper.ui.base.ScreenContext import com.tonapps.tonkeeper.ui.base.WalletContextScreen import com.tonapps.tonkeeperx.BuildConfig import com.tonapps.tonkeeperx.R import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.localization.Localization -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import uikit.base.BaseFragment import uikit.extensions.doKeyboardAnimation -import uikit.extensions.pinToBottomInsets -import uikit.extensions.scrollDown import uikit.extensions.scrollView import uikit.widget.HeaderView import uikit.widget.TextHeaderView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupScreen.kt index fcdf00200..b01836f0f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupScreen.kt @@ -53,7 +53,9 @@ class BackupScreen(wallet: WalletEntity): BaseListWalletScreen + val ctx = context ?: return + + viewModel.getRecoveryPhrase(ctx) { words, error -> if (error != null) { navigation?.toast(error.bestMessage) } else { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt index 23e09d367..c2813bcf1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt @@ -2,8 +2,6 @@ package com.tonapps.tonkeeper.ui.screen.backup.main import android.app.Application import android.content.Context -import android.util.Log -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tonapps.extensions.filterList import com.tonapps.icu.Coins @@ -11,6 +9,7 @@ import com.tonapps.icu.Coins.Companion.sumOf import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.core.BalanceType import com.tonapps.tonkeeper.core.entities.AssetsEntity +import com.tonapps.tonkeeper.core.entities.AssetsEntity.Companion.sumOfVerifiedFiat import com.tonapps.tonkeeper.core.entities.StakedEntity import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.backup.main.list.Item @@ -27,12 +26,8 @@ import com.tonapps.wallet.data.staking.StakingRepository import com.tonapps.wallet.data.token.TokenRepository import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -112,7 +107,7 @@ class BackupViewModel( private suspend fun getBalanceType( balanceFiat: Coins, ): Int { - val rates = ratesRepository.getTONRates(settingsRepository.currency) + val rates = ratesRepository.getTONRates(wallet.network, settingsRepository.currency) val balanceTON = rates.convertFromFiat(TokenEntity.TON.address, balanceFiat) return BalanceType.getBalanceType(balanceTON) } @@ -124,7 +119,7 @@ class BackupViewModel( return if (wallet.testnet) { assets.first().fiat } else { - assets.map { it.fiat }.sumOf { it } + assets.sumOfVerifiedFiat() } } @@ -132,8 +127,8 @@ class BackupViewModel( wallet: WalletEntity, ): List { val currency = settingsRepository.currency - val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet) ?: emptyList() - val staking = stakingRepository.get(wallet.accountId, wallet.testnet) + val tokens = tokenRepository.get(currency, wallet.accountId, wallet.network) ?: emptyList() + val staking = stakingRepository.get(wallet.accountId, wallet.network) val staked = StakedEntity.create(wallet, staking, tokens, currency, ratesRepository, api) val liquid = staked.find { it.isTonstakers }?.liquidToken val filteredTokens = if (liquid == null) tokens else tokens.filter { !liquid.token.address.contains(it.address) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryScreen.kt index 25142c665..335ee7d46 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.battery import android.os.Bundle import android.view.View -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.BaseHolderWalletScreen import com.tonapps.tonkeeper.ui.base.ScreenContext diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryViewModel.kt index 7ac98fe0f..6e700c973 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/BatteryViewModel.kt @@ -1,27 +1,18 @@ package com.tonapps.tonkeeper.ui.screen.battery import android.app.Application -import android.util.Log import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.extensions.MutableEffectFlow import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.battery.recharge.BatteryRechargeScreen -import com.tonapps.tonkeeper.ui.screen.settings.main.SettingsViewModel -import com.tonapps.wallet.api.API import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.entities.WalletEntity -import com.tonapps.wallet.data.battery.BatteryMapper import com.tonapps.wallet.data.battery.BatteryRepository -import com.tonapps.wallet.data.battery.entity.BatteryBalanceEntity import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.token.TokenRepository -import com.tonapps.wallet.data.token.entities.AccountTokenEntity -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class BatteryViewModel( @@ -50,10 +41,10 @@ class BatteryViewModel( private fun openRecharge(jetton: String) { viewModelScope.launch { val rechargeToken = - batteryRepository.getRechargeMethodByJetton(wallet.testnet, jetton)?.jettonMaster + batteryRepository.getRechargeMethodByJetton(wallet.network, jetton)?.jettonMaster ?: "TON" val tokens = - tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.testnet) + tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) ?: return@launch val token = tokens.firstOrNull { it.address.equalsAddress(rechargeToken) } ?: return@launch @@ -78,7 +69,7 @@ class BatteryViewModel( batteryRepository.getBalance( tonProofToken = tonProofToken, publicKey = wallet.publicKey, - testnet = wallet.testnet, + network = wallet.network, ignoreCache = true ) } 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 227f62496..df6df749e 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 @@ -1,8 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.battery.recharge import android.app.Application -import android.net.Uri -import android.util.Log +import androidx.core.net.toUri import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.TonAddressTags import com.tonapps.blockchain.ton.TonNetwork @@ -10,12 +9,12 @@ import com.tonapps.blockchain.ton.TonTransferHelper import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.isValidTonAddress +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.filterList import com.tonapps.extensions.state import com.tonapps.icu.Coins import com.tonapps.icu.CurrencyFormatter -import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.core.entities.TransferEntity import com.tonapps.tonkeeper.extensions.toGrams import com.tonapps.tonkeeper.ui.base.BaseWalletVM @@ -44,6 +43,7 @@ import com.tonapps.wallet.data.rates.RatesRepository import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.token.TokenRepository import com.tonapps.wallet.data.token.entities.AccountTokenEntity +import io.tonapi.models.AccountStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.async @@ -66,8 +66,7 @@ import kotlinx.coroutines.withContext import org.ton.block.AddrStd import uikit.extensions.collectFlow import java.math.BigDecimal -import androidx.core.net.toUri -import io.tonapi.models.AccountStatus +import java.math.RoundingMode class BatteryRechargeViewModel( app: Application, @@ -103,7 +102,7 @@ class BatteryRechargeViewModel( SendDestination.Empty } else { _destinationLoadingFlow.tryEmit(true) - val destination = getDestinationAccount(address, wallet.testnet) + val destination = getDestinationAccount(address, wallet.network) _destinationLoadingFlow.tryEmit(false) destination } @@ -148,8 +147,9 @@ class BatteryRechargeViewModel( val customAmount = selected.second val batteryBalance = getBatteryBalance(wallet) - val ton = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.testnet) - ?.find { it.isTon } ?: return@combine emptyList() + val ton = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) + ?.find { it.isTon } + ?: return@combine emptyList() val hasEnoughTonBalance = ton.balance.value >= Coins.of(0.1) val hasBatteryBalance = batteryBalance.balance > Coins.ZERO val rechargeMethod = getRechargeMethod(wallet, token) @@ -189,7 +189,7 @@ class BatteryRechargeViewModel( uiItems.addAll(uiItemsPacks(packs, selectedPackType, customAmount)) - if (BuildConfig.DEBUG || !api.config.batteryPromoDisable) { + if (BuildConfig.DEBUG || !api.getConfig(wallet.network).batteryPromoDisable) { uiItems.add(Item.Space) uiItems.add(Item.Promo(promoState)) } @@ -226,7 +226,7 @@ class BatteryRechargeViewModel( currency = token.symbol, value = remainingBalance ), formattedMinAmount = CurrencyFormatter.format( - currency = token.symbol, value = minAmount + currency = token.symbol, value = minAmount, scale = 3, // TODO maybe we shouldn't hardcode ), isInsufficientBalance = remainingBalance.isNegative, isLessThanMin = isLessThanMin, @@ -256,7 +256,7 @@ class BatteryRechargeViewModel( init { viewModelScope.launch(Dispatchers.IO) { - val appliedPromo = batteryRepository.getAppliedPromo(wallet.testnet) + val appliedPromo = batteryRepository.getAppliedPromo(wallet.network) if (appliedPromo.isNullOrBlank()) { promoStateFlow.tryEmit(PromoState.Default) @@ -341,7 +341,7 @@ class BatteryRechargeViewModel( val rechargeMethod = getRechargeMethod(wallet, token) val batteryBalance = getBatteryBalance(wallet) val config = getBatteryConfig(wallet) - val batteryMaxInputAmount = rechargeMethod.fromTon(api.config.batteryMaxInputAmount) + val batteryMaxInputAmount = rechargeMethod.fromTon(api.getConfig(wallet.network).batteryMaxInputAmount) val amount = _selectedPackTypeFlow.value?.let { packType -> rechargeMethod.fromTon( @@ -365,16 +365,14 @@ class BatteryRechargeViewModel( val fundReceiver = config.fundReceiver ?: return@combine val recipientAddress = if (destination is SendDestination.TonAccount) { destination.address - } else null + } else { + null + } val payload = wallet.contract.createBatteryBody( recipientAddress, - appliedPromo = batteryRepository.getAppliedPromo(wallet.testnet) + appliedPromo = batteryRepository.getAppliedPromo(wallet.network) ) - val validUntil = accountRepository.getValidUntil(wallet.testnet) - val network = when (wallet.testnet) { - true -> TonNetwork.TESTNET - false -> TonNetwork.MAINNET - } + val validUntil = accountRepository.getValidUntil(wallet.network) val forceRelayer = when { token.isTon -> false @@ -386,11 +384,13 @@ class BatteryRechargeViewModel( else -> false } - val account = api.resolveAccount(wallet.address, wallet.testnet) - val accountStatus = account?.status ?: AccountStatus.unknown + val account = api.resolveAccount(wallet.address, wallet.network) + val accountStatus = account?.status val stateInit = if (accountStatus == AccountStatus.nonexist || accountStatus == AccountStatus.uninit) { wallet.contract.stateInitCell() - } else null + } else { + null + } if (token.isTon) { val request = SignRequestEntity.Builder() @@ -405,14 +405,14 @@ class BatteryRechargeViewModel( payloadValue = payload.base64() ) ) - .setNetwork(network) + .setNetwork(wallet.network) .build("https://battery.tonkeeper.com/".toUri()) _eventFlow.tryEmit(BatteryRechargeEvent.Sign(request, forceRelayer)) } else { val queryId = TransferEntity.newWalletQueryId() val customPayload = if (token.isRequestMinting) { - api.getJettonCustomPayload(wallet.accountId, wallet.testnet, token.address) + api.getJettonCustomPayload(wallet.accountId, wallet.network, token.address) } else { null } @@ -438,7 +438,7 @@ class BatteryRechargeViewModel( payloadValue = jettonPayload.base64() ) ) - .setNetwork(network) + .setNetwork(wallet.network) .build("https://battery.tonkeeper.com/".toUri()) _eventFlow.tryEmit(BatteryRechargeEvent.Sign(request, forceRelayer)) @@ -485,7 +485,7 @@ class BatteryRechargeViewModel( private suspend fun getBatteryConfig( wallet: WalletEntity ): BatteryConfigEntity { - return batteryRepository.getConfig(wallet.testnet) + return batteryRepository.getConfig(wallet.network) } private suspend fun getBatteryBalance( @@ -494,7 +494,7 @@ class BatteryRechargeViewModel( val tonProofToken = accountRepository.requestTonProofToken(wallet) ?: return BatteryBalanceEntity.Empty return batteryRepository.getBalance( - tonProofToken = tonProofToken, publicKey = wallet.publicKey, testnet = wallet.testnet + tonProofToken = tonProofToken, publicKey = wallet.publicKey, network = wallet.network ) } @@ -502,7 +502,7 @@ class BatteryRechargeViewModel( return tokenRepository.get( currency = settingsRepository.currency, accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ) ?: emptyList() } @@ -541,9 +541,9 @@ class BatteryRechargeViewModel( willBePaidManually: Boolean, shouldMinusReservedAmount: Boolean ): List { - val fiatRate = ratesRepository.getRates(settingsRepository.currency, token.address) + val fiatRate = ratesRepository.getRates(wallet.network, settingsRepository.currency, token.address) .getRate(token.address) - val config = api.config + val serverConfig = api.getConfig(wallet.network) return arrayOf( RechargePackType.LARGE, RechargePackType.MEDIUM, RechargePackType.SMALL @@ -553,7 +553,7 @@ class BatteryRechargeViewModel( rechargeMethod = rechargeMethod, fiatRate = fiatRate, token = token, - config = config, + config = serverConfig, shouldMinusReservedAmount = shouldMinusReservedAmount, willBePaidManually = willBePaidManually, currency = settingsRepository.currency, @@ -563,11 +563,11 @@ class BatteryRechargeViewModel( } private suspend fun getDestinationAccount( - userInput: String, testnet: Boolean + userInput: String, network: TonNetwork ) = withContext(Dispatchers.IO) { val addressTags = TonAddressTags.of(userInput) - val accountDeferred = async { api.resolveAccount(userInput, testnet) } - val publicKeyDeferred = async { api.safeGetPublicKey(userInput, testnet) } + val accountDeferred = async { api.resolveAccount(userInput, network) } + val publicKeyDeferred = async { api.safeGetPublicKey(userInput, network) } val account = accountDeferred.await() ?: return@withContext SendDestination.NotFound val publicKey = publicKeyDeferred.await() @@ -585,21 +585,21 @@ class BatteryRechargeViewModel( fun applyPromo(promo: String) { viewModelScope.launch(Dispatchers.IO) { if (promo.isEmpty()) { - batteryRepository.setAppliedPromo(wallet.testnet, null) + batteryRepository.setAppliedPromo(wallet.network, null) promoStateFlow.tryEmit(PromoState.Default) return@launch } promoStateFlow.tryEmit(PromoState.Loading) try { - if (api.batteryVerifyPurchasePromo(wallet.testnet, promo)) { - batteryRepository.setAppliedPromo(wallet.testnet, promo) + if (api.batteryVerifyPurchasePromo(wallet.network, promo)) { + batteryRepository.setAppliedPromo(wallet.network, promo) promoStateFlow.tryEmit(PromoState.Applied(promo)) } else { throw IllegalStateException("promo code is invalid") } } catch (_: Exception) { - batteryRepository.setAppliedPromo(wallet.testnet, null) + batteryRepository.setAppliedPromo(wallet.network, null) promoStateFlow.tryEmit(PromoState.Error) } } @@ -620,4 +620,4 @@ class BatteryRechargeViewModel( emit(boc) }.flowOn(Dispatchers.IO) -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/list/holder/AddressHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/list/holder/AddressHolder.kt index 8a1593be1..ab8ca94ab 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/list/holder/AddressHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/list/holder/AddressHolder.kt @@ -1,6 +1,5 @@ package com.tonapps.tonkeeper.ui.screen.battery.recharge.list.holder -import android.util.Log import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.AppCompatImageView @@ -8,7 +7,6 @@ import androidx.appcompat.widget.AppCompatTextView import com.tonapps.tonkeeper.extensions.clipboardText import com.tonapps.tonkeeper.ui.screen.battery.recharge.list.Item import com.tonapps.tonkeeperx.R -import uikit.extensions.hideKeyboard import uikit.widget.InputView import uikit.widget.RowLayout 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 4783b3e5b..cad1572f9 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 @@ -10,12 +10,11 @@ import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.billing.BillingManager import com.tonapps.tonkeeper.billing.priceFormatted -import com.tonapps.tonkeeper.core.AnalyticsHelper -import com.tonapps.tonkeeper.koin.remoteConfig +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.log.L import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.battery.refill.entity.PromoState import com.tonapps.tonkeeper.ui.screen.battery.refill.list.Item -import com.tonapps.tonkeeperx.BuildConfig import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.ConfigEntity @@ -63,12 +62,22 @@ class BatteryRefillViewModel( private val environment: Environment, private val analytics: AnalyticsHelper, ) : BaseWalletVM(app) { - + + companion object { + private val requiredTopupAssets = setOf( + TokenEntity.TON.address, + TokenEntity.USDT.address, + ) + } + + private val config: ConfigEntity + get() = api.getConfig(wallet.network) + private val isBatteryDisabled: Boolean - get() = api.config.flags.disableBattery + get() = config.flags.disableBattery private val isCryptoDisabled: Boolean - get() = api.config.disableBatteryCryptoRechargeModule + get() = config.disableBatteryCryptoRechargeModule private val _promoFlow = MutableStateFlow(null) private val promoFlow = _promoFlow.asStateFlow() @@ -101,7 +110,7 @@ class BatteryRefillViewModel( uiItems.add(uiItemBattery(batteryBalance, batteryConfig)) uiItems.add(Item.Space) - if (!api.config.batteryPromoDisable && !isBatteryDisabled) { + if (!config.batteryPromoDisable && !isBatteryDisabled) { uiItems.add(Item.Promo(promoState, promoCode)) uiItems.add(Item.Space) } @@ -111,16 +120,16 @@ class BatteryRefillViewModel( uiItems.add(Item.Space) } - if (environment.isGooglePlayServicesAvailable && !api.config.disableBatteryIapModule && !isBatteryDisabled) { + if (environment.isGooglePlayServicesAvailable && !config.disableBatteryIapModule && !isBatteryDisabled) { val tonPriceInUsd = - ratesRepository.getTONRates(WalletCurrency.USD).getRate(TokenEntity.TON.address) + ratesRepository.getTONRates(wallet.network, WalletCurrency.USD).getRate(TokenEntity.TON.address) if (tonPriceInUsd > Coins.ZERO) { uiItems.addAll( uiItemsPackages( tonPriceInUsd = tonPriceInUsd, batteryBalance = batteryBalance, - config = api.config, + config = config, products = iapProducts, isProcessing = isProcessing, batteryConfig = batteryConfig, @@ -141,7 +150,7 @@ class BatteryRefillViewModel( uiItems.add( Item.Refund( - wallet = wallet, refundUrl = "${api.config.batteryRefundEndpoint}?token=${ + wallet = wallet, refundUrl = "${config.batteryRefundEndpoint}?token=${ URLEncoder.encode( tonProofToken, "UTF-8" ) @@ -158,12 +167,12 @@ class BatteryRefillViewModel( init { viewModelScope.launch(Dispatchers.IO) { if (environment.isGooglePlayServicesAvailable) { - billingManager.loadProducts(api.config.iapPackages.map { it.productId }) + billingManager.loadProducts(config.iapPackages.map { it.productId }) } else { billingManager.setEmptyProducts() } - val appliedPromo = batteryRepository.getAppliedPromo(wallet.testnet) + val appliedPromo = batteryRepository.getAppliedPromo(wallet.network) if (appliedPromo.isNullOrBlank()) { _promoStateFlow.value = PromoState.Default @@ -265,7 +274,7 @@ class BatteryRefillViewModel( private suspend fun getBatteryConfig( wallet: WalletEntity ): BatteryConfigEntity { - return batteryRepository.getConfig(wallet.testnet) + return batteryRepository.getConfig(wallet.network) } private suspend fun getBatteryBalance( @@ -274,7 +283,7 @@ class BatteryRefillViewModel( val tonProofToken = accountRepository.requestTonProofToken(wallet) ?: return BatteryBalanceEntity.Empty return batteryRepository.getBalance( - tonProofToken = tonProofToken, publicKey = wallet.publicKey, testnet = wallet.testnet + tonProofToken = tonProofToken, publicKey = wallet.publicKey, network = wallet.network ) } @@ -282,7 +291,7 @@ class BatteryRefillViewModel( return tokenRepository.get( currency = settingsRepository.currency, accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ) ?: emptyList() } @@ -297,8 +306,10 @@ class BatteryRefillViewModel( it.jettonMaster } } + return tokens.filter { token -> - supportTokenAddress.contains(token.address) && token.balance.value.isPositive + supportTokenAddress.contains(token.address) + && (token.balance.value.isPositive || token.address in requiredTopupAssets) }.sortedWith(compareByDescending { token -> token.isUsdt // Place USDT at the top }.thenBy { token -> @@ -316,19 +327,19 @@ class BatteryRefillViewModel( fun submitPromo(promo: String) { viewModelScope.launch(Dispatchers.IO) { if (promo.isEmpty()) { - batteryRepository.setAppliedPromo(wallet.testnet, null) + batteryRepository.setAppliedPromo(wallet.network, null) _promoStateFlow.value = PromoState.Default } else { _promoStateFlow.value = PromoState.Loading try { - if (api.batteryVerifyPurchasePromo(wallet.testnet, promo)) { - batteryRepository.setAppliedPromo(wallet.testnet, promo) + if (api.batteryVerifyPurchasePromo(wallet.network, promo)) { + batteryRepository.setAppliedPromo(wallet.network, promo) _promoStateFlow.value = PromoState.Applied(promo) } else { throw IllegalStateException("promo code is invalid") } } catch (_: Exception) { - batteryRepository.setAppliedPromo(wallet.testnet, null) + batteryRepository.setAppliedPromo(wallet.network, null) _promoStateFlow.value = PromoState.Error } } @@ -346,22 +357,32 @@ class BatteryRefillViewModel( AndroidBatteryPurchaseRequestPurchasesInner( productId = purchase.products.first(), token = purchase.purchaseToken, - promo = batteryRepository.getAppliedPromo(wallet.testnet) + promo = batteryRepository.getAppliedPromo(wallet.network) ) ) ) - api.battery(wallet.testnet).androidBatteryPurchase(tonProofToken, request) - batteryRepository.getBalance( - tonProofToken, wallet.publicKey, wallet.testnet, ignoreCache = true - ) - val promoCode = (_promoStateFlow.value as? PromoState.Applied)?.appliedPromo ?: "null" - withContext(Dispatchers.Main) { - analytics.batterySuccess("fiat", promoCode, "") + + val purchaseStatus = + api.battery(wallet.network).androidBatteryPurchase(tonProofToken, request) + + val firstTransaction = purchaseStatus.purchases.firstOrNull { it.productId == purchase.products.first() } + + if (firstTransaction != null && firstTransaction.success) { + batteryRepository.getBalance( + tonProofToken, wallet.publicKey, wallet.network, ignoreCache = true + ) + val promoCode = (_promoStateFlow.value as? PromoState.Applied)?.appliedPromo ?: "null" + withContext(Dispatchers.Main) { + analytics.batterySuccess("fiat", promoCode, "", null) + } + billingManager.consumeProduct(purchase.purchaseToken) + toast(Localization.battery_refilled) + } else { + toast(Localization.error) } - billingManager.consumeProduct(purchase.purchaseToken) } - toast(Localization.battery_refilled) } catch (e: Exception) { + L.e(e) toast(Localization.error) } finally { purchaseInProgress.tryEmit(false) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/Holder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/Holder.kt index 45fe855a9..c8d9b6f4d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/Holder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/refill/list/holder/Holder.kt @@ -1,6 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.battery.refill.list.holder -import android.util.Log +import com.tonapps.log.L import android.view.MotionEvent import android.view.ViewGroup import androidx.annotation.LayoutRes diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/settings/BatterySettingsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/settings/BatterySettingsViewModel.kt index 39ac3e81c..2563fe55b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/settings/BatterySettingsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/settings/BatterySettingsViewModel.kt @@ -100,14 +100,14 @@ class BatterySettingsViewModel( return batteryRepository.getBalance( tonProofToken = tonProofToken, publicKey = wallet.publicKey, - testnet = wallet.testnet + network = wallet.network ) } private suspend fun getBatteryConfig( wallet: WalletEntity ): BatteryConfigEntity { - return batteryRepository.getConfig(wallet.testnet) + return batteryRepository.getConfig(wallet.network) } private fun getTransactionMeanPrice( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseScreen.kt index df3e19c6d..e9adc79d1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.browser.base import android.os.Bundle -import android.util.Log +import com.tonapps.log.L 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/ui/screen/browser/base/BrowserBaseViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseViewModel.kt index 69f239ca3..12fe83f5b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/base/BrowserBaseViewModel.kt @@ -39,7 +39,7 @@ class BrowserBaseViewModel( suspend fun hasCategory(category: String): Boolean = withContext(Dispatchers.IO) { val categories = browserRepository.loadCategories( country = environment.country, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale() ) categories.any { it == category } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/confirm/DAppConfirmComposable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/confirm/DAppConfirmComposable.kt index 5df8b0a98..38cf07d38 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/confirm/DAppConfirmComposable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/confirm/DAppConfirmComposable.kt @@ -31,15 +31,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.wallet.localization.Localization -import ui.components.Checkbox -import ui.components.Header import ui.components.TextHeader import ui.components.button.TKButton +import ui.components.moon.MoonCheckbox +import ui.components.moon.MoonTopAppBar import ui.theme.Dimens import ui.theme.Shapes import ui.theme.UIKit @@ -111,7 +110,7 @@ fun DAppConfirmComposable( .fillMaxWidth() .windowInsetsPadding(WindowInsets.navigationBars) ) { - Header( + MoonTopAppBar( title = "", actionIconRes = UIKitIcon.ic_close_16, onActionClick = { onFinishClick() }, @@ -147,7 +146,7 @@ fun DAppConfirmComposable( var isChecked by remember { mutableStateOf(false) } Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox( + MoonCheckbox( checked = isChecked, onCheckedChange = { isChecked = it diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppBridge.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppBridge.kt index 001d407c7..3dc56de9c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppBridge.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppBridge.kt @@ -1,6 +1,5 @@ package com.tonapps.tonkeeper.ui.screen.browser.dapp -import android.util.Log import com.tonapps.tonkeeper.manager.tonconnect.ConnectRequest import okhttp3.Headers import okhttp3.Response @@ -13,7 +12,7 @@ class DAppBridge( val deviceInfo: String, val isWalletBrowser: Boolean = true, val protocolVersion: Int = 2, - val send: suspend (array: JSONArray) -> JSONObject, + val send: suspend (array: JSONObject) -> JSONObject, val connect: suspend (protocolVersion: Int, request: ConnectRequest) -> JSONObject, val restoreConnection: suspend () -> JSONObject, val disconnect: suspend () -> Unit, @@ -31,7 +30,7 @@ class DAppBridge( override suspend fun invokeFunction(name: String, args: JSONArray): Any? { return when (name) { "connect" -> connect(protocolVersion, ConnectRequest.parse(args.getJSONObject(1))).toString() - "send" -> send(args).toString() + "send" -> send(args.getJSONObject(0)).toString() "restoreConnection" -> restoreConnection().toString() "disconnect" -> disconnect() "tonapi.fetch" -> { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt index fc2045727..85c18eb31 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt @@ -5,15 +5,11 @@ import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.os.Bundle -import android.util.Log import android.view.View import android.view.ViewGroup import android.webkit.PermissionRequest import android.webkit.WebChromeClient -import android.webkit.WebResourceError import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView import androidx.appcompat.widget.AppCompatTextView import androidx.core.app.ShareCompat import androidx.core.content.pm.ShortcutInfoCompat @@ -171,7 +167,7 @@ class DAppScreen(wallet: WalletEntity) : InjectedTonConnectScreen(R.layout.fragm override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - analytics?.trackEventClickDApp( + analytics?.dappClick( url = args.url.toString(), name = args.title, source = args.source, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainScreen.kt index 744fd78fd..65d7dfb90 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainScreen.kt @@ -159,11 +159,9 @@ class BrowserMainScreen(wallet: WalletEntity): WalletContextScreen(R.layout.frag collectFlow(insets, ::onApplyWindowInsets) } - val isDappsDisable = requireContext().remoteConfig?.isDappsDisable == true + exploreTabView.isVisible = !viewModel.isDappsDisabled - exploreTabView.isVisible = !isDappsDisable - - clickTab(if (isDappsDisable) connectedTabView else exploreTabView, animated = false) + clickTab(if (viewModel.isDappsDisabled) connectedTabView else exploreTabView, animated = false) } private fun onApplyWindowInsets(insets: WindowInsetsCompat) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainViewModel.kt index c2bfe6b5a..484020dfa 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/BrowserMainViewModel.kt @@ -43,19 +43,20 @@ class BrowserMainViewModel( ConnectedItem(wallet, it) } + val isDappsDisabled: Boolean + get() = api.getConfig(wallet.network).flags.disableDApps + private val _uiExploreItemsFlow = MutableStateFlow>(emptyList()) val uiExploreItemsFlow = _uiExploreItemsFlow.asStateFlow() init { - val isDappsDisable = context.remoteConfig?.isDappsDisable == true - - if (!isDappsDisable) { + if (!isDappsDisabled) { viewModelScope.launch(Dispatchers.IO) { val code = environment.country val locale = settingsRepository.getLocale() _uiExploreItemsFlow.value = emptyList() - browserRepository.load(code, wallet.testnet, locale)?.let { setData(it) } - browserRepository.loadRemote(code, wallet.testnet, locale)?.let { setData(it) } + browserRepository.load(code, wallet.network, locale)?.let { setData(it) } + browserRepository.loadRemote(code, wallet.network, locale)?.let { setData(it) } } } } @@ -83,7 +84,7 @@ class BrowserMainViewModel( private fun setData(data: BrowserDataEntity) { val items = mutableListOf() if (data.apps.isNotEmpty()) { - items.add(ExploreItem.Banners(data.apps, api.config.featuredPlayInterval, wallet, environment.country)) + items.add(ExploreItem.Banners(data.apps, api.getConfig(wallet.network).featuredPlayInterval, wallet, environment.country)) } var adsItem: ExploreItem.Ads? = null diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/list/explore/list/holder/ExploreAdsHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/list/explore/list/holder/ExploreAdsHolder.kt index 1d3ec3eba..fe265487d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/list/explore/list/holder/ExploreAdsHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/main/list/explore/list/holder/ExploreAdsHolder.kt @@ -1,6 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.browser.main.list.explore.list.holder -import android.util.Log +import com.tonapps.log.L import android.view.ViewGroup import android.widget.Button import androidx.appcompat.widget.AppCompatTextView @@ -27,7 +27,7 @@ class ExploreAdsHolder(parent: ViewGroup): ExploreHolder(parent actionButton.text = item.button.title actionButton.setOnClickListener { - Log.d("ExploreAdsHolderLog", "url: ${item.uri}") + L.d("ExploreAdsHolderLog", "url: ${item.uri}") activity?.processDeepLink(item.uri, true, context.packageName) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreScreen.kt index 5346a90f2..74fd82819 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.browser.more import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.BaseListWalletScreen diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreViewModel.kt index 7538685dc..e4d244490 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/more/BrowserMoreViewModel.kt @@ -23,7 +23,7 @@ class BrowserMoreViewModel( private val flow = browserRepository.dataFlow( country = settingsRepository.country, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale() ).map { it.categories }.map { categories -> categories.first { it.id == id } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/safe/DAppSafeComposable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/safe/DAppSafeComposable.kt index 7c3d3c522..006b3dfcf 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/safe/DAppSafeComposable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/safe/DAppSafeComposable.kt @@ -1,6 +1,5 @@ package com.tonapps.tonkeeper.ui.screen.browser.safe -import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -18,16 +17,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.wallet.localization.Localization -import ui.components.Header import ui.components.TextHeader import ui.components.button.TKButton +import ui.components.moon.MoonTopAppBar import ui.theme.ButtonColorsSecondary import ui.theme.Dimens -import ui.theme.UIKit @Composable fun DAppSafeComposable( @@ -43,7 +40,7 @@ fun DAppSafeComposable( verticalArrangement = Arrangement.spacedBy(Dimens.offsetMedium), horizontalAlignment = Alignment.CenterHorizontally ) { - Header( + MoonTopAppBar( title = "", actionIconRes = UIKitIcon.ic_close_16, onActionClick = onClose, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchScreen.kt index 10c21df4f..6a7e07acc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.browser.search import android.net.Uri import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchViewModel.kt index 9978c7a38..c628e7678 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/search/BrowserSearchViewModel.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.browser.search import android.app.Application import android.net.Uri -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.toUriOrNull import com.tonapps.network.get @@ -14,6 +14,7 @@ import com.tonapps.wallet.api.API import com.tonapps.wallet.data.browser.BrowserRepository import com.tonapps.wallet.data.core.SearchEngine import com.tonapps.wallet.data.settings.SettingsRepository +import com.tonapps.blockchain.ton.TonNetwork import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.asSharedFlow @@ -60,7 +61,7 @@ class BrowserSearchViewModel( return@withContext emptyList() } - val isSafeModeEnabled = settingsRepository.isSafeModeEnabled(api) + val isSafeModeEnabled = settingsRepository.isSafeModeEnabled(api, TonNetwork.MAINNET) var uri = uri(query)?.let { DeepLinkRoute.normalize(it) } if (uri?.scheme == "tonkeeper") { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/share/DAppShareComposable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/share/DAppShareComposable.kt index c7bd22185..96af90de9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/share/DAppShareComposable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/share/DAppShareComposable.kt @@ -28,16 +28,13 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.core.net.toUri import coil3.compose.AsyncImage import com.tonapps.uikit.icon.UIKitIcon -import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.localization.Localization -import ui.components.Header import ui.components.TextHeader import ui.components.button.TKButton +import ui.components.moon.MoonTopAppBar import ui.theme.ButtonColorsSecondary import ui.theme.Dimens import ui.theme.Shapes @@ -141,7 +138,7 @@ fun DAppShareComposable( .fillMaxWidth() .windowInsetsPadding(WindowInsets.navigationBars) ) { - Header( + MoonTopAppBar( title = "", actionIconRes = UIKitIcon.ic_close_16, onActionClick = { onFinishClick() }, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/camera/CameraScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/camera/CameraScreen.kt index ea05bb974..1969650e4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/camera/CameraScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/camera/CameraScreen.kt @@ -30,6 +30,7 @@ import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.QRScannerExtendsEntity import com.tonapps.wallet.api.entity.TokenEntity +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.Dispatchers @@ -53,7 +54,7 @@ class CameraScreen : QRCameraScreen(R.layout.fragment_camera), BaseFragment.Bott private val accountRepository: AccountRepository by inject() private val api: API by inject() private val qrScannerExtends: List - get() = api.config.qrScannerExtends.filter { it.version == 1 } + get() = api.getConfig(TonNetwork.MAINNET).qrScannerExtends.filter { it.version == 1 } private val mode: CameraMode by lazy { requireArguments().getParcelableCompat(ARG_MODE)!! } private val chains: List by lazy { @@ -114,7 +115,7 @@ class CameraScreen : QRCameraScreen(R.layout.fragment_camera), BaseFragment.Bott val deeplink = DeepLink(DeepLink.fixBadUri(uri), true, null) val route = deeplink.route if (mode == CameraMode.Address && route is DeepLinkRoute.Transfer) { - rootViewModel.processTransferDeepLink(route) + rootViewModel.processTransferDeepLink(deeplink, route) finish() } else if (mode == CameraMode.TonConnect && route is DeepLinkRoute.TonConnect) { rootViewModel.processTonConnectDeepLink(deeplink, fromPackageName = null) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/card/CardBridge.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/card/CardBridge.kt index 0c115d5f4..ea8392b95 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/card/CardBridge.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/card/CardBridge.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.card import com.tonapps.tonkeeper.manager.tonconnect.ConnectRequest +import com.tonapps.wallet.api.readBody import okhttp3.Headers import okhttp3.Response import org.json.JSONArray @@ -12,7 +13,7 @@ class CardBridge( val deviceInfo: String, val isWalletBrowser: Boolean = true, val protocolVersion: Int = 2, - val send: suspend (array: JSONArray) -> JSONObject, + val send: suspend (array: JSONObject) -> JSONObject, val connect: suspend (protocolVersion: Int, request: ConnectRequest) -> JSONObject, val restoreConnection: suspend () -> JSONObject, val disconnect: suspend () -> Unit, @@ -30,7 +31,7 @@ class CardBridge( override suspend fun invokeFunction(name: String, args: JSONArray): Any? { return when (name) { "connect" -> connect(protocolVersion, ConnectRequest.parse(args.getJSONObject(1))).toString() - "send" -> send(args).toString() + "send" -> send(args.getJSONObject(0)).toString() "restoreConnection" -> restoreConnection().toString() "disconnect" -> disconnect() "tonapi.fetch" -> { @@ -42,7 +43,7 @@ class CardBridge( } private fun webAPIResponse(response: Response): JSONObject { - val body = response.body?.string() ?: "" + val body = response.readBody() val json = JSONObject() json.put("body", body) json.put("ok", response.isSuccessful) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt index 78b5d75a2..c81306b12 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt @@ -7,7 +7,7 @@ import androidx.core.view.updatePadding import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.isLightTheme import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.UiListState diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt index 19038a910..744f63e58 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.collectibles.main import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.extensions.flattenFirst @@ -49,7 +49,7 @@ class CollectiblesViewModel( private val expiringDomainsFlow = flow { emit(collectiblesRepository.getDnsSoonExpiring( accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ).associateBy { it.addressRaw }) } @@ -97,9 +97,9 @@ class CollectiblesViewModel( hiddenBalances: Boolean, isOnline: Boolean, expiringDomains: Map - ): Flow = collectiblesRepository.getFlow(wallet.address, wallet.testnet, isOnline).map { result -> + ): Flow = collectiblesRepository.getFlow(wallet.address, wallet.network, isOnline).map { result -> hasNfts = result.list.isNotEmpty() - val safeMode = settingsRepository.isSafeModeEnabled(api) + val safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network) val uiItems = mutableListOf() for (nft in result.list) { if (safeMode && !nft.verified) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt index c30d9f521..0d98a3945 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt @@ -33,7 +33,7 @@ sealed class Item(type: Int): BaseListItem(type) { get() = entity.collectionName val testnet: Boolean - get() = entity.testnet + get() = entity.network.isTestnet val verifier: Boolean get() = entity.verified @@ -47,4 +47,4 @@ sealed class Item(type: Int): BaseListItem(type) { data class Skeleton(val value: Boolean = true): Item(TYPE_SKELETON) -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt index 234ad1d9d..ef431234f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.collectibles.manage import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.extensions.MutableEffectFlow @@ -41,7 +41,7 @@ class CollectiblesManageViewModel( val spamItem = Item.Title(getString(Localization.spam)) private val safeMode: Boolean - get() = settingsRepository.isSafeModeEnabled(api) + get() = settingsRepository.isSafeModeEnabled(api, wallet.network) private val _showedAllFlow = MutableStateFlow(false) private val showedAllFlow = _showedAllFlow.asStateFlow() @@ -51,7 +51,7 @@ class CollectiblesManageViewModel( private val collectiblesFlow = collectiblesRepository.getFlow( address = wallet.address, - testnet = wallet.testnet, + network = wallet.network, isOnline = true ).map { it.list } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevScreen.kt index 4fd462670..a8cc05ed4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevScreen.kt @@ -1,15 +1,20 @@ package com.tonapps.tonkeeper.ui.screen.dev +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.Button import android.widget.EditText import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatTextView +import androidx.core.app.ShareCompat +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.tonapps.extensions.locale +import com.tonapps.extensions.retrieveUri +import com.tonapps.log.L import com.tonapps.security.Security import com.tonapps.tonkeeper.App import com.tonapps.tonkeeper.core.DevSettings @@ -26,6 +31,7 @@ import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentRedColor import com.tonapps.uikit.list.LinearLayoutManager import com.tonapps.uikit.list.ListCell +import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.viewModel import uikit.base.BaseFragment @@ -33,6 +39,7 @@ import uikit.dialog.alert.AlertDialog import uikit.extensions.collectFlow import uikit.widget.HeaderView import uikit.widget.item.ItemSwitchView +import uikit.widget.item.ItemTextView class DevScreen: BaseWalletScreen(R.layout.fragment_dev, ScreenContext.None), BaseFragment.BottomSheet { @@ -41,8 +48,11 @@ class DevScreen: BaseWalletScreen(R.layout.fragment_dev, Scr override val viewModel: DevViewModel by viewModel() private lateinit var iconsView: RecyclerView + private lateinit var tetraView: ItemSwitchView private lateinit var blurView: ItemSwitchView private lateinit var tonConnectLogsView: ItemSwitchView + private lateinit var logs: ItemSwitchView + private lateinit var shareLogs: ItemTextView private lateinit var importMnemonicAgainView: View private lateinit var logView: View private lateinit var logDataView: AppCompatEditText @@ -66,6 +76,14 @@ class DevScreen: BaseWalletScreen(R.layout.fragment_dev, Scr iconsView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL) iconsView.adapter = LauncherAdapter() + tetraView = view.findViewById(R.id.tetra) + tetraView.setChecked(DevSettings.tetraEnabled, false) + tetraView.doOnCheckedChanged = { isChecked, byUser -> + if (byUser) { + DevSettings.tetraEnabled = isChecked + } + } + dnsAllView = view.findViewById(R.id.dns_all) dnsAllView.setChecked(DevSettings.dnsAll, false) dnsAllView.doOnCheckedChanged = { isChecked, byUser -> @@ -102,6 +120,49 @@ class DevScreen: BaseWalletScreen(R.layout.fragment_dev, Scr } } + logs = view.findViewById(R.id.logs) + logs.setChecked(DevSettings.isLogsEnabled, false) + logs.doOnCheckedChanged = { isChecked, byUser -> + if (byUser) { + DevSettings.isLogsEnabled = isChecked + L.setTargets(L.defaultTargets(requireContext(), isChecked)) + } + } + + shareLogs = view.findViewById(R.id.share_logs) + shareLogs.setOnClickListener { + if (!L.hasLogs()) { + navigation?.toast("No logs found!") + return@setOnClickListener + } + + L.capture { file -> + val context = requireContext() + lifecycleScope.launch { + DevSettings.isLogsEnabled = false + L.setTargets(L.defaultTargets(context, false)) + + ShareCompat.IntentBuilder(context) + .run { + val uri = retrieveUri(context, file ?: return@launch) + setStream(uri) + + intent.apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, "Share logs") + + setDataAndType(uri, "*/*") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + setChooserTitle("Share logs") + startChooser() + } + } + } + } + + importMnemonicAgainView = view.findViewById(R.id.import_mnemonic_again) importMnemonicAgainView.setOnClickListener { importMnemonicAgain(false) } importMnemonicAgainView.setOnLongClickListener { importMnemonicAgain(true); true } @@ -150,6 +211,11 @@ class DevScreen: BaseWalletScreen(R.layout.fragment_dev, Scr copyFirebasePushToken() } + view.findViewById(R.id.copy_install_id).setOnClickListener { + requireContext().copyToClipboard(viewModel.installId, true) + navigation?.toast("Install ID copied to clipboard") + } + logCopy = view.findViewById(R.id.log_copy) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevViewModel.kt index 9e823ce7c..d1a0985d4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dev/DevViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import android.widget.Toast import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.extensions.bestMessage import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.core.DevSettings @@ -14,6 +15,7 @@ import com.tonapps.wallet.api.API import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.dapps.DAppsRepository import com.tonapps.wallet.data.rn.RNLegacy +import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -33,8 +35,12 @@ class DevViewModel( private val dAppsRepository: DAppsRepository, private val environment: Environment, private val api: API, + private val settingsRepository: SettingsRepository ): BaseWalletVM(app) { + val installId: String + get() = settingsRepository.installId + val debugCountryFlow = environment.countryDataFlow.map { val lines = mutableListOf() lines.add("Store: ${it.fromStore ?: "unknown"}") @@ -69,7 +75,7 @@ class DevViewModel( DevSettings.country = country?.uppercase() environment.setDebugCountry(DevSettings.country) viewModelScope.launch { - api.refreshConfig(false) + api.refreshConfig() } } @@ -129,7 +135,7 @@ class DevViewModel( if (tcApps.mainnet.isNotEmpty()) { for (apps in tcApps.mainnet) { - dAppsRepository.migrationFromLegacy(apps, false) + dAppsRepository.migrationFromLegacy(apps, TonNetwork.MAINNET) lines.add("Mainnet app imported: ${apps.address}") } } @@ -138,7 +144,7 @@ class DevViewModel( lines.add("Testnet apps found: ${tcApps.testnet.size}") if (tcApps.testnet.isNotEmpty()) { for (apps in tcApps.testnet) { - dAppsRepository.migrationFromLegacy(apps, true) + dAppsRepository.migrationFromLegacy(apps, TonNetwork.TESTNET) lines.add("Testnet app imported: ${apps.address}") } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dns/renew/DNSRenewViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dns/renew/DNSRenewViewModel.kt index 27d20c3f0..e0964f315 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dns/renew/DNSRenewViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/dns/renew/DNSRenewViewModel.kt @@ -43,11 +43,11 @@ class DNSRenewViewModel( ) : BaseWalletVM(app) { private val dnsExpiringFlow = flow { - emit(collectiblesRepository.getDnsExpiring(wallet.accountId, wallet.testnet, 366)) + emit(collectiblesRepository.getDnsExpiring(wallet.accountId, wallet.network, 366)) }.stateIn(viewModelScope, SharingStarted.Eagerly, entities) val uiItemsFlow = dnsExpiringFlow.map { - val items = collectiblesRepository.getDnsExpiring(wallet.accountId, wallet.testnet, 366) + val items = collectiblesRepository.getDnsExpiring(wallet.accountId, wallet.network, 366) val uiItems = items.mapIndexed { index, dnsExpiringEntity -> Item( position = ListCell.getPosition(items.size, index), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/details/TxDetailsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/details/TxDetailsViewModel.kt index 4618fe120..db49c6a13 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/details/TxDetailsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/details/TxDetailsViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.events.compose.details import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.toUserFriendly import com.tonapps.extensions.withApproximately @@ -185,7 +185,7 @@ class TxDetailsViewModel( } private suspend fun updateData() { - val rates = ratesRepository.getRates(currency, action.tokens.map { it.address }) + val rates = ratesRepository.getRates(wallet.network, currency, action.tokens.map { it.address }) val rateAmount = if (!isUsdt && primaryValue != null) { val value = rates.convert(primaryValue.currency.code, primaryValue.value) if (value.isPositive) { @@ -413,7 +413,8 @@ class TxDetailsViewModel( ) fun openTx() { - val url = api.config.formatTransactionExplorer(wallet.testnet, tx.blockchain == Blockchain.TRON, txId) + val url = api.getConfig(wallet.network) + .formatTransactionExplorer(wallet.testnet, tx.blockchain == Blockchain.TRON, txId) BrowserHelper.open(context, url) } @@ -427,7 +428,7 @@ class TxDetailsViewModel( try { val nft = collectiblesRepository.getNft( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, address = nftAddress ) ?: throw Throwable() openScreen(NftScreen.newInstance(wallet, nft)) @@ -475,7 +476,7 @@ class TxDetailsViewModel( viewModelScope.launch { settingsRepository.setSpamStateTransaction(wallet.id, txId, SpamTransactionState.NOT_SPAM) - eventsRepository.removeSpam(wallet.accountId, wallet.testnet, txId) + eventsRepository.removeSpam(wallet.accountId, wallet.network, txId) toast(Localization.tx_marked_as_not_spam) updateUiActionItems() @@ -500,7 +501,7 @@ class TxDetailsViewModel( comment = comment, recipient = wallet.accountId ) - eventsRepository.markAsSpam(wallet.accountId, wallet.testnet, txId) + eventsRepository.markAsSpam(wallet.accountId, wallet.network, txId) loading(false) toast(Localization.tx_marked_as_spam) } catch (ignored: Throwable) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/TxEventsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/TxEventsViewModel.kt index b4bce5434..a8ffb1154 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/TxEventsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/TxEventsViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.events.compose.history import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig @@ -25,11 +25,13 @@ import com.tonapps.tonkeeper.ui.screen.qr.QRScreen import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.collectibles.CollectiblesRepository +import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.data.events.EventsRepository import com.tonapps.wallet.data.events.tx.model.TxActionBody import com.tonapps.wallet.data.passcode.PasscodeManager import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.localization.Localization +import io.tonapi.models.NftItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -201,13 +203,28 @@ class TxEventsViewModel( private fun onClick(id: String, part: EventItemClickPart) { val tx = TxPagingSource.get(id) ?: return - viewModelScope.launch { - if (part is EventItemClickPart.Product) { + if (part is EventItemClickPart.Product) { + viewModelScope.launch(Dispatchers.IO) { val product = tx.actions[part.index].product ?: return@launch + if (product.type == TxActionBody.Product.Type.Nft) { - openNft(product.id) + try { + val nftItem = collectiblesRepository.getNft( + accountId = wallet.accountId, + network = wallet.network, + address = product.id + ) ?: throw IOException() + + viewModelScope.launch { + openNft(nftItem) + } + } catch (ignored: Throwable) { + toast(Localization.unknown_error) + } } - } else if (part is EventItemClickPart.Encrypted) { + } + } else if (part is EventItemClickPart.Encrypted) { + viewModelScope.launch { decryptComment( wallet = wallet, tx = tx, @@ -217,23 +234,16 @@ class TxEventsViewModel( passcodeManager = passcodeManager, eventsRepository = eventsRepository ) - } else if (part is EventItemClickPart.Action) { + } + } else if (part is EventItemClickPart.Action) { + viewModelScope.launch { openDetails(tx, part.index) } } } - private suspend fun openNft(address: String) { - try { - val nftItem = collectiblesRepository.getNft( - accountId = wallet.accountId, - testnet = wallet.testnet, - address = address - ) ?: throw IOException() - openScreen(NftScreen.newInstance(wallet, nftItem)) - } catch (ignored: Throwable) { - toast(Localization.unknown_error) - } + private suspend fun openNft(nftItem: NftEntity) { + openScreen(NftScreen.newInstance(wallet, nftItem)) } private suspend fun openDetails(tx: TxEvent, actionIndex: Int) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxPagingSource.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxPagingSource.kt index bfd1bcc6f..1870381eb 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxPagingSource.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxPagingSource.kt @@ -92,14 +92,6 @@ internal class TxPagingSource( return processEvents(eventsRepository.fetch(query)) } - private suspend fun prevLoad(afterTimestamp: Timestamp, limit: Int): TxPage { - val query = query( - afterTimestamp = afterTimestamp, - limit = limit - ) - return processEvents(eventsRepository.fetch(query)) - } - private suspend fun initialLoad(limit: Int): TxPage { val query = query( limit = limit diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxTronParamsProvider.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxTronParamsProvider.kt index 09b284c26..44de7c6b0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxTronParamsProvider.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/paging/TxTronParamsProvider.kt @@ -1,6 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.events.compose.history.paging -import android.util.Log +import com.tonapps.log.L import com.tonapps.tonkeeper.ui.screen.events.compose.history.state.TxTronParams import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.entities.WalletEntity diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxEventsItems.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxEventsItems.kt index 9d136ed54..ac0e31cca 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxEventsItems.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxEventsItems.kt @@ -7,16 +7,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ListItem -import androidx.compose.material3.Scaffold import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -29,10 +24,10 @@ import com.tonapps.tonkeeper.ui.screen.events.compose.history.TxEventsAction import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import ui.components.PullToRefreshIndicator -import ui.components.TKFooterLoader -import ui.components.TKFooterRetry import ui.components.events.UiEvent +import ui.components.moon.MoonRefresh +import ui.components.moon.cell.MoonLoaderCell +import ui.components.moon.cell.MoonRetryCell import ui.theme.Dimens @OptIn(ExperimentalMaterial3Api::class) @@ -65,7 +60,7 @@ fun TxEventsItems( onRefresh = { items.refresh() }, state = refreshState, indicator = { - PullToRefreshIndicator( + MoonRefresh( modifier = Modifier.align(Alignment.TopCenter), state = refreshState ) @@ -94,14 +89,14 @@ fun TxEventsItems( item(key = "offset", contentType = UiEvent.CONTENT_TYPE_OTHER) { when(loadState.append) { is LoadState.Error -> { - TKFooterRetry( + MoonRetryCell( message = stringResource(Localization.unknown_error), buttonText = stringResource(Localization.retry), onRetry = { items.retry() } ) } is LoadState.Loading -> { - TKFooterLoader() + MoonLoaderCell() } is LoadState.NotLoading -> { } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxHistoryEmpty.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxHistoryEmpty.kt index a5d2ff091..f5a1b9bed 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxHistoryEmpty.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/compose/history/ui/TxHistoryEmpty.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.res.stringResource import com.tonapps.tonkeeper.ui.screen.events.compose.history.TxEventsAction import com.tonapps.tonkeeper.ui.screen.events.compose.history.TxEventsViewModel import com.tonapps.wallet.localization.Localization -import ui.components.TKEmptyPlaceholder +import ui.components.moon.cell.MoonEmptyCell import ui.theme.Dimens @Composable @@ -21,7 +21,7 @@ fun TxHistoryEmpty(viewModel: TxEventsViewModel) { .padding(horizontal = Dimens.offsetLarge), contentAlignment = Alignment.Center ) { - TKEmptyPlaceholder( + MoonEmptyCell( title = stringResource(Localization.empty_history_title), subtitle = stringResource(Localization.empty_history_subtitle), firstButtonText = stringResource(Localization.buy_toncoin), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsScreen.kt index 622008f45..8854d4193 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsScreen.kt @@ -1,14 +1,14 @@ package com.tonapps.tonkeeper.ui.screen.events.main import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.Gravity import android.view.View import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.facebook.shimmer.ShimmerFrameLayout -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.history.list.HistoryAdapter import com.tonapps.tonkeeper.core.history.list.HistoryItemDecoration import com.tonapps.tonkeeper.core.history.list.item.HistoryItem diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsViewModel.kt index 1f916efd7..6e6418453 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/main/EventsViewModel.kt @@ -377,7 +377,7 @@ class EventsViewModel( val eventItems = historyHelper.mapping( wallet = wallet, events = events.map { it.copy() }, options = ActionOptions( spamFilter = ActionOptions.SpamFilter.NOT_SPAM, - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), hiddenBalances = settingsRepository.hiddenBalances, tronEnabled = settingsRepository.getTronUsdtEnabled(wallet.id), ) @@ -393,7 +393,7 @@ class EventsViewModel( tronAddress = tronAddress, options = ActionOptions( spamFilter = ActionOptions.SpamFilter.NOT_SPAM, - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), hiddenBalances = settingsRepository.hiddenBalances ) ) @@ -419,7 +419,7 @@ class EventsViewModel( private suspend fun loadDefault(beforeLt: Long?, limit: Int): List { val response = eventsRepository.getRemote( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, beforeLt = beforeLt, limit = limit, ) ?: throw IllegalStateException("Failed to load events") @@ -435,7 +435,7 @@ class EventsViewModel( private suspend fun cache(): List { val list = eventsRepository.getLocal( - accountId = wallet.accountId, testnet = wallet.testnet + accountId = wallet.accountId, network = wallet.network )?.events?.map { AccountEventWrap.cached(it) } return list ?: emptyList() } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsScreen.kt index 4999d038f..f635a174c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.events.spam import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import com.tonapps.tonkeeper.core.history.list.HistoryAdapter import com.tonapps.tonkeeper.core.history.list.HistoryItemDecoration diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsViewModel.kt index fca4e3655..647f2654a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/spam/SpamEventsViewModel.kt @@ -177,12 +177,12 @@ class SpamEventsViewModel( private suspend fun getLocalSpam() = eventsRepository.getLocalSpam( accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ) private suspend fun getRemoteSpam(startBeforeLt: Long? = null) = eventsRepository.getRemoteSpam( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, startBeforeLt = startBeforeLt ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignArgs.kt index c234f1814..33068af1a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignArgs.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.external.qr.keystone.sign import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import com.tonapps.extensions.getParcelableCompat import com.tonapps.ur.UR import com.tonapps.ur.registry.CryptoKeypath diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignScreen.kt index 9d57a1f0f..e2486ee80 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/external/qr/keystone/sign/KeystoneSignScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.external.qr.keystone.sign import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.camera.view.PreviewView import androidx.lifecycle.lifecycleScope diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt index 6438f6f71..a73e845c0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt @@ -24,7 +24,7 @@ data class InitArgs( ) : BaseArgs() { enum class Type { - New, Import, Watch, Testnet, Signer, SignerQR, Ledger, Keystone, + New, Import, Watch, Testnet, Signer, SignerQR, Ledger, Keystone, Tetra } private companion object { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt index 782dac046..5e94fc8ae 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt @@ -3,7 +3,6 @@ package com.tonapps.tonkeeper.ui.screen.init import android.app.Application import android.content.Context import android.graphics.Color -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.MnemonicHelper @@ -18,15 +17,13 @@ import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.blockchain.ton.extensions.toWalletAddress +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.emoji.Emoji import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.logError import com.tonapps.icu.Coins import com.tonapps.icu.CurrencyFormatter -import com.tonapps.security.clear import com.tonapps.tonkeeper.Environment -import com.tonapps.tonkeeper.core.AnalyticsHelper -import com.tonapps.tonkeeper.extensions.consistentBucketFor import com.tonapps.tonkeeper.extensions.fixW5Title import com.tonapps.tonkeeper.manager.push.PushManager import com.tonapps.tonkeeper.ui.base.BaseWalletVM @@ -52,30 +49,21 @@ import io.tonapi.models.AccountStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.ton.api.pub.PublicKeyEd25519 import org.ton.block.AddrStd import org.ton.mnemonic.Mnemonic -import java.nio.ByteBuffer -import java.nio.ByteOrder import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger -import kotlin.String @OptIn(FlowPreview::class) class InitViewModel( @@ -102,6 +90,7 @@ class InitViewModel( private val savedState = InitModelState(savedStateHandle) private val type = args.type private val testnet: Boolean = type == InitArgs.Type.Testnet + private val tetra: Boolean = type == InitArgs.Type.Tetra private val walletsCount = AtomicInteger(-1) val watchRecoveryAccountId = args.watchRecoveryAccountId @@ -110,7 +99,11 @@ class InitViewModel( } private val tonNetwork: TonNetwork - get() = if (testnet) TonNetwork.TESTNET else TonNetwork.MAINNET + get() = when { + testnet -> TonNetwork.TESTNET + tetra -> TonNetwork.TETRA + else -> TonNetwork.MAINNET + } private val _uiTopOffset = MutableStateFlow(0) val uiTopOffset = _uiTopOffset.asStateFlow() @@ -126,7 +119,7 @@ class InitViewModel( .debounce(1000) .filter { it.isNotBlank() } .map { - val account = api.resolveAddressOrName(it, testnet) + val account = api.resolveAddressOrName(it, tonNetwork) if (account == null || account.walletVersion == WalletVersion.UNKNOWN) { setWatchAccount(null, null) return@map null @@ -143,7 +136,7 @@ class InitViewModel( private val isPinSet = AtomicBoolean(false) private val requestSetPinCode: Boolean - get() = (type == InitArgs.Type.New || type == InitArgs.Type.Import || type == InitArgs.Type.Testnet) && !isPinSet.get() + get() = (type == InitArgs.Type.New || type == InitArgs.Type.Import || type == InitArgs.Type.Testnet || type == InitArgs.Type.Tetra) && !isPinSet.get() var wordsCount: Int get() = savedState.wordsCount @@ -184,7 +177,7 @@ class InitViewModel( when (type) { InitArgs.Type.Watch -> routeTo(InitRoute.WatchAccount) - InitArgs.Type.Import, InitArgs.Type.Testnet -> routeTo(InitRoute.ImportWords) + InitArgs.Type.Import, InitArgs.Type.Testnet, InitArgs.Type.Tetra -> routeTo(InitRoute.ImportWords) InitArgs.Type.Signer, InitArgs.Type.SignerQR -> resolveWallets(savedState.publicKey!!) InitArgs.Type.Ledger -> routeTo(InitRoute.SelectAccount) InitArgs.Type.Keystone -> { @@ -294,25 +287,26 @@ class InitViewModel( val accounts = if (publicKey.new) { mutableListOf() } else { - api.resolvePublicKey(publicKey.publicKey, testnet).filter { + api.resolvePublicKey(publicKey.publicKey, tonNetwork).filter { it.walletVersion != WalletVersion.UNKNOWN }.sortedByDescending { it.walletVersion.index }.toMutableList() } if (accounts.count { it.walletVersion == WalletVersion.V5R1 } == 0) { - val contract = WalletV5R1Contract(publicKey.publicKey, tonNetwork) + val network = if (tonNetwork.isTetra) TonNetwork.TETRA else tonNetwork + val contract = WalletV5R1Contract(publicKey.publicKey, network) val query = contract.address.toAccountId() if (publicKey.new) { accounts.add( 0, - AccountDetailsEntity(contract, testnet, new = true, initialized = false) + AccountDetailsEntity(contract, tonNetwork, new = true, initialized = false) ) } else { - val apiAccount = api.resolveAccount(query, testnet) + val apiAccount = api.resolveAccount(query, tonNetwork) val account = if (apiAccount == null) { AccountDetailsEntity( contract, - testnet = testnet, + tonNetwork, new = true, initialized = false ) @@ -320,7 +314,7 @@ class InitViewModel( AccountDetailsEntity( query, apiAccount.copy( interfaces = listOf("wallet_v5r1") - ), testnet, false + ), tonNetwork, false ) } accounts.add(0, account) @@ -332,9 +326,10 @@ class InitViewModel( } val recoveryWallet = getRecoveryWatchWallet(publicKey = publicKey.publicKey) - val recoveryAccountItem = accounts.firstOrNull { it.preview.accountId == recoveryWallet?.accountId }?.let { - getAccountItem(it, ListCell.Position.SINGLE) - } + val recoveryAccountItem = + accounts.firstOrNull { it.preview.accountId == recoveryWallet?.accountId }?.let { + getAccountItem(it, ListCell.Position.SINGLE) + } val items = mutableListOf() if (recoveryAccountItem != null) { @@ -419,8 +414,8 @@ class InitViewModel( initialized = false ) } else { - val tokensDeferred = async { api.getJettonsBalances(account.address, testnet) } - val nftItemsDeferred = async { api.getNftItems(account.address, testnet, 1) } + val tokensDeferred = async { api.getJettonsBalances(account.address, tonNetwork) } + val nftItemsDeferred = async { api.getNftItems(account.address, tonNetwork, 1) } val tokens = tokensDeferred.await() ?: emptyList() val nftItems = nftItemsDeferred.await() ?: emptyList() val balance = Coins.of(account.balance) @@ -464,7 +459,7 @@ class InitViewModel( } val publicKey = account?.address?.let { - api.safeGetPublicKey(it, testnet) + api.safeGetPublicKey(it, tonNetwork) } setPublicKey(publicKey) @@ -568,20 +563,21 @@ class InitViewModel( MnemonicHelper.privateKey(mnemonic).publicKey() ) - suspend fun getRecoveryWatchWallet(publicKey: PublicKeyEd25519): WalletEntity? = withContext(Dispatchers.IO) { - if (watchRecoveryAccountId == null) { - return@withContext null - } + suspend fun getRecoveryWatchWallet(publicKey: PublicKeyEd25519): WalletEntity? = + withContext(Dispatchers.IO) { + if (watchRecoveryAccountId == null) { + return@withContext null + } - val wallet = accountRepository.getWallets() - .first { it.accountId == watchRecoveryAccountId && it.type == Wallet.Type.Watch } + val wallet = accountRepository.getWallets() + .first { it.accountId == watchRecoveryAccountId && it.type == Wallet.Type.Watch } - if (wallet.publicKey == publicKey) { - wallet - } else { - null + if (wallet.publicKey == publicKey) { + wallet + } else { + null + } } - } private fun execute(context: Context) { setLoading(true) @@ -593,9 +589,9 @@ class InitViewModel( } val alreadyWalletCount = accountRepository.getWallets() - if (alreadyWalletCount.isEmpty() && api.config.flags.safeModeEnabled) { + if (alreadyWalletCount.isEmpty() && api.getConfig(TonNetwork.MAINNET).flags.safeModeEnabled) { settingsRepository.setSafeModeState(SafeModeState.Enabled) - if (type == InitArgs.Type.Import || type == InitArgs.Type.Testnet) { + if (type == InitArgs.Type.Import || type == InitArgs.Type.Testnet || type == InitArgs.Type.Tetra) { settingsRepository.showSafeModeSetup = true } } @@ -604,7 +600,7 @@ class InitViewModel( when (type) { InitArgs.Type.Watch -> wallets.add(saveWatchWallet()) InitArgs.Type.New -> wallets.add(newWallet(context)) - InitArgs.Type.Import, InitArgs.Type.Testnet -> wallets.addAll( + InitArgs.Type.Import, InitArgs.Type.Testnet, InitArgs.Type.Tetra -> wallets.addAll( importWallet( context ) @@ -616,7 +612,7 @@ class InitViewModel( InitArgs.Type.Keystone -> wallets.addAll(keystoneWallet()) } - if (type == InitArgs.Type.Import || type == InitArgs.Type.Testnet) { + if (type == InitArgs.Type.Import || type == InitArgs.Type.Testnet || type == InitArgs.Type.Tetra) { backupRepository.addBackups(wallets.map { it.id }) } @@ -766,12 +762,18 @@ class InitViewModel( ) } + val type = when { + testnet -> Wallet.Type.Testnet + tetra -> Wallet.Type.Tetra + else -> Wallet.Type.Default + } + val wallets = accountRepository.importWallet( ids, label, mnemonic, accounts.map { it.walletVersion }, - testnet, + type, accounts.map { it.initialized }) // delete watch only wallet @@ -780,7 +782,7 @@ class InitViewModel( accountRepository.delete(it) } - if (!testnet) { + if (!testnet && !tetra) { checkTronBalance(wallets) } @@ -866,10 +868,10 @@ class InitViewModel( val contact = BaseWalletContract.create( publicKey.publicKey, WalletVersion.V4R2.title, - tonNetwork.value + tonNetwork ) val account = - api.resolveAccount(contact.address.toWalletAddress(testnet = testnet), testnet) + api.resolveAccount(contact.address.toWalletAddress(testnet = testnet), tonNetwork) val initialized = account != null && (account.status == AccountStatus.active || account.status == AccountStatus.frozen) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/LabelScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/LabelScreen.kt index 9f5301dda..8256be5a9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/LabelScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/LabelScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.init.step import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope @@ -43,7 +43,7 @@ class LabelScreen: BaseFragment(R.layout.fragment_init_label) { } collectFlow(initViewModel.labelFlow) { label -> - Log.d("InitViewModelLog", "setNewLabel: $label") + L.d("InitViewModelLog", "setNewLabel: $label") with(editorView) { name = label.name emoji = label.emoji diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/WordsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/WordsScreen.kt index 434e123c8..e26b3bd69 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/WordsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/WordsScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.init.step import android.os.Bundle import android.text.Editable -import android.util.Log +import com.tonapps.log.L import android.view.Gravity import android.view.View import android.view.ViewGroup @@ -16,7 +16,7 @@ import androidx.core.widget.NestedScrollView import androidx.lifecycle.lifecycleScope import com.tonapps.blockchain.MnemonicHelper import com.tonapps.blockchain.ton.TonMnemonic -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.clipboardText import com.tonapps.tonkeeper.extensions.hideKeyboard import com.tonapps.tonkeeper.extensions.toast diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/ledger/steps/LedgerConnectionViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/ledger/steps/LedgerConnectionViewModel.kt index 122ecb48c..dfa67d724 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/ledger/steps/LedgerConnectionViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/ledger/steps/LedgerConnectionViewModel.kt @@ -7,11 +7,12 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.tonapps.ledger.ble.BleManager import com.tonapps.ledger.ble.BleManagerFactory +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.blockchain.ton.extensions.toWalletAddress @@ -290,7 +291,7 @@ class LedgerConnectionViewModel( viewModelScope.launch { delay(1000) - Log.d("LEDGER", "Reconnecting to device $deviceId") + L.d("LEDGER", "Reconnecting to device $deviceId") connectBle(deviceId) } @@ -417,7 +418,7 @@ class LedgerConnectionViewModel( _connectionState.tryEmit(ConnectionState.Signed) _eventFlow.tryEmit(LedgerEvent.SignedProof(proof)) } catch (e: Exception) { - Log.d("LEDGER", "Error signing transaction", e) + L.d("LEDGER", "Error signing transaction", e) if (e.instanceOf(TransportStatusException.DeniedByUser::class)) { _eventFlow.tryEmit(LedgerEvent.Rejected) } else { @@ -457,7 +458,7 @@ class LedgerConnectionViewModel( _connectionState.tryEmit(ConnectionState.Signed) _eventFlow.tryEmit(LedgerEvent.SignedTransaction(signedBody)) } catch (e: Exception) { - Log.d("LEDGER", "Error signing transaction", e) + L.d("LEDGER", "Error signing transaction", e) if (e.instanceOf(TransportStatusException.DeniedByUser::class)) { _eventFlow.tryEmit(LedgerEvent.Rejected) } else if (e.instanceOf(TransportStatusException.BlindSigningDisabled::class)) { @@ -517,7 +518,7 @@ class LedgerConnectionViewModel( val deferredAccounts = mutableListOf>() for (account in ledgerData.accounts) { deferredAccounts.add(async { - api.resolveAccount(account.address.toAccountId(), false) + api.resolveAccount(account.address.toAccountId(), TonNetwork.MAINNET) }) } @@ -527,7 +528,7 @@ class LedgerConnectionViewModel( tokenRepository.get( settingsRepository.currency, account.address.toAccountId(), - false + TonNetwork.MAINNET ) ?: emptyList() }) } @@ -537,7 +538,7 @@ class LedgerConnectionViewModel( deferredCollectibles.add(async { collectiblesRepository.get( account.address.toAccountId(), - false + TonNetwork.MAINNET ) ?: emptyList() }) } @@ -582,13 +583,13 @@ class LedgerConnectionViewModel( } while (!isAppOpen()) { - Log.d("LEDGER", "Waiting for app to open") + L.d("LEDGER", "Waiting for app to open") delay(1000) } setTonTransport(tonTransport, type) } catch (e: Throwable) { - Log.d("LEDGER", "Error waiting for TON app", e) + L.d("LEDGER", "Error waiting for TON app", e) _connectionState.tryEmit(ConnectionState.Disconnected()) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainScreen.kt index 2ca0982cf..4118b5fe4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.main import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.annotation.LayoutRes import androidx.fragment.app.Fragment @@ -278,9 +278,9 @@ class MainScreen: BaseWalletScreen(R.layout.fragment_main, S } try { transaction.commitNow() - Log.d("MainScreenLog", "Set fragment: $fragment") + L.d("MainScreenLog", "Set fragment: $fragment") } catch (e: Throwable) { - Log.e("MainScreenLog", "Failed to set fragment", e) + L.e("MainScreenLog", "Failed to set fragment", e) FirebaseCrashlytics.getInstance().recordException(e) postDelayed(1000) { setFragment(fragment, forceScrollUp, from,extra, attempt + 1) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainViewModel.kt index 2e9a93016..7a2e0616e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/main/MainViewModel.kt @@ -16,6 +16,7 @@ import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -37,7 +38,9 @@ class MainViewModel( val selectedWalletFlow = accountRepository.selectedWalletFlow - val disbleNftsFlow = api.configFlow.map { it.flags.disableNfts } + val disbleNftsFlow = combine(selectedWalletFlow, api.configFlow) { wallet, _ -> + api.getConfig(wallet.network).flags.disableNfts + } fun setBottomScrolled(value: Boolean) { _childBottomScrolled.tryEmit(value) @@ -55,13 +58,13 @@ class MainViewModel( private fun prefetchTabs(wallet: WalletEntity, itemId: Int) { viewModelScope.launch(Dispatchers.IO) { if (itemId != R.id.activity) { - async { eventsRepository.get(wallet.accountId, wallet.testnet) } + async { eventsRepository.get(wallet.accountId, wallet.network) } } if (itemId != R.id.browser) { async { prefetchBrowser(wallet) } } if (itemId != R.id.collectibles) { - async { collectiblesRepository.get(wallet.address, wallet.testnet) } + async { collectiblesRepository.get(wallet.address, wallet.network) } } } } @@ -71,7 +74,7 @@ class MainViewModel( browserRepository.load( country = country, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale() ) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/base/NameFragment.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/base/NameFragment.kt index 10690f339..98ae5fa94 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/base/NameFragment.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/base/NameFragment.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.ui.screen.name.base import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/edit/EditNameScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/edit/EditNameScreen.kt index 2269f2d1e..66c25c966 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/edit/EditNameScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/name/edit/EditNameScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.name.edit import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.lifecycle.lifecycleScope import com.tonapps.tonkeeper.koin.walletViewModel 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 135dc4241..5d9611c3e 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 @@ -11,6 +11,8 @@ import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.core.widget.NestedScrollView import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.locale import com.tonapps.extensions.short4 @@ -53,8 +55,8 @@ import uikit.extensions.inflate import uikit.extensions.roundTop import uikit.extensions.setRightDrawable import uikit.extensions.topScrolled -import uikit.widget.ColumnLayout import uikit.widget.AsyncImageView +import uikit.widget.ColumnLayout import uikit.widget.HeaderView class NftScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_nft, wallet), @@ -146,7 +148,8 @@ class NftScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_nf SendScreen.newInstance( wallet = wallet, nftAddress = nftEntity.address, - type = SendScreen.Companion.Type.Nft + type = SendScreen.Companion.Type.Nft, + from = Events.SendNative.SendNativeFrom.JettonScreen ) ) } @@ -262,7 +265,8 @@ class NftScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_nf wallet = wallet, targetAddress = viewModel.burnAddress, nftAddress = nftEntity.address, - type = SendScreen.Companion.Type.Nft + type = SendScreen.Companion.Type.Nft, + from = Events.SendNative.SendNativeFrom.JettonScreen, ) ) finish() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt index 08ef6067f..487f26ce8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.nft import android.app.Application import android.net.Uri -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.extensions.currentTimeSeconds @@ -44,7 +44,7 @@ class NftViewModel( if (nft.isDomain && !nft.isTelegramUsername && !wallet.isWatchOnly) { collectiblesRepository.getDnsNftExpiring( accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, nftAddress = nft.address )?.let { emit(it) } } @@ -70,7 +70,7 @@ class NftViewModel( } } - private fun getNft() = collectiblesRepository.getNft(wallet.id, wallet.testnet, nft.address) + private fun getNft() = collectiblesRepository.getNft(wallet.id, wallet.network, nft.address) fun reportSpam(spam: Boolean, callback: () -> Unit) { viewModelScope.launch(Dispatchers.IO) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/BaseOnRampScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/BaseOnRampScreen.kt index 0e88de002..193d27f05 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/BaseOnRampScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/BaseOnRampScreen.kt @@ -4,13 +4,11 @@ import android.os.Bundle import android.text.SpannableStringBuilder import android.text.Spanned import android.text.method.LinkMovementMethod -import android.util.Log import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.AppCompatTextView import androidx.lifecycle.lifecycleScope import com.tonapps.extensions.toUriOrNull -import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.hideKeyboard import com.tonapps.tonkeeper.extensions.isOverlapping import com.tonapps.tonkeeper.helper.BrowserHelper diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampScreen.kt index 448c26d15..0dc9b2574 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.onramp.main import android.content.Context import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo @@ -16,7 +16,7 @@ import uikit.base.BaseFragment import uikit.extensions.collectFlow import androidx.core.view.updateLayoutParams import androidx.core.widget.NestedScrollView -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.helper.TwinInput import com.tonapps.tonkeeper.koin.remoteConfig import com.tonapps.tonkeeper.ui.screen.onramp.main.state.OnRampPaymentMethodState diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampViewModel.kt index bd877a564..cda535d21 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/OnRampViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.onramp.main import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.singleValue @@ -146,7 +146,7 @@ class OnRampViewModel( val ratesFlow = twinInput.currenciesStateFlow.map { inputCurrencies -> val fiatCurrency = inputCurrencies.fiat ?: settingsRepository.currency val tokens = inputCurrencies.cryptoTokens.map { it.tokenQuery } - ratesRepository.getRates(fiatCurrency, tokens) + ratesRepository.getRates(wallet.network, fiatCurrency, tokens) }.stateIn(viewModelScope, SharingStarted.Eagerly, null).filterNotNull() val minAmountFlow = combine( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/view/CurrencyInputView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/view/CurrencyInputView.kt index b78bbf7a0..f1adb7f02 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/view/CurrencyInputView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/main/view/CurrencyInputView.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.ui.screen.onramp.main.view import android.content.Context import android.graphics.Color import android.util.AttributeSet -import android.util.Log +import com.tonapps.log.L import android.view.Gravity import android.view.View import android.view.inputmethod.EditorInfo @@ -174,7 +174,7 @@ class CurrencyInputView @JvmOverloads constructor( fun setInsufficientBalance() { tokenBalanceView.visibility = View.VISIBLE tokenBalanceMaxView.visibility = View.GONE - tokenBalanceView.setTextColor(context.resolveColor(com.tonapps.uikit.color.R.attr.accentRedColor)) + tokenBalanceView.setTextColor(context.resolveColor(com.tonapps.ui.uikit.color.R.attr.accentRedColor)) tokenBalanceView.setText(Localization.insufficient_balance) } @@ -197,7 +197,7 @@ class CurrencyInputView @JvmOverloads constructor( } private fun showTokenBalance(value: Coins) { - tokenBalanceView.setTextColor(context.resolveColor(com.tonapps.uikit.color.R.attr.textSecondaryColor)) + tokenBalanceView.setTextColor(context.resolveColor(com.tonapps.ui.uikit.color.R.attr.textSecondaryColor)) tokenBalanceView.visibility = View.VISIBLE tokenBalanceMaxView.visibility = View.VISIBLE tokenBalanceMaxView.setOnClickListener { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerScreen.kt index 759dda380..465ed83dd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.onramp.picker.currency import android.content.Context import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerViewModel.kt index cd63a31d7..8c4f81686 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/OnRampPickerViewModel.kt @@ -82,7 +82,7 @@ class OnRampPickerViewModel( } else { uiItems.add(Item.Currency(TokenEntity.TON, ListCell.Position.FIRST)) uiItems.add(Item.Currency(TokenEntity.USDT, ListCell.Position.MIDDLE)) - val tokens = tokenRepository.getTokens(wallet.testnet, jettonAddress).toMutableList() + val tokens = tokenRepository.getTokens(wallet.network, jettonAddress).toMutableList() tokens.removeIf { it.isTon || it.isUsdt } for (token in tokens) { uiItems.add(Item.Currency(token, ListCell.Position.MIDDLE)) @@ -325,7 +325,7 @@ class OnRampPickerViewModel( private fun setJetton(address: String) { viewModelScope.launch { - val token = tokenRepository.getToken(wallet.accountId, wallet.testnet, address) ?: return@launch + val token = tokenRepository.getToken(wallet.accountId, wallet.network, address) ?: return@launch val currency = WalletCurrency.of(token.address) ?: WalletCurrency( code = token.symbol, title = token.name, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/main/OnRampCurrencyPickerScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/main/OnRampCurrencyPickerScreen.kt index 574b5963d..5fd378730 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/main/OnRampCurrencyPickerScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/onramp/picker/currency/main/OnRampCurrencyPickerScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.onramp.picker.currency.main import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.core.view.doOnLayout import androidx.recyclerview.widget.RecyclerView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/phrase/PhraseScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/phrase/PhraseScreen.kt index b12f5d826..ce13c3618 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/phrase/PhraseScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/phrase/PhraseScreen.kt @@ -1,8 +1,14 @@ package com.tonapps.tonkeeper.ui.screen.phrase +import android.content.res.Configuration import android.os.Bundle +import android.util.TypedValue +import android.view.ContextThemeWrapper +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.widget.Button +import android.widget.TextView import androidx.appcompat.widget.AppCompatTextView import androidx.lifecycle.lifecycleScope import com.tonapps.tonkeeper.extensions.copyToClipboard @@ -41,6 +47,19 @@ class PhraseScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_ private lateinit var tronButton: Button private lateinit var checkButton: Button + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val config = + Configuration(requireContext().resources.configuration).apply { fontScale = 1f } + val configContext = requireContext().createConfigurationContext(config) + val themedContext = ContextThemeWrapper(configContext, requireContext().theme) + val noScaleInflater = inflater.cloneInContext(themedContext) + return super.onCreateView(noScaleInflater, container, savedInstanceState) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) headerView = view.findViewById(R.id.header) @@ -59,6 +78,12 @@ class PhraseScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_ wordsView = view.findViewById(R.id.words) wordsView.setWords(args.words) + applyFontScaleFix(view, requireContext().resources.configuration.fontScale) + + if (wordsView.isSmallScreen) { + textHeaderView.descriptionView.visibility = View.GONE + } + copyButton = view.findViewById(R.id.copy) copyButton.setOnClickListener { requireContext().copyToClipboard(args.words.joinToString(" "), true) @@ -95,6 +120,18 @@ class PhraseScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_ } } + private fun applyFontScaleFix(view: View, fontScale: Float) { + if (fontScale <= 1f) return + if (view is TextView) { + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, view.textSize / fontScale) + } + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + applyFontScaleFix(view.getChildAt(i), fontScale) + } + } + } + companion object { fun newInstance( @@ -109,4 +146,4 @@ class PhraseScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_ return fragment } } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseScreen.kt index 429876ccc..df58374d6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseScreen.kt @@ -1,13 +1,13 @@ package com.tonapps.tonkeeper.ui.screen.purchase import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.appcompat.widget.AppCompatTextView import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.tonapps.tonkeeper.api.getCurrencyCodeByCountry -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.extensions.countryEmoji import com.tonapps.tonkeeper.helper.BrowserHelper @@ -105,7 +105,7 @@ class PurchaseScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragmen method = method, wallet = screenContext.wallet, currency = currency, - config = api.config + config = api.getConfig(screenContext.wallet.network) ) if (viewModel.isPurchaseOpenConfirm(method)) { confirmDialog.show(method) { showAgain -> diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseViewModel.kt index 2a6c7ea42..a512ec039 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/PurchaseViewModel.kt @@ -46,7 +46,7 @@ class PurchaseViewModel( val countryFlow = environment.countryFlow private val dataFlow = countryFlow.map { country -> - purchaseRepository.get(wallet.testnet, country, settingsRepository.getLocale()) + purchaseRepository.get(wallet.network, country, settingsRepository.getLocale()) }.filterNotNull().flowOn(Dispatchers.IO) val uiItemsFlow = combine(dataFlow, tabFlow) { (buy, sell), tab -> diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/EnableTronDialog.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/EnableTronDialog.kt index 529dedc7b..c9c5bf65e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/EnableTronDialog.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/EnableTronDialog.kt @@ -3,14 +3,17 @@ package com.tonapps.tonkeeper.ui.screen.qr import android.os.Bundle import android.widget.Button import androidx.lifecycle.lifecycleScope +import com.tonapps.tonkeeper.koin.serverConfig import com.tonapps.tonkeeperx.R import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity +import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.launch import uikit.base.BaseFragment import uikit.dialog.modal.ModalDialog import uikit.widget.AsyncImageView import uikit.widget.HeaderView +import uikit.widget.TextHeaderView class EnableTronDialog( fragment: BaseFragment, @@ -24,6 +27,11 @@ class EnableTronDialog( private lateinit var networkIconView: AsyncImageView private lateinit var buttonView: Button private lateinit var laterButtonView: Button + private lateinit var textHeaderView: TextHeaderView + + + val isBatteryDisabled: Boolean + get() = context.serverConfig?.flags?.disableBattery == true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -33,6 +41,7 @@ class EnableTronDialog( networkIconView = findViewById(R.id.network_icon)!! buttonView = findViewById(R.id.button)!! laterButtonView = findViewById(R.id.later)!! + textHeaderView = findViewById(R.id.text_header)!! headerView.doOnActionClick = { dismiss() } iconView.setImageURI(TokenEntity.USDT_ICON_URI) @@ -44,5 +53,11 @@ class EnableTronDialog( } } laterButtonView.setOnClickListener { dismiss() } + + textHeaderView.desciption = if (isBatteryDisabled) { + context.getString(Localization.tron_toggle_trc_text) + } else { + context.getString(Localization.tron_toggle_text) + } } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRComposable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRComposable.kt index 170e3cf3a..eb02185e7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRComposable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRComposable.kt @@ -9,14 +9,17 @@ import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.CircleShape @@ -35,21 +38,23 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import coil3.compose.AsyncImage -import ui.components.image.AsyncImage import com.tonapps.qr.ui.QRView import com.tonapps.tonkeeperx.R import com.tonapps.uikit.icon.UIKitIcon -import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.TokenEntity +import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.data.account.Wallet import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.localization.Localization -import ui.components.Header import ui.components.TextHeader +import ui.components.button.TKButton +import ui.components.image.AsyncImage +import ui.components.moon.MoonTopAppBar +import ui.theme.ButtonColorsSecondary +import ui.theme.ButtonSizeLarge import ui.theme.Dimens import ui.theme.Shapes import ui.theme.UIKit @@ -201,7 +206,9 @@ fun QrActions( containerColor = UIKit.colorScheme.buttonSecondary.primaryBackground, contentColor = UIKit.colorScheme.buttonSecondary.primaryForeground, disabledContainerColor = UIKit.colorScheme.buttonSecondary.primaryBackgroundDisable, - disabledContentColor = UIKit.colorScheme.buttonSecondary.primaryForeground.copy(alpha = 0.48f) + disabledContentColor = UIKit.colorScheme.buttonSecondary.primaryForeground.copy( + alpha = 0.48f + ) ), elevation = ButtonDefaults.buttonElevation(0.dp, 0.dp, 0.dp, 0.dp, 0.dp), contentPadding = PaddingValues(horizontal = 20.dp) @@ -293,13 +300,15 @@ fun QrComposable( address: String, qrContent: String?, showBlockchain: Boolean, + showBuyButton: Boolean, onFinishClick: () -> Unit, onShareClick: () -> Unit, onCopyClick: () -> Unit, - onTabClick: (tab: QRViewModel.Tab) -> Unit + onTabClick: (tab: QRViewModel.Tab) -> Unit, + onBuyClick: () -> Unit, ) { Box(modifier = Modifier.fillMaxSize()) { - Header( + MoonTopAppBar( title = "", navigationIconRes = UIKitIcon.ic_chevron_down_16, onNavigationClick = { onFinishClick() }, @@ -319,6 +328,7 @@ fun QrComposable( modifier = Modifier .align(Alignment.Center) .padding(horizontal = Dimens.offsetLarge) + .padding(bottom = if (showBuyButton) 88.dp else 0.dp) .width(IntrinsicSize.Max), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -340,7 +350,7 @@ fun QrComposable( TextHeader( title = stringResource(id = Localization.receive_coin, name), - description = if (token.isTrc20) { + description = if (token.isTrc20 || token.isTrx) { stringResource(id = Localization.receive_tron_description, name) } else { stringResource(id = Localization.receive_coin_description, name) @@ -362,5 +372,22 @@ fun QrComposable( onCopyClick = { onCopyClick() } ) } + if (showBuyButton) { + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(Dimens.offsetMedium) + .windowInsetsPadding(WindowInsets.navigationBars), + ) { + TKButton( + modifier = Modifier.fillMaxWidth(), + onClick = onBuyClick, + text = stringResource(id = Localization.buy_ton), + size = ButtonSizeLarge, + buttonColors = ButtonColorsSecondary + ) + } + } } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRScreen.kt index 4179cb6a0..703f59163 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRScreen.kt @@ -9,12 +9,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.tonapps.blockchain.ton.extensions.toUserFriendly import com.tonapps.extensions.getParcelableCompat -import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.copyToClipboard import com.tonapps.tonkeeper.extensions.toast -import com.tonapps.tonkeeper.koin.api import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.compose.ComposeWalletScreen +import com.tonapps.tonkeeper.ui.screen.onramp.main.OnRampScreen import com.tonapps.uikit.color.accentOrangeColor import com.tonapps.uikit.color.backgroundContentTintColor import com.tonapps.wallet.api.entity.TokenEntity @@ -23,8 +22,10 @@ import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.localization.Localization import org.koin.core.parameter.parametersOf import uikit.base.BaseFragment +import uikit.navigation.Navigation.Companion.navigation -class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment.BottomSheet { +class QRScreen(wallet: WalletEntity, private val withBuyButton: Boolean) : + ComposeWalletScreen(wallet), BaseFragment.BottomSheet { override val fragmentName: String = "QRScreen" @@ -45,7 +46,7 @@ class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment } private fun getQrContent(address: String, token: TokenEntity): String { - if (token.isTrc20) { + if (token.isTrc20 || token.isTrx) { return address } @@ -80,7 +81,8 @@ class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment @Composable override fun ScreenContent() { val hasTronBalance by viewModel.hasTronBalanceFlow.collectAsState(false) - val tabsVisible = !hasToken && wallet.hasPrivateKey && !wallet.testnet && (!viewModel.isTronDisabled || hasTronBalance) + val tabsVisible = + !hasToken && wallet.hasPrivateKey && wallet.network.isMainnet && (!viewModel.isTronDisabled || hasTronBalance) val qrContent by remember { derivedStateOf { if (viewModel.address.isNotEmpty()) { @@ -97,6 +99,7 @@ class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment address = viewModel.address, qrContent = qrContent, showBlockchain = viewModel.tronUsdtEnabled, + showBuyButton = withBuyButton && viewModel.token.isTon, onFinishClick = { finish() }, onShareClick = { share() }, onCopyClick = { copy() }, @@ -113,6 +116,15 @@ class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment } } } + }, + onBuyClick = { + requireContext().navigation?.add( + OnRampScreen.newInstance( + requireContext(), + wallet, + "qr_screen" + ) + ) } ) } @@ -124,9 +136,10 @@ class QRScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFragment fun newInstance( wallet: WalletEntity, - token: TokenEntity? = null + token: TokenEntity? = null, + withBuyButton: Boolean = false ): BaseFragment { - val screen = QRScreen(wallet) + val screen = QRScreen(wallet, withBuyButton) screen.putParcelableArg(ARG_TOKEN, token ?: TokenEntity.TON) screen.putBooleanArg(ARG_HAS_TOKEN, token != null) return screen diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRViewModel.kt index 1de9e7169..364bc2ed8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/qr/QRViewModel.kt @@ -33,7 +33,7 @@ class QRViewModel( TON, TRON } - private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api) + private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api, wallet.network) val installId: String get() = settingsRepository.installId @@ -42,7 +42,7 @@ class QRViewModel( get() = settingsRepository.getTronUsdtEnabled(wallet.id) val isTronDisabled: Boolean - get() = api.config.flags.disableTron + get() = api.getConfig(wallet.network).flags.disableTron var token: TokenEntity by mutableStateOf(initialToken) private set @@ -53,7 +53,7 @@ class QRViewModel( private lateinit var tronAddress: String private val tokensFlow = settingsRepository.tokenPrefsChangedFlow.map { _ -> - tokenRepository.mustGet(settingsRepository.currency, wallet.accountId, wallet.testnet) + tokenRepository.mustGet(settingsRepository.currency, wallet.accountId, wallet.network) .mapNotNull { token -> if (safeMode && !token.verified) { return@mapNotNull null @@ -77,7 +77,7 @@ class QRViewModel( init { viewModelScope.launch { tronAddress = accountRepository.getTronAddress(wallet.id) ?: "" - address = if (token.isTrc20) { + address = if (token.isTrc20 || token.isTrx) { tronAddress } else { wallet.address 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 c079ef827..b68cde469 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 @@ -8,7 +8,7 @@ import android.net.Uri import android.os.Bundle import android.os.Handler import android.provider.Browser -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.core.app.ActivityCompat import androidx.core.view.ViewCompat @@ -23,7 +23,7 @@ import com.tonapps.extensions.getStringValue import com.tonapps.extensions.isPositive import com.tonapps.extensions.toUriOrNull import com.tonapps.tonkeeper.App -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.DevSettings import com.tonapps.tonkeeper.core.entities.TransferEntity import com.tonapps.tonkeeper.deeplink.DeepLink @@ -78,6 +78,7 @@ import androidx.core.net.toUri import com.tonapps.blockchain.ton.TonSendMode import com.tonapps.blockchain.ton.extensions.asCellRef import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.bus.generated.Events import com.tonapps.icu.Coins.Companion.isPositive import com.tonapps.tonkeeper.extensions.compose import com.tonapps.tonkeeper.koin.analytics @@ -296,13 +297,14 @@ class RootActivity : BaseWalletActivity() { lifecycleScope.launch { openSend( targetAddress = event.address, + source = event.source, tokenAddress = event.jettonAddress, amount = event.amount, text = event.text, wallet = event.wallet, bin = event.bin, initStateBase64 = event.initStateBase64, - validUnit = event.validUnit + validUnit = event.validUnit, ) } } @@ -365,7 +367,7 @@ class RootActivity : BaseWalletActivity() { validUnit: Long? ) { val message = if (tokenAddress != null) { - val tokens = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.testnet) ?: emptyList() + val tokens = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) ?: emptyList() val token = tokens.find { it.address.equalsAddress(tokenAddress) } ?: throw IllegalStateException("Token not found") @@ -411,6 +413,7 @@ class RootActivity : BaseWalletActivity() { private suspend fun openSend( wallet: WalletEntity, + source: DeepLink.Source, targetAddress: String? = null, tokenAddress: String?, amount: com.tonapps.icu.Coins?, @@ -418,7 +421,7 @@ class RootActivity : BaseWalletActivity() { nftAddress: String? = null, bin: Cell? = null, initStateBase64: String? = null, - validUnit: Long? + validUnit: Long?, ) { if ((bin != null || initStateBase64 != null) && !amount.isPositive()) { toast(Localization.invalid_link) @@ -428,7 +431,7 @@ class RootActivity : BaseWalletActivity() { val fragment = supportFragmentManager.findFragment() if (targetAddress != null && amount.isPositive() && nftAddress.isNullOrBlank()) { - val isScam = viewModel.isScamAddress(targetAddress, wallet.testnet) + val isScam = viewModel.isScamAddress(targetAddress, wallet.network) if (isScam) { toast(Localization.scam_address_error) } else if (bin != null || initStateBase64 != null) { @@ -454,6 +457,7 @@ class RootActivity : BaseWalletActivity() { .setAmount(amount) .setText(text) .setType(SendScreen.Companion.Type.Direct) + .setFrom(Events.SendNative.SendNativeFrom.DeepLink) ) } } else if (fragment == null) { @@ -466,18 +470,21 @@ class RootActivity : BaseWalletActivity() { text = text, nftAddress = nftAddress, bin = bin, - type = SendScreen.Companion.Type.Default + type = SendScreen.Companion.Type.Default, + from = Events.SendNative.SendNativeFrom.DeepLink ) ) } else { runOnUiThread { + fragment.initializeBus(source.analytic) + fragment.initializeArgs( targetAddress = targetAddress, tokenAddress = tokenAddress, amount = amount, text = text, bin = bin, - type = SendScreen.Companion.Type.Default + type = SendScreen.Companion.Type.Default, ) } } 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 255faefd3..6c46d9e1c 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 @@ -5,6 +5,8 @@ import com.tonapps.icu.Coins import com.tonapps.ledger.ton.LedgerConnectData import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.core.history.list.item.HistoryItem +import com.tonapps.tonkeeper.deeplink.DeepLink +import com.tonapps.tonkeeper.deeplink.DeepLinkRoute import com.tonapps.tonkeeper.ui.screen.init.list.AccountItem import com.tonapps.wallet.api.entity.StoryEntity import com.tonapps.wallet.data.account.entities.WalletEntity @@ -49,6 +51,7 @@ sealed class RootEvent { val bin: Cell?, val initStateBase64: String?, val validUnit: Long?, + val source: DeepLink.Source, ): 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 4a3689a2e..c8df91104 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 @@ -4,7 +4,7 @@ import android.app.Application import android.net.Uri import android.os.Build import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.webkit.WebView import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -21,6 +21,7 @@ import com.google.firebase.Firebase import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.crashlytics import com.google.firebase.crashlytics.setCustomKeys +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.extensions.MutableEffectFlow @@ -36,7 +37,8 @@ import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.api.getCurrencyCodeByCountry import com.tonapps.tonkeeper.billing.BillingManager import com.tonapps.tonkeeper.client.safemode.SafeModeClient -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.tonkeeper.core.DevSettings import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.core.history.ActionOptions @@ -184,10 +186,10 @@ class RootViewModel( } private suspend fun sendFirstLaunchEvent() = withContext(Dispatchers.IO) { - if (0 >= DevSettings.firstLaunchDate) { + if (DevSettings.firstLaunchDate <= 0) { val referrer = referrerClientHelper.getInstallReferrer() val deeplink = DevSettings.firstLaunchDeeplink.ifBlank { null } - analyticsHelper.firstLaunch(referrer, deeplink) + analyticsHelper.installApp(referrer, deeplink) DevSettings.firstLaunchDate = currentTimeSeconds() } } @@ -222,7 +224,7 @@ class RootViewModel( try { environment.setCountryFromStore(billingManager.getCountry()) } catch (_: Throwable) { - Log.d("RootViewModel", "Failed to get country from billing manager") + L.d("RootViewModel", "Failed to get country from billing manager") } api.setCountry(deviceCountry = environment.country, storeCountry = environment.storeCountry) api.initConfig() @@ -254,11 +256,11 @@ class RootViewModel( viewModelScope.launch(Dispatchers.IO) { val firebaseToken = FirebasePush.requestToken() settingsRepository.firebaseToken = firebaseToken - ratesRepository.updateAll(settingsRepository.currency) + ratesRepository.updateAll(TonNetwork.MAINNET, settingsRepository.currency) if (firebaseToken.isNullOrBlank()) { - Log.e("TonkeeperFirebasePush", "Failed to get Firebase push token") + L.e("TonkeeperFirebasePush", "Failed to get Firebase push token") } else { - Log.d("TonkeeperFirebasePush", "Firebase push token: $firebaseToken") + L.d("TonkeeperFirebasePush", "Firebase push token: $firebaseToken") } } @@ -267,10 +269,19 @@ class RootViewModel( initShortcuts(wallet) } - api.configFlow.filter { !it.empty }.take(1).collectFlow { config -> - analyticsHelper.setConfig(context, config) - sendFirstLaunchEvent() - } + api.configFlow + .filter { !it.empty } + .take(1) + .collectFlow { config -> + val config = AnalyticsHelper.Config( + aptabaseAppKey = config.aptabaseAppKey, + aptabaseEndpoint = config.aptabaseEndpoint, + installId = settingsRepository.installId, + ) + + analyticsHelper.setConfig(context, config) + sendFirstLaunchEvent() + } combine( accountRepository.selectedWalletFlow.take(1), @@ -411,7 +422,7 @@ class RootViewModel( val wallets = accountRepository.getWalletsByAccountId( accountId = connection.accountId, - testnet = connection.testnet + network = connection.network ).filter { it.isTonConnectSupported } @@ -421,7 +432,10 @@ class RootViewModel( } val wallet = wallets.find { it.hasPrivateKey } ?: wallets.first() try { - val boc = SendTransactionScreen.run(context, wallet, signRequest) + val boc = SendTransactionScreen.run( + context, wallet, signRequest, + sendNativeFrom = Events.SendNative.SendNativeFrom.TonconnectRemote + ) tonConnectManager.sendTransactionResponseSuccess(connection, boc, eventId) } catch (e: Throwable) { DevSettings.tonConnectLog( @@ -619,7 +633,13 @@ class RootViewModel( } else if (route is DeepLinkRoute.Tabs) { _eventFlow.tryEmit(RootEvent.OpenTab(route.tabUri.toUri(), wallet, route.from)) } else if (route is DeepLinkRoute.Send && !wallet.isWatchOnly) { - openScreen(SendScreen.newInstance(wallet, type = SendScreen.Companion.Type.Default)) + openScreen( + SendScreen.newInstance( + wallet, + type = SendScreen.Companion.Type.Default, + from = deeplink.source.analytic, + ) + ) } else if (route is DeepLinkRoute.Staking && !wallet.isWatchOnly) { openScreen(StakingScreen.newInstance(wallet, from = "deeplink")) } else if (route is DeepLinkRoute.StakingPool) { @@ -631,14 +651,14 @@ class RootViewModel( showTransaction(route.address, route.eventId) } } else if (route is DeepLinkRoute.Transfer && !wallet.isWatchOnly) { - processTransferDeepLink(wallet, route) + processTransferDeepLink(wallet, deeplink, route) } else if (route is DeepLinkRoute.PickWallet) { accountRepository.setSelectedWallet(route.walletId) - } else if (route is DeepLinkRoute.Swap && !api.config.flags.disableSwap) { + } else if (route is DeepLinkRoute.Swap && !api.getConfig(wallet.network).flags.disableSwap) { _eventFlow.tryEmit( RootEvent.Swap( wallet = wallet, - uri = api.config.swapUri, + uri = api.getConfig(wallet.network).swapUri, address = wallet.address, from = route.from, to = route.to @@ -651,7 +671,7 @@ class RootViewModel( } else if (route is DeepLinkRoute.Exchange && !wallet.isWatchOnly) { val method = purchaseRepository.getMethod( id = route.methodName, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale() ) if (method == null) { @@ -662,7 +682,7 @@ class RootViewModel( method = method, wallet = wallet, currency = api.getCurrencyCodeByCountry(settingsRepository), - config = api.config + config = api.getConfig(wallet.network) ) ) } @@ -692,7 +712,7 @@ class RootViewModel( val isTrustedApp = browserRepository.isTrustedApp( country = settingsRepository.country, - testnet = wallet.testnet, + network = wallet.network, locale = settingsRepository.getLocale(), deeplink = dAppUri ) @@ -757,7 +777,7 @@ class RootViewModel( openScreen(BatteryScreen.newInstance(wallet, from = "deeplink", jetton = route.jetton)) } else { loading(true) - val validCode = api.batteryVerifyPurchasePromo(wallet.testnet, promoCode) + val validCode = api.batteryVerifyPurchasePromo(wallet.network, promoCode) loading(false) if (validCode) { openScreen( @@ -776,18 +796,19 @@ class RootViewModel( private suspend fun openTokenViewer(wallet: WalletEntity, route: DeepLinkRoute.Jetton) { val token = - tokenRepository.getToken(wallet.accountId, wallet.testnet, route.address) ?: return + tokenRepository.getToken(wallet.accountId, wallet.network, route.address) ?: return openScreen(TokenScreen.newInstance(wallet, token.address, token.name, token.symbol)) } - fun processTransferDeepLink(route: DeepLinkRoute.Transfer) { + fun processTransferDeepLink(deepLink: DeepLink, route: DeepLinkRoute.Transfer) { selectedWalletFlow.take(1).collectFlow { - processTransferDeepLink(it, route) + processTransferDeepLink(it, deepLink, route) } } private suspend fun processTransferDeepLink( wallet: WalletEntity, + deepLink: DeepLink, route: DeepLinkRoute.Transfer ) { if (route.isExpired) { @@ -795,7 +816,7 @@ class RootViewModel( return } val decimals = route.jettonAddress?.let { - tokenRepository.getToken(wallet.accountId, wallet.testnet, it) + tokenRepository.getToken(wallet.accountId, wallet.network, it) }?.decimals ?: WalletCurrency.TON.decimals val amount = route.amount?.let { @@ -811,7 +832,8 @@ class RootViewModel( jettonAddress = route.jettonAddress, bin = route.bin, initStateBase64 = route.initStateBase64, - validUnit = route.exp + validUnit = route.exp, + source = deepLink.source, ) ) } @@ -832,20 +854,20 @@ class RootViewModel( wallet = wallet, eventId = hash, options = ActionOptions( - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), ) ).filterIsInstance().firstOrNull() ?: return openScreen(TransactionScreen.newInstance(tx)) } private suspend fun showTransaction(accountId: String, hash: String) { - val wallet = accountRepository.getWalletByAccountId(accountId, false) ?: return - val event = api.getTransactionEvents(wallet.accountId, wallet.testnet, hash) ?: return + val wallet = accountRepository.getWalletByAccountId(accountId) ?: return + val event = api.getTransactionEvents(wallet.accountId, wallet.network, hash) ?: return val tx = historyHelper.mapping( wallet = wallet, event = event, options = ActionOptions( - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), ) ).filterIsInstance().firstOrNull() ?: return openScreen(TransactionScreen.newInstance(tx)) @@ -885,7 +907,7 @@ class RootViewModel( } } - suspend fun isScamAddress(address: String, testnet: Boolean): Boolean { - return api.resolveAccount(address, testnet)?.isScam ?: false + suspend fun isScamAddress(address: String, network: TonNetwork): Boolean { + return api.resolveAccount(address, network)?.isScam ?: false } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/InsufficientFundsDialog.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/InsufficientFundsDialog.kt index 4076bc342..4aca56871 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/InsufficientFundsDialog.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/InsufficientFundsDialog.kt @@ -14,10 +14,11 @@ import com.tonapps.tonkeeper.koin.api import com.tonapps.tonkeeper.ui.screen.battery.BatteryScreen import com.tonapps.tonkeeper.ui.screen.browser.more.BrowserMoreScreen import com.tonapps.tonkeeper.ui.screen.onramp.main.OnRampScreen -import com.tonapps.tonkeeper.ui.screen.purchase.PurchaseScreen +import com.tonapps.tonkeeper.ui.screen.qr.QRScreen import com.tonapps.tonkeeper.ui.screen.send.main.helper.InsufficientBalanceType import com.tonapps.tonkeeper.view.BatteryView import com.tonapps.tonkeeperx.R +import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.Wallet import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.core.currency.WalletCurrency @@ -27,7 +28,8 @@ import uikit.dialog.modal.ModalDialog import uikit.navigation.Navigation import uikit.widget.TextHeaderView -class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog(fragment.requireContext(), R.layout.dialog_insufficient_funds) { +class InsufficientFundsDialog(private val fragment: BaseFragment) : + ModalDialog(fragment.requireContext(), R.layout.dialog_insufficient_funds) { private val navigation: Navigation? get() = Navigation.from(context) @@ -43,8 +45,7 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( } fun show( - wallet: WalletEntity, - e: InsufficientFundsException + wallet: WalletEntity, e: InsufficientFundsException ) { super.show() applyWalletTitle(wallet.label, e.singleWallet, e.type) @@ -53,8 +54,7 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( val isBattery = e.type == InsufficientBalanceType.InsufficientBatteryChargesForFee - tonButton.visibility = - if (isBattery) View.GONE else View.VISIBLE + tonButton.visibility = if (isBattery) View.GONE else View.VISIBLE iconView.visibility = if (isBattery) View.GONE else View.VISIBLE batteryView.setBatteryLevel(BatteryView.MIN_LEVEL) batteryView.visibility = if (isBattery) View.VISIBLE else View.GONE @@ -87,21 +87,27 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( super.show() applyWalletTitle(wallet.label, singleWallet, type) applyDescription(balance, required, withRechargeBattery, type) - val isBatteryDisabled = context.api?.config?.flags?.disableBattery ?: false - batteryButton.visibility = if (withRechargeBattery && !isBatteryDisabled) View.VISIBLE else View.GONE + val isBatteryDisabled = context.api?.getConfig(wallet.network)?.flags?.disableBattery ?: false + batteryButton.visibility = + if (withRechargeBattery && !isBatteryDisabled) View.VISIBLE else View.GONE val isBattery = type == InsufficientBalanceType.InsufficientBatteryChargesForFee - tonButton.visibility = - if (isBattery) View.GONE else View.VISIBLE + tonButton.visibility = if (isBattery) View.GONE else View.VISIBLE iconView.visibility = if (isBattery) View.GONE else View.VISIBLE batteryView.setBatteryLevel(BatteryView.MIN_LEVEL) batteryView.visibility = if (isBattery) View.VISIBLE else View.GONE - tonButton.text = context.getString(Localization.buy_ton).replace("TON", required.symbol) + tonButton.text = if (required.isTrx) { + context.getString(Localization.get_token, TokenEntity.TRX.symbol) + } else { + context.getString(Localization.buy_ton).replace("TON", required.symbol) + } tonButton.setOnClickListener { if (required.isTon) { navigation?.add(OnRampScreen.newInstance(context, wallet, "insufficientFunds")) + } else if (required.isTrx) { + navigation?.add(QRScreen.newInstance(wallet = wallet, token = TokenEntity.TRX)) } else { fragment.finish() navigation?.add(BrowserMoreScreen.newInstance(wallet, "defi")) @@ -116,12 +122,12 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( } private fun applyWalletTitle( - label: Wallet.Label, - singleWallet: Boolean, - type: InsufficientBalanceType + label: Wallet.Label, singleWallet: Boolean, type: InsufficientBalanceType ) { if (type == InsufficientBalanceType.InsufficientBatteryChargesForFee) { textView.titleView.setText(Localization.insufficient_battery_charges) + } else if (type == InsufficientBalanceType.InsufficientBalanceForFee) { + textView.titleView.setText(Localization.insufficient_trx_balance) } else if (!singleWallet) { val walletTitle = label.getTitle(context, textView.titleView, 16) val spannable = @@ -142,12 +148,11 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( type: InsufficientBalanceType ) { if (type == InsufficientBalanceType.InsufficientBatteryChargesForFee) { - textView.descriptionView.text = - context.getString( - Localization.insufficient_balance_charges, - CurrencyFormatter.format(value = required.value), - CurrencyFormatter.format(value = balance.value) - ) + textView.descriptionView.text = context.getString( + Localization.insufficient_balance_charges, + CurrencyFormatter.format(value = required.value), + CurrencyFormatter.format(value = balance.value) + ) return } else { val balanceFormat = @@ -171,20 +176,17 @@ class InsufficientFundsDialog(private val fragment: BaseFragment) : ModalDialog( type: InsufficientBalanceType ) { if (type == InsufficientBalanceType.InsufficientBatteryChargesForFee) { - textView.descriptionView.text = - context.getString( - Localization.insufficient_balance_charges, - CurrencyFormatter.format(value = required.value), - CurrencyFormatter.format(value = balance.value) - ) + textView.descriptionView.text = context.getString( + Localization.insufficient_balance_charges, + CurrencyFormatter.format(value = required.value), + CurrencyFormatter.format(value = balance.value) + ) return } else { val balanceFormat = - CurrencyFormatter.format(currency.code, balance.value) - .withCustomSymbol(context) + CurrencyFormatter.format(currency.code, balance.value).withCustomSymbol(context) val requiredFormat = - CurrencyFormatter.format(currency.code, required.value) - .withCustomSymbol(context) + CurrencyFormatter.format(currency.code, required.value).withCustomSymbol(context) val resId = if (withRechargeBattery || type == InsufficientBalanceType.InsufficientBalanceForFee) Localization.insufficient_balance_fees else Localization.insufficient_balance_default diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionScreen.kt index 9ca5f631a..9b522d71b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionScreen.kt @@ -174,7 +174,7 @@ class RemoveExtensionScreen( ) setSpan( ForegroundColorSpan( - requireContext().resolveColor(com.tonapps.uikit.color.R.attr.textTertiaryColor) + requireContext().resolveColor(com.tonapps.ui.uikit.color.R.attr.textTertiaryColor) .withAlpha(0.7f) ), 0, secondLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionViewModel.kt index be2e597e3..98be052e2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/boc/RemoveExtensionViewModel.kt @@ -64,7 +64,7 @@ class RemoveExtensionViewModel( viewModelScope.launch(Dispatchers.IO) { try { val seqNo = accountRepository.getSeqno(wallet) - val validUntil = accountRepository.getValidUntil(wallet.testnet) + val validUntil = accountRepository.getValidUntil(wallet.network) val queryId = TransferEntity.newWalletQueryId() val address = AddrStd(pluginAddress) @@ -156,14 +156,14 @@ class RemoveExtensionViewModel( val balance = tokenRepository.getTON( settingsRepository.currency, wallet.accountId, - wallet.testnet + wallet.network )?.balance?.value return balance ?: Coins.ZERO } fun send() = flow { val seqNo = accountRepository.getSeqno(wallet) - val validUntil = accountRepository.getValidUntil(wallet.testnet) + val validUntil = accountRepository.getValidUntil(wallet.network) val queryId = TransferEntity.newWalletQueryId() val unsignedBody = wallet.contract.removePlugin( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactScreen.kt index 3d1e0ad8f..a5a5b2039 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactScreen.kt @@ -1,7 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.send.contacts.add import android.os.Bundle -import android.util.Log import android.view.View import android.widget.Button import com.tonapps.tonkeeper.extensions.hideKeyboard diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactViewModel.kt index a7d184f74..e3d827a42 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/add/AddContactViewModel.kt @@ -77,7 +77,7 @@ class AddContactViewModel( } else { _accountFlow.value = AddressAccount.Loading - val account = api.resolveAccount(address, wallet.testnet) + val account = api.resolveAccount(address, wallet.network) if (account == null || !account.isWallet || account.status == AccountStatus.nonexist) { _accountFlow.value = AddressAccount.Error } else { @@ -91,7 +91,7 @@ class AddContactViewModel( viewModelScope.launch { try { val userInput = _userInputFlow.value - contactsRepository.add(userInput.name, userInput.address, wallet.testnet) + contactsRepository.add(userInput.name, userInput.address, wallet.network) } catch (e: Throwable) { toast(e.bestMessage) } finally { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/edit/EditContactScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/edit/EditContactScreen.kt index 9ef4b8020..c6523999d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/edit/EditContactScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/edit/EditContactScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.send.contacts.edit import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.widget.Button import com.tonapps.extensions.getParcelableCompat diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/main/SendContactsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/main/SendContactsViewModel.kt index feb535863..baa34a3d7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/main/SendContactsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/main/SendContactsViewModel.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.send.contacts.main import android.app.Application -import android.util.Log +import com.tonapps.log.L import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.extensions.filterList @@ -88,7 +88,7 @@ class SendContactsViewModel( } fun hideContact(address: String) { - contactsRepository.hide(address.toRawAddress(), wallet.testnet) + contactsRepository.hide(address.toRawAddress(), wallet.network) } fun deleteContact(contact: ContactEntity) { @@ -134,7 +134,7 @@ class SendContactsViewModel( tronLatestTransactionsFlow, ) { _, events -> events.filter { - !contactsRepository.isHidden(it.to, wallet.testnet) + !contactsRepository.isHidden(it.to, wallet.network) }.mapIndexed { index, event -> val position = ListCell.getPosition(events.size, index) Item.LatestContact(position, event.to, event.timestamp.value) @@ -145,13 +145,13 @@ class SendContactsViewModel( contactsRepository.hiddenFlow, eventsRepository.latestRecipientsFlow( accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ), latestTronContactsFlow ) { _, recipients, tronContacts -> - val gasProxyAddresses = batteryRepository.getConfig(wallet.testnet).gasProxy + val gasProxyAddresses = batteryRepository.getConfig(wallet.network).gasProxy val tonContacts = recipients.filter { - !contactsRepository.isHidden(it.account.address.toRawAddress(), wallet.testnet) + !contactsRepository.isHidden(it.account.address.toRawAddress(), wallet.network) }.mapIndexed { index, recipient -> val position = ListCell.getPosition(recipients.size, index) Item.LatestContact(position, recipient.account, recipient.timestamp, wallet.testnet) 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 ec0c2b237..75fd8d531 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 @@ -3,7 +3,7 @@ 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.cellFromBase64 -import com.tonapps.blockchain.ton.extensions.toRawAddress +import com.tonapps.bus.generated.Events import com.tonapps.extensions.getEnum import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.putEnum @@ -18,7 +18,8 @@ data class SendArgs( val text: String?, val nftAddress: String, val type: SendScreen.Companion.Type, - val bin: Cell? = null + val bin: Cell? = null, + val from: Events.SendNative.SendNativeFrom = Events.SendNative.SendNativeFrom.WalletScreen ): BaseArgs() { private companion object { @@ -29,6 +30,7 @@ data class SendArgs( private const val ARG_NFT_ADDRESS = "nft_address" private const val ARG_TYPE = "type" private const val ARG_BIN = "bin" + private const val ARG_FROM = "from" private fun normalizeAmount(amount: Coins?): Coins? { if (amount?.isPositive == true) { @@ -48,7 +50,8 @@ data class SendArgs( text = bundle.getString(ARG_TEXT), nftAddress = bundle.getString(ARG_NFT_ADDRESS) ?: "", type = bundle.getEnum(ARG_TYPE, SendScreen.Companion.Type.Default), - bin = bundle.getString(ARG_BIN)?.cellFromBase64() + bin = bundle.getString(ARG_BIN)?.cellFromBase64(), + from = bundle.getEnum(ARG_FROM, Events.SendNative.SendNativeFrom.WalletScreen) ) override fun toBundle(): Bundle { @@ -62,6 +65,7 @@ data class SendArgs( bin?.let { bundle.putString(ARG_BIN, it.base64()) } + bundle.putEnum(ARG_FROM, from) 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/SendEvent.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt index 5469b0ef4..94a903dd8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt @@ -5,6 +5,7 @@ import com.tonapps.tonkeeper.core.Amount import com.tonapps.tonkeeper.core.Fee import com.tonapps.tonkeeper.ui.screen.send.main.helper.InsufficientBalanceType import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesEmulation import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.core.currency.WalletCurrency @@ -19,7 +20,9 @@ sealed class SendEvent { val required: Amount, val withRechargeBattery: Boolean, val singleWallet: Boolean, - val type: InsufficientBalanceType + val type: InsufficientBalanceType, + val tronFees: Boolean = false, + val tronFeesEmulation: TronFeesEmulation? = null, ) data object Confirm: SendEvent() 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 96dd649d7..837f84d6c 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 @@ -3,9 +3,10 @@ package com.tonapps.tonkeeper.ui.screen.send.main import android.graphics.drawable.Drawable import android.os.Bundle import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned import android.text.method.LinkMovementMethod import android.text.style.ForegroundColorSpan -import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.Button @@ -14,8 +15,11 @@ import androidx.appcompat.widget.LinearLayoutCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import com.tonapps.blockchain.tron.isValidTronAddress +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.getUserMessage +import com.tonapps.extensions.isPositive import com.tonapps.extensions.shortTron import com.tonapps.extensions.uri import com.tonapps.icu.Coins @@ -38,13 +42,14 @@ import com.tonapps.tonkeeper.ui.screen.camera.CameraScreen import com.tonapps.tonkeeper.ui.screen.nft.NftScreen import com.tonapps.tonkeeper.ui.screen.send.InsufficientFundsDialog import com.tonapps.tonkeeper.ui.screen.send.contacts.main.SendContactsScreen -import com.tonapps.tonkeeper.ui.screen.send.main.SendEvent import com.tonapps.tonkeeper.ui.screen.send.main.helper.InsufficientBalanceType import com.tonapps.tonkeeper.ui.screen.send.main.state.SendAmountState import com.tonapps.tonkeeper.ui.screen.send.main.state.SendDestination import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee import com.tonapps.tonkeeper.ui.screen.send.main.state.SendTransaction import com.tonapps.tonkeeper.ui.screen.token.viewer.TokenScreen +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreen +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreenType import com.tonapps.tonkeeper.view.TransactionDetailView import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentBlueColor @@ -54,15 +59,18 @@ import com.tonapps.uikit.color.fieldActiveBorderColor import com.tonapps.uikit.color.fieldErrorBorderColor import com.tonapps.uikit.color.textAccentColor import com.tonapps.uikit.color.textSecondaryColor +import com.tonapps.uikit.color.textTertiaryColor import com.tonapps.uikit.icon.UIKitIcon -import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.TokenEntity +import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.data.core.HIDDEN_BALANCE import com.tonapps.wallet.localization.Localization import com.tonapps.wallet.localization.Plurals import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch @@ -145,7 +153,7 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - analytics?.simpleTrackEvent("send_open") + initializeBus(args.from) navigation?.setFragmentResultListener(contractsRequestKey) { bundle -> val contact = bundle.getParcelableCompat("contact") @@ -154,6 +162,10 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s } } + fun initializeBus(from: Events.SendNative.SendNativeFrom) { + viewModel.initializeBus(from) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) slidesView = view.findViewById(R.id.slides) @@ -233,7 +245,16 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s button = view.findViewById(R.id.button) button.setOnClickListener { - analytics?.simpleTrackEvent("send_click") + lifecycleScope.launch { + val token = viewModel.uiInputTokenFlow.firstOrNull() ?: return@launch + AnalyticsHelper.Default.events.sendNative.sendClick( + from = args.from, + assetNetwork = token.tokenType, + tokenSymbol = token.plainSymbol, + amount = viewModel.currentAmountDouble, + ) + } + next() } @@ -270,7 +291,18 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s } confirmButton.setOnClickListener { - analytics?.simpleTrackEvent("send_confirm") + lifecycleScope.launch { + val token = viewModel.uiInputTokenFlow.firstOrNull() ?: return@launch + + AnalyticsHelper.Default.events.sendNative.sendConfirm( + from = args.from, + assetNetwork = token.tokenType, + tokenSymbol = token.plainSymbol, + amount = viewModel.currentAmountDouble, + feePaidIn = viewModel.currentFeePaidIn, + appId = null, + ) + } signAndSend() } confirmButton.setText(if (wallet.hasPrivateKey) Localization.confirm else Localization.continue_action) @@ -285,9 +317,11 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s is SendDestination.TokenError -> { applyTokenError(destination) } + is SendDestination.NotFound -> { applyInvalidAddressError() } + is SendDestination.Scam -> { applyScamAddressError() } @@ -339,7 +373,7 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s args.text, args.tokenAddress, args.bin, - args.type + args.type, ) } @@ -455,18 +489,19 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s text: String?, tokenAddress: String?, bin: Cell? = null, - type: Type + type: Type, ) { viewModel.initializeTokenAndAmount( tokenAddress = tokenAddress, amount = amount, - type = type + type = type, ) text?.let { commentInput.text = it } targetAddress?.let { addressInput.text = it } bin?.let { viewModel.userInputBin(it) } + if (targetAddress != null) { lifecycleScope.launch(Dispatchers.Main) { if (viewModel.isNeedMemoAddress(targetAddress) || targetAddress.isValidTronAddress()) { @@ -557,13 +592,23 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s } private fun openInsufficientBalance(event: SendEvent.InsufficientBalance) { - showInsufficientFundsDialog( - balance = event.balance, - required = event.required, - withRechargeBattery = event.withRechargeBattery, - singleWallet = event.singleWallet, - type = event.type - ) + if (event.tronFees) { + navigation?.add( + TronFeesScreen.newInstance( + wallet = wallet, + type = TronFeesScreenType.InsufficientBalance, + emulation = event.tronFeesEmulation + ) + ) + } else { + showInsufficientFundsDialog( + balance = event.balance, + required = event.required, + withRechargeBattery = event.withRechargeBattery, + singleWallet = event.singleWallet, + type = event.type + ) + } if (args.type == Type.Direct) { finish() } @@ -765,12 +810,18 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s event.fee.charges, CurrencyFormatter.format(value = event.fee.charges.toBigDecimal()) ) - reviewRecipientFeeView.description = requireContext().getString( - Localization.out_of_available_charges, - CurrencyFormatter.format( - value = event.fee.chargesBalance.toBigDecimal() + reviewRecipientFeeView.description = if (event.fee.excessCharges.isPositive()) { + "≈ ${getString(Localization.battery_excess, CurrencyFormatter.format( + value = event.fee.excessCharges.toBigDecimal() + ))}" + } else { + requireContext().getString( + Localization.out_of_available_charges, + CurrencyFormatter.format( + value = event.fee.chargesBalance.toBigDecimal() + ) ) - ) + } } else -> {} @@ -883,10 +934,32 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s fee.fiatCurrency.code, fee.fiatAmount, ) + val subtitle: CharSequence = + if ((fee is SendFee.TronTrx && !fee.enoughBalance) || (fee is SendFee.TronTon && !fee.enoughBalance)) { + SpannableStringBuilder().apply { + append( + getString(Localization.no_enough_funds), + ForegroundColorSpan(requireContext().textSecondaryColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append( + " · ", + ForegroundColorSpan(requireContext().textTertiaryColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append( + getString(Localization.refill), + ForegroundColorSpan(requireContext().textAccentColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } else { + "≈ $formattedFiat ($formattedAmount)" + } feeMethodSelector.addItem( id = fee.amount.token.symbol.hashCode().toLong(), title = fee.amount.token.symbol, - subtitle = "≈ $formattedAmount · $formattedFiat", + subtitle = subtitle, imageUri = fee.amount.token.imageUri, icon = if (currentFee == fee) { getDrawable(UIKitIcon.ic_done_16) @@ -903,10 +976,36 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s fee.charges, CurrencyFormatter.format(value = fee.charges.toBigDecimal()) ) + val formattedFiat = CurrencyFormatter.formatFiat( + fee.fiatCurrency.code, + fee.fiatAmount, + ) + val subtitle: CharSequence = + if (!fee.enoughCharges) { + SpannableStringBuilder().apply { + append( + getString(Localization.no_enough_funds), + ForegroundColorSpan(requireContext().textSecondaryColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append( + " · ", + ForegroundColorSpan(requireContext().textTertiaryColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append( + getString(Localization.refill), + ForegroundColorSpan(requireContext().textAccentColor), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } else { + "≈ $formattedFiat ($formattedCharges)" + } feeMethodSelector.addItem( id = "battery".hashCode().toLong(), title = getString(Localization.battery_refill_title), - subtitle = "≈ $formattedCharges", + subtitle = subtitle, imageUri = UIKitIcon.ic_flash_24.uri(), imageTintColor = requireContext().accentGreenColor, icon = if (currentFee == fee) { @@ -935,6 +1034,8 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s private var nftAddress: String? = null private var type: Type = Type.Default private var bin: Cell? = null + private var from: Events.SendNative.SendNativeFrom = + Events.SendNative.SendNativeFrom.WalletScreen fun setTargetAddress(targetAddress: String) = apply { this.targetAddress = targetAddress @@ -964,6 +1065,10 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s this.bin = bin } + fun setFrom(from: Events.SendNative.SendNativeFrom) = apply { + this.from = from + } + fun build() = newInstance( wallet, targetAddress, @@ -972,7 +1077,8 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s text, nftAddress, type, - bin + bin, + from ) } @@ -988,7 +1094,8 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s text: String? = null, nftAddress: String? = null, type: Type, - bin: Cell? = null + bin: Cell? = null, + from: Events.SendNative.SendNativeFrom = Events.SendNative.SendNativeFrom.WalletScreen ): SendScreen { val args = SendArgs( targetAddress = targetAddress, @@ -996,7 +1103,8 @@ class SendScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_s amount = amount, text = text, nftAddress = nftAddress ?: "", type = type, - bin = bin + bin = bin, + from = from ) return SendScreen(wallet).apply { setArgs(args) 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 437b288bb..696cd0959 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 @@ -1,32 +1,40 @@ package com.tonapps.tonkeeper.ui.screen.send.main import android.app.Application -import android.util.Log import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.blockchain.ton.TonAddressTags import com.tonapps.blockchain.ton.contract.WalletFeature +import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 +import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.blockchain.ton.extensions.equalsAddress import com.tonapps.blockchain.ton.extensions.isValidTonAddress import com.tonapps.blockchain.tron.TronTransfer import com.tonapps.blockchain.tron.isValidTronAddress +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.filterList import com.tonapps.extensions.singleValue import com.tonapps.extensions.state import com.tonapps.icu.Coins import com.tonapps.icu.CurrencyFormatter +import com.tonapps.log.L import com.tonapps.tonkeeper.core.Amount -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.extensions.isPositive import com.tonapps.tonkeeper.core.Fee import com.tonapps.tonkeeper.core.SendBlockchainException import com.tonapps.tonkeeper.core.entities.SendMetadataEntity import com.tonapps.tonkeeper.core.entities.TransferEntity import com.tonapps.tonkeeper.extensions.isPrintableAscii import com.tonapps.tonkeeper.extensions.isSafeModeEnabled +import com.tonapps.tonkeeper.extensions.method +import com.tonapps.tonkeeper.extensions.tronMethod import com.tonapps.tonkeeper.extensions.with import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.ui.base.BaseWalletVM +import com.tonapps.tonkeeper.ui.screen.battery.BatteryScreen +import com.tonapps.tonkeeper.ui.screen.qr.QRScreen import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen.Companion.Type import com.tonapps.tonkeeper.ui.screen.send.main.helper.InsufficientBalanceType import com.tonapps.tonkeeper.ui.screen.send.main.helper.isEmptyBalance @@ -36,6 +44,7 @@ import com.tonapps.tonkeeper.ui.screen.send.main.state.SendDestination import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee import com.tonapps.tonkeeper.ui.screen.send.main.state.SendTransaction import com.tonapps.tonkeeper.ui.screen.send.main.state.TonTransaction +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesEmulation import com.tonapps.tonkeeper.usecase.emulation.EmulationUseCase import com.tonapps.tonkeeper.usecase.emulation.InsufficientBalanceError import com.tonapps.tonkeeper.usecase.sign.SignUseCase @@ -56,12 +65,15 @@ import com.tonapps.wallet.data.rates.RatesRepository import com.tonapps.wallet.data.settings.BatteryTransaction import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.data.settings.entities.PreferredFeeMethod +import com.tonapps.wallet.data.settings.entities.PreferredTronFeeMethod import com.tonapps.wallet.data.token.TokenRepository import com.tonapps.wallet.data.token.entities.AccountTokenEntity import com.tonapps.wallet.localization.Localization +import io.batteryapi.models.EstimatedTronTx import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -76,7 +88,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.update @@ -118,10 +129,29 @@ class SendViewModel( get() = settingsRepository.installId val isTronDisabled: Boolean - get() = api.config.flags.disableTron + get() = api.getConfig(wallet.network).flags.disableTron val isBatteryDisabled: Boolean - get() = api.config.flags.disableBattery + get() = api.getConfig(wallet.network).flags.disableBattery + + private var analyticsFrom: Events.SendNative.SendNativeFrom = + Events.SendNative.SendNativeFrom.WalletScreen + + val currentToken: TokenEntity + get() = selectedTokenFlow.value.token + + val currentAmountDouble: Double + get() = _userInputFlow.value.amount.value.toDouble() + + val currentFeePaidIn: Events.SendNative.SendNativeFeePaidIn + get() = when (_feeFlow.value) { + is SendFee.Ton -> Events.SendNative.SendNativeFeePaidIn.Ton + is SendFee.Gasless -> Events.SendNative.SendNativeFeePaidIn.Gasless + is SendFee.Battery -> Events.SendNative.SendNativeFeePaidIn.Battery + is SendFee.TronTrx -> Events.SendNative.SendNativeFeePaidIn.Trx + is SendFee.TronTon -> Events.SendNative.SendNativeFeePaidIn.Ton + null -> Events.SendNative.SendNativeFeePaidIn.Ton + } data class UserInput( val address: String = "", @@ -197,16 +227,20 @@ class SendViewModel( private var tonFee: SendFee.Ton? = null private var gaslessFee: SendFee.Gasless? = null private var batteryFee: SendFee.Battery? = null + private var tronTrxFee: SendFee.TronTrx? = null + private var tronTonFee: SendFee.TronTon? = null val feeOptions: List get() = listOfNotNull( batteryFee, tonFee, gaslessFee, + tronTonFee, + tronTrxFee, ) private val ratesTokenFlow = selectedTokenFlow.map { token -> - ratesRepository.getRates(currency, token.address) + ratesRepository.getRates(wallet.network, currency, token.address) }.state(viewModelScope) val uiInputAddressErrorFlow = @@ -275,7 +309,7 @@ class SendViewModel( val (balance, currencyCode) = if (amountCurrency) { Pair(token.fiat, currency.code) } else { - Pair(token.balance.value, token.symbol) + Pair(token.balance.uiBalance, token.symbol) } val remaining = balance - amount @@ -288,7 +322,7 @@ class SendViewModel( } val remainingToken = if (!amountCurrency) { - token.balance.value - amount + token.balance.uiBalance - amount } else { rates.convertFromFiat(token.address, token.fiat - amount) } @@ -370,12 +404,7 @@ class SendViewModel( ) }.stateIn(viewModelScope, SharingStarted.Eagerly, TonTransaction.Amount()) - private val preferredFeeMethodFlow = - MutableStateFlow(settingsRepository.getPreferredFeeMethod(wallet.id)) - private val _tronResourcesFlow = MutableStateFlow(null) - private val tronResourcesFlow = - _tronResourcesFlow.shareIn(viewModelScope, SharingStarted.Eagerly, 1).filterNotNull() private val _tronTransferFlow = MutableStateFlow(null) private val tronTransferFlow = _tronTransferFlow.asStateFlow() @@ -441,17 +470,17 @@ class SendViewModel( ) { input, selected, amountCurrency -> val tokenAddress = selected.address val amount = if (amountCurrency) { - val rates = ratesRepository.getRates(currency, tokenAddress) + val rates = ratesRepository.getRates(wallet.network, currency, tokenAddress) rates.convertFromFiat(tokenAddress, input.amount) } else { input.amount } - amount >= selected.balance.value + amount >= selected.balance.uiBalance }.flowOn(Dispatchers.IO) init { viewModelScope.launch(Dispatchers.IO) { - _tokensFlow.value = tokenRepository.get(currency, wallet.accountId, wallet.testnet) + _tokensFlow.value = tokenRepository.get(currency, wallet.accountId, wallet.network) } if (isNft) { @@ -459,8 +488,17 @@ class SendViewModel( } } + fun initializeBus( + from: Events.SendNative.SendNativeFrom, + ) { + analyticsFrom = from + AnalyticsHelper.Default.events.sendNative.sendOpen(from = from) + } + fun initializeTokenAndAmount( - tokenAddress: String?, amount: Coins?, type: Type + tokenAddress: String?, + amount: Coins?, + type: Type, ) { tokensFlow.take(1).filter { it.isNotEmpty() @@ -471,7 +509,7 @@ class SendViewModel( it.address.equalsAddress(TokenEntity.TON.address) } }.map { it.firstOrNull()?.balance?.token }.map { token -> - token ?: tokenAddress?.let { tokenRepository.getToken(tokenAddress, wallet.testnet) } + token ?: tokenAddress?.let { tokenRepository.getToken(tokenAddress, wallet.network) } ?: TokenEntity.TON }.flowOn(Dispatchers.IO).onEach { token -> userInputToken(token) @@ -484,7 +522,7 @@ class SendViewModel( } suspend fun isNeedMemoAddress(targetAddress: String): Boolean = withContext(Dispatchers.IO) { - api.resolveAccount(targetAddress, wallet.testnet)?.memoRequired == true + api.resolveAccount(targetAddress, wallet.network)?.memoRequired == true } private fun applyAmount(token: TokenEntity, amount: Coins?) { @@ -499,8 +537,8 @@ class SendViewModel( return@withContext SendDestination.NotFound } - val accountDeferred = async { api.resolveAccount(userInput, wallet.testnet) } - val publicKeyDeferred = async { api.safeGetPublicKey(userInput, wallet.testnet) } + val accountDeferred = async { api.resolveAccount(userInput, wallet.network) } + val publicKeyDeferred = async { api.safeGetPublicKey(userInput, wallet.network) } val account = accountDeferred.await() ?: return@withContext SendDestination.NotFound val publicKey = publicKeyDeferred.await() @@ -533,7 +571,10 @@ class SendViewModel( } else if (fee is SendFee.Gasless && !transfer.max && fee.amount.value + transfer.amount > transfer.token.value) { showInsufficientBalance( type = InsufficientBalanceType.InsufficientGaslessBalance, - amount = Amount(value = fee.amount.value + transfer.amount, token = transfer.token.token), + amount = Amount( + value = fee.amount.value + transfer.amount, + token = transfer.token.token + ), balance = Amount(value = transfer.token.value, token = transfer.token.token), gaslessFee = fee.amount.value ) @@ -551,13 +592,13 @@ class SendViewModel( private suspend fun getBatteryCharges(): Int = withContext(Dispatchers.IO) { accountRepository.requestTonProofToken(wallet)?.let { - batteryRepository.getCharges(it, wallet.publicKey, wallet.testnet, true) + batteryRepository.getCharges(it, wallet.publicKey, wallet.network, true) } ?: 0 } private suspend fun getBatteryBalance(): BatteryBalanceEntity = withContext(Dispatchers.IO) { accountRepository.requestTonProofToken(wallet)?.let { - batteryRepository.getBalance(it, wallet.publicKey, wallet.testnet, true) + batteryRepository.getBalance(it, wallet.publicKey, wallet.network, true) } ?: BatteryBalanceEntity.Empty } @@ -606,16 +647,21 @@ class SendViewModel( if (!userInputFlow.value.amountCurrency) { amount } else { - val rates = ratesRepository.getRates(currency, token.address) + val rates = ratesRepository.getRates(wallet.network, currency, token.address) rates.convertFromFiat(token.address, amount) } } + private suspend fun getTrxBalance(): Coins = withContext(Dispatchers.IO) { + tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) + ?.find { it.isTrx }?.balance?.value ?: Coins.ZERO + } + private suspend fun getTONBalance(): Coins = withContext(Dispatchers.IO) { if (selectedTokenFlow.value.isTon) { selectedTokenFlow.value.balance.value } else { - tokenRepository.getTON(currency, wallet.accountId, wallet.testnet)?.balance?.value + tokenRepository.getTON(currency, wallet.accountId, wallet.network)?.balance?.value ?: Coins.ZERO } } @@ -623,40 +669,149 @@ class SendViewModel( private suspend fun checkTonFee(transfer: TransferEntity) { val fee = calculateFee(transfer) _feeFlow.tryEmit(fee) - eventFee(transfer, fee)?.let(::showPreview) + eventFee(fee)?.let(::showPreview) } - private suspend fun checkTronFee(transfer: TronTransfer) { - val batteryCharges = getBatteryCharges() - val estimation = api.tron.estimateBatteryCharges(transfer) - _tronResourcesFlow.value = estimation.resources - - val fee = SendFee.Battery( - charges = estimation.charges, - chargesBalance = batteryCharges, - // not used in this case - excessesAddress = AddrStd(wallet.address), - extra = 0L - ) - _feeFlow.tryEmit(fee) - _uiFeeFlow.tryEmit( - SendEvent.Fee( - fee = fee, - failed = false + private suspend fun getTronBatteryFee( + transfer: TronTransfer, + resources: TronResourcesEntity + ): SendFee.Battery? { + try { + val batteryCharges = getBatteryCharges() + + if (isBatteryDisabled && batteryCharges == 0) { + return null + } + + val batteryEstimation = api.tron.estimateBatteryCharges(transfer, resources) + val batteryConfig = batteryRepository.getConfig(wallet.network) + val tonAmount = BatteryMapper.convertFromCharges( + batteryEstimation.charges, + batteryConfig.chargeCost + ) + + val fee = SendFee.Battery( + charges = batteryEstimation.charges, + chargesBalance = batteryCharges, + fiatAmount = ratesRepository.getRates(wallet.network, currency, TokenEntity.TON.address) + .convert(TokenEntity.TON.address, tonAmount), + fiatCurrency = currency, + // not used in this case + excessesAddress = AddrStd(wallet.address), + extra = 0L, + estimatedTron = batteryEstimation.estimated + ) + + if (isBatteryDisabled && fee.charges > fee.chargesBalance) { + return null + } + + return fee + } catch (_: Exception) { + return null + } + } + + private suspend fun getTronTonFee( + batteryEstimated: EstimatedTronTx? + ): SendFee.TronTon? { + if (isBatteryDisabled || batteryEstimated == null) { + return null + } + + try { + val tonEstimation = api.tron.estimateTonFee(batteryEstimated) + return SendFee.TronTon( + amount = Fee( + value = tonEstimation.fee, + isRefund = false + ), + balance = getTONBalance(), + fiatAmount = ratesRepository.getRates(wallet.network, currency, TokenEntity.TON.address) + .convert(TokenEntity.TON.address, tonEstimation.fee), + fiatCurrency = currency, + sendToAddress = tonEstimation.sendToAddress, ) + } catch (_: Exception) { + return null + } + } + + private suspend fun getTronTrxFee(resources: TronResourcesEntity): SendFee.TronTrx { + val trxEstimation = api.tron.estimateTrxFee(resources) + val trxBalance = getTrxBalance() + return SendFee.TronTrx( + amount = Fee( + value = trxEstimation.fee, + isRefund = false, + token = TokenEntity.TRX + ), + fiatAmount = ratesRepository.getRates(wallet.network, currency, TokenEntity.TRX.address) + .convert(TokenEntity.TRX.address, trxEstimation.fee), + fiatCurrency = currency, + balance = trxBalance, ) + } + + private suspend fun checkTronFee(transfer: TronTransfer) = withContext(Dispatchers.IO) { + resetFees() + val resources = api.tron.estimateTransferResources(transfer) + _tronResourcesFlow.value = resources + + coroutineScope { + val batteryFeeDeferred = async { getTronBatteryFee(transfer, resources) } + val trxFeeDeferred = async { getTronTrxFee(resources) } + + batteryFee = batteryFeeDeferred.await() + tronTonFee = getTronTonFee(batteryFee?.estimatedTron) + tronTrxFee = trxFeeDeferred.await() + } - if (estimation.charges > batteryCharges) { + var fee: SendFee? = null + + // unspecified preferred fee method logic + if (batteryFee != null && batteryFee!!.enoughCharges) { + fee = batteryFee + } else if (tronTonFee != null && tronTonFee!!.enoughBalance) { + fee = tronTonFee + } else if (tronTrxFee != null && tronTrxFee!!.enoughBalance) { + fee = tronTrxFee + } + + // preferred fee method logic + val preferredFeeMethod = settingsRepository.getPreferredTronFeeMethod(wallet.id) + if (preferredFeeMethod == PreferredTronFeeMethod.BATTERY && batteryFee != null && batteryFee!!.enoughCharges) { + fee = batteryFee + } else if (preferredFeeMethod == PreferredTronFeeMethod.TON && tronTonFee != null && tronTonFee!!.enoughBalance) { + fee = tronTonFee + } else if (preferredFeeMethod == PreferredTronFeeMethod.TRX && tronTrxFee != null && tronTrxFee!!.enoughBalance) { + fee = tronTrxFee + } + + if (fee != null) { + _feeFlow.tryEmit(fee) + eventFee(fee)?.let { + _uiFeeFlow.tryEmit(it) + } + delay(100) + _uiEventFlow.tryEmit(SendEvent.Confirm) + } else { _uiInsufficientBalanceFlow.tryEmit( SendEvent.InsufficientBalance( - balance = Amount(Coins.of(batteryCharges.toBigDecimal())), - required = Amount(Coins.of(estimation.charges.toBigDecimal())), - withRechargeBattery = true, + balance = Amount(tronTrxFee!!.balance, TokenEntity.TRX), + required = Amount(tronTrxFee!!.amount.value, TokenEntity.TRX), singleWallet = 1 >= getWalletCount(), - type = InsufficientBalanceType.InsufficientBatteryChargesForFee + withRechargeBattery = false, + type = InsufficientBalanceType.InsufficientBalanceForFee, + tronFees = !api.getConfig(wallet.network).flags.disableBattery, + tronFeesEmulation = TronFeesEmulation( + ton = tronTonFee?.amount?.value?.let { it + TransferEntity.POINT_ONE_TON }, + trx = tronTrxFee?.amount?.value, + batteryCharges = batteryFee?.charges, + ) ) ) - throw SendException.InsufficientBalance() + _uiFeeFlow.tryEmit(SendEvent.Fee(failed = true)) } } @@ -707,9 +862,14 @@ class SendViewModel( builder.setAmount(Coins.ZERO) builder.setMax(false) } else if (!token.isTon) { - builder.setMax(amount.value == token.balance.value) + val isMax = amount.value == token.balance.uiBalance + builder.setMax(isMax) builder.setBounceable(true) - builder.setAmount(amount.value) + if (isMax) { + builder.setAmount(token.balance.value) + } else { + builder.setAmount(token.balance.fromUIBalance(amount.value)) + } } else { builder.setMax(amount.value == getTONBalance()) builder.setAmount(amount.value) @@ -728,25 +888,23 @@ class SendViewModel( val transfer = buildTronTransfer() _tronTransferFlow.value = transfer checkTronFee(transfer) - delay(100) - _uiEventFlow.tryEmit(SendEvent.Confirm) } fun next() { - Log.d("SendViewModelLog", "next() called with: ") + L.d("SendViewModelLog", "next() called with: ") _tonTransferFlow.value = null _tronTransferFlow.value = null viewModelScope.launch(Dispatchers.IO) { - Log.d("SendViewModelLog", "next: start") + L.d("SendViewModelLog", "next: start") try { if (selectedTokenFlow.value.isTrc20) { nextTron() } else { nextTon() } - Log.d("SendViewModelLog", "next: success") + L.d("SendViewModelLog", "next: success") } catch (e: Throwable) { - Log.e("SendViewModelLog", "next error", e) + L.e("SendViewModelLog", "next error", e) delay(100) _uiEventFlow.tryEmit(SendEvent.Failed(e)) } @@ -763,7 +921,7 @@ class SendViewModel( private fun loadNft() { viewModelScope.launch(Dispatchers.IO) { val nft = collectiblesRepository.getNft( - accountId = wallet.accountId, testnet = wallet.testnet, address = nftAddress + accountId = wallet.accountId, network = wallet.network, address = nftAddress ) ?: return@launch val pref = settingsRepository.getTokenPrefs(wallet.id, nftAddress) userInputNft(nft.with(pref)) @@ -784,13 +942,22 @@ class SendViewModel( return settingsRepository.batteryIsEnabledTx(transfer.wallet.accountId, transactionType) } + private fun resetFees() { + tonFee = null + gaslessFee = null + batteryFee = null + tronTonFee = null + tronTrxFee = null + } + private suspend fun calculateFee( transfer: TransferEntity, ): SendFee = withContext(Dispatchers.IO) { + resetFees() val wallet = transfer.wallet val withRelayer = shouldAttemptWithRelayer(transfer) val tonProofToken = accountRepository.requestTonProofToken(wallet) - val batteryConfig = batteryRepository.getConfig(wallet.testnet) + val batteryConfig = batteryRepository.getConfig(wallet.network) val tokenAddress = transfer.token.token.address val excessesAddress = batteryConfig.excessesAddress val isGaslessToken = !transfer.token.isTon && batteryConfig.rechargeMethods.any { @@ -854,7 +1021,7 @@ class SendViewModel( excessesAddress: AddrStd, tonProofToken: String, ): SendFee.Battery? { - if (api.config.batterySendDisabled) { + if (api.getConfig(wallet.network).batterySendDisabled) { return null } @@ -865,24 +1032,25 @@ class SendViewModel( ) try { - val (consequences, withBattery) = batteryRepository.emulate( + val result = batteryRepository.emulate( tonProofToken = tonProofToken, publicKey = wallet.publicKey, - testnet = wallet.testnet, + network = wallet.network, boc = message, - safeModeEnabled = settingsRepository.isSafeModeEnabled(api) + safeModeEnabled = settingsRepository.isSafeModeEnabled(api, wallet.network) ) ?: return null - if (!withBattery) { + if (!result.withBattery) { return null } - val extra = consequences.event.extra + val extra = result.consequences.event.extra + val tonAmount = Coins.of(abs(extra)) val chargesBalance = getBatteryCharges() - val batteryConfig = batteryRepository.getConfig(wallet.testnet) + val batteryConfig = batteryRepository.getConfig(wallet.network) val charges = BatteryMapper.calculateChargesAmount( - Coins.of(abs(extra)).value, + tonAmount.value, batteryConfig.chargeCost ) @@ -890,11 +1058,22 @@ class SendViewModel( return null } + val excess = result.excess + val excessCharges = when { + excess.isPositive() -> BatteryMapper.calculateChargesAmount( + Coins.of(excess).value, batteryConfig.chargeCost).toLong() + else -> null + } + return SendFee.Battery( charges = charges, chargesBalance = chargesBalance, extra = extra, excessesAddress = excessesAddress, + fiatAmount = ratesRepository.getRates(wallet.network, currency, TokenEntity.TON.address) + .convert(TokenEntity.TON.address, tonAmount), + fiatCurrency = currency, + excessCharges = excessCharges, ) } catch (_: Exception) { return null @@ -908,8 +1087,8 @@ class SendViewModel( tokenAddress: String, ): SendFee.Gasless? { try { - if (api.config.flags.disableGasless) { - Log.d("SendViewModel", "Gasless fee calculation disabled by config") + if (api.getConfig(wallet.network).flags.disableGasless) { + L.d("SendViewModel", "Gasless fee calculation disabled by config") return null } @@ -934,7 +1113,7 @@ class SendViewModel( tonProofToken = tonProofToken, jettonMaster = tokenAddress, cell = message, - testnet = wallet.testnet, + network = wallet.network, ) ?: throw IllegalStateException("Can't estimate gasless cost") val gaslessFee = Coins.ofNano(commission, transfer.token.decimals) @@ -952,7 +1131,7 @@ class SendViewModel( token = transfer.token.token, ) - val rates = ratesRepository.getRates(currency, fee.token.address) + val rates = ratesRepository.getRates(wallet.network, currency, fee.token.address) val converted = rates.convert(fee.token.address, fee.value) return SendFee.Gasless( @@ -962,7 +1141,7 @@ class SendViewModel( excessesAddress = excessesAddress ) } catch (e: Exception) { - Log.d("SendViewModel", "Gasless fee calculation failed: ${e.message}") + L.d("SendViewModel", "Gasless fee calculation failed: ${e.message}") return null } } @@ -981,10 +1160,10 @@ class SendViewModel( // Emulate with higher balance to calculate fair amount to send val emulated = api.emulate( cell = message, - testnet = transfer.wallet.testnet, + network = transfer.wallet.network, address = transfer.wallet.accountId, balance = (Coins.ONE + Coins.ONE).toLong(), - safeModeEnabled = settingsRepository.isSafeModeEnabled(api) + safeModeEnabled = settingsRepository.isSafeModeEnabled(api, transfer.wallet.network) ) val fee = Fee(emulated?.event?.extra ?: 0L) @@ -1008,7 +1187,7 @@ class SendViewModel( checkTonBalance = !transfer.isTon || !transfer.max, ) - val fee = Fee( emulated.extra.value, emulated.extra.isRefund) + val fee = Fee(emulated.extra.value, emulated.extra.isRefund) return SendFee.Ton( amount = fee, @@ -1019,7 +1198,6 @@ class SendViewModel( } private suspend fun eventFee( - transfer: TransferEntity, fee: SendFee, ): SendEvent.Fee? { return try { @@ -1034,7 +1212,7 @@ class SendViewModel( ) } else "", convertedFormat = if (fee is SendFee.TokenFee) { - val rates = ratesRepository.getRates(currency, fee.amount.token.address) + val rates = ratesRepository.getRates(wallet.network, currency, fee.amount.token.address) val converted = rates.convert(fee.amount.token.address, fee.amount.value) CurrencyFormatter.format( currency.code, converted @@ -1085,7 +1263,7 @@ class SendViewModel( }.filterList { it.address.equalsAddress(tokenAddress) }.map { it.firstOrNull()?.balance?.token }.map { token -> - token ?: tokenRepository.getToken(tokenAddress, wallet.testnet) ?: TokenEntity.TON + token ?: tokenRepository.getToken(tokenAddress, wallet.network) ?: TokenEntity.TON }.flowOn(Dispatchers.IO).onEach { token -> userInputToken(token) }.launchIn(viewModelScope) @@ -1120,26 +1298,47 @@ class SendViewModel( val coins = if (amountCurrency) { token.fiat } else { - token.balance.value + token.balance.uiBalance } _uiInputAmountFlow.tryEmit(coins) } } fun setFeeMethod(fee: SendFee) { - val preferredMethod = when (fee) { - is SendFee.Ton -> PreferredFeeMethod.TON - is SendFee.Battery -> PreferredFeeMethod.BATTERY - is SendFee.Gasless -> PreferredFeeMethod.GASLESS - } - settingsRepository.setPreferredFeeMethod(wallet.id, preferredMethod) - preferredFeeMethodFlow.value = preferredMethod + if (fee is SendFee.TronTrx && !fee.enoughBalance) { + viewModelScope.launch { + openScreen(QRScreen.newInstance(wallet = wallet, token = TokenEntity.TRX)) + } + } else if (fee is SendFee.TronTon && !fee.enoughBalance) { + viewModelScope.launch { + openScreen( + QRScreen.newInstance( + wallet = wallet, + token = TokenEntity.TON, + withBuyButton = true + ) + ) + } + } else if (fee is SendFee.Battery && !fee.enoughCharges) { + viewModelScope.launch { + openScreen(BatteryScreen.newInstance(wallet = wallet, from = "send")) + } + } else { + if (selectedTokenFlow.value.isTrc20) { + fee.tronMethod?.let { + settingsRepository.setPreferredTronFeeMethod(wallet.id, it) + } + } else { + fee.method?.let { + settingsRepository.setPreferredFeeMethod(wallet.id, it) + } + } - val tonTx = _tonTransferFlow.value ?: return - viewModelScope.launch(Dispatchers.IO) { - _feeFlow.tryEmit(fee) - eventFee(tonTx, fee)?.let { - _uiFeeFlow.tryEmit(it) + viewModelScope.launch(Dispatchers.IO) { + _feeFlow.tryEmit(fee) + eventFee(fee)?.let { + _uiFeeFlow.tryEmit(it) + } } } } @@ -1148,7 +1347,7 @@ class SendViewModel( wallet: WalletEntity, ): SendMetadataEntity = withContext(Dispatchers.IO) { val seqnoDeferred = async { accountRepository.getSeqno(wallet) } - val validUntilDeferred = async { accountRepository.getValidUntil(wallet.testnet) } + val validUntilDeferred = async { accountRepository.getValidUntil(wallet.network) } val seqno = seqnoDeferred.await() val validUntil = validUntilDeferred.await() @@ -1167,7 +1366,7 @@ class SendViewModel( if (fee is SendFee.Battery) { val batteryCharges = getBatteryCharges() - val batteryConfig = batteryRepository.getConfig(wallet.testnet) + val batteryConfig = batteryRepository.getConfig(wallet.network) val txCharges = BatteryMapper.calculateChargesAmount( Coins.of(abs(fee.extra)).value, batteryConfig.chargeCost @@ -1224,11 +1423,13 @@ class SendViewModel( else -> Coins.of(abs(fee.extra)) + TransferEntity.BASE_FORWARD_AMOUNT } } + is SendFee.Ton -> when { transfer.token.isRequestMinting || transfer.token.customPayloadApiUri != null -> TransferEntity.POINT_ONE_TON fee.amount.isRefund -> TransferEntity.BASE_FORWARD_AMOUNT else -> fee.amount.value + TransferEntity.BASE_FORWARD_AMOUNT } + else -> TransferEntity.POINT_ONE_TON } @@ -1254,7 +1455,6 @@ class SendViewModel( Triple(boc, transfer.wallet, internalMessage) send(boc, wallet, internalMessage) - analytics.simpleTrackEvent("send_success") } fun sign() { @@ -1266,11 +1466,31 @@ class SendViewModel( } else { signTon() } + analytics.events.sendNative.sendSuccess( + from = analyticsFrom, + assetNetwork = currentToken.blockchain.id, + tokenSymbol = currentToken.symbol, + amount = currentAmountDouble, + feePaidIn = currentFeePaidIn, + transactionId = "", + appId = null, + ) _uiEventFlow.tryEmit(SendEvent.Success) } catch (e: Throwable) { + L.d("SendViewModelLog", "sign error", e) if (e is CancellationException) { _uiEventFlow.tryEmit(SendEvent.Canceled) } else { + analytics.events.sendNative.sendFailed( + from = analyticsFrom, + assetNetwork = currentToken.blockchain.id, + tokenSymbol = currentToken.symbol, + amount = currentAmountDouble, + feePaidIn = currentFeePaidIn, + errorCode = 0, + errorMessage = e.message ?: "unknown", + appId = null, + ) FirebaseCrashlytics.getInstance().recordException(e) _uiEventFlow.tryEmit(SendEvent.Failed(e)) } @@ -1279,11 +1499,16 @@ class SendViewModel( } private suspend fun signTron() { + val fee = feeFlow.value ?: throw IllegalStateException("Fee is null") val transfer = tronTransferFlow.value ?: throw IllegalStateException("Tron transfer is null") val transaction = api.tron.buildSmartContractTransaction(transfer).extendExpiration() val resources = _tronResourcesFlow.value ?: throw IllegalStateException("Tron resources is null") + val privateKey = accountRepository.getPrivateKey(wallet.id) + ?: throw IllegalStateException("Private key is null") + val tonToken = tokenRepository.getTON(currency, wallet.accountId, wallet.network) + ?: throw IllegalStateException("TON token is not found") val signedTransaction = signUseCase( context = context, @@ -1294,13 +1519,51 @@ class SendViewModel( val tonProofToken = accountRepository.requestTonProofToken(wallet) ?: throw IllegalStateException("TonProofToken is null") - api.tron.sendTransaction( - transaction = signedTransaction, - resources = resources, - tronAddress = transfer.from, - tonProofToken = tonProofToken, - ) - analytics.simpleTrackEvent("send_success") + when (fee) { + is SendFee.Battery -> { + api.tron.sendWithBattery( + transaction = signedTransaction, + resources = resources, + tronAddress = transfer.from, + tonProofToken = tonProofToken, + ) + } + + is SendFee.TronTon -> { + val sendMetadata = getSendParams(wallet) + val instantFeeTx = TransferEntity.Builder(wallet) + .setToken(tonToken.balance) + .setSeqno(sendMetadata.seqno) + .setValidUntil(sendMetadata.validUntil) + .setAmount(fee.amount.value) + .setComment("Tron gas fee", false) + .setDestination(AddrStd(fee.sendToAddress), EmptyPrivateKeyEd25519.publicKey()) + .build().sign( + privateKey = privateKey, + jettonTransferAmount = TransferEntity.BASE_FORWARD_AMOUNT + ) + L.d("SendViewModelLog", "sendToAddress: ${fee.sendToAddress}") + api.tron.sendWithTon( + transaction = signedTransaction, + instantFeeTx = instantFeeTx, + resources = resources, + tronAddress = transfer.from, + userPublicKey = wallet.publicKey.base64() + ) + } + + is SendFee.TronTrx -> { + api.tron.sendWithTrx( + transaction = signedTransaction, + resources = resources, + tronAddress = transfer.from, + ) + } + + else -> { + throw IllegalStateException("Invalid fee type for tron transfer") + } + } getBatteryBalance() } @@ -1335,7 +1598,7 @@ class SendViewModel( if (tokenCustomPayload == null) { tokenCustomPayload = - api.getJettonCustomPayload(wallet.accountId, wallet.testnet, token.address) + api.getJettonCustomPayload(wallet.accountId, wallet.network, token.address) } return tokenCustomPayload ?: TokenEntity.TransferPayload.empty(token.address) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/InsufficientBalanceType.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/InsufficientBalanceType.kt index 84d92c30f..4524ee4a9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/InsufficientBalanceType.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/InsufficientBalanceType.kt @@ -8,7 +8,7 @@ enum class InsufficientBalanceType { InsufficientGaslessBalance, InsufficientBalanceWithFee, InsufficientBatteryChargesForFee, - InsufficientBalanceForFee + InsufficientBalanceForFee, } fun InsufficientBalanceType.isTON(): Boolean { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFee.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFee.kt index e83b182c7..7df059fe0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFee.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFee.kt @@ -3,8 +3,10 @@ package com.tonapps.tonkeeper.ui.screen.send.main.state import com.tonapps.icu.Coins import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.core.Fee +import com.tonapps.tonkeeper.core.entities.TransferEntity import com.tonapps.tonkeeper.extensions.symbol import com.tonapps.wallet.data.core.currency.WalletCurrency +import io.batteryapi.models.EstimatedTronTx import org.ton.block.AddrStd sealed class SendFee { @@ -34,14 +36,42 @@ sealed class SendFee { override val amount: Fee, override val fiatAmount: Coins, override val fiatCurrency: WalletCurrency, - override val excessesAddress: AddrStd + override val excessesAddress: AddrStd, ) : SendFee(), TokenFee, RelayerFee data class Battery( val charges: Int, val chargesBalance: Int, override val extra: Long, - override val excessesAddress: AddrStd - ) : SendFee(), RelayerFee, Extra + override val excessesAddress: AddrStd, + val fiatAmount: Coins, + val fiatCurrency: WalletCurrency, + val estimatedTron: EstimatedTronTx? = null, + val excessCharges: Long? = null, + ) : SendFee(), RelayerFee, Extra { + val enoughCharges : Boolean + get() = chargesBalance >= charges + } + + data class TronTrx( + override val amount: Fee, + override val fiatAmount: Coins, + override val fiatCurrency: WalletCurrency, + val balance: Coins, + ) : SendFee(), TokenFee { + val enoughBalance : Boolean + get() = balance >= amount.value + } + + data class TronTon( + override val amount: Fee, + override val fiatAmount: Coins, + override val fiatCurrency: WalletCurrency, + val sendToAddress: String, + val balance: Coins, + ) : SendFee(), TokenFee { + val enoughBalance : Boolean + get() = balance >= amount.value + TransferEntity.POINT_ONE_TON + } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionArgs.kt index fb9d542bb..9f259319a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionArgs.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.send.transaction import android.os.Bundle +import com.tonapps.bus.generated.Events import com.tonapps.extensions.getParcelableCompat import com.tonapps.wallet.data.core.entity.SignRequestEntity import com.tonapps.wallet.data.settings.BatteryTransaction @@ -9,19 +10,24 @@ import uikit.base.BaseArgs data class SendTransactionArgs( val request: SignRequestEntity, val batteryTransactionType: BatteryTransaction, - val forceRelayer: Boolean + val forceRelayer: Boolean, + val sendNativeFrom: Events.SendNative.SendNativeFrom? = null ): BaseArgs() { companion object { private const val ARG_REQUEST = "request" private const val ARG_BATTERY_TRANSACTION_TYPE = "battery_transaction_type" private const val ARG_FORCE_RELAYER = "force_relayer" + private const val ARG_SEND_NATIVE_FROM = "send_native_from" } constructor(bundle: Bundle) : this( request = bundle.getParcelableCompat(ARG_REQUEST)!!, batteryTransactionType = BatteryTransaction.of(bundle.getInt(ARG_BATTERY_TRANSACTION_TYPE, -1)), - forceRelayer = bundle.getBoolean(ARG_FORCE_RELAYER) + forceRelayer = bundle.getBoolean(ARG_FORCE_RELAYER), + sendNativeFrom = bundle.getString(ARG_SEND_NATIVE_FROM)?.let { key -> + Events.SendNative.SendNativeFrom.entries.find { it.key == key } + } ) override fun toBundle(): Bundle { @@ -29,6 +35,7 @@ data class SendTransactionArgs( bundle.putParcelable(ARG_REQUEST, request) bundle.putInt(ARG_BATTERY_TRANSACTION_TYPE, batteryTransactionType.code) bundle.putBoolean(ARG_FORCE_RELAYER, forceRelayer) + sendNativeFrom?.let { bundle.putString(ARG_SEND_NATIVE_FROM, it.key) } return bundle } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionScreen.kt index 195486f1c..ec77bfa2d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionScreen.kt @@ -7,27 +7,31 @@ import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan -import android.util.Log import android.view.View import androidx.appcompat.widget.AppCompatTextView import androidx.core.view.doOnNextLayout +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tonapps.bus.generated.Events import com.tonapps.extensions.getParcelableCompat import com.tonapps.tonkeeper.core.history.list.HistoryAdapter +import com.tonapps.tonkeeper.extensions.addFeeItem import com.tonapps.tonkeeper.extensions.getTitle +import com.tonapps.tonkeeper.extensions.id import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.manager.tonconnect.bridge.BridgeException import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeError +import com.tonapps.tonkeeper.popup.ActionSheet import com.tonapps.tonkeeper.ui.base.WalletContextScreen import com.tonapps.tonkeeper.ui.screen.send.InsufficientFundsDialog +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentOrangeColor import com.tonapps.uikit.color.resolveColor import com.tonapps.uikit.color.textSecondaryColor import com.tonapps.uikit.icon.UIKitIcon -import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.core.entity.SignRequestEntity import com.tonapps.wallet.data.settings.BatteryTransaction @@ -44,6 +48,8 @@ import uikit.extensions.addForResult import uikit.extensions.applyNavBottomMargin import uikit.extensions.bottomScrolled import uikit.extensions.collectFlow +import uikit.extensions.dp +import uikit.extensions.getDimensionPixelSize import uikit.extensions.isMaxScrollReached import uikit.extensions.setOnClickListener import uikit.extensions.setRightDrawable @@ -54,21 +60,6 @@ import uikit.widget.ProcessTaskView import uikit.widget.SimpleRecyclerView import uikit.widget.SlideActionView import java.util.concurrent.CancellationException -import androidx.core.view.isVisible -import com.tonapps.extensions.uri -import com.tonapps.icu.CurrencyFormatter -import com.tonapps.tonkeeper.extensions.addFeeItem -import com.tonapps.tonkeeper.extensions.formattedAmount -import com.tonapps.tonkeeper.extensions.formattedCharges -import com.tonapps.tonkeeper.extensions.formattedFiat -import com.tonapps.tonkeeper.extensions.id -import com.tonapps.tonkeeper.extensions.symbol -import com.tonapps.tonkeeper.popup.ActionSheet -import com.tonapps.tonkeeper.ui.screen.send.main.state.SendFee -import com.tonapps.uikit.color.accentGreenColor -import com.tonapps.wallet.localization.Plurals -import uikit.extensions.dp -import uikit.extensions.getDimensionPixelSize class SendTransactionScreen(wallet: WalletEntity) : WalletContextScreen(R.layout.fragment_send_transaction, wallet), BaseFragment.Modal, @@ -83,7 +74,7 @@ class SendTransactionScreen(wallet: WalletEntity) : } override val viewModel: SendTransactionViewModel by walletViewModel { - parametersOf(args.request, args.batteryTransactionType, args.forceRelayer) + parametersOf(args.request, args.batteryTransactionType, args.forceRelayer, args.sendNativeFrom) } private val feeMethodSelector: ActionSheet by lazy { @@ -191,7 +182,7 @@ class SendTransactionScreen(wallet: WalletEntity) : ) setSpan( ForegroundColorSpan( - requireContext().resolveColor(com.tonapps.uikit.color.R.attr.textTertiaryColor) + requireContext().resolveColor(com.tonapps.ui.uikit.color.R.attr.textTertiaryColor) .withAlpha(0.7f) ), 0, secondLineText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) @@ -374,10 +365,11 @@ class SendTransactionScreen(wallet: WalletEntity) : wallet: WalletEntity, request: SignRequestEntity, batteryTransactionType: BatteryTransaction = BatteryTransaction.UNKNOWN, - forceRelayer: Boolean = false + forceRelayer: Boolean = false, + sendNativeFrom: Events.SendNative.SendNativeFrom? = null ): SendTransactionScreen { val screen = SendTransactionScreen(wallet) - screen.setArgs(SendTransactionArgs(request, batteryTransactionType, forceRelayer)) + screen.setArgs(SendTransactionArgs(request, batteryTransactionType, forceRelayer, sendNativeFrom)) return screen } @@ -387,9 +379,10 @@ class SendTransactionScreen(wallet: WalletEntity) : request: SignRequestEntity, batteryTxType: BatteryTransaction = BatteryTransaction.UNKNOWN, forceRelayer: Boolean = false, + sendNativeFrom: Events.SendNative.SendNativeFrom? = null, ): String { val activity = context.activity ?: throw IllegalArgumentException("Context must be an Activity") - val fragment = newInstance(wallet, request, batteryTxType, forceRelayer) + val fragment = newInstance(wallet, request, batteryTxType, forceRelayer, sendNativeFrom) val result = activity.addForResult(fragment) if (result.containsKey(ERROR)) { val error = result.getParcelableCompat(ERROR)!! diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionViewModel.kt index d1f978ae7..02f367da0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/transaction/SendTransactionViewModel.kt @@ -8,9 +8,11 @@ import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.icu.Coins import com.tonapps.ledger.ton.Transaction import com.tonapps.tonkeeper.core.Amount -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.tonkeeper.core.history.HistoryHelper import com.tonapps.tonkeeper.extensions.getTransfers +import com.tonapps.tonkeeper.extensions.method import com.tonapps.tonkeeper.helper.BatteryHelper import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.ui.base.BaseWalletVM @@ -55,6 +57,7 @@ class SendTransactionViewModel( private val request: SignRequestEntity, private val batteryTransactionType: BatteryTransaction, private val forceRelayer: Boolean, + private val sendNativeFrom: Events.SendNative.SendNativeFrom?, private val accountRepository: AccountRepository, private val tokenRepository: TokenRepository, private val settingsRepository: SettingsRepository, @@ -240,7 +243,7 @@ class SendTransactionViewModel( val balance = tokenRepository.getTON( settingsRepository.currency, wallet.accountId, - wallet.testnet + wallet.network )?.balance?.value return balance ?: Coins.ZERO } @@ -279,8 +282,27 @@ class SendTransactionViewModel( ) } + private val currentFeePaidIn: Events.SendNative.SendNativeFeePaidIn + get() = if (isBattery.get()) { + Events.SendNative.SendNativeFeePaidIn.Battery + } else { + Events.SendNative.SendNativeFeePaidIn.Ton + } + fun send() = flow { val isBattery = isBattery.get() + +// sendNativeFrom?.let { from -> +// analytics.events.sendNative.sendConfirm( +// from = from, +// assetNetwork = "ton", +// tokenSymbol = "TON", +// amount = 0.0, +// feePaidIn = currentFeePaidIn, +// appId = request.appUri.host, +// ) +// } + val compressedTokens = getTokens().filter { it.isRequestMinting } val transfers = transfers(compressedTokens, false, isBattery) val message = messageBody(transfers) @@ -345,8 +367,31 @@ class SendTransactionViewModel( address = request.targetAddressValue, feePaid = feePaid ) +// sendNativeFrom?.let { from -> +// analytics.events.sendNative.sendSuccess( +// from = from, +// assetNetwork = "ton", +// tokenSymbol = "TON", +// amount = 0.0, +// feePaidIn = currentFeePaidIn, +// transactionId = "", +// appId = request.appUri.host, +// ) +// } emit(cells.map { it.base64() }.toTypedArray()) } else { +// sendNativeFrom?.let { from -> +// analytics.events.sendNative.sendFailed( +// from = from, +// assetNetwork = "ton", +// tokenSymbol = "TON", +// amount = 0.0, +// feePaidIn = currentFeePaidIn, +// errorCode = 0, +// errorMessage = "Failed to send transaction to blockchain: $states", +// appId = request.appUri.host, +// ) +// } throw IllegalStateException("Failed to send transaction to blockchain: $states") } }.flowOn(Dispatchers.IO) @@ -355,7 +400,7 @@ class SendTransactionViewModel( return emulationReadyDate.get() - System.currentTimeMillis() } - // private suspend fun getTonBalance() = tokenRepository.getTonBalance(settingsRepository.currency, wallet.accountId, wallet.testnet) + // private suspend fun getTonBalance() = tokenRepository.getTonBalance(settingsRepository.currency, wallet.accountId, wallet.network) private suspend fun transfers( compressedTokens: List, @@ -363,7 +408,7 @@ class SendTransactionViewModel( batteryEnabled: Boolean ): List { val excessesAddress = if (!forEmulation && isBattery.get()) { - batteryRepository.getConfig(wallet.testnet).excessesAddress + batteryRepository.getConfig(wallet.network).excessesAddress } else null return request.getTransfers( @@ -377,16 +422,13 @@ class SendTransactionViewModel( } private suspend fun getTokens(): List { - return tokenRepository.get(currency, wallet.accountId, wallet.testnet, true) ?: emptyList() + return tokenRepository.get(currency, wallet.accountId, wallet.network, true) ?: emptyList() } fun setFeeMethod(fee: SendFee) { - val preferredMethod = when (fee) { - is SendFee.Ton -> PreferredFeeMethod.TON - is SendFee.Battery -> PreferredFeeMethod.BATTERY - is SendFee.Gasless -> PreferredFeeMethod.GASLESS + fee.method?.let { + settingsRepository.setPreferredFeeMethod(wallet.id, it) } - settingsRepository.setPreferredFeeMethod(wallet.id, preferredMethod) if (fee is SendFee.Battery) { _stateFlow.value = batteryDetails!! diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsScreen.kt index 716cb901f..dfd2e49f9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsScreen.kt @@ -1,7 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.settings.apps import android.os.Bundle -import android.util.Log import android.view.View import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.BaseListWalletScreen diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsViewModel.kt index 45e8fea9e..d4545dd0a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/apps/AppsViewModel.kt @@ -1,21 +1,14 @@ package com.tonapps.tonkeeper.ui.screen.settings.apps import android.app.Application -import android.util.Log -import androidx.lifecycle.viewModelScope -import com.tonapps.extensions.mapList -import com.tonapps.extensions.singleValue import com.tonapps.tonkeeper.manager.tonconnect.TonConnectManager import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.settings.apps.list.Item import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.dapps.entities.AppEntity -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take -import kotlinx.coroutines.launch class AppsViewModel( application: Application, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/extensions/ExtensionsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/extensions/ExtensionsViewModel.kt index 17edb7a24..36d72ac0c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/extensions/ExtensionsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/extensions/ExtensionsViewModel.kt @@ -18,7 +18,7 @@ class ExtensionsViewModel( val uiItemsFlow = pluginsRepository.updatedFlow.map { _ -> val plugins = - pluginsRepository.getPlugins(wallet.accountId, wallet.testnet, refresh = false) + pluginsRepository.getPlugins(wallet.accountId, wallet.network, refresh = false) plugins.mapIndexed { index, plugin -> Item.Plugin( plugin = plugin, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt index 10d1f7a9f..149cbb8dd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt @@ -8,7 +8,7 @@ import com.google.android.gms.tasks.Task import com.google.android.play.core.review.ReviewInfo import com.google.android.play.core.review.ReviewManager import com.google.android.play.core.review.ReviewManagerFactory -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.toastLoading import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.manager.widget.WidgetManager diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsViewModel.kt index 12f76b860..c7e364f5f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsViewModel.kt @@ -9,7 +9,7 @@ import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.blockchain.ton.extensions.toAccountId import com.tonapps.extensions.appVersionCode import com.tonapps.tonkeeper.Environment -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.FirebaseHelper import com.tonapps.tonkeeper.core.entities.AssetsEntity import com.tonapps.tonkeeper.core.entities.AssetsExtendedEntity @@ -67,7 +67,7 @@ class SettingsViewModel( private val analytics: AnalyticsHelper ) : BaseWalletVM(application) { - private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api) + private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api, wallet.network) private val _uiItemsFlow = MutableStateFlow>(emptyList()) val uiItemsFlow = _uiItemsFlow.asStateFlow().filter { it.isNotEmpty() } @@ -84,16 +84,23 @@ class SettingsViewModel( } private val tokensFlow = settingsRepository.tokenPrefsChangedFlow.map { _ -> - tokenRepository.mustGet(settingsRepository.currency, wallet.accountId, wallet.testnet).mapNotNull { token -> - if (safeMode && !token.verified) { - return@mapNotNull null + tokenRepository.mustGet(settingsRepository.currency, wallet.accountId, wallet.network) + .mapNotNull { token -> + if (safeMode && !token.verified) { + return@mapNotNull null + } + AssetsExtendedEntity( + raw = AssetsEntity.Token(token), + prefs = settingsRepository.getTokenPrefs( + wallet.id, + token.address, + token.blacklist + ), + accountId = wallet.accountId, + ) } - AssetsExtendedEntity( - raw = AssetsEntity.Token(token), - prefs = settingsRepository.getTokenPrefs(wallet.id, token.address, token.blacklist), - accountId = wallet.accountId, - ) - }.filter { !it.isTon }.sortedBy { it.index } + .filter { !it.isTon } + .sortedBy { it.index } } init { @@ -151,7 +158,7 @@ class SettingsViewModel( names = listOf(newLabel.name), emoji = newLabel.emoji, color = newLabel.color, - ), mnemonic, versions, wallet.testnet, listOf(false) + ), mnemonic, versions, wallet.type, listOf(false) ) backupRepository.addBackup(walletId) accountRepository.setSelectedWallet(walletId) @@ -181,7 +188,7 @@ class SettingsViewModel( FirebaseHelper.trc20Enabled(!isHidden) if (!isHidden) { - settingsRepository.setTokenPinned(wallet.id, TokenEntity.TRC20_USDT , true) + settingsRepository.setTokenPinned(wallet.id, TokenEntity.TRC20_USDT, true) settingsRepository.setTokensSort(wallet.id, sortAddresses) } } @@ -193,9 +200,9 @@ class SettingsViewModel( } else if (wallet.type == Wallet.Type.Watch || wallet.type == Wallet.Type.Lockup || wallet.type == Wallet.Type.Ledger) { return true } - val w5Contact = BaseWalletContract.create(wallet.publicKey, "v5r1", wallet.testnet) + val w5Contact = BaseWalletContract.create(wallet.publicKey, "v5r1", wallet.network) val accountId = w5Contact.address.toAccountId() - return accountRepository.getWalletByAccountId(accountId, wallet.testnet) != null + return accountRepository.getWalletByAccountId(accountId, wallet.network) != null } private suspend fun hasV4R2(): Boolean { @@ -205,9 +212,9 @@ class SettingsViewModel( if (wallet.type == Wallet.Type.Watch || wallet.type == Wallet.Type.Lockup || wallet.type == Wallet.Type.Ledger) { return true } - val v4R2Contact = BaseWalletContract.create(wallet.publicKey, "v4r2", wallet.testnet) + val v4R2Contact = BaseWalletContract.create(wallet.publicKey, "v4r2", wallet.network) val accountId = v4R2Contact.address.toAccountId() - return accountRepository.getWalletByAccountId(accountId, wallet.testnet) != null + return accountRepository.getWalletByAccountId(accountId, wallet.network) != null } private suspend fun buildUiItems( @@ -217,6 +224,7 @@ class SettingsViewModel( searchEngine: SearchEngine, hasBackup: Boolean ) { + val config = api.getConfig(wallet.network) val hasW5 = hasW5() val hasV4R2 = hasV4R2() val uiItems = mutableListOf() @@ -232,7 +240,7 @@ class SettingsViewModel( uiItems.add(Item.Space) - if (wallet.hasPrivateKey && !wallet.testnet && !api.config.flags.disableTron) { + if (wallet.hasPrivateKey && wallet.network.isMainnet && !config.flags.disableTron) { val tronUsdtEnabled = settingsRepository.getTronUsdtEnabled(displayWallet.id) uiItems.add(Item.TronToggle(enabled = tronUsdtEnabled)) uiItems.add(Item.Space) @@ -276,7 +284,7 @@ class SettingsViewModel( }.capitalized, ListCell.Position.MIDDLE)) val batteryCharges = getBatteryCharges() - if (wallet.hasPrivateKey && (!api.config.flags.disableBattery || batteryCharges > 0)) { + if (wallet.hasPrivateKey && (!config.flags.disableBattery || batteryCharges > 0)) { uiItems.add(Item.Battery(ListCell.Position.MIDDLE)) } if (WidgetManager.isRequestPinAppWidgetSupported) { @@ -285,10 +293,10 @@ class SettingsViewModel( uiItems.add(Item.Theme(ListCell.Position.LAST)) uiItems.add(Item.Space) - uiItems.add(Item.FAQ(ListCell.Position.FIRST, api.config.faqUrl)) + uiItems.add(Item.FAQ(ListCell.Position.FIRST, config.faqUrl)) uiItems.add(Item.Support(ListCell.Position.MIDDLE, getSupportUrl())) - uiItems.add(Item.News(ListCell.Position.MIDDLE, api.config.tonkeeperNewsUrl)) - uiItems.add(Item.Contact(ListCell.Position.MIDDLE, api.config.supportLink)) + uiItems.add(Item.News(ListCell.Position.MIDDLE, config.tonkeeperNewsUrl)) + uiItems.add(Item.Contact(ListCell.Position.MIDDLE, config.supportLink)) if (environment.isGooglePlayServicesAvailable) { uiItems.add(Item.Rate(ListCell.Position.MIDDLE)) } @@ -308,19 +316,19 @@ class SettingsViewModel( private fun getSupportUrl(): String { val startParams = "android${Build.VERSION.SDK_INT}app${context.appVersionCode}" - val builder = api.config.directSupportUrl.toUri().buildUpon() + val builder = api.getConfig(wallet.network).directSupportUrl.toUri().buildUpon() builder.appendQueryParameter("start", startParams) return builder.toString() } private suspend fun getBatteryCharges(): Int = withContext(Dispatchers.IO) { accountRepository.requestTonProofToken(wallet)?.let { - batteryRepository.getCharges(it, wallet.publicKey, wallet.testnet, true) + batteryRepository.getCharges(it, wallet.publicKey, wallet.network, true) } ?: 0 } private suspend fun hasInstalledExtensions(): Boolean = withContext(Dispatchers.IO) { - val plugins = pluginsRepository.getPlugins(wallet.accountId, wallet.testnet) + val plugins = pluginsRepository.getPlugins(wallet.accountId, wallet.network) plugins.isNotEmpty() } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/Item.kt index c4c40f241..d91eff248 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/Item.kt @@ -112,7 +112,7 @@ sealed class Item(type: Int, val name: String): BaseListItem(type) { override val position: ListCell.Position ): Icon( titleRes = Localization.widget, - iconRes = com.tonapps.uikit.icon.R.drawable.ic_link_square_28, + iconRes = com.tonapps.ui.uikit.icon.R.drawable.ic_link_square_28, position = position, secondaryIcon = false, name = "widget" diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/holder/TronHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/holder/TronHolder.kt index 0c8e3bcdd..5c52434e4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/holder/TronHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/list/holder/TronHolder.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.settings.main.list.holder import android.view.ViewGroup import androidx.appcompat.widget.AppCompatTextView +import com.tonapps.tonkeeper.koin.serverConfig import com.tonapps.tonkeeper.ui.screen.settings.main.list.Item import com.tonapps.tonkeeperx.R import com.tonapps.uikit.list.ListCell @@ -17,8 +18,12 @@ class TronHolder( ) : Holder(parent, R.layout.view_tron_toggle, onClick) { private val titleView = findViewById(R.id.title) + private val descriptionView = findViewById(R.id.description) private val switchView = findViewById(R.id.toggle) + val isBatteryDisabled: Boolean + get() = context.serverConfig?.flags?.disableBattery == true + override fun onBind(item: Item.TronToggle) { itemView.background = ListCell.Position.SINGLE.drawable(context) @@ -31,5 +36,11 @@ class TronHolder( } titleView.text = TokenEntity.USDT.symbol.withDefaultBadge(context, Localization.trc20) + + descriptionView.text = if (isBatteryDisabled) { + context.getString(Localization.tron_toggle_trc_text) + } else { + context.getString(Localization.tron_toggle_text) + } } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityScreen.kt index 4486dee04..8f52741ce 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityScreen.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.settings.security import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.appcompat.widget.AppCompatTextView import androidx.lifecycle.lifecycleScope diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityViewModel.kt index 94900306f..3a38c63c6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/security/SecurityViewModel.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.settings.security import android.app.Application import android.content.Context +import com.tonapps.blockchain.ton.TonNetwork import com.tonapps.tonkeeper.core.FirebaseHelper import com.tonapps.tonkeeper.extensions.isSafeModeEnabled import com.tonapps.tonkeeper.ui.base.BaseWalletVM @@ -35,7 +36,7 @@ class SecurityViewModel( val safeModeFlow: Flow get() = settingsRepository.safeModeStateFlow - fun isSafeModeEnabled() = settingsRepository.isSafeModeEnabled(api) + fun isSafeModeEnabled() = settingsRepository.isSafeModeEnabled(api, TonNetwork.MAINNET) fun setSafeModeState(state: SafeModeState) { settingsRepository.setSafeModeState(state) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/ThemeScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/ThemeScreen.kt index 04da28f30..debef3d6b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/ThemeScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/ThemeScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.settings.theme import android.graphics.Rect import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/list/holder/IconHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/list/holder/IconHolder.kt index 791a82eea..79c977ee3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/list/holder/IconHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/theme/list/holder/IconHolder.kt @@ -7,7 +7,7 @@ import android.graphics.Paint import android.graphics.Path import android.graphics.RectF import android.graphics.drawable.AdaptiveIconDrawable -import android.util.Log +import com.tonapps.log.L import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.widget.AppCompatImageView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/sign/SignDataScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/sign/SignDataScreen.kt index 014fa1c16..1d9c24aed 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/sign/SignDataScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/sign/SignDataScreen.kt @@ -9,7 +9,7 @@ import android.text.SpannableStringBuilder import android.text.method.ScrollingMovementMethod import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.appcompat.widget.AppCompatTextView import androidx.core.text.color @@ -86,7 +86,7 @@ class SignDataScreen(wallet: WalletEntity): BaseWalletScreen - val tokens = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.testnet) ?: emptyList() + val tokens = tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) ?: emptyList() tokens.firstOrNull() }.filterNotNull() @@ -106,7 +106,7 @@ class StakingViewModel( } private val ratesFlow = tokenFlow.map { token -> - ratesRepository.getRates(settingsRepository.currency, token.address) + ratesRepository.getRates(wallet.network, settingsRepository.currency, token.address) }.flowOn(Dispatchers.IO) val availableUiStateFlow = combine( @@ -192,8 +192,8 @@ class StakingViewModel( updateAmount(0.0) viewModelScope.launch(Dispatchers.IO) { - _poolsFlow.value = stakingRepository.get(wallet.accountId, wallet.testnet).pools.filter { - api.config.enabledStaking.contains(it.implementation.title) + _poolsFlow.value = stakingRepository.get(wallet.accountId, wallet.network).pools.filter { + api.getConfig(wallet.network).enabledStaking.contains(it.implementation.title) } } } @@ -214,7 +214,7 @@ class StakingViewModel( wallet: WalletEntity, ): SendMetadataEntity = withContext(Dispatchers.IO) { val seqnoDeferred = async { accountRepository.getSeqno(wallet) } - val validUntilDeferred = async { accountRepository.getValidUntil(wallet.testnet) } + val validUntilDeferred = async { accountRepository.getValidUntil(wallet.network) } SendMetadataEntity( seqno = seqnoDeferred.await(), @@ -305,7 +305,7 @@ class StakingViewModel( selectedPoolFlow, ) { extra, pool -> val currency = settingsRepository.currency - val rates = ratesRepository.getTONRates(currency) + val rates = ratesRepository.getTONRates(wallet.network, currency) val fee = StakingPool.getTotalFee(extra.value, pool.implementation) val fiat = rates.convertTON(fee) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/UnStakeViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/UnStakeViewModel.kt index 4aa717396..2def9ff90 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/UnStakeViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/UnStakeViewModel.kt @@ -117,7 +117,7 @@ class UnStakeViewModel( ) { amount, stake -> val balance = stake.balance val balanceFormat = CurrencyFormatter.format(token, balance) - val rates = ratesRepository.getRates(currency, token) + val rates = ratesRepository.getRates(wallet.network, currency, token) val fiat = rates.convert(token, amount) val fiatFormat = CurrencyFormatter.format(currency.code, fiat, replaceSymbol = false) if (amount == Coins.ZERO) { @@ -148,7 +148,7 @@ class UnStakeViewModel( val tokenFlow = poolFlow.map { pool -> val tokens = - tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.testnet) + tokenRepository.get(settingsRepository.currency, wallet.accountId, wallet.network) ?: emptyList() tokens.firstOrNull() @@ -175,7 +175,7 @@ class UnStakeViewModel( _stakeFlow.value = staked _poolInfoFlow.value = stakingRepository.get( wallet.accountId, - wallet.testnet + wallet.network ).pools.find { it.implementation == staked?.pool?.implementation } } } @@ -206,7 +206,7 @@ class UnStakeViewModel( poolFlow ) { extra, pool -> val currency = settingsRepository.currency - val rates = ratesRepository.getTONRates(currency) + val rates = ratesRepository.getTONRates(wallet.network, currency) val fee = StakingPool.getTotalFee(extra.value, pool.implementation) val fiat = rates.convertTON(fee) @@ -294,7 +294,7 @@ class UnStakeViewModel( val tokens = tokenRepository.get( currency = settingsRepository.currency, accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ) ?: return null return tokens.find { it.address.equalsAddress(tokenAddress) } } @@ -311,7 +311,7 @@ class UnStakeViewModel( testnet = wallet.testnet ) - val rates = ratesRepository.getRates(WalletCurrency.TON, tsTONToken.address) + val rates = ratesRepository.getRates(wallet.network, WalletCurrency.TON, tsTONToken.address) val tokenRate = rates.getRate(tsTONToken.address) val convertedAmount = Coins.of((amount / tokenRate).value, tsTONToken.decimals) @@ -370,7 +370,7 @@ class UnStakeViewModel( wallet: WalletEntity, ): SendMetadataEntity = withContext(Dispatchers.IO) { val seqnoDeferred = async { accountRepository.getSeqno(wallet) } - val validUntilDeferred = async { accountRepository.getValidUntil(wallet.testnet) } + val validUntilDeferred = async { accountRepository.getValidUntil(wallet.network) } SendMetadataEntity( seqno = seqnoDeferred.await(), @@ -381,8 +381,8 @@ class UnStakeViewModel( private suspend fun loadStake(): StakedEntity? { try { val tokens = - tokenRepository.get(currency, wallet.accountId, wallet.testnet) ?: return null - val staking = stakingRepository.get(wallet.accountId, wallet.testnet) + tokenRepository.get(currency, wallet.accountId, wallet.network) ?: return null + val staking = stakingRepository.get(wallet.accountId, wallet.network) val staked = StakedEntity.create(wallet, staking, tokens, currency, ratesRepository, api) return staked.find { it.pool.address.equalsAddress(poolAddress) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/amount/UnStakeAmountFragment.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/amount/UnStakeAmountFragment.kt index 598b9e8ec..ebd84d6a0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/amount/UnStakeAmountFragment.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/unstake/amount/UnStakeAmountFragment.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.staking.unstake.amount import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import android.widget.Button import androidx.appcompat.widget.AppCompatTextView diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/StakeViewerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/StakeViewerViewModel.kt index cf467bee4..dccb961a4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/StakeViewerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/StakeViewerViewModel.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import java.math.BigDecimal class StakeViewerViewModel( app: Application, @@ -45,7 +46,7 @@ class StakeViewerViewModel( ) : BaseWalletVM(app) { val usdeDisabled: Boolean - get() = api.config.flags.disableUsde + get() = api.getConfig(wallet.network).flags.disableUsde private val ethenaMethodType: EthenaEntity.Method.Type? = if (ethenaType.isNotEmpty()) EthenaEntity.Method.Type.fromId(ethenaType) else null @@ -77,6 +78,7 @@ class StakeViewerViewModel( } val rates = ratesRepository.getRates( + wallet.network, settingsRepository.currency, listOfNotNull(tokenUsde.address, tokenTsUsde.address) ) @@ -102,12 +104,14 @@ class StakeViewerViewModel( hiddenBalance = settingsRepository.hiddenBalances, ) ) - if (!usdeDisabled) { + + if (!usdeDisabled || balance.isPositive) { uiItems.add( Item.Actions( wallet = wallet, ethenaMethod = method, - unstakeDisabled = balance.isZero + unstakeDisabled = balance.isZero, + stakeDisabled = usdeDisabled, ) ) uiItems.add(Item.Space) @@ -170,7 +174,7 @@ class StakeViewerViewModel( val liquidToken = staked.liquidToken val currencyCode = TokenEntity.TON.symbol val rates = ratesRepository.getRates( - currency, listOfNotNull( + wallet.network, currency, listOfNotNull( currencyCode, liquidToken?.token?.address ) ) @@ -192,13 +196,16 @@ class StakeViewerViewModel( ) ) - val stakingDisabled = !api.config.enabledStaking.contains(staked.pool.implementation.title) || api.config.flags.disableStaking + val config = api.getConfig(wallet.network) + val stakingDisabled = !config.enabledStaking.contains(staked.pool.implementation.title) || config.flags.disableStaking - if (!stakingDisabled) { + if (!stakingDisabled || amount.isPositive) { uiItems.add( Item.Actions( wallet = wallet, poolAddress = poolAddress, + unstakeDisabled = amount.isZero, + stakeDisabled = stakingDisabled, ) ) } @@ -268,7 +275,7 @@ class StakeViewerViewModel( private suspend fun getData(refresh: Boolean = false) { val tokens = - tokenRepository.get(currency, wallet.accountId, wallet.testnet, refresh = refresh) + tokenRepository.get(currency, wallet.accountId, wallet.network, refresh = refresh) ?: return _tokensFlow.value = tokens @@ -279,7 +286,7 @@ class StakeViewerViewModel( } ethenaData?.let { _ethenaDataFlow.value = it } - val staking = stakingRepository.get(wallet.accountId, wallet.testnet) + val staking = stakingRepository.get(wallet.accountId, wallet.network) val staked = StakedEntity.create(wallet, staking, tokens, currency, ratesRepository, api) val item = staked.find { it.pool.address.equalsAddress(poolAddress) } ?: return diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/Item.kt index 81c7d0c33..329328c10 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/Item.kt @@ -33,9 +33,9 @@ sealed class Item(type: Int): BaseListItem(type) { val currencyIcon: Int by lazy { if (ethenaType != null) { - com.tonapps.wallet.api.R.drawable.ic_udse_ethena_with_bg + com.tonapps.apps.wallet.api.R.drawable.ic_udse_ethena_with_bg } else { - com.tonapps.wallet.api.R.drawable.ic_ton_with_bg + com.tonapps.apps.wallet.api.R.drawable.ic_ton_with_bg } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/holder/ActionsHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/holder/ActionsHolder.kt index e9b5eeb30..cfed4d95d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/holder/ActionsHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/viewer/list/holder/ActionsHolder.kt @@ -57,6 +57,4 @@ class ActionsHolder( } } } - - } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/withdraw/StakeWithdrawViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/withdraw/StakeWithdrawViewModel.kt index 599a8509d..b7e65cd20 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/withdraw/StakeWithdrawViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/withdraw/StakeWithdrawViewModel.kt @@ -88,7 +88,7 @@ class StakeWithdrawViewModel( } val amountFormatFlow = amountFlow.map { amount -> - val fiat = ratesRepository.getTONRates(currency).convertTON(amount) + val fiat = ratesRepository.getTONRates(wallet.network, currency).convertTON(amount) val amountFormat = CurrencyFormatter.format(TokenEntity.TON.symbol, amount) val fiatFormat = CurrencyFormatter.formatFiat(currency.code, fiat, replaceSymbol = false) Pair(amountFormat, fiatFormat) @@ -96,8 +96,8 @@ class StakeWithdrawViewModel( init { viewModelScope.launch(Dispatchers.IO) { - val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet) ?: return@launch - val staking = stakingRepository.get(wallet.accountId, wallet.testnet) + val tokens = tokenRepository.get(currency, wallet.accountId, wallet.network) ?: return@launch + val staking = stakingRepository.get(wallet.accountId, wallet.network) val staked = StakedEntity.create(wallet, staking, tokens, currency, ratesRepository, api) val item = staked.find { it.pool.address.equalsAddress(poolAddress) } ?: return@launch val details = staking.getDetails(item.pool.implementation) ?: return@launch @@ -118,7 +118,7 @@ class StakeWithdrawViewModel( stakeFlow ) { extra, stake -> val currency = settingsRepository.currency - val rates = ratesRepository.getTONRates(currency) + val rates = ratesRepository.getTONRates(wallet.network, currency) val fee = StakingPool.getTotalFee(extra.value, stake.pool.implementation) val amount = CurrencyFormatter.format(TokenEntity.TON.symbol, fee) @@ -154,7 +154,7 @@ class StakeWithdrawViewModel( wallet: WalletEntity, ): SendMetadataEntity = withContext(Dispatchers.IO) { val seqnoDeferred = async { accountRepository.getSeqno(wallet) } - val validUntilDeferred = async { accountRepository.getValidUntil(wallet.testnet) } + val validUntilDeferred = async { accountRepository.getValidUntil(wallet.network) } SendMetadataEntity( seqno = seqnoDeferred.await(), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/stories/remote/RemoteStoriesScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/stories/remote/RemoteStoriesScreen.kt index 971ec2b95..d2bdccac8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/stories/remote/RemoteStoriesScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/stories/remote/RemoteStoriesScreen.kt @@ -6,7 +6,7 @@ import androidx.core.net.toUri import com.tonapps.extensions.containsQuery import com.tonapps.extensions.getParcelableCompat import com.tonapps.extensions.toUriOrNull -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.koin.analytics import com.tonapps.wallet.api.entity.StoryEntity import com.tonapps.wallet.data.settings.SettingsRepository @@ -62,7 +62,9 @@ class RemoteStoriesScreen : BaseStoriesScreen() { context?.analytics?.trackStoryClick( storiesId = stories.id, - button = button, + payload = button.payload, + type = button.type, + title = button.title, index = index ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportComposable.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportComposable.kt index 808e079c0..3b1e5bc4b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportComposable.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportComposable.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -30,15 +29,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tonapps.tonkeeperx.R import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.wallet.localization.Localization -import ui.components.Header import ui.components.TextHeader import ui.components.button.TKButton +import ui.components.moon.MoonTopAppBar import ui.theme.ButtonColorsPrimary import ui.theme.ButtonColorsSecondary import ui.theme.ButtonSizeLarge @@ -128,7 +126,7 @@ fun SupportComposable( .fillMaxWidth() .windowInsetsPadding(WindowInsets.navigationBars) ) { - Header( + MoonTopAppBar( title = "", actionIconRes = UIKitIcon.ic_close_16, onActionClick = { onCloseClick() }, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportScreen.kt index 70dfb823b..9f033fcfd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/support/SupportScreen.kt @@ -20,7 +20,7 @@ class SupportScreen(wallet: WalletEntity) : ComposeWalletScreen(wallet), BaseFra private fun getSupportUrl(): String { val startParams = "android${Build.VERSION.SDK_INT}app${requireContext().appVersionCode}" - val builder = requireContext().api?.config?.directSupportUrl?.toUri()?.buildUpon() ?: return "" + val builder = requireContext().api?.getConfig(wallet.network)?.directSupportUrl?.toUri()?.buildUpon() ?: return "" builder.appendQueryParameter("start", startParams) return builder.toString() } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/StonfiBridge2.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/StonfiBridge2.kt index 294c0f76f..8100dd874 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/StonfiBridge2.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/StonfiBridge2.kt @@ -1,7 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.swap import android.net.Uri -import android.util.Log +import com.tonapps.log.L import com.tonapps.wallet.data.core.entity.SignRequestEntity import org.json.JSONArray import uikit.widget.webview.bridge.JsBridge diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonScreen.kt index 03efded79..948d145a7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonScreen.kt @@ -5,7 +5,7 @@ import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned import android.text.method.LinkMovementMethod -import android.util.Log +import com.tonapps.log.L import android.util.TypedValue import android.view.View import android.view.inputmethod.EditorInfo @@ -13,7 +13,7 @@ import android.widget.Button import androidx.appcompat.widget.AppCompatTextView import androidx.lifecycle.lifecycleScope import com.tonapps.icu.CurrencyFormatter -import com.tonapps.tonkeeper.core.AnalyticsHelper +import com.tonapps.bus.core.AnalyticsHelper import com.tonapps.tonkeeper.core.InsufficientFundsException import com.tonapps.tonkeeper.extensions.addFeeItem import com.tonapps.tonkeeper.extensions.finishDelay diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonViewModel.kt index ec699d34c..3564d2cd3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/omniston/OmnistonViewModel.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.swap.omniston import android.app.Application import android.net.Uri -import android.util.Log +import com.tonapps.log.L import androidx.compose.ui.graphics.Color import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.base64 @@ -115,7 +115,7 @@ class OmnistonViewModel( get() = settingsRepository.installId val swapUri: Uri - get() = api.config.swapUri + get() = api.getConfig(wallet.network).swapUri private var swapStreamJob: Job? = null @@ -144,7 +144,7 @@ class OmnistonViewModel( private val ratesFlow = swapRepository.assetsFlow .mapList { it.address } - .map { ratesRepository.getRates(settingsRepository.currency, it) } + .map { ratesRepository.getRates(wallet.network, settingsRepository.currency, it) } val sendPlaceholderValueFlow = twinInput.createConvertFlow(ratesFlow, TwinInput.Type.Send).map { it.value.asString2(3) @@ -409,7 +409,7 @@ class OmnistonViewModel( return tokenRepository.getTON( currency = settingsRepository.currency, accountId = wallet.accountId, - testnet = wallet.testnet, + network = wallet.network, ) } @@ -436,6 +436,7 @@ class OmnistonViewModel( val toCurrency = twinInput.state.receive.currency val bidUnits = Coins.ofNano(stateMessages.bidUnits, fromCurrency.decimals) val askUnits = Coins.ofNano(stateMessages.askUnits, toCurrency.decimals) + val isMaxTon = fromCurrency == WalletCurrency.TON && bidUnits.compareTo(stateToken.balance) == 0 if (bidUnits > stateToken.balance) { throw InsufficientFundsException( currency = fromCurrency, @@ -455,7 +456,7 @@ class OmnistonViewModel( val estimatedGasConsumption = Coins.ofNano(stateMessages.estimatedGasConsumption) val totalTonFee = tx.tonEmulated?.totalFees ?: Coins.ZERO val maxRequiredFee = listOf(gasBudget, estimatedGasConsumption, totalTonFee).max() - if (fromCurrency == WalletCurrency.TON && (bidUnits + maxRequiredFee) > tonBalance.balance.value) { + if (fromCurrency == WalletCurrency.TON && !isMaxTon && (bidUnits + maxRequiredFee) > tonBalance.balance.value) { val requiredTONBalance = bidUnits + maxRequiredFee if (requiredTONBalance >= tonBalance.balance.value) { throw InsufficientFundsException( @@ -555,7 +556,9 @@ class OmnistonViewModel( } fun setFeeMethod(fee: SendFee) { - settingsRepository.setPreferredFeeMethod(wallet.id, fee.method) + fee.method?.let { + settingsRepository.setPreferredFeeMethod(wallet.id, it) + } _quoteStateFlow.update { state -> state.copy(selectedFee = fee) } @@ -568,7 +571,7 @@ class OmnistonViewModel( try { val isBattery = state.isPreferredFeeMethodBattery val transfers = transfers(signRequest,false, isBattery) - val validUntil = accountRepository.getValidUntil(wallet.testnet) + val validUntil = accountRepository.getValidUntil(wallet.network) val message = accountRepository.messageBody(wallet, validUntil, transfers) val unsignedBody = message.createUnsignedBody(isBattery) val ledgerTransactions = getLedgerTransaction(message) @@ -663,7 +666,7 @@ class OmnistonViewModel( params = true ) - private suspend fun getTonBalance() = tokenRepository.getTonBalance(settingsRepository.currency, wallet.accountId, wallet.testnet) + private suspend fun getTonBalance() = tokenRepository.getTonBalance(settingsRepository.currency, wallet.accountId, wallet.network) private suspend fun transfers( request: SignRequestEntity, @@ -671,7 +674,7 @@ class OmnistonViewModel( batteryEnabled: Boolean ): List { val excessesAddress = if (false) { // !forEmulation && batteryEnabled - batteryRepository.getConfig(wallet.testnet).excessesAddress + batteryRepository.getConfig(wallet.network).excessesAddress } else null return request.getTransfers( @@ -688,7 +691,7 @@ class OmnistonViewModel( signRequest: SignRequestEntity, batteryEnabled: Boolean ): SwapQuoteState.Tx = withContext(Dispatchers.IO) { - val validUntil = accountRepository.getValidUntil(wallet.testnet) + val validUntil = accountRepository.getValidUntil(wallet.network) val messageBody = MessageBodyEntity( wallet = wallet, seqNo = getSeqNo(), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerScreen.kt index f7cff9450..9076cc4b8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerScreen.kt @@ -2,7 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.swap.picker import android.content.Context import android.os.Bundle -import android.util.Log +import com.tonapps.log.L import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerViewModel.kt index 509de795b..c5d470ff9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/swap/picker/SwapPickerViewModel.kt @@ -73,6 +73,6 @@ class SwapPickerViewModel( private suspend fun getTokens() = tokenRepository.get( currency = settingsRepository.currency, accountId = wallet.accountId, - testnet = wallet.testnet + network = wallet.network ) ?: emptyList() } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt index 09b8ee78e..ec3391ac2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt @@ -33,7 +33,7 @@ import uikit.extensions.context class TokenPickerViewModel( app: Application, - wallet: WalletEntity, + private val wallet: WalletEntity, selectedToken: TokenEntity, allowedTokens: List, private val settingsRepository: SettingsRepository, @@ -41,7 +41,7 @@ class TokenPickerViewModel( private val api: API, ): BaseWalletVM(app) { - private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api) + private val safeMode: Boolean = settingsRepository.isSafeModeEnabled(api, wallet.network) private val _selectedTokenFlow = MutableStateFlow(selectedToken) @@ -53,7 +53,7 @@ class TokenPickerViewModel( private val queryFlow = _queryFlow.asSharedFlow() private val tokensFlow = settingsRepository.currencyFlow.map { currency -> - val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet)?.filter { + val tokens = tokenRepository.get(currency, wallet.accountId, wallet.network)?.filter { it.balance.isTransferable } ?: emptyList() @@ -100,7 +100,7 @@ class TokenPickerViewModel( position = ListCell.getPosition(sortedTokens.size, index), raw = token, selected = token.address == selectedToken.address, - balance = CurrencyFormatter.format(token.symbol, token.balance.value), + balance = CurrencyFormatter.format(token.symbol, token.balance.uiBalance), hiddenBalance = settingsRepository.hiddenBalances, showNetwork = tronUsdtEnabled && (token.isUsdt || token.isTrc20) ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenScreen.kt index 34a5d47bd..f8ca0df5d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenScreen.kt @@ -7,6 +7,8 @@ import androidx.core.net.toUri import androidx.core.view.doOnLayout import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.RecyclerView +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events import com.tonapps.tonkeeper.core.history.list.HistoryAdapter import com.tonapps.tonkeeper.core.history.list.HistoryItemDecoration import com.tonapps.tonkeeper.core.history.list.item.HistoryItem @@ -171,7 +173,8 @@ class TokenScreen(wallet: WalletEntity) : targetAddress = viewModel.burnAddress, tokenAddress = token.address, amount = token.balance.value, - type = SendScreen.Companion.Type.Default + type = SendScreen.Companion.Type.Default, + from = Events.SendNative.SendNativeFrom.JettonScreen ) ) finish() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenViewModel.kt index f8d21259f..ad31fba6e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/TokenViewModel.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.token.viewer import android.app.Application import androidx.lifecycle.viewModelScope +import com.tonapps.async.Async import com.tonapps.blockchain.ton.contract.BaseWalletContract import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.blockchain.ton.extensions.toAccountId @@ -15,19 +16,20 @@ import com.tonapps.tonkeeper.extensions.isSafeModeEnabled import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.token.viewer.list.Item +import com.tonapps.tonkeeper.usecase.emulation.EmulationUseCase +import com.tonapps.tonkeeper.usecase.emulation.Trc20TransferDefaultFees import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.api.API -import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.api.entity.ChartEntity import com.tonapps.wallet.api.entity.EthenaEntity import com.tonapps.wallet.api.entity.TokenEntity +import com.tonapps.wallet.api.entity.value.Blockchain import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.account.Wallet import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.battery.BatteryRepository import com.tonapps.wallet.data.core.currency.WalletCurrency import com.tonapps.wallet.data.events.EventsRepository -import com.tonapps.wallet.data.purchase.PurchaseRepository import com.tonapps.wallet.data.rates.RatesRepository import com.tonapps.wallet.data.settings.ChartPeriod import com.tonapps.wallet.data.settings.SettingsRepository @@ -40,11 +42,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext // TODO Refactor this class @@ -60,9 +64,9 @@ class TokenViewModel( private val eventsRepository: EventsRepository, private val historyHelper: HistoryHelper, private val batteryRepository: BatteryRepository, - private val purchaseRepository: PurchaseRepository, private val ratesRepository: RatesRepository, private val transactionManager: TransactionManager, + private val emulationUseCase: EmulationUseCase, ) : BaseWalletVM(app) { val burnAddress: String by lazy { @@ -79,7 +83,7 @@ class TokenViewModel( private set val usdeDisabled: Boolean - get() = api.config.flags.disableUsde + get() = api.getConfig(wallet.network).flags.disableUsde private val _tokensFlow = MutableStateFlow?>(null) val tokensFlow = _tokensFlow.asStateFlow().filterNotNull() @@ -97,26 +101,47 @@ class TokenViewModel( private val _chartFlow = MutableStateFlow?>(null) private val chartFlow = _chartFlow.asStateFlow().filterNotNull() + private val trc20TransferDefaultFeesFlow = + combine(tokenFlow, settingsRepository.currencyFlow) { token, currency -> + if (token.isTrc20) { + emulationUseCase.getTrc20TransferDefaultFees(wallet, currency) + } else null + } + .flowOn(Dispatchers.IO) + + private val scope = viewModelScope + Async.Default + init { viewModelScope.launch(Dispatchers.IO) { getData() } + transactionManager.eventsFlow(wallet).collectFlow { getData(true) } combine( - tokenFlow, tokensFlow, chartFlow, settingsRepository.walletPrefsChangedFlow - ) { token, list, chart, _ -> - buildItems(token, list, chart, tokenRepository.getEthena(wallet.accountId)) - }.launchIn(viewModelScope) + tokenFlow, + tokensFlow, + chartFlow, + settingsRepository.walletPrefsChangedFlow, + trc20TransferDefaultFeesFlow + ) { token, list, chart, _, trc20DefaultFees -> + buildItems( + token, + list, + chart, + tokenRepository.getEthena(wallet.accountId), + trc20DefaultFees + ) + }.launchIn(scope) } private suspend fun getData(refresh: Boolean = false) { tronAddress = accountRepository.getTronAddress(wallet.id) val list = tokenRepository.get( - settingsRepository.currency, wallet.accountId, wallet.testnet, refresh = refresh + settingsRepository.currency, wallet.accountId, wallet.network, refresh = refresh ) ?: return val token = list.firstOrNull { it.address == tokenAddress } ?: return @@ -129,7 +154,7 @@ class TokenViewModel( } _tokensFlow.value = list - buildItems(token, list, emptyList(), ethena) + buildItems(token, list, emptyList(), ethena, null) load(token) } @@ -184,9 +209,9 @@ class TokenViewModel( } else if (wallet.type == Wallet.Type.Watch || wallet.type == Wallet.Type.Lockup || wallet.type == Wallet.Type.Ledger) { return true } - val w5Contact = BaseWalletContract.create(wallet.publicKey, "v5r1", wallet.testnet) + val w5Contact = BaseWalletContract.create(wallet.publicKey, "v5r1", wallet.network) val accountId = w5Contact.address.toAccountId() - return accountRepository.getWalletByAccountId(accountId, wallet.testnet) != null + return accountRepository.getWalletByAccountId(accountId, wallet.network) != null } private suspend fun buildItems( @@ -194,18 +219,19 @@ class TokenViewModel( tokens: List, charts: List, ethena: EthenaEntity?, + trc20DefaultFees: Trc20TransferDefaultFees?, ) { val currency = settingsRepository.currency.code val items = mutableListOf() - var headerBalance = token.balance.value + var headerBalance = token.balance.uiBalance val balanceItems = mutableListOf() val tokenTsUsde = tokens.firstOrNull { it.isTsUSDe } val rates = ratesRepository.getRates( - settingsRepository.currency, listOfNotNull(token.address, tokenTsUsde?.address) + wallet.network, settingsRepository.currency, listOfNotNull(token.address, tokenTsUsde?.address) ) if (token.isUSDe && !rawUsde) { @@ -267,6 +293,12 @@ class TokenViewModel( val headerFiat = rates.convert(token.address, headerBalance) + val totalAvailableTransfers = if (api.getConfig(wallet.network).flags.disableBattery) { + trc20DefaultFees?.trxFee?.availableTransfers + } else { + trc20DefaultFees?.totalAvailableTransfers + } + items.add( Item.Balance( balance = CurrencyFormatter.formatFull( @@ -277,13 +309,16 @@ class TokenViewModel( hiddenBalance = settingsRepository.hiddenBalances, showNetwork = tronUsdtEnabled && (token.isUsdt || token.isTrc20), blockchain = token.token.blockchain, + wallet = wallet, + availableTransfers = if (token.isTrc20) totalAvailableTransfers else null ) ) items.add( Item.Actions( - swapUri = api.config.swapUri, - tronSwapUrl = if (token.isTrc20) api.config.tronSwapUrl else null, - swapDisabled = api.config.flags.disableSwap || ((token.isUSDe || token.isTsUSDe) && usdeDisabled), + swapUri = api.getConfig(wallet.network).swapUri, + tronSwapUrl = if (token.isTrc20) api.getConfig(wallet.network).tronSwapUrl else null, + swapDisabled = api.getConfig(wallet.network).flags.disableSwap || ((token.isUSDe || token.isTsUSDe) && usdeDisabled), + tronTransfersDisabled = token.isTrc20 && trc20DefaultFees != null && totalAvailableTransfers == 0, token = token.balance.token, wallet = wallet, ) @@ -319,7 +354,7 @@ class TokenViewModel( } } - if (token.isUsdt && !wallet.isW5 && wallet.hasPrivateKey && !api.config.flags.disableGasless && settingsRepository.isUSDTW5( + if (token.isUsdt && !wallet.isW5 && wallet.hasPrivateKey && !api.getConfig(wallet.network).flags.disableGasless && settingsRepository.isUSDTW5( wallet.id ) ) { @@ -330,15 +365,19 @@ class TokenViewModel( ) } - if (wallet.hasPrivateKey && token.isTrc20 && !api.config.flags.disableBattery) { - val batteryCharges = getBatteryCharges() - if (batteryCharges < 300) { - items.add( - Item.BatteryBanner( - wallet = wallet, token = token.balance.token + if (wallet.hasPrivateKey && token.isTrc20 && trc20DefaultFees != null && totalAvailableTransfers == 0) { + items.add( + Item.TronBanner( + wallet = wallet, + onlyTrx = api.getConfig(wallet.network).flags.disableBattery, + trxAmountFormat = CurrencyFormatter.format( + TokenEntity.TRX.symbol, trc20DefaultFees.trxFee.amount + ), + trxBalanceFormat = CurrencyFormatter.format( + TokenEntity.TRX.symbol, trc20DefaultFees.trxFee.balance ) ) - } + ) } if (!token.isUsdt && !token.isTrc20 && !token.isUSDe) { @@ -415,7 +454,7 @@ class TokenViewModel( token: AccountTokenEntity, beforeLt: Long? = null ) = withContext(Dispatchers.IO) { val accountEvents = - eventsRepository.loadForToken(token.address, wallet.accountId, wallet.testnet, beforeLt) + eventsRepository.loadForToken(token.address, wallet.accountId, wallet.network, beforeLt) ?: return@withContext val walletEventItems = mapping(wallet, accountEvents.events) if (beforeLt == null) { @@ -450,7 +489,7 @@ class TokenViewModel( tronAddress = tronAddress!!, events = tronEvents, options = ActionOptions( - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), hiddenBalances = settingsRepository.hiddenBalances, ) ) @@ -477,7 +516,7 @@ class TokenViewModel( ): List { return historyHelper.mapping( wallet = wallet, events = events, options = ActionOptions( - safeMode = settingsRepository.isSafeModeEnabled(api), + safeMode = settingsRepository.isSafeModeEnabled(api, wallet.network), hiddenBalances = settingsRepository.hiddenBalances, tronEnabled = tronUsdtEnabled, ) @@ -562,7 +601,7 @@ class TokenViewModel( private suspend fun getBatteryCharges(): Int = withContext(Dispatchers.IO) { accountRepository.requestTonProofToken(wallet)?.let { - batteryRepository.getCharges(it, wallet.publicKey, wallet.testnet, true) + batteryRepository.getCharges(it, wallet.publicKey, wallet.network, true) } ?: 0 } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt index 72b861cdb..f0b3560a5 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt @@ -2,7 +2,6 @@ package com.tonapps.tonkeeper.ui.screen.token.viewer.list import android.net.Uri import com.tonapps.icu.Coins -import com.tonapps.tonkeeper.core.entities.WalletPurchaseMethodEntity import com.tonapps.tonkeeper.extensions.asCurrency import com.tonapps.tonkeeperx.R import com.tonapps.uikit.list.BaseListItem @@ -28,6 +27,7 @@ sealed class Item(type: Int): BaseListItem(type) { const val TYPE_SPACE = 7 const val TYPE_ETHENA_BALANCE = 8 const val TYPE_ETHENA_METHOD = 9 + const val TYPE_TRON_BANNER = 10 } data class Balance( @@ -37,6 +37,8 @@ sealed class Item(type: Int): BaseListItem(type) { val showNetwork: Boolean, val blockchain: Blockchain, val hiddenBalance: Boolean, + val wallet: WalletEntity, + val availableTransfers: Int?, ): Item(TYPE_BALANCE) { val networkIconRes: Int get() = when (blockchain) { @@ -50,6 +52,7 @@ sealed class Item(type: Int): BaseListItem(type) { val swapUri: Uri, val tronSwapUrl: String?, val swapDisabled: Boolean, + val tronTransfersDisabled: Boolean, val token: TokenEntity, ): Item(TYPE_ACTIONS) { @@ -154,4 +157,11 @@ sealed class Item(type: Int): BaseListItem(type) { } } } + + data class TronBanner( + val wallet: WalletEntity, + val trxAmountFormat: CharSequence, + val trxBalanceFormat: CharSequence, + val onlyTrx: Boolean + ): Item(TYPE_TRON_BANNER) } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/TokenAdapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/TokenAdapter.kt index 6201f5ae2..fe4f94d46 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/TokenAdapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/TokenAdapter.kt @@ -9,6 +9,7 @@ import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.ChartHolder import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.EthenaBalanceHolder import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.EthenaMethodHolder import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.SpaceHolder +import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.TronBannerHolder import com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder.W5BannerHolder import com.tonapps.uikit.list.BaseListAdapter import com.tonapps.uikit.list.BaseListHolder @@ -29,7 +30,8 @@ class TokenAdapter( Item.TYPE_SPACE -> SpaceHolder(parent) Item.TYPE_ETHENA_BALANCE -> EthenaBalanceHolder(parent) Item.TYPE_ETHENA_METHOD -> EthenaMethodHolder(parent) + Item.TYPE_TRON_BANNER -> TronBannerHolder(parent) else -> throw IllegalArgumentException("Unknown view type: $viewType") } } -} \ No newline at end of file +} diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt index e14216e41..36fb68bc1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt @@ -2,12 +2,17 @@ package com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder import android.view.View import android.view.ViewGroup +import com.tonapps.bus.core.AnalyticsHelper +import com.tonapps.bus.generated.Events +import androidx.core.view.isVisible import com.tonapps.tonkeeper.helper.BrowserHelper import com.tonapps.tonkeeper.koin.serverFlags import com.tonapps.tonkeeper.ui.screen.qr.QRScreen import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeper.ui.screen.swap.SwapScreen import com.tonapps.tonkeeper.ui.screen.token.viewer.list.Item +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreen +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreenType import com.tonapps.tonkeeperx.R import uikit.navigation.Navigation import uikit.widget.ButtonsLayout @@ -26,22 +31,29 @@ class ActionsHolder(parent: ViewGroup) : Holder(parent, R.layout.v buttonsView.maxColumnCount = item.maxColumnCount sendView.isEnabled = item.send sendView.setOnClickListener { - navigation?.add( - SendScreen.newInstance( - wallet = item.wallet, - tokenAddress = item.tokenAddress, - type = SendScreen.Companion.Type.Default + if (item.tronTransfersDisabled) { + navigation?.add( + TronFeesScreen.newInstance( + wallet = item.wallet, + type = TronFeesScreenType.InsufficientBalance + ) + ) + } else { + navigation?.add( + SendScreen.newInstance( + wallet = item.wallet, + tokenAddress = item.tokenAddress, + type = SendScreen.Companion.Type.Default, + from = Events.SendNative.SendNativeFrom.JettonScreen + ) ) - ) + } } receiveView.setOnClickListener { navigation?.add(QRScreen.newInstance(item.wallet, item.token)) } - swapView.visibility = if (item.swap) { - View.VISIBLE - } else { - View.GONE - } + + swapView.isVisible = item.swap swapView.setOnClickListener { if (item.tronSwapUrl != null) { BrowserHelper.open(context, item.tronSwapUrl) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/BalanceHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/BalanceHolder.kt index d9b8d976b..9151ba871 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/BalanceHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/BalanceHolder.kt @@ -5,8 +5,11 @@ import android.view.ViewGroup import androidx.appcompat.widget.AppCompatTextView import com.tonapps.icu.CurrencyFormatter.withCustomSymbol import com.tonapps.tonkeeper.ui.screen.token.viewer.list.Item +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreen import com.tonapps.tonkeeperx.R import com.tonapps.wallet.data.core.HIDDEN_BALANCE +import com.tonapps.wallet.localization.Plurals +import uikit.navigation.Navigation.Companion.navigation import uikit.widget.AsyncImageView class BalanceHolder(parent: ViewGroup): Holder(parent, R.layout.view_token_balance) { @@ -15,6 +18,8 @@ class BalanceHolder(parent: ViewGroup): Holder(parent, R.layout.vi private val fiatBalanceView = findViewById(R.id.fiat_balance) private val iconView = findViewById(R.id.icon) private val networkIconView = findViewById(R.id.network_icon) + private val availableTransfersContainerView = findViewById(R.id.available_transfers_container) + private val availableTransfersView = findViewById(R.id.available_transfers) override fun onBind(item: Item.Balance) { balanceView.text = if (item.hiddenBalance) HIDDEN_BALANCE else item.balance.withCustomSymbol(context) @@ -23,5 +28,18 @@ class BalanceHolder(parent: ViewGroup): Holder(parent, R.layout.vi networkIconView.setLocalRes(item.networkIconRes) networkIconView.visibility = if (item.showNetwork) View.VISIBLE else View.GONE + if (item.availableTransfers != null && item.availableTransfers > 0) { + availableTransfersContainerView.visibility = View.VISIBLE + availableTransfersView.text = context.resources.getQuantityString( + Plurals.transfers_available, + item.availableTransfers, + item.availableTransfers + ) + availableTransfersContainerView.setOnClickListener { + context.navigation?.add(TronFeesScreen.newInstance(item.wallet)) + } + } else { + availableTransfersContainerView.visibility = View.GONE + } } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/TronBannerHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/TronBannerHolder.kt new file mode 100644 index 000000000..31229e17b --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/TronBannerHolder.kt @@ -0,0 +1,55 @@ +package com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder + +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.appcompat.widget.AppCompatTextView +import com.tonapps.tonkeeper.ui.screen.qr.QRScreen +import com.tonapps.tonkeeper.ui.screen.token.viewer.list.Item +import com.tonapps.tonkeeper.ui.screen.tronfees.TronFeesScreen +import com.tonapps.tonkeeperx.R +import com.tonapps.wallet.api.entity.TokenEntity +import com.tonapps.wallet.localization.Localization +import uikit.navigation.Navigation.Companion.navigation + +class TronBannerHolder(parent: ViewGroup): Holder(parent, R.layout.view_token_battery_banner) { + + private val buttonView = findViewById