diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 4deafcaa4..9372310aa 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -27,11 +27,14 @@ jobs:
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 }}
+ CERTIFICATE_BASE64: ${{ secrets.ANDROID_DIST_SIGNING_KEY }}
run: |
- echo $CERTIFICATE_BASE64 | base64 --decode > google-release.keystore
+ echo $CERTIFICATE_BASE64 | base64 --decode > google-release.keystore
- name: Build android beta
run: bundle exec fastlane android beta
diff --git a/.gitignore b/.gitignore
index 33eef6ddc..0e69164b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,5 @@ captures
.cxx
*.apk
.kotlin
-build
+/build
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b86273d94..312bf2eab 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index fb6ee35d1..8cfc672e9 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -47,6 +47,7 @@
+
@@ -57,6 +58,9 @@
+
+
+
@@ -81,6 +85,7 @@
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index d4b7accba..c224ad564 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b2c751a35..99b6bd53c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
+
-
+
diff --git a/Gemfile.lock b/Gemfile.lock
index 5c88840c5..ab3f6a0a7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -222,4 +222,4 @@ RUBY VERSION
ruby 3.3.0p0
BUNDLED WITH
- 2.5.4
+ 2.5.4
\ No newline at end of file
diff --git a/apps/signer/build.gradle.kts b/apps/signer/build.gradle.kts
index 10c0c5cb5..97604812e 100644
--- a/apps/signer/build.gradle.kts
+++ b/apps/signer/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
@@ -9,10 +11,10 @@ android {
defaultConfig {
applicationId = Build.namespacePrefix("signer")
- minSdk = Build.minSdkVersion
- targetSdk = 34
- versionCode = 22
- versionName = "0.2.2"
+ minSdk = 26
+ targetSdk = Build.compileSdkVersion
+ versionCode = 23
+ versionName = "0.2.3"
}
lint {
@@ -43,51 +45,42 @@ android {
}
}
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = "1.8"
- }
-
packaging {
resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}
dependencies {
- implementation(Dependence.AndroidX.core)
- implementation(Dependence.AndroidX.appCompat)
- implementation(Dependence.AndroidX.activity)
- implementation(Dependence.AndroidX.fragment)
- implementation(Dependence.AndroidX.recyclerView)
- implementation(Dependence.AndroidX.viewPager2)
- implementation(Dependence.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(Dependence.UI.material)
- implementation(Dependence.UI.flexbox)
- implementation(Dependence.AndroidX.Camera.base)
- implementation(Dependence.AndroidX.Camera.core)
- implementation(Dependence.AndroidX.Camera.lifecycle)
- implementation(Dependence.AndroidX.Camera.view)
- implementation(Dependence.AndroidX.security)
- implementation(Dependence.AndroidX.constraintlayout)
- implementation(Dependence.AndroidX.lifecycleSavedState)
- implementation(project(Dependence.Lib.blockchain))
- implementation(project(Dependence.Lib.extensions))
+ 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(Dependence.KotlinX.guava)
+ implementation(libs.kotlinX.coroutines.guava)
- implementation(project(Dependence.UIKit.core)) {
+ implementation(project(ProjectModules.UIKit.core)) {
exclude("com.airbnb.android", "lottie")
exclude("com.facebook.fresco", "fresco")
}
- implementation(project(Dependence.Lib.qr))
- implementation(project(Dependence.Lib.security))
- implementation(project(Dependence.Lib.icu))
- implementation(Dependence.Koin.core)
+ implementation(project(ProjectModules.Lib.qr))
+ implementation(project(ProjectModules.Lib.security))
+ implementation(project(ProjectModules.Lib.icu))
+ implementation(libs.koin.core)
}
diff --git a/apps/signer/lint-baseline.xml b/apps/signer/lint-baseline.xml
new file mode 100644
index 000000000..65ada1635
--- /dev/null
+++ b/apps/signer/lint-baseline.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/apps/signer/proguard-rules.pro b/apps/signer/proguard-rules.pro
index de400b7f8..71a6747bd 100644
--- a/apps/signer/proguard-rules.pro
+++ b/apps/signer/proguard-rules.pro
@@ -7,3 +7,9 @@
-repackageclasses ''
-renamesourcefileattribute SourceFile
-dontskipnonpubliclibraryclasses
+
+-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
+-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
diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/camera/CameraFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/camera/CameraFragment.kt
index 8221904de..5ee72bcd8 100644
--- a/apps/signer/src/main/java/com/tonapps/signer/screen/camera/CameraFragment.kt
+++ b/apps/signer/src/main/java/com/tonapps/signer/screen/camera/CameraFragment.kt
@@ -71,6 +71,8 @@ class CameraFragment: BaseFragment(R.layout.fragment_camera), BaseFragment.Botto
headerView.background = null
headerView.doOnCloseClick = { finish() }
+ view.findViewById(R.id.permission_header).doOnCloseClick = { finish() }
+
cameraView = view.findViewById(R.id.camera)
flashView = view.findViewById(R.id.flash)
diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/settings/SettingsFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/settings/SettingsFragment.kt
index f2c5ce05a..777211bc1 100644
--- a/apps/signer/src/main/java/com/tonapps/signer/screen/settings/SettingsFragment.kt
+++ b/apps/signer/src/main/java/com/tonapps/signer/screen/settings/SettingsFragment.kt
@@ -54,7 +54,7 @@ class SettingsFragment: BaseFragment(R.layout.fragment_settings), BaseFragment.S
}
private fun openSupport() {
- val uri = Uri.parse("https://t.me/help_tonkeeper_bot")
+ val uri = Uri.parse("https://t.me/tonkeeper")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
}
diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignFragment.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignFragment.kt
index 67d354e1b..4c7339234 100644
--- a/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignFragment.kt
+++ b/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignFragment.kt
@@ -6,8 +6,11 @@ import android.os.Bundle
import android.text.SpannableString
import android.text.method.ScrollingMovementMethod
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.view.ViewCompat
+import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import com.tonapps.blockchain.ton.TonNetwork
import com.tonapps.security.hex
@@ -36,7 +39,9 @@ import org.koin.core.parameter.parametersOf
import org.ton.cell.Cell
import uikit.HapticHelper
import uikit.base.BaseFragment
+import uikit.extensions.bottomBarsOffset
import uikit.extensions.collectFlow
+import uikit.extensions.pinToBottomInsets
import uikit.extensions.setColor
import uikit.navigation.Navigation.Companion.navigation
import uikit.widget.LoaderView
@@ -129,6 +134,13 @@ class SignFragment: BaseFragment(R.layout.fragment_sign), BaseFragment.Modal {
collectFlow(signViewModel.actionsFlow, adapter::submitList)
collectFlow(signViewModel.keyEntity, ::setKeyEntity)
+
+ ViewCompat.setOnApplyWindowInsetsListener(view) { view, insets ->
+ actionView.updateLayoutParams {
+ bottomMargin = insets.bottomBarsOffset
+ }
+ insets
+ }
}
private fun showError() {
@@ -137,7 +149,11 @@ class SignFragment: BaseFragment(R.layout.fragment_sign), BaseFragment.Modal {
}
private fun copyBody() {
- requireContext().copyToClipboard(args.bodyHex)
+ signViewModel.openEmulate().catch {
+ navigation?.toast(R.string.unknown_error)
+ }.onEach{
+ requireContext().copyToClipboard(it)
+ }.launchIn(lifecycleScope)
}
private fun reject() {
diff --git a/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignViewModel.kt b/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignViewModel.kt
index 7a7724169..37402c17b 100644
--- a/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignViewModel.kt
+++ b/apps/signer/src/main/java/com/tonapps/signer/screen/sign/SignViewModel.kt
@@ -7,9 +7,9 @@ import com.tonapps.blockchain.ton.TonNetwork
import com.tonapps.blockchain.ton.contract.BaseWalletContract
import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519
import com.tonapps.blockchain.ton.extensions.hex
+import com.tonapps.blockchain.ton.extensions.loadString
import com.tonapps.blockchain.ton.tlb.JettonTransfer
import com.tonapps.blockchain.ton.tlb.NftTransfer
-import com.tonapps.blockchain.ton.tlb.StringTlbConstructor
import com.tonapps.icu.CurrencyFormatter
import com.tonapps.signer.core.repository.KeyRepository
import com.tonapps.signer.password.Password
@@ -38,6 +38,7 @@ import org.ton.tlb.constructor.AnyTlbConstructor
import org.ton.tlb.loadTlb
import com.tonapps.security.vault.safeArea
import com.tonapps.uikit.list.ListCell
+import org.ton.cell.loadRef
import java.math.BigDecimal
class SignViewModel(
@@ -52,6 +53,8 @@ class SignViewModel(
val keyEntity = repository.getKey(id).filterNotNull()
+ private var normalizedV = v
+
private val _actionsFlow = MutableStateFlow?>(null)
val actionsFlow = _actionsFlow.asStateFlow().filterNotNull()
@@ -68,8 +71,13 @@ class SignViewModel(
}.flowOn(Dispatchers.IO).take(1)
fun openEmulate() = keyEntity.map {
- val contract = BaseWalletContract.create(it.publicKey, v, network.value)
- val cell = contract.createTransferMessageCell(contract.address, EmptyPrivateKeyEd25519.invoke(), seqno, unsignedBody)
+ val contract = BaseWalletContract.create(it.publicKey, normalizedV, network.value)
+ val cell = contract.createTransferMessageCell(
+ address = contract.address,
+ privateKey = EmptyPrivateKeyEd25519.invoke(),
+ seqNo = seqno,
+ unsignedBody = unsignedBody
+ )
cell.hex()
}.flowOn(Dispatchers.IO).take(1)
@@ -77,7 +85,7 @@ class SignViewModel(
return privateKey.sign(unsignedBody.hash().toByteArray())
}
- private fun parseBoc(): List {
+ private fun parseV4Boc(): List {
val items = mutableListOf()
try {
val slice = unsignedBody.beginParse()
@@ -92,7 +100,48 @@ class SignViewModel(
val item = parseMessage(msg, position)
items.add(item)
}
- } catch (ignored: Throwable) {}
+ } catch (_: Throwable) {}
+ return items
+ }
+
+ private fun parseV5Boc(): List {
+ val items = mutableListOf()
+ try {
+ val slice = unsignedBody.beginParse()
+ val opCode = slice.loadUInt(32)
+ val serialized = slice.loadInt(32)
+ val seqno = slice.loadUInt(32)
+ val validUntil = slice.loadUInt(32)
+
+ var list = slice.loadRef()
+ while (!list.bits.isEmpty() || list.refs.isNotEmpty()) {
+ val cellSlice = list.beginParse()
+
+ val prev = cellSlice.loadRef()
+ val tag = cellSlice.loadUInt(32)
+ val sendMode = cellSlice.loadUInt(8)
+ val msg = cellSlice.loadRef {
+ loadTlb(MessageRelaxed.tlbCodec(AnyTlbConstructor))
+ }
+ val position = ListCell.getPosition(1, 0)
+ items.add(parseMessage(msg, position))
+
+ list = prev
+ }
+ } catch (_: Throwable) {}
+ return items
+ }
+
+ private fun parseBoc(): List {
+ val items = mutableListOf()
+ items.addAll(parseV4Boc())
+
+ val v5Boc = parseV5Boc()
+ if (!v5Boc.isEmpty()) {
+ items.addAll(parseV5Boc())
+ normalizedV = "v5r1"
+ }
+
if (items.isEmpty()) {
items.add(SignItem.Unknown(ListCell.Position.SINGLE))
}
@@ -101,20 +150,13 @@ class SignViewModel(
private fun parseMessage(msg: MessageRelaxed| , position: ListCell.Position): SignItem {
try {
-
val info = msg.info as CommonMsgInfoRelaxed.IntMsgInfoRelaxed
val body = getBody(msg.body)
val opCode = parseOpCode(body)
val jettonTransfer = parseJettonTransfer(opCode, body)
val nftTransfer = parseNftTransfer(opCode, body)
- val target = if (nftTransfer != null) {
- nftTransfer.newOwnerAddress
- } else if (jettonTransfer != null) {
- jettonTransfer.toAddress
- } else {
- info.dest
- }
+ val target = nftTransfer?.newOwnerAddress?: (jettonTransfer?.toAddress ?: info.dest)
val value = if (nftTransfer != null) {
"NFT"
@@ -137,7 +179,7 @@ class SignViewModel(
value2 = value2,
extra = nftTransfer != null || jettonTransfer != null
)
- } catch (e: Throwable) {
+ } catch (_: Throwable) {
return SignItem.Unknown(position)
}
}
@@ -186,7 +228,7 @@ class SignViewModel(
} else if (nftTransfer != null) {
nftTransfer.comment
} else {
- cell?.parse { loadTlb(StringTlbConstructor) }
+ cell?.parse { loadString() }
}
if (string != null) {
return string
@@ -200,7 +242,7 @@ class SignViewModel(
private fun formatCoins(coins: Coins): String {
val value = BigDecimal(coins.amount.toLong() / 1000000000L.toDouble())
- return CurrencyFormatter.format("TON", value, 9).toString()
+ return CurrencyFormatter.format("TON", value).toString()
}
private fun parseAddress(address: MsgAddressInt, bounceable: Boolean = true): String {
diff --git a/apps/signer/src/main/res/layout/fragment_emulate.xml b/apps/signer/src/main/res/layout/fragment_emulate.xml
index e70961b5e..46de28bf2 100644
--- a/apps/signer/src/main/res/layout/fragment_emulate.xml
+++ b/apps/signer/src/main/res/layout/fragment_emulate.xml
@@ -2,7 +2,6 @@
by lazy {
- Serializer.moshi.adapter(MessageConsequences::class.java)
+ val tron: TronApi by lazy {
+ TronApi(config, defaultHttpClient, batteryProvider.default.get(false))
}
- val tron = TronApi(config, defaultHttpClient, batteryApi.get(false))
-
fun accounts(testnet: Boolean) = provider.accounts.get(testnet)
fun jettons(testnet: Boolean) = provider.jettons.get(testnet)
@@ -173,7 +173,11 @@ class API(
fun rates() = provider.rates.get(false)
- fun battery(testnet: Boolean) = batteryApi.get(testnet)
+ fun battery(testnet: Boolean) = batteryProvider.default.get(testnet)
+
+ fun batteryWallet(testnet: Boolean) = batteryProvider.wallet.get(testnet)
+
+ fun batteryEmulation(testnet: Boolean) = batteryProvider.emulation.get(testnet)
fun getBatteryConfig(testnet: Boolean): Config? {
return withRetry { battery(testnet).getConfig() }
@@ -183,24 +187,38 @@ class API(
return withRetry { battery(testnet).getRechargeMethods(false) }
}
- fun getOnRampData(country: String) = internalApi.getOnRampData(country)
+ fun getOnRampData() = internalApi.getOnRampData(config.webSwapsUrl)
- suspend fun calculateOnRamp(args: OnRampArgsEntity): List = withContext(Dispatchers.IO) {
- val data = internalApi.calculateOnRamp(args) ?: return@withContext emptyList()
- val items = JSONObject(data).getJSONArray("items")
- items.map { OnRampMerchantEntity(it) }
+ fun getOnRampPaymentMethods(currency: String) = internalApi.getOnRampPaymentMethods(config.webSwapsUrl, currency)
+
+ fun getOnRampMerchants() = internalApi.getOnRampMerchants(config.webSwapsUrl)
+
+ fun getSwapAssets(): JSONArray = runCatching {
+ swapApi.getSwapAssets(config.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 json = JSONObject(data)
+ val items = json.getJSONArray("items").map { OnRampMerchantEntity(it) }
+ val suggested = json.optJSONArray("suggested")?.map { OnRampMerchantEntity(it) } ?: emptyList()
+ OnRampMerchantEntity.Data(
+ items = items,
+ suggested = suggested
+ )
}
- suspend fun getEthenaStakingAPY(address: String): BigDecimal = withContext(Dispatchers.IO) {
- internalApi.getEthenaStakingAPY(address)
+ suspend fun getEthena(accountId: String): EthenaEntity? = withContext(Dispatchers.IO) {
+ withRetry { internalApi.getEthena(accountId) }
}
fun getBatteryBalance(
tonProofToken: String,
testnet: Boolean,
- units: UnitsGetBalance = UnitsGetBalance.ton
+ units: DefaultApi.UnitsGetBalance = DefaultApi.UnitsGetBalance.ton
): Balance? {
- return withRetry { battery(testnet).getBalance(tonProofToken, units) }
+ return withRetry { battery(testnet).getBalance(tonProofToken, units, region = config.region) }
}
fun getAlertNotifications() = withRetry {
@@ -210,7 +228,7 @@ class API(
private fun isOkStatus(testnet: Boolean): Boolean {
try {
val status = withRetry {
- provider.blockchain.get(testnet).status()
+ provider.utilities.get(testnet).status()
} ?: return false
if (!status.restOnline) {
return false
@@ -235,6 +253,16 @@ class API(
return seeHttpClient.sse(url, onFailure = onFailure)
}
+ suspend fun refreshConfig(testnet: Boolean) {
+ configRepository.refresh(testnet)
+ }
+
+ fun swapStream(
+ from: SwapAssetParam,
+ to: SwapAssetParam,
+ userAddress: String
+ ) = swapApi.stream(config.webSwapsUrl, from, to, userAddress)
+
suspend fun getPageTitle(url: String): String = withContext(Dispatchers.IO) {
try {
val headers = ArrayMap().apply {
@@ -276,6 +304,14 @@ class API(
"UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ"
}
+ suspend fun getDnsExpiring(
+ accountId: String,
+ testnet: Boolean,
+ period: Int
+ ) = withContext(Dispatchers.IO) {
+ withRetry { accounts(testnet).getAccountDnsExpiring(accountId, period).items } ?: emptyList()
+ }
+
fun getEvents(
accountId: String,
testnet: Boolean,
@@ -290,6 +326,35 @@ class API(
)
}
+ fun fetchTonEvents(
+ accountId: String,
+ testnet: Boolean,
+ beforeLt: Long? = null,
+ beforeTimestamp: Timestamp? = null,
+ afterTimestamp: Timestamp? = null,
+ limit: Int,
+ ): List {
+ val response = withRetry {
+ accounts(testnet).getAccountEvents(
+ accountId = accountId,
+ beforeLt = beforeLt,
+ endDate = beforeTimestamp?.seconds(),
+ startDate = afterTimestamp?.seconds(),
+ limit = limit,
+ subjectOnly = true,
+ )
+ } ?: throw Exception("Failed to get events")
+ return response.events
+ }
+
+ fun fetchTronTransactions(
+ tronAddress: String,
+ tonProofToken: String,
+ beforeTimestamp: Timestamp? = null,
+ afterTimestamp: Timestamp? = null,
+ limit: Int
+ ) = tron.getTronHistory(tronAddress, tonProofToken, limit, beforeTimestamp, afterTimestamp)
+
suspend fun getTransactionByHash(
accountId: String,
testnet: Boolean,
@@ -329,6 +394,7 @@ class API(
lt = event.lt,
inProgress = event.inProgress,
extra = 0L,
+ progress = 0f,
)
listOf(accountEvent)
}
@@ -400,7 +466,7 @@ class API(
accounts(testnet).getAccountJettonsBalances(
accountId = accountId,
currencies = currency?.let { listOf(it) },
- extensions = extensions,
+ supportedExtensions = extensions,
).balances
} ?: return null
return jettonsBalances.map { BalanceEntity(it) }.filter { it.value.isPositive }
@@ -439,7 +505,11 @@ class API(
val wallets = withRetry {
wallet(testnet).getWalletsByPublicKey(query).accounts
} ?: return emptyList()
- wallets.map { AccountDetailsEntity(query, it, testnet) }.map {
+ wallets.map { AccountDetailsEntity(
+ query = query,
+ wallet = it,
+ testnet = testnet
+ ) }.map {
if (it.walletVersion == WalletVersion.UNKNOWN) {
it.copy(
walletVersion = BaseWalletContract.resolveVersion(
@@ -467,6 +537,15 @@ class API(
}
}
+ fun getRates(from: String, to: String): Map? {
+ return withRetry {
+ rates().getRates(
+ tokens = listOf(from),
+ currencies = listOf(to)
+ ).rates
+ }
+ }
+
fun getNft(address: String, testnet: Boolean): NftItem? {
return withRetry { nft(testnet).getNftItemByAddress(address) }
}
@@ -566,7 +645,7 @@ class API(
cell: Cell,
testnet: Boolean,
): String? {
- val request = io.batteryapi.models.EstimateGaslessCostRequest(cell.base64(), false)
+ val request = EstimateGaslessCostRequest(cell.base64(), false)
return withRetry {
battery(testnet).estimateGaslessCost(jettonMaster, request, tonProofToken).commission
@@ -601,7 +680,11 @@ class API(
val withBattery = supportedByBattery && allowedByBattery
val string = response.body?.string() ?: return null
- val consequences = emulationJSONAdapter.fromJson(string) ?: return null
+ val consequences = try {
+ Serializer.JSON.decodeFromString(string)
+ } catch (e: Throwable) {
+ return null
+ }
return Pair(consequences, withBattery)
}
@@ -619,7 +702,7 @@ class API(
val request = EmulateMessageToWalletRequest(
boc = boc,
params = params,
- safeMode = safeModeEnabled
+ // safeMode = safeModeEnabled
)
withRetry {
emulation(testnet).emulateMessageToWallet(request)
@@ -667,12 +750,15 @@ class API(
return@withContext SendBlockchainState.STATUS_ERROR
}
+ val meta = hashMapOf(
+ "platform" to "android",
+ "version" to appVersionName,
+ "source" to source,
+ "confirmation_time" to confirmationTime.toString()
+ )
val request = SendBlockchainMessageRequest(
boc = boc,
- platform = "android",
- version = appVersionName,
- source = source,
- confirmationTime = confirmationTime
+ meta = meta
)
withRetry {
blockchain(testnet).sendBlockchainMessage(request)
@@ -889,17 +975,26 @@ class API(
}
}
- fun getServerTime(testnet: Boolean) = withRetry {
- liteServer(testnet).getRawTime().time
- } ?: (System.currentTimeMillis() / 1000).toInt()
-
- suspend fun resolveCountry(): String? = withContext(Dispatchers.IO) {
- if (cachedCountry == null) {
- cachedCountry = internalApi.resolveCountry()
+ fun getServerTime(testnet: Boolean): Int {
+ /*val time = serverTimeProvider.getServerTime(testnet)
+ if (time == null) {
+ val serverTimeSeconds = withRetry { liteServer(testnet).getRawTime().time }
+ if (serverTimeSeconds == null) {
+ return (System.currentTimeMillis() / 1000).toInt()
+ }
+ serverTimeProvider.setServerTime(testnet, serverTimeSeconds)
+ return serverTimeSeconds
}
- cachedCountry
+ return time*/
+ val serverTimeSeconds = withRetry { liteServer(testnet).getRawTime().time }
+ if (serverTimeSeconds == null) {
+ return (System.currentTimeMillis() / 1000).toInt()
+ }
+ return serverTimeSeconds
}
+ suspend fun resolveCountry(): String? = internalApi.resolveCountry()
+
suspend fun reportNtfSpam(
nftAddress: String,
scam: Boolean
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
new file mode 100644
index 000000000..0ef717b48
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/BatteryProvider.kt
@@ -0,0 +1,23 @@
+package com.tonapps.wallet.api
+
+import com.tonapps.wallet.api.core.BatteryAPI
+import com.tonapps.wallet.api.core.SourceAPI
+import okhttp3.OkHttpClient
+
+internal class BatteryProvider(
+ mainnetHost: String,
+ testnetHost: String,
+ okHttpClient: OkHttpClient,
+) {
+
+ private val main = BatteryAPI(mainnetHost, okHttpClient)
+ private val test = BatteryAPI(testnetHost, okHttpClient)
+
+ val default = SourceAPI(main.default, test.default)
+
+ val emulation = SourceAPI(main.emulation, test.emulation)
+
+ val wallet = SourceAPI(main.wallet, test.wallet)
+
+
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Constants.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Constants.kt
new file mode 100644
index 000000000..eed64766c
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Constants.kt
@@ -0,0 +1,6 @@
+package com.tonapps.wallet.api
+
+internal object Constants {
+
+ const val SWAP_PREFIX = "https://swap.tonkeeper.com"
+}
\ 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 906dcec0b..e11e3a200 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
@@ -26,7 +26,7 @@ abstract class CoreAPI(private val context: Context) {
val defaultHttpClient = baseOkHttpClientBuilder(
cronetEngine = { cronetEngine },
- timeoutSeconds = 15,
+ timeoutSeconds = 30,
interceptors = listOf(
UserAgentInterceptor(userAgent),
)
@@ -34,7 +34,7 @@ abstract class CoreAPI(private val context: Context) {
val seeHttpClient = baseOkHttpClientBuilder(
cronetEngine = { null },
- timeoutSeconds = 30,
+ timeoutSeconds = 60,
interceptors = listOf(
UserAgentInterceptor(userAgent),
)
@@ -71,7 +71,7 @@ abstract class CoreAPI(private val context: Context) {
private fun baseOkHttpClientBuilder(
cronetEngine: () -> CronetEngine?,
- timeoutSeconds: Long = 5,
+ timeoutSeconds: Long = 30,
interceptors: List = emptyList()
): OkHttpClient.Builder {
val builder = OkHttpClient().newBuilder()
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 a299e0091..3719a108f 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,35 +1,15 @@
package com.tonapps.wallet.api
import android.os.SystemClock
-import io.tonapi.infrastructure.Serializer
-import com.squareup.moshi.adapter
-import io.tonapi.infrastructure.ClientException
import android.util.Log
-import android.widget.Toast
-import com.google.firebase.crashlytics.BuildConfig
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tonapps.network.OkHttpError
-import io.tonapi.infrastructure.ClientError
-import io.tonapi.infrastructure.Response
-import io.tonapi.infrastructure.ServerError
-import kotlinx.coroutines.delay
+import io.infrastructure.ClientError
+import io.infrastructure.ClientException
+import io.infrastructure.ServerError
import kotlinx.coroutines.CancellationException
import kotlinx.io.IOException
import java.net.SocketTimeoutException
-import java.net.UnknownHostException
-
-@OptIn(ExperimentalStdlibApi::class)
-inline fun toJSON(obj: T?): String {
- if (obj == null) {
- return ""
- }
- return Serializer.moshi.adapter().toJson(obj)
-}
-
-@OptIn(ExperimentalStdlibApi::class)
-inline fun fromJSON(json: String): T {
- return Serializer.moshi.adapter().fromJson(json)!!
-}
fun withRetry(
times: Int = 5,
@@ -44,14 +24,15 @@ fun withRetry(
} catch (e: CancellationException) {
throw e
} catch (e: SocketTimeoutException) {
+ Log.e("RetryLogNew", "SocketTimeoutException occurred: ${e.message}", e)
SystemClock.sleep(delay + 100)
return null
} catch (e: IOException) {
- Log.e("WithRetryLog", "IOException: ${e.message}", e)
+ Log.e("RetryLogNew", "IOException occurred: ${e.message}", e)
SystemClock.sleep(delay + 100)
return null
} catch (e: Throwable) {
- Log.e("WithRetryLog", "Error: ${e.message}", e)
+ Log.e("RetryLogNew", "Error occurred: ${e.message}", e)
val statusCode = e.getHttpStatusCode()
if (statusCode == 429 || statusCode == 401 || statusCode == 502 || statusCode == 520) {
SystemClock.sleep(delay + 100)
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 05c65ed23..dd14fd69f 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
@@ -42,4 +42,6 @@ internal class Provider(
val wallet = SourceAPI(main.wallet, test.wallet)
val gasless = SourceAPI(main.gasless, test.gasless)
+
+ val utilities = SourceAPI(main.utilities, test.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
new file mode 100644
index 000000000..b409a3e61
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/ServerTimeProvider.kt
@@ -0,0 +1,48 @@
+package com.tonapps.wallet.api
+
+import android.content.Context
+import android.os.SystemClock
+import com.tonapps.extensions.prefs
+import androidx.core.content.edit
+
+internal class ServerTimeProvider(context: Context) {
+
+ private companion object {
+ const val SERVER_TIME_KEY = "server_time"
+ const val LOCAL_TIME_KEY = "local_time"
+
+ // 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 getLocalTimePrefKey(testnet: Boolean) = "${LOCAL_TIME_KEY}_${if (testnet) "test" else "main"}"
+
+ }
+
+ private val prefs = context.prefs("server_time")
+
+ fun setServerTime(testnet: Boolean, serverTimeSeconds: Int) {
+ val localTimeMillis = SystemClock.elapsedRealtime()
+
+ prefs.edit {
+ putInt(getServerTimePrefKey(testnet), serverTimeSeconds)
+ putLong(getLocalTimePrefKey(testnet), localTimeMillis)
+ }
+ }
+
+ fun getServerTime(testnet: Boolean): Int? {
+ val savedServerSeconds = prefs.getInt(getServerTimePrefKey(testnet), 0)
+ val savedLocalMillis = prefs.getLong(getLocalTimePrefKey(testnet), 0L)
+ if (0 >= savedServerSeconds || 0 >= savedLocalMillis) {
+ return null
+ }
+ val elapsedTimeMillis = SystemClock.elapsedRealtime() - savedLocalMillis
+ if (elapsedTimeMillis > CACHE_EXPIRATION_MS) {
+ return null
+ }
+ val elapsedSeconds = elapsedTimeMillis / 1000
+ val currentServerTime = savedServerSeconds + elapsedSeconds
+ return currentServerTime.toInt()
+ }
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/SwapAssetParam.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/SwapAssetParam.kt
new file mode 100644
index 000000000..e1bb70ec2
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/SwapAssetParam.kt
@@ -0,0 +1,24 @@
+package com.tonapps.wallet.api
+
+import android.net.Uri
+
+data class SwapAssetParam(
+ val address: String,
+ val amount: String?,
+) {
+
+ val isEmpty: Boolean
+ get() = amount.isNullOrBlank() || amount == "0"
+
+ fun apply(prefix: String, builder: Uri.Builder): Uri.Builder {
+ if (address.equals("ton", true)) {
+ builder.appendQueryParameter("${prefix}Asset", "0:0000000000000000000000000000000000000000000000000000000000000000")
+ } else {
+ builder.appendQueryParameter("${prefix}Asset", address)
+ }
+ amount?.let {
+ builder.appendQueryParameter("${prefix}Amount", it)
+ }
+ return builder
+ }
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BaseAPI.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BaseAPI.kt
index f9f003714..f4627c99d 100644
--- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BaseAPI.kt
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BaseAPI.kt
@@ -1,6 +1,5 @@
package com.tonapps.wallet.api.core
-import io.batteryapi.apis.BatteryApi
import io.tonapi.apis.AccountsApi
import io.tonapi.apis.BlockchainApi
import io.tonapi.apis.ConnectApi
@@ -15,6 +14,7 @@ import io.tonapi.apis.RatesApi
import io.tonapi.apis.StakingApi
import io.tonapi.apis.StorageApi
import io.tonapi.apis.TracesApi
+import io.tonapi.apis.UtilitiesApi
import io.tonapi.apis.WalletApi
import okhttp3.OkHttpClient
@@ -53,4 +53,6 @@ class BaseAPI(
val gasless: GaslessApi by lazy { GaslessApi(basePath, okHttpClient) }
+ val utilities: UtilitiesApi by lazy { UtilitiesApi(basePath, okHttpClient) }
+
}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BatteryAPI.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BatteryAPI.kt
new file mode 100644
index 000000000..52703cddc
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/core/BatteryAPI.kt
@@ -0,0 +1,18 @@
+package com.tonapps.wallet.api.core
+
+import io.batteryapi.apis.DefaultApi
+import io.batteryapi.apis.EmulationApi
+import io.batteryapi.apis.WalletApi
+import okhttp3.OkHttpClient
+
+class BatteryAPI(
+ basePath: String,
+ okHttpClient: OkHttpClient
+) {
+
+ val emulation: EmulationApi by lazy { EmulationApi(basePath, okHttpClient) }
+
+ val default: DefaultApi by lazy { DefaultApi(basePath, okHttpClient) }
+
+ val wallet: WalletApi by lazy { WalletApi(basePath, okHttpClient) }
+}
\ 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
index 9cf9dd622..a7cc7ecea 100644
--- 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
@@ -8,6 +8,7 @@
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;
@@ -77,7 +78,7 @@ private CronetInterceptor(RequestResponseConverter converter) {
@Override
public Response intercept(Chain chain) throws IOException {
if (chain.call().isCanceled()) {
- throw new IOException("Canceled");
+ throw new CancellationException();
}
Request request = chain.request();
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 cb14f235a..2e1a7c20f 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
@@ -9,6 +9,7 @@ import com.tonapps.blockchain.ton.extensions.toRawAddress
import com.tonapps.blockchain.ton.extensions.toUserFriendly
import io.tonapi.models.Account
import io.tonapi.models.AccountStatus
+import io.tonapi.models.Wallet
import kotlinx.parcelize.Parcelize
@Parcelize
@@ -61,6 +62,22 @@ data class AccountDetailsEntity(
testnet = testnet,
)
+ constructor(
+ query: String,
+ wallet: Wallet,
+ testnet: Boolean,
+ new: Boolean = false
+ ) : this(
+ query = query,
+ preview = AccountEntity(wallet, testnet),
+ active = wallet.status == AccountStatus.active,
+ walletVersion = resolveVersion(testnet, wallet.interfaces, wallet.address),
+ balance = wallet.balance,
+ new = new,
+ initialized = wallet.status == AccountStatus.active || wallet.status == AccountStatus.frozen,
+ testnet = testnet,
+ )
+
private companion object {
private fun resolveVersion(testnet: Boolean, interfaces: List?, address: String): WalletVersion {
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 b72a2aa44..9d563eab0 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
@@ -9,8 +9,10 @@ import com.tonapps.blockchain.ton.extensions.toWalletAddress
import com.tonapps.extensions.short4
import io.tonapi.models.Account
import io.tonapi.models.AccountAddress
+import io.tonapi.models.Wallet
import kotlinx.parcelize.Parcelize
import org.ton.block.AddrStd
+import androidx.core.net.toUri
@Parcelize
data class AccountEntity(
@@ -43,7 +45,7 @@ data class AccountEntity(
address = model.address.toUserFriendly(model.isWallet, testnet),
accountId = model.address.toRawAddress(),
name = model.name,
- iconUri = model.icon?.let { Uri.parse(it) },
+ iconUri = model.icon?.toUri(),
isWallet = model.isWallet,
isScam = model.isScam
)
@@ -52,8 +54,17 @@ data class AccountEntity(
address = account.address.toUserFriendly(account.isWallet, testnet),
accountId = account.address.toRawAddress(),
name = account.name,
- iconUri = account.icon?.let { Uri.parse(it) },
+ iconUri = account.icon?.toUri(),
isWallet = account.isWallet,
isScam = account.isScam ?: false
)
+
+ constructor(wallet: Wallet, testnet: Boolean) : this(
+ address = wallet.address.toUserFriendly(wallet.isWallet, testnet),
+ accountId = wallet.address.toRawAddress(),
+ name = wallet.name,
+ iconUri = wallet.icon?.toUri(),
+ isWallet = wallet.isWallet,
+ isScam = false
+ )
}
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 d021ea68c..e2f61d979 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
@@ -1,8 +1,8 @@
package com.tonapps.wallet.api.entity
import android.os.Parcelable
-import android.util.Log
import com.tonapps.icu.Coins
+import com.tonapps.wallet.api.entity.value.Blockchain
import io.tonapi.models.JettonBalance
import io.tonapi.models.TokenRates
import kotlinx.parcelize.IgnoredOnParcel
@@ -55,6 +55,9 @@ data class BalanceEntity(
val customPayloadApiUri: String?
get() = token.customPayloadApiUri
+ val blockchain: Blockchain
+ get() = token.blockchain
+
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),
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 44266a929..cbaacb6d1 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
@@ -8,6 +8,8 @@ import com.tonapps.icu.Coins
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.json.JSONObject
+import androidx.core.net.toUri
+import com.tonapps.wallet.api.Constants
@Parcelize
data class ConfigEntity(
@@ -15,6 +17,7 @@ data class ConfigEntity(
val supportLink: String,
val nftExplorer: String,
val transactionExplorer: String,
+ val accountExplorer: String,
val mercuryoSecret: String,
val tonapiMainnetHost: String,
val tonapiTestnetHost: String,
@@ -35,16 +38,9 @@ data class ConfigEntity(
val batteryHost: String,
val batteryTestnetHost: String,
val batteryBeta: Boolean,
- val batteryDisabled: Boolean,
val batterySendDisabled: Boolean,
- val batteryMeanFees: String,
- val batteryMeanPriceNft: String,
- val batteryMeanPriceSwap: String,
- val batteryMeanPriceJetton: String,
- val batteryMeanPriceTrcMin: String,
- val batteryMeanPriceTrcMax: String,
val disableBatteryIapModule: Boolean,
- val batteryReservedAmount: String,
+ val disableBatteryCryptoRechargeModule: Boolean,
val batteryMaxInputAmount: String,
val batteryRefundEndpoint: String,
val batteryPromoDisable: Boolean,
@@ -59,15 +55,21 @@ data class ConfigEntity(
val apkDownloadUrl: String?,
val apkName: AppVersion?,
val tronApiUrl: String,
+ val enabledStaking: List,
+ val qrScannerExtends: List,
+ val region: String,
+ val tonkeeperApiUrl: String,
+ val tronSwapUrl: String,
+ val tronSwapTitle: String,
+ val tronApiKey: String? = null,
+ val privacyPolicyUrl: String,
+ val termsOfUseUrl: String,
+ val webSwapsUrl: String,
): Parcelable {
@IgnoredOnParcel
val swapUri: Uri
- get() = Uri.parse(stonfiUrl)
-
- @IgnoredOnParcel
- val isBatteryDisabled: Boolean
- get() = batteryDisabled || batterySendDisabled
+ get() = stonfiUrl.toUri()
@IgnoredOnParcel
val domains: List by lazy {
@@ -86,6 +88,7 @@ data class ConfigEntity(
supportLink = json.getString("supportLink"),
nftExplorer = json.getString("NFTOnExplorerUrl"),
transactionExplorer = json.getString("transactionExplorer"),
+ accountExplorer = json.getString("accountExplorer"),
mercuryoSecret = json.getString("mercuryoSecret"),
tonapiMainnetHost = json.getString("tonapiMainnetHost"),
tonapiTestnetHost = json.getString("tonapiTestnetHost"),
@@ -110,16 +113,9 @@ data class ConfigEntity(
batteryHost = json.optString("batteryHost", "https://battery.tonkeeper.com"),
batteryTestnetHost = json.optString("batteryTestnetHost", "https://testnet-battery.tonkeeper.com"),
batteryBeta = json.optBoolean("battery_beta", true),
- batteryDisabled = json.optBoolean("disable_battery", false),
batterySendDisabled = json.optBoolean("disable_battery_send", false),
- batteryMeanFees = json.optString("batteryMeanFees", "0.0055"),
disableBatteryIapModule = json.optBoolean("disable_battery_iap_module", false),
- batteryMeanPriceNft = json.optString("batteryMeanPrice_nft", "0.03"),
- batteryMeanPriceSwap = json.optString("batteryMeanPrice_swap", "0.22"),
- batteryMeanPriceJetton = json.optString("batteryMeanPrice_jetton", "0.06"),
- batteryMeanPriceTrcMin = json.optString("batteryMeanPrice_trc20_min", "0.312"),
- batteryMeanPriceTrcMax = json.optString("batteryMeanPrice_trc20_max", "0.78"),
- batteryReservedAmount = json.optString("batteryReservedAmount", "0.3"),
+ disableBatteryCryptoRechargeModule = json.optBoolean("disable_battery_crypto_recharge_module", false),
batteryMaxInputAmount = json.optString("batteryMaxInputAmount", "3"),
batteryRefundEndpoint = json.optString("batteryRefundEndpoint", "https://battery-refund-app.vercel.app"),
batteryPromoDisable = json.optBoolean("disable_battery_promo_module", true),
@@ -136,6 +132,20 @@ data class ConfigEntity(
apkDownloadUrl = json.optString("apk_download_url"),
apkName = json.optString("apk_name")?.let { AppVersion(it.removePrefix("v")) },
tronApiUrl = json.optString("tron_api_url", "https://api.trongrid.io"),
+ enabledStaking = json.optJSONArray("enabled_staking")?.let { array ->
+ (0 until array.length()).map { array.getString(it) }
+ } ?: emptyList(),
+ qrScannerExtends = json.optJSONArray("qr_scanner_extends")?.let { array ->
+ QRScannerExtendsEntity.of(array)
+ } ?: emptyList(),
+ region = json.getString("region"),
+ tonkeeperApiUrl = json.optString("tonkeeper_api_url", "https://api.tonkeeper.com"),
+ tronSwapUrl = json.optString("tron_swap_url", "https://widget.letsexchange.io/en?affiliate_id=ffzymmunvvyxyypo&coin_from=ton&coin_to=USDT-TRC20&is_iframe=true"),
+ tronSwapTitle = json.optString("tron_swap_title", "LetsExchange"),
+ // 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)
)
constructor() : this(
@@ -143,6 +153,7 @@ data class ConfigEntity(
supportLink = "mailto:support@tonkeeper.com",
nftExplorer = "https://tonviewer.com/nft/%s",
transactionExplorer = "https://tonviewer.com/transaction/%s",
+ accountExplorer = "https://tonviewer.com/%s",
mercuryoSecret = "",
tonapiMainnetHost = "https://keeper.tonapi.io",
tonapiTestnetHost = "https://testnet.tonapi.io",
@@ -163,16 +174,9 @@ data class ConfigEntity(
batteryHost = "https://battery.tonkeeper.com",
batteryTestnetHost = "https://testnet-battery.tonkeeper.com",
batteryBeta = true,
- batteryDisabled = false,
batterySendDisabled = false,
- batteryMeanFees = "0.0055",
disableBatteryIapModule = false,
- batteryMeanPriceNft = "0.03",
- batteryMeanPriceSwap = "0.22",
- batteryMeanPriceJetton = "0.06",
- batteryMeanPriceTrcMin = "0.312",
- batteryMeanPriceTrcMax = "0.78",
- batteryReservedAmount = "0.3",
+ disableBatteryCryptoRechargeModule = false,
batteryMaxInputAmount = "3",
batteryRefundEndpoint = "https://battery-refund-app.vercel.app",
batteryPromoDisable = true,
@@ -187,8 +191,27 @@ data class ConfigEntity(
apkDownloadUrl = null,
apkName = null,
tronApiUrl = "https://api.trongrid.io",
+ enabledStaking = emptyList(),
+ qrScannerExtends = emptyList(),
+ region = "US",
+ tonkeeperApiUrl = "https://api.tonkeeper.com",
+ tronSwapUrl = "https://widget.letsexchange.io/en?affiliate_id=ffzymmunvvyxyypo&coin_from=ton&coin_to=USDT-TRC20&is_iframe=true",
+ tronSwapTitle = "LetsExchange",
+ privacyPolicyUrl = "https://tonkeeper.com/privacy",
+ termsOfUseUrl = "https://tonkeeper.com/terms",
+ webSwapsUrl = Constants.SWAP_PREFIX
)
+ fun formatTransactionExplorer(testnet: Boolean, tron: Boolean, hash: String): String {
+ return if (tron) {
+ "https://tronscan.org/#/transaction/$hash"
+ } else if (testnet) {
+ "https://testnet.tonviewer.com/transaction/$hash"
+ } else {
+ transactionExplorer.format(hash)
+ }
+ }
+
companion object {
val default = ConfigEntity()
}
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EthenaEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EthenaEntity.kt
new file mode 100644
index 000000000..e07c77c0b
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/EthenaEntity.kt
@@ -0,0 +1,87 @@
+package com.tonapps.wallet.api.entity
+
+import android.os.Parcelable
+import com.tonapps.extensions.map
+import kotlinx.parcelize.Parcelize
+import org.json.JSONObject
+import java.math.BigDecimal
+
+@Parcelize
+data class EthenaEntity(
+ val methods: List,
+ val about: About,
+) : Parcelable {
+
+ @Parcelize
+ data class About(
+ val description: String,
+ val tsusdeDescription: String,
+ val faqUrl: String,
+ val aboutUrl: String,
+ val stakeTitle: String,
+ val stakeDescription: String,
+ ) : Parcelable {
+ constructor(json: JSONObject) : this(
+ description = json.getString("description"),
+ tsusdeDescription = json.getString("tsusde_description"),
+ faqUrl = json.getString("faq_url"),
+ aboutUrl = json.getString("about_url"),
+ stakeTitle = json.getString("tsusde_stake_title"),
+ stakeDescription = json.getString("tsusde_stake_description"),
+ )
+ }
+
+ @Parcelize
+ data class Method(
+ val type: Type,
+ val name: String,
+ val apy: BigDecimal,
+ val apyTitle: String,
+ val apyDescription: String,
+ val bonusApy: BigDecimal?,
+ val bonusTitle: String?,
+ val bonusDescription: String?,
+ val eligibleBonusUrl: String?,
+ val depositUrl: String,
+ val withdrawalUrl: String,
+ val jettonMaster: String,
+ val links: List,
+ ) : Parcelable {
+ enum class Type(val id: String) {
+ STONFI("stonfi"),
+ AFFLUENT("affluent");
+
+ companion object {
+ fun fromId(id: String): Type {
+ return entries.find { it.id.equals(id, ignoreCase = true) }
+ ?: throw IllegalArgumentException("Invalid type: $id")
+ }
+ }
+ }
+
+ constructor(json: JSONObject) : this(
+ type = Type.fromId(json.getString("type")),
+ name = json.getString("name"),
+ apy = BigDecimal.valueOf(json.getDouble("apy")),
+ apyTitle = json.getString("apy_title"),
+ apyDescription = json.getString("apy_description"),
+ bonusApy = json.optString("bonus_apy").takeIf { it.isNotEmpty() }?.let {
+ BigDecimal(it)
+ },
+ bonusTitle = json.optString("apy_bonus_title"),
+ bonusDescription = json.optString("apy_bonus_description"),
+ eligibleBonusUrl = json.optString("eligible_bonus_url"),
+ depositUrl = json.getString("deposit_url"),
+ withdrawalUrl = json.getString("withdrawal_url"),
+ jettonMaster = json.getString("jetton_master"),
+ links = json.optJSONArray("links")?.let { array ->
+ (0 until array.length()).map { array.getString(it) }
+ } ?: emptyList()
+ )
+ }
+
+ constructor(json: JSONObject) : this(
+ methods = json.getJSONArray("methods").map { Method(it) },
+ about = About(json.getJSONObject("about"))
+ )
+}
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 25d7eb95f..46dfe74f0 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
@@ -1,7 +1,6 @@
package com.tonapps.wallet.api.entity
import android.os.Parcelable
-import android.util.Log
import kotlinx.parcelize.Parcelize
import org.json.JSONObject
@@ -10,29 +9,47 @@ data class FlagsEntity(
val disableSwap: Boolean,
val disableExchangeMethods: Boolean,
val disableDApps: Boolean,
- val disableBlur: Boolean,
- val disableLegacyBlur: Boolean,
val disableSigner: Boolean,
val safeModeEnabled: Boolean,
-): Parcelable {
+ val disableStaking: Boolean,
+ val disableTron: Boolean,
+ val disableBattery: Boolean,
+ val disableGasless: Boolean,
+ val disableUsde: Boolean,
+ val disableNativeSwap: Boolean,
+ val disableOnboardingStory: Boolean,
+ val disableNfts: Boolean
+) : Parcelable {
constructor(json: JSONObject) : this(
disableSwap = json.optBoolean("disable_swap", false),
disableExchangeMethods = json.optBoolean("disable_exchange_methods", false),
disableDApps = json.optBoolean("disable_dapps", false),
- disableBlur = json.optBoolean("disable_blur", false),
- disableLegacyBlur = json.optBoolean("disable_legacy_blur", false),
disableSigner = json.optBoolean("disable_signer", false),
- safeModeEnabled = json.optBoolean("safe_mode_enabled", false)
+ safeModeEnabled = json.optBoolean("safe_mode_enabled", false),
+ disableStaking = json.optBoolean("disable_staking", false),
+ disableTron = json.optBoolean("disable_tron", false),
+ disableBattery = json.optBoolean("disable_battery", false),
+ disableGasless = json.optBoolean("disable_gaseless", false),
+ 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)
)
constructor() : this(
disableSwap = false,
disableExchangeMethods = false,
disableDApps = false,
- disableBlur = false,
- disableLegacyBlur = false,
disableSigner = false,
- safeModeEnabled = false
+ safeModeEnabled = false,
+ disableStaking = false,
+ disableTron = false,
+ disableBattery = false,
+ disableGasless = false,
+ disableUsde = false,
+ disableNativeSwap = false,
+ disableOnboardingStory = false,
+ disableNfts = false
)
}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampArgsEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampArgsEntity.kt
index 7b29ca647..ae94bdefc 100644
--- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampArgsEntity.kt
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampArgsEntity.kt
@@ -6,22 +6,35 @@ import org.json.JSONObject
data class OnRampArgsEntity(
val from: String,
val to: String,
- val network: String,
+ val fromNetwork: String?,
+ val toNetwork: String?,
val wallet: String,
val purchaseType: String,
val amount: Coins,
- val country: String,
val paymentMethod: String?
) {
+ val isSwap: Boolean
+ get() = purchaseType == "swap"
+
+ val isSell: Boolean
+ get() = purchaseType == "sell"
+
+ val withoutPaymentMethod: Boolean
+ get() = isSwap || isSell
+
fun toJSON() = JSONObject().apply {
put("from", from)
put("to", to)
- put("network", network)
+ fromNetwork?.let {
+ put("from_network", it)
+ }
+ toNetwork?.let {
+ put("to_network", it)
+ }
put("wallet", wallet)
put("purchase_type", purchaseType)
put("amount", amount.value.toPlainString())
- put("country", country)
paymentMethod?.let {
put("payment_method", it)
}
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampMerchantEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampMerchantEntity.kt
index f854b6239..498fbe376 100644
--- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampMerchantEntity.kt
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/OnRampMerchantEntity.kt
@@ -5,12 +5,26 @@ import org.json.JSONObject
data class OnRampMerchantEntity(
val merchant: String,
val amount: Double,
- val widgetUrl: String
+ val widgetUrl: String,
+ val minAmount: Double,
) {
+ data class Data(
+ val items: List = emptyList(),
+ val suggested: List = emptyList()
+ ) {
+
+ val isEmpty: Boolean
+ get() = items.isEmpty() && suggested.isEmpty()
+
+ val size: Int
+ get() = items.size + suggested.size
+ }
+
constructor(json: JSONObject) : this(
merchant = json.getString("merchant"),
amount = json.getDouble("amount"),
- widgetUrl = json.getString("widget_url")
+ widgetUrl = json.getString("widget_url"),
+ minAmount = json.optDouble("min_amount", 0.0)
)
}
\ 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
new file mode 100644
index 000000000..3ff6fadcd
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/QRScannerExtendsEntity.kt
@@ -0,0 +1,55 @@
+package com.tonapps.wallet.api.entity
+
+import android.os.Parcelable
+import android.util.Log
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
+import org.json.JSONArray
+import org.json.JSONObject
+import java.net.URLEncoder
+import java.nio.charset.StandardCharsets
+
+@Parcelize
+data class QRScannerExtendsEntity(
+ val version: Int,
+ val regexp: String,
+ val url: String
+): Parcelable {
+
+ @IgnoredOnParcel
+ val regex: Regex by lazy {
+ Regex(regexp)
+ }
+
+ constructor(json: JSONObject) : this(
+ version = json.getInt("version"),
+ regexp = json.getString("regexp"),
+ url = json.getString("url")
+ )
+
+ fun isMatch(input: String): Boolean {
+ return regex.containsMatchIn(input)
+ }
+
+ fun buildUrl(input: String): String? {
+ if (!isMatch(input)) {
+ return null
+ }
+ val encoded = URLEncoder.encode(input, StandardCharsets.UTF_8.name())
+ return url.replace("{{QR_CODE}}", encoded)
+ }
+
+ companion object {
+
+ fun of(array: JSONArray): List {
+ return (0 until array.length()).mapNotNull {
+ val json = array.getJSONObject(it)
+ if (json.getInt("version") == 1) {
+ QRScannerExtendsEntity(array.getJSONObject(it))
+ } else {
+ null
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/SwapEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/SwapEntity.kt
new file mode 100644
index 000000000..dc04e6d3e
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/SwapEntity.kt
@@ -0,0 +1,51 @@
+package com.tonapps.wallet.api.entity
+
+import io.Serializer
+import kotlinx.serialization.Serializable
+
+object SwapEntity {
+
+ @Serializable
+ data class Message(
+ val targetAddress: String,
+ val sendAmount: String,
+ val payload: String?,
+ )
+
+ @Serializable
+ data class Messages(
+ val messages: List,
+ val quoteId: String,
+ val resolverName: String,
+ val askUnits: String,
+ val bidUnits: String,
+ val protocolFeeUnits: String,
+ val tradeStartDeadline: String,
+ val gasBudget: String,
+ val estimatedGasConsumption: String,
+ val slippage: Int
+ ) {
+
+ val isEmpty: Boolean
+ get() = messages.isEmpty()
+ }
+
+ val empty = Messages(
+ messages = emptyList(),
+ quoteId = "",
+ resolverName = "",
+ askUnits = "",
+ bidUnits = "",
+ protocolFeeUnits = "",
+ tradeStartDeadline = "",
+ gasBudget = "",
+ estimatedGasConsumption = "",
+ slippage = 100
+ )
+
+ fun parse(data: String) = try {
+ Serializer.fromJSON(data)
+ } catch (ignored: Throwable) {
+ null
+ }
+}
\ No newline at end of file
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 4e01f4633..d5ed94e17 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
@@ -6,6 +6,7 @@ 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.wallet.api.entity.value.Blockchain
import io.tonapi.models.JettonBalanceLock
import io.tonapi.models.JettonInfo
import io.tonapi.models.JettonPreview
@@ -90,9 +91,14 @@ data class TokenEntity(
val TON_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_ton_with_bg.toString()).build()
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()
const val TRC20_USDT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
const val TON_USDT = "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe"
+ const val TON_USDE = "0:086fa2a675f74347b08dd4606a549b8fdb98829cb282bc1949d3b12fbaed9dcc"
+
+ const val TON_TS_USDE = "0:d0e545323c7acb7102653c073377f7e3c67f122eb94d430a250739f109d4a57d"
val TON = TokenEntity(
blockchain = Blockchain.TON,
@@ -133,6 +139,32 @@ data class TokenEntity(
customPayloadApiUri = null
)
+ val USDE = TokenEntity(
+ blockchain = Blockchain.TON,
+ address = TON_USDE,
+ name = "Ethena USDe",
+ symbol = "USDe",
+ imageUri = USDE_ICON_URI,
+ decimals = 6,
+ verification = Verification.whitelist,
+ isRequestMinting = false,
+ isTransferable = true,
+ customPayloadApiUri = null
+ )
+
+ val TS_USDE = TokenEntity(
+ blockchain = Blockchain.TON,
+ address = TON_TS_USDE,
+ name = "Ethena tsUSDe",
+ symbol = "tsUSDe",
+ imageUri = TS_USDE_ICON_URI,
+ decimals = 6,
+ verification = Verification.whitelist,
+ isRequestMinting = false,
+ isTransferable = true,
+ customPayloadApiUri = null
+ )
+
private fun convertVerification(verification: JettonVerificationType): Verification {
return when (verification) {
JettonVerificationType.whitelist -> Verification.whitelist
@@ -194,6 +226,6 @@ 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.metadata.customPayloadApiUri
)
}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/Blockchain.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Blockchain.kt
similarity index 50%
rename from apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/Blockchain.kt
rename to apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Blockchain.kt
index c9b9d181e..38d1499d7 100644
--- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/Blockchain.kt
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Blockchain.kt
@@ -1,6 +1,6 @@
-package com.tonapps.wallet.api.entity
+package com.tonapps.wallet.api.entity.value
enum class Blockchain(val id: String) {
- TON("ton"),
+ TON("TON"),
TRON("TRON");
}
\ No newline at end of file
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
new file mode 100644
index 000000000..3ff6a6180
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/BlockchainAddress.kt
@@ -0,0 +1,42 @@
+package com.tonapps.wallet.api.entity.value
+
+import android.os.Parcelable
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class BlockchainAddress(
+ val value: String,
+ val testnet: Boolean,
+ val blockchain: Blockchain,
+): Parcelable {
+
+ @IgnoredOnParcel
+ val key: String by lazy {
+ if (testnet) {
+ "${blockchain.id}:$value:testnet"
+ } else {
+ "${blockchain.id}:$value"
+ }
+ }
+
+ companion object {
+
+ fun valueOf(value: String): BlockchainAddress {
+ val split = value.split(":")
+ return if (split.size == 2) {
+ BlockchainAddress(
+ value = split[1],
+ testnet = false,
+ blockchain = Blockchain.valueOf(split[0])
+ )
+ } else {
+ BlockchainAddress(
+ value = split[2],
+ testnet = split[1] == "testnet",
+ blockchain = Blockchain.valueOf(split[0])
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Timestamp.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Timestamp.kt
new file mode 100644
index 000000000..89811bf79
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/Timestamp.kt
@@ -0,0 +1,32 @@
+package com.tonapps.wallet.api.entity.value
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@JvmInline
+@Parcelize
+value class Timestamp(val value: Long) : Parcelable, Comparable {
+
+ data class Range(
+ val from: Timestamp,
+ val to: Timestamp
+ )
+
+ fun toLong() = value
+
+ fun seconds() = value / 1000
+
+ override fun compareTo(other: Timestamp): Int {
+ return value.compareTo(other.value)
+ }
+
+ companion object {
+
+ val zero = Timestamp(0)
+ val now = Timestamp(System.currentTimeMillis())
+
+ fun from(value: Long) = if (value < 1_000_000_000_000L) {
+ Timestamp(value * 1000)
+ } else Timestamp(value)
+ }
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/ValueConverters.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/ValueConverters.kt
new file mode 100644
index 000000000..b07afe671
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/entity/value/ValueConverters.kt
@@ -0,0 +1,31 @@
+package com.tonapps.wallet.api.entity.value
+
+import androidx.room.TypeConverter
+
+object ValueConverters {
+
+ @TypeConverter
+ @JvmStatic
+ fun fromTimestamp(value: Timestamp) = value.toLong()
+
+ @TypeConverter
+ @JvmStatic
+ fun toTimestamp(value: Long) = Timestamp(value)
+
+ @TypeConverter
+ @JvmStatic
+ fun fromBlockchain(value: Blockchain) = value.id
+
+ @TypeConverter
+ @JvmStatic
+ fun toBlockchain(value: String) = Blockchain.valueOf(value)
+
+ @TypeConverter
+ @JvmStatic
+ fun fromAddress(value: BlockchainAddress) = value.key
+
+ @TypeConverter
+ @JvmStatic
+ fun toAddress(value: String) = BlockchainAddress.valueOf(value)
+
+}
\ No newline at end of file
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 7e0bed499..21ba64b93 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,7 +1,6 @@
package com.tonapps.wallet.api.internal
import android.content.Context
-import android.util.Log
import com.tonapps.extensions.file
import com.tonapps.extensions.toByteArray
import com.tonapps.extensions.toParcel
@@ -10,7 +9,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -29,15 +27,16 @@ internal class ConfigRepository(
private set (value) {
field = value
_stream.value = value.copy()
+ internalApi.setApiUrl(value.tonkeeperApiUrl)
}
init {
scope.launch(Dispatchers.IO) {
- readCache()?.let {
- setConfig(it)
- }
- remote(false)?.let {
- setConfig(it)
+ val cached = readCache()
+ if (cached != null) {
+ setConfig(cached)
+ } else {
+ initConfig()
}
}
}
@@ -59,4 +58,15 @@ internal class ConfigRepository(
config
}
+ suspend fun refresh(testnet: Boolean) {
+ val config = remote(testnet) ?: return
+ setConfig(config)
+ }
+
+ suspend fun initConfig() {
+ remote(false)?.let {
+ setConfig(it)
+ }
+ }
+
}
\ 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 ee70b799f..380086a4b 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,18 +1,17 @@
package com.tonapps.wallet.api.internal
import android.content.Context
-import android.net.Uri
-import android.util.ArrayMap
import android.util.Log
+import androidx.collection.ArrayMap
+import androidx.core.net.toUri
import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.tonapps.extensions.deviceCountry
-import com.tonapps.extensions.getStoreCountry
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.EthenaEntity
import com.tonapps.wallet.api.entity.NotificationEntity
import com.tonapps.wallet.api.entity.OnRampArgsEntity
import com.tonapps.wallet.api.entity.StoryEntity
@@ -22,7 +21,6 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.json.JSONObject
-import java.math.BigDecimal
import java.util.Locale
internal class InternalApi(
@@ -31,16 +29,39 @@ internal class InternalApi(
private val appVersionName: String
) {
+ private var _deviceCountry: String? = null
+ private var _storeCountry: String? = null
+ private var _apiEndpoint = "https://api.tonkeeper.com".toUri()
+
+ val country: String
+ get() = _storeCountry ?: _deviceCountry ?: Locale.getDefault().country.uppercase()
+
+ fun setCountry(deviceCountry: String, storeCountry: String?) {
+ _deviceCountry = deviceCountry.uppercase()
+ _storeCountry = storeCountry?.uppercase()
+ }
+
+ fun setApiUrl(url: String) {
+ _apiEndpoint = url.toUri()
+ }
+
private fun endpoint(
path: String,
testnet: Boolean,
platform: String,
build: String,
boot: Boolean = false,
+ queryParams: Map = emptyMap(),
+ bootFallback: Boolean = false,
): String = runBlocking {
- val builder = Uri.Builder()
- builder.scheme("https")
- .authority(if (boot) "boot.tonkeeper.com" else "api.tonkeeper.com")
+ val builder = if (bootFallback) {
+ "https://block.tonkeeper.com".toUri().buildUpon()
+ } else if (boot) {
+ "https://boot.tonkeeper.com".toUri().buildUpon()
+ } else {
+ _apiEndpoint.buildUpon()
+ }
+ builder
.appendEncodedPath(path)
.appendQueryParameter("lang", context.locale.language)
.appendQueryParameter("build", build)
@@ -48,11 +69,16 @@ internal class InternalApi(
.appendQueryParameter("chainName", if (testnet) "testnet" else "mainnet")
.appendQueryParameter("bundle_id", context.packageName)
- val storeCountry = context.getStoreCountry()
- storeCountry?.let {
- builder.appendQueryParameter("store_country_code", storeCountry)
+ _storeCountry?.let {
+ builder.appendQueryParameter("store_country_code", it)
+ }
+ _deviceCountry?.let {
+ builder.appendQueryParameter("device_country_code", it)
+ }
+
+ queryParams.forEach {
+ builder.appendQueryParameter(it.key, it.value)
}
- builder.appendQueryParameter("device_country_code", context.deviceCountry)
builder.build().toString()
}
@@ -64,8 +90,10 @@ internal class InternalApi(
build: String = appVersionName,
locale: Locale,
boot: Boolean = false,
+ queryParams: Map = emptyMap(),
+ bootFallback: Boolean = false,
): JSONObject {
- val url = endpoint(path, testnet, platform, build, boot)
+ val url = endpoint(path, testnet, platform, build, boot, queryParams, bootFallback)
val headers = ArrayMap()
headers["Accept-Language"] = locale.toString()
val body = withRetry {
@@ -74,18 +102,40 @@ internal class InternalApi(
return JSONObject(body)
}
- fun getOnRampData(country: String) = withRetry {
- okHttpClient.get("https://swap.tonkeeper.com/v2/onramp/currencies?country=${country.uppercase()}")
+ private fun swapEndpoint(prefix: String, path: String): String {
+ val builder = prefix.toUri().buildUpon()
+ .appendEncodedPath(path)
+ _deviceCountry?.let {
+ builder.appendQueryParameter("device_country_code", _deviceCountry)
+ builder.appendQueryParameter("country", _storeCountry ?: _deviceCountry)
+ }
+ _storeCountry?.let {
+ builder.appendQueryParameter("store_country_code", _storeCountry)
+ }
+ return builder.build().toString()
}
- fun getEthenaStakingAPY(address: String): BigDecimal = withRetry {
- val json = request("ethena/staking?address=$address", false, locale = context.locale)
- BigDecimal.valueOf(json.getDouble("value"))
- } ?: BigDecimal.ZERO
+ fun getOnRampData(prefix: String) = withRetry {
+ okHttpClient.get(swapEndpoint(prefix, "v2/onramp/currencies"))
+ }
- fun calculateOnRamp(args: OnRampArgsEntity) = withRetry {
- val url = "https://swap.tonkeeper.com/v2/onramp/calculate"
- okHttpClient.postJSON(url, args.toJSON().toString()).body?.string()
+ fun getOnRampPaymentMethods(prefix: String, currency: String) = withRetry {
+ okHttpClient.get(swapEndpoint(prefix, "v2/onramp/payment_methods"))
+ }
+
+ fun getOnRampMerchants(prefix: String) = withRetry {
+ okHttpClient.get(swapEndpoint(prefix, "v2/onramp/merchants"))
+ }
+
+ fun calculateOnRamp(prefix: String, args: OnRampArgsEntity): String? {
+ val json = args.toJSON()
+ _deviceCountry?.let { json.put("country", _deviceCountry) }
+ return withRetry {
+ okHttpClient.postJSON(
+ swapEndpoint(prefix, "v2/onramp/calculate"),
+ json.toString()
+ ).body.string()
+ }
}
fun getNotifications(): List {
@@ -113,7 +163,11 @@ internal class InternalApi(
val telegramBots = domains.filter { it.startsWith("@") }.map { "t.me/${it.substring(1)}" }
val maskDomains = domains.filter { it.startsWith("*.") }
val cleanDomains = domains.filter { domain ->
- !domain.startsWith("@") && !domain.startsWith("*.") && maskDomains.none { mask -> domain.endsWith(".$mask") }
+ !domain.startsWith("@") && !domain.startsWith("*.") && maskDomains.none { mask ->
+ domain.endsWith(
+ ".$mask"
+ )
+ }
}
return (maskDomains + cleanDomains + telegramBots).toTypedArray()
@@ -129,13 +183,23 @@ internal class InternalApi(
return data.getJSONObject("data")
}
- fun downloadConfig(testnet: Boolean): ConfigEntity? {
+ fun downloadConfig(testnet: Boolean, fallback: Boolean = false): ConfigEntity? {
return try {
- val json = request("keys", testnet, locale = context.locale, boot = true)
+ val json = request(
+ "keys",
+ testnet,
+ locale = context.locale,
+ boot = true,
+ bootFallback = fallback
+ )
ConfigEntity(json, context.isDebug)
} catch (e: Throwable) {
- FirebaseCrashlytics.getInstance().recordException(e)
- null
+ if (!fallback) {
+ downloadConfig(testnet, true)
+ } else {
+ FirebaseCrashlytics.getInstance().recordException(e)
+ null
+ }
}
}
@@ -173,4 +237,14 @@ internal class InternalApi(
}
}
+ fun getEthena(accountId: String): EthenaEntity? = withRetry {
+ val json = request(
+ "staking/ethena",
+ false,
+ locale = context.locale,
+ queryParams = mapOf("address" to accountId)
+ )
+ EthenaEntity(json)
+ }
+
}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/SwapApi.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/SwapApi.kt
new file mode 100644
index 000000000..aeb4bb6da
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/SwapApi.kt
@@ -0,0 +1,41 @@
+package com.tonapps.wallet.api.internal
+
+import androidx.core.net.toUri
+import com.tonapps.network.get
+import com.tonapps.network.sse
+import com.tonapps.wallet.api.SwapAssetParam
+import com.tonapps.wallet.api.entity.SwapEntity
+import com.tonapps.wallet.api.withRetry
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
+import okhttp3.OkHttpClient
+
+internal class SwapApi(
+ private val okHttpClient: OkHttpClient
+) {
+
+ fun getSwapAssets(prefix: String) = withRetry {
+ okHttpClient.get("$prefix/v2/swap/assets")
+ }
+
+ fun stream(
+ prefix: String,
+ from: SwapAssetParam,
+ to: SwapAssetParam,
+ userAddress: String
+ ): Flow {
+ if (from.isEmpty && to.isEmpty) {
+ return emptyFlow()
+ }
+ val builder = "$prefix/v2/swap/omniston/stream".toUri().buildUpon()
+ from.apply("from", builder)
+ to.apply("to", builder)
+ builder.appendQueryParameter("userAddress", userAddress)
+ val url = builder.build().toString()
+ return okHttpClient.sse(url) { }.map {
+ SwapEntity.parse(it.data)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/omniston/Omniston.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/omniston/Omniston.kt
new file mode 100644
index 000000000..28010a31f
--- /dev/null
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/omniston/Omniston.kt
@@ -0,0 +1,155 @@
+package com.tonapps.wallet.api.omniston
+
+import kotlinx.serialization.Serializable
+
+object Omniston {
+
+ fun fixAddress(address: String): String {
+ if (address.equals("ton", true)) {
+ return "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"
+ }
+ return address
+ }
+
+ @Serializable
+ data class AssetAddress(
+ val blockchain: Int = 607,
+ val address: String
+ )
+
+ @Serializable
+ data class Amount(
+ val offer_units: String? = null,
+ val ask_units: String? = null
+ )
+
+ @Serializable
+ data class SettlementParams(
+ val max_price_slippage_bps: Int = 500,
+ val max_outgoing_messages: Int = 4
+ )
+
+ @Serializable
+ data class QuoteParams(
+ val offer_asset_address: AssetAddress,
+ val ask_asset_address: AssetAddress,
+ val amount: Amount,
+ val referrer_fee_bps: Int = 0,
+ val settlement_methods: List = listOf(0),
+ val settlement_params: SettlementParams
+ )
+
+ @Serializable
+ data class QuoteResult(
+ val quote_id: String,
+ val offer_asset_address: AssetAddress,
+ val ask_asset_address: AssetAddress,
+ val offer_amount: Amount,
+ val ask_amount: Amount,
+ val rate: String,
+ val expires_at: Long,
+ val settlement_methods: List
+ )
+
+ @Serializable
+ data class EventWrapper(
+ val jsonrpc: String,
+ val method: String,
+ val params: EventParams
+ )
+
+ @Serializable
+ data class EventParams(
+ val subscription: Long,
+ val result: EventResult
+ )
+
+ @Serializable
+ data class EventResult(
+ val event: Event
+ )
+
+ @Serializable
+ data class Event(
+ val quote_updated: QuoteUpdated? = null,
+ )
+
+ @Serializable
+ data class QuoteUpdated(
+ val quote_id: String,
+ val resolver_id: String,
+ val resolver_name: String,
+ val offer_asset_address: AssetAddress,
+ val ask_asset_address: AssetAddress,
+ val offer_units: String,
+ val ask_units: String,
+ val referrer_address: String?,
+ val referrer_fee_units: String,
+ val protocol_fee_units: String,
+ val quote_timestamp: Long,
+ val trade_start_deadline: Long,
+ val gas_budget: String,
+ val estimated_gas_consumption: String,
+ val referrer_fee_asset: AssetAddress,
+ val protocol_fee_asset: AssetAddress,
+ val params: SwapParams
+ )
+
+ @Serializable
+ data class SwapParams(
+ val swap: SwapDetails
+ )
+
+ @Serializable
+ data class SwapDetails(
+ val routes: List
+ )
+
+ @Serializable
+ data class Route(
+ val steps: List,
+ val gas_budget: String
+ )
+
+ @Serializable
+ data class Step(
+ val offer_asset_address: AssetAddress,
+ val ask_asset_address: AssetAddress,
+ val chunks: List
+ )
+
+ @Serializable
+ data class Chunk(
+ val protocol: String,
+ val offer_amount: String,
+ val ask_amount: String,
+ val extra_version: Int,
+ val extra: List
+ )
+
+ @Serializable
+ data class TonEventResult(
+ val ton: TonPayload
+ )
+
+ @Serializable
+ data class TonPayload(
+ val messages: List
+ )
+
+ @Serializable
+ data class TonMessage(
+ val target_address: String,
+ val send_amount: String,
+ val payload: String
+ )
+
+ @Serializable
+ data class TransactionBuildTransfer(
+ val destination_address: AssetAddress,
+ val gas_excess_address: AssetAddress,
+ val source_address: AssetAddress,
+ val quote: QuoteUpdated,
+ )
+
+}
\ No newline at end of file
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 407d8d9fe..322f1258e 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
@@ -1,5 +1,7 @@
package com.tonapps.wallet.api.tron
+import android.net.Uri
+import androidx.collection.arrayMapOf
import androidx.core.net.toUri
import com.tonapps.blockchain.tron.TronTransaction
import com.tonapps.blockchain.tron.TronTransfer
@@ -12,13 +14,15 @@ import com.tonapps.network.postJSON
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.tron.entity.TronEstimationEntity
import com.tonapps.wallet.api.tron.entity.TronEventEntity
import com.tonapps.wallet.api.tron.entity.TronResourcesEntity
import com.tonapps.wallet.api.withRetry
-import io.batteryapi.apis.BatteryApi
+import io.batteryapi.apis.DefaultApi
import io.batteryapi.models.TronSendRequest
import io.ktor.util.encodeBase64
+import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.OkHttpClient
@@ -28,7 +32,7 @@ import java.math.BigInteger
class TronApi(
private val config: ConfigEntity,
private val okHttpClient: OkHttpClient,
- private val batteryApi: BatteryApi
+ private val batteryApi: DefaultApi
) {
companion object {
@@ -39,13 +43,33 @@ class TronApi(
private var safetyMargin: Double? = null
+ private val tronApiKey: String?
+ get() = config.tronApiKey?.ifBlank { null }
+
+ private fun headers() = arrayMapOf().apply {
+ tronApiKey?.let {
+ put("TRON-PRO-API-KEY", it)
+ }
+ }
+
+ private fun post(
+ uri: Uri,
+ body: JsonObject
+ ) = tronRetry {
+ okHttpClient.postJSON(uri.toString(), body.toString(), headers())
+ } ?: throw Exception("tron api failed")
+
+ private fun get(uri: Uri) = tronRetry {
+ okHttpClient.get(uri.toString(), headers())
+ } ?: throw Exception("tron api failed")
+
fun getTronUsdtBalance(
tronAddress: String,
): BalanceEntity {
try {
val builder = config.tronApiUrl.toUri().buildUpon()
.appendEncodedPath("wallet/triggersmartcontract")
- val url = builder.build().toString()
+ val url = builder.build()
val requestBody = buildJsonObject {
put("owner_address", tronAddress.tronHex())
@@ -54,10 +78,8 @@ class TronApi(
put("parameter", tronAddress.encodeTronAddress())
}
- val response = withRetry {
- okHttpClient.postJSON(url, requestBody.toString())
- } ?: throw Exception("tron api failed")
- val body = response.body?.string() ?: throw Exception("empty response")
+ val response = post(url, requestBody)
+ val body = response.body.string()
val json = JSONObject(body)
val constantResultArray = json.optJSONArray("constant_result")
@@ -78,36 +100,36 @@ class TronApi(
}
}
-
private fun getTronBlockchainHistory(
tronAddress: String,
limit: Int,
- beforeLt: Long? = null
+ beforeTimestamp: Timestamp?,
+ afterTimestamp: Timestamp?,
): List {
val builder = config.tronApiUrl.toUri().buildUpon()
.appendEncodedPath("v1/accounts/$tronAddress/transactions/trc20")
.appendQueryParameter("limit", limit.toString())
- beforeLt?.let {
- builder.appendQueryParameter("max_timestamp", (it * 1000 - 1).toString())
+ beforeTimestamp?.toLong()?.let {
+ builder.appendQueryParameter("max_timestamp", (it - 1).toString())
+ }
+ afterTimestamp?.toLong()?.let {
+ builder.appendQueryParameter("min_timestamp", it.toString())
}
- val url = builder.build().toString()
- val body = withRetry {
- okHttpClient.get(url)
- } ?: throw Exception("tron api failed")
+ val body = get(builder.build())
val json = JSONObject(body).getJSONArray("data")
- val events = json.map { TronEventEntity(it) }
-
- return events
+ return json.map { TronEventEntity(it) }
}
private fun getBatteryTransfersHistory(
batteryAuthToken: String,
limit: Int,
- beforeLt: Long? = null
+ beforeTimestamp: Timestamp?,
): List {
- val maxTimestamp = beforeLt?.let { it * 1000 - 1 }
- val response = batteryApi.getTronTransactions(batteryAuthToken, limit, maxTimestamp);
+ val maxTimestamp = beforeTimestamp?.toLong()?.let { it - 1 }
+ val response = withRetry {
+ batteryApi.getTronTransactions(batteryAuthToken, limit, maxTimestamp)
+ } ?: return emptyList()
return response.transactions.filter { it.txid.isNotEmpty() }.map { TronEventEntity(it) }
}
@@ -116,12 +138,14 @@ class TronApi(
tronAddress: String,
tonProofToken: String,
limit: Int,
- beforeLt: Long?,
+ beforeTimestamp: Timestamp?,
+ afterTimestamp: Timestamp? = null,
): List {
- val blockchainEvents = getTronBlockchainHistory(tronAddress, limit, beforeLt)
- val batteryEvents = getBatteryTransfersHistory(tonProofToken, limit, beforeLt)
+ val blockchainEvents = getTronBlockchainHistory(tronAddress, limit, beforeTimestamp, afterTimestamp)
+ val batteryEvents = getBatteryTransfersHistory(tonProofToken, limit, beforeTimestamp)
- return (batteryEvents + blockchainEvents).distinctBy { it.transactionHash }
+ return (batteryEvents + blockchainEvents)
+ .distinctBy { it.transactionHash }
.sortedByDescending { it.timestamp }
}
@@ -129,7 +153,7 @@ class TronApi(
val builder =
config.tronApiUrl.toUri().buildUpon()
.appendEncodedPath("wallet/triggerconstantcontract")
- val url = builder.build().toString()
+ val url = builder.build()
val requestBody = buildJsonObject {
put("owner_address", transfer.from)
@@ -139,10 +163,8 @@ class TronApi(
put("visible", true)
}
- val response = withRetry {
- okHttpClient.postJSON(url, requestBody.toString())
- } ?: throw Exception("tron api failed")
- val body = response.body?.string() ?: throw Exception("empty response")
+ val response = post(url, requestBody)
+ val body = response.body.string()
val json = JSONObject(body)
val resultObj = json.optJSONObject("result")
@@ -183,11 +205,8 @@ class TronApi(
private fun getAccountBandwidth(tronAddress: String): Int {
val builder =
config.tronApiUrl.toUri().buildUpon().appendEncodedPath("v1/accounts/$tronAddress")
- val url = builder.build().toString()
- val body = withRetry {
- okHttpClient.get(url)
- } ?: throw Exception("tron api failed")
+ val body = get(builder.build())
val json = JSONObject(body)
val dataArray = json.optJSONArray("data")
@@ -231,13 +250,13 @@ class TronApi(
bandwidth = resources.bandwidth,
)
- batteryApi.tronSend(tonProofToken, request)
+ batteryApi.tronSend(request, tonProofToken)
}
fun buildSmartContractTransaction(transfer: TronTransfer): TronTransaction {
val builder = config.tronApiUrl.toUri().buildUpon()
.appendEncodedPath("wallet/triggersmartcontract")
- val url = builder.build().toString()
+ val url = builder.build()
val requestBody = buildJsonObject {
put("contract_address", transfer.contractAddress.tronHex())
@@ -248,20 +267,24 @@ class TronApi(
put("fee_limit", 150000000)
}
- val response = withRetry {
- okHttpClient.postJSON(url, requestBody.toString())
- } ?: throw Exception("tron api failed")
- val body = response.body?.string() ?: throw Exception("empty response")
+ val response = post(url, requestBody)
+ val body = response.body.string()
val json = JSONObject(body)
return TronTransaction(json = json.getJSONObject("transaction"))
}
+ private fun tronRetry(retryBlock: () -> R) = withRetry(
+ delay = (1000L..3000L).random()
+ ) {
+ retryBlock()
+ }
+
fun activateWallet(
tronAddress: String,
tonProofToken: String,
) {
- batteryApi.tronSend(tonProofToken, TronSendRequest(wallet = tronAddress, tx = ""))
+ 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/TronEventEntity.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEventEntity.kt
index b8d481170..d4087d9a4 100644
--- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEventEntity.kt
+++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/tron/entity/TronEventEntity.kt
@@ -2,12 +2,13 @@ package com.tonapps.wallet.api.tron.entity
import com.tonapps.icu.Coins
import com.tonapps.wallet.api.entity.TokenEntity
+import com.tonapps.wallet.api.entity.value.Timestamp
import io.batteryapi.models.TronTransactionsListTransactionsInner
import org.json.JSONObject
data class TronEventEntity(
val amount: Coins,
- val timestamp: Long,
+ val timestamp: Timestamp,
val transactionHash: String,
val from: String,
val to: String,
@@ -17,7 +18,7 @@ data class TronEventEntity(
) {
constructor(json: JSONObject) : this(
amount = Coins.ofNano(json.getString("value"), decimals = TokenEntity.TRON_USDT.decimals),
- timestamp = json.getLong("block_timestamp") / 1000,
+ timestamp = Timestamp.from(json.getLong("block_timestamp")),
transactionHash = json.getString("transaction_id"),
from = json.getString("from"),
to = json.getString("to"),
@@ -25,7 +26,7 @@ data class TronEventEntity(
constructor(transaction: TronTransactionsListTransactionsInner) : this(
amount = Coins.ofNano(transaction.amount, decimals = TokenEntity.TRON_USDT.decimals),
- timestamp = transaction.timestamp,
+ timestamp = Timestamp.from(transaction.timestamp),
transactionHash = transaction.txid,
from = transaction.fromAccount,
to = transaction.toAccount,
diff --git a/apps/wallet/api/src/main/res/drawable/ic_tsusde_with_bg.png b/apps/wallet/api/src/main/res/drawable/ic_tsusde_with_bg.png
new file mode 100644
index 000000000..da88b1238
Binary files /dev/null and b/apps/wallet/api/src/main/res/drawable/ic_tsusde_with_bg.png differ
diff --git a/apps/wallet/data/account/build.gradle.kts b/apps/wallet/data/account/build.gradle.kts
index dd77e2ecf..38b476884 100644
--- a/apps/wallet/data/account/build.gradle.kts
+++ b/apps/wallet/data/account/build.gradle.kts
@@ -9,24 +9,24 @@ android {
}
dependencies {
- implementation(Dependence.KotlinX.serializationJSON)
- implementation(Dependence.KotlinX.coroutines)
- implementation(Dependence.Koin.core)
- implementation(Dependence.TON.tvm)
- implementation(Dependence.TON.crypto)
- implementation(Dependence.TON.tlb)
- implementation(Dependence.TON.blockTlb)
- implementation(Dependence.TON.tonapiTl)
- implementation(Dependence.TON.contract)
- implementation(project(Dependence.Module.tonApi))
- implementation(project(Dependence.Wallet.Data.core))
- implementation(project(Dependence.Wallet.Data.rn))
- implementation(project(Dependence.Wallet.Data.rates))
- implementation(project(Dependence.Wallet.api))
- implementation(project(Dependence.Lib.security))
- implementation(project(Dependence.Lib.network))
- implementation(project(Dependence.Lib.extensions))
- implementation(project(Dependence.Lib.blockchain))
- implementation(project(Dependence.Lib.sqlite))
- implementation(project(Dependence.Lib.ledger))
+ implementation(libs.kotlinX.serialization.json)
+ implementation(libs.kotlinX.coroutines.android)
+ implementation(libs.koin.core)
+ implementation(libs.ton.tvm)
+ implementation(libs.ton.crypto)
+ implementation(libs.ton.tlb)
+ 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))
}
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 65038ae93..b31d52ee2 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
@@ -13,6 +13,8 @@ import com.tonapps.blockchain.ton.extensions.toAccountId
import com.tonapps.blockchain.tron.KeychainTrxAccountsProvider
import com.tonapps.ledger.ton.LedgerAccount
import com.tonapps.wallet.api.API
+import com.tonapps.wallet.api.entity.value.Blockchain
+import com.tonapps.wallet.api.entity.value.BlockchainAddress
import com.tonapps.wallet.data.account.entities.MessageBodyEntity
import com.tonapps.wallet.data.account.entities.WalletEntity
import com.tonapps.wallet.data.account.source.DatabaseSource
@@ -273,6 +275,15 @@ class AccountRepository(
return trxAccountsProvider.getAddress()
}
+ suspend fun getTronBlockchainAddress(id: String): BlockchainAddress? {
+ val address = getTronAddress(id) ?: return null
+ return BlockchainAddress(
+ value = address,
+ testnet = false,
+ blockchain = Blockchain.TRON
+ )
+ }
+
suspend fun getTronPrivateKey(id: String): BigInteger? {
val trxAccountsProvider = getTrxAccountsProvider(id) ?: return null
return trxAccountsProvider.getPrivateKey()
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 5b15cb4b3..7ce2decbf 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
@@ -1,6 +1,8 @@
package com.tonapps.wallet.data.account
+import android.graphics.Color
import android.os.Parcelable
+import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
sealed class Wallet {
@@ -18,25 +20,49 @@ sealed class Wallet {
@Parcelize
data class Label(
- val accountName: String,
- val emoji: CharSequence,
- val color: Int
+ val accountName: String = "",
+ val emoji: CharSequence = "",
+ val color: Int = WalletColor.all.first()
): Parcelable {
- val isEmpty: Boolean
- get() = accountName.isBlank() && emoji.isBlank()
+ @IgnoredOnParcel
+ val isEmpty: Boolean by lazy {
+ accountName.isBlank() && emoji.isBlank()
+ }
val name: String
get() = accountName
- val title: CharSequence?
- get() = if (isEmpty) {
+ @IgnoredOnParcel
+ val title: CharSequence? by lazy {
+ if (isEmpty) {
null
} else if (emoji.startsWith("custom_")) {
name
} else {
String.format("%s %s", emoji, name)
}
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as Label
+ if (accountName != other.accountName) return false
+ if (emoji != other.emoji) return false
+ if (color != other.color) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = color
+ result = 31 * result + accountName.hashCode()
+ result = 31 * result + emoji.hashCode()
+ result = 31 * result + isEmpty.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + (title?.hashCode() ?: 0)
+ return result
+ }
}
diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/WalletColor.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/WalletColor.kt
index 19d5c0405..4fe759847 100644
--- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/WalletColor.kt
+++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/WalletColor.kt
@@ -1,32 +1,33 @@
package com.tonapps.wallet.data.account
import android.graphics.Color
+import androidx.core.graphics.toColorInt
object WalletColor {
- val SteelGray = Color.parseColor("#293342")
- val LightSteelGray = Color.parseColor("#424C5C")
- val Gray = Color.parseColor("#9DA2A4")
- val LightRed = Color.parseColor("#FF8585")
- val LightOrange = Color.parseColor("#FFA970")
- val LightYellow = Color.parseColor("#FFC95C")
- val LightGreen = Color.parseColor("#85CC7A")
- val LightBlue = Color.parseColor("#70A0FF")
- val LightAquamarine = Color.parseColor("#6CCCF5")
- val LightPurple = Color.parseColor("#AD89F5")
- val LightViolet = Color.parseColor("#F57FF5")
- val LightMagenta = Color.parseColor("#F576B1")
- val LightFireOrange = Color.parseColor("#F57F87")
- val Red = Color.parseColor("#FF5252")
- val Orange = Color.parseColor("#FF8B3D")
- val Yellow = Color.parseColor("#FFB92E")
- val Green = Color.parseColor("#69CC5A")
- val Blue = Color.parseColor("#528BFF")
- val Aquamarine = Color.parseColor("#47C8FF")
- val Purple = Color.parseColor("#925CFF")
- val Violet = Color.parseColor("#FF5CFF")
- val Magenta = Color.parseColor("#FF479D")
- val FireOrange = Color.parseColor("#FF525D")
+ val SteelGray = "#293342".toColorInt()
+ val LightSteelGray = "#424C5C".toColorInt()
+ val Gray = "#9DA2A4".toColorInt()
+ val LightRed = "#FF8585".toColorInt()
+ val LightOrange = "#FFA970".toColorInt()
+ val LightYellow = "#FFC95C".toColorInt()
+ val LightGreen = "#85CC7A".toColorInt()
+ val LightBlue = "#70A0FF".toColorInt()
+ val LightAquamarine = "#6CCCF5".toColorInt()
+ val LightPurple = "#AD89F5".toColorInt()
+ val LightViolet = "#F57FF5".toColorInt()
+ val LightMagenta = "#F576B1".toColorInt()
+ val LightFireOrange = "#F57F87".toColorInt()
+ val Red = "#FF5252".toColorInt()
+ val Orange = "#FF8B3D".toColorInt()
+ val Yellow = "#FFB92E".toColorInt()
+ val Green = "#69CC5A".toColorInt()
+ val Blue = "#528BFF".toColorInt()
+ val Aquamarine = "#47C8FF".toColorInt()
+ val Purple = "#925CFF".toColorInt()
+ val Violet = "#FF5CFF".toColorInt()
+ val Magenta = "#FF479D".toColorInt()
+ val FireOrange = "#FF525D".toColorInt()
val all = listOf(
SteelGray,
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 2e3dcb7ce..797e36415 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
@@ -17,6 +17,8 @@ import com.tonapps.extensions.readEnum
import com.tonapps.extensions.readParcelableCompat
import com.tonapps.extensions.writeBooleanCompat
import com.tonapps.extensions.writeEnum
+import com.tonapps.wallet.api.entity.value.Blockchain
+import com.tonapps.wallet.api.entity.value.BlockchainAddress
import com.tonapps.wallet.data.account.Wallet
import kotlinx.parcelize.Parcelize
import org.ton.api.pk.PrivateKeyEd25519
@@ -91,6 +93,13 @@ data class WalletEntity(
val address: String = contract.address.toWalletAddress(testnet)
+ val blockchainAddress: BlockchainAddress
+ get() = BlockchainAddress(
+ value = address,
+ testnet = testnet,
+ blockchain = Blockchain.TON
+ )
+
val isWatchOnly: Boolean
get() = type == Wallet.Type.Watch
diff --git a/apps/wallet/data/backup/build.gradle.kts b/apps/wallet/data/backup/build.gradle.kts
index fa81eb7ab..f9429764e 100644
--- a/apps/wallet/data/backup/build.gradle.kts
+++ b/apps/wallet/data/backup/build.gradle.kts
@@ -8,8 +8,8 @@ android {
}
dependencies {
- implementation(project(Dependence.Lib.sqlite))
- implementation(project(Dependence.Lib.extensions))
- implementation(project(Dependence.Wallet.Data.rn))
+ implementation(project(ProjectModules.Lib.sqlite))
+ implementation(project(ProjectModules.Lib.extensions))
+ implementation(project(ProjectModules.Wallet.Data.rn))
}
diff --git a/apps/wallet/data/battery/build.gradle.kts b/apps/wallet/data/battery/build.gradle.kts
index 56d40c87c..631851974 100644
--- a/apps/wallet/data/battery/build.gradle.kts
+++ b/apps/wallet/data/battery/build.gradle.kts
@@ -8,16 +8,14 @@ android {
}
dependencies {
- implementation(Dependence.Squareup.moshi)
- implementation(Dependence.Squareup.moshiAdapters)
- implementation(Dependence.Squareup.okhttp)
+ implementation(libs.okhttp)
- implementation(project(Dependence.Module.tonApi))
- implementation(project(Dependence.Wallet.Data.core))
- implementation(project(Dependence.Wallet.api))
- implementation(project(Dependence.Lib.blockchain))
- implementation(project(Dependence.Lib.extensions))
- implementation(project(Dependence.Lib.network))
- implementation(project(Dependence.Lib.icu))
- implementation(project(Dependence.Lib.security))
+ 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))
}
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 53a1d8f7b..52ef57364 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
@@ -11,7 +11,13 @@ object BatteryMapper {
balance: Coins,
meanFees: String
): Int {
+ if (!balance.isPositive) {
+ return 0
+ }
val meanFeesBigDecimal = BigDecimal(meanFees)
+ if (BigDecimal.ZERO >= meanFeesBigDecimal) {
+ return 0
+ }
return balance.value.divide(meanFeesBigDecimal, 0, RoundingMode.UP).toInt()
}
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 19d1b2f57..8f6f6bb1c 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
@@ -109,7 +109,8 @@ class BatteryRepository(
ignoreCache: Boolean = false,
): Int = withContext(Dispatchers.IO) {
val balance = getBalance(tonProofToken, publicKey, testnet, ignoreCache)
- val charges = BatteryMapper.convertToCharges(balance.balance, api.config.batteryMeanFees)
+ val config = getConfig(testnet, ignoreCache)
+ val charges = BatteryMapper.convertToCharges(balance.balance, config.chargeCost)
charges
}
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 2e13f35b7..0174189e4 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
@@ -1,8 +1,10 @@
package com.tonapps.wallet.data.battery.entity
import android.os.Parcelable
+import io.batteryapi.models.ConfigMeanPrices
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
+import kotlinx.serialization.SerialName
import org.ton.block.AddrStd
@Parcelize
@@ -10,9 +12,20 @@ data class BatteryConfigEntity(
val excessesAccount: String?,
val fundReceiver: String?,
val rechargeMethods: List,
- val gasProxy: List
+ val gasProxy: List,
+ val meanPrices: MeanPrices,
+ val chargeCost: String,
+ val reservedAmount: String,
) : Parcelable {
+ @Parcelize
+ data class MeanPrices(
+ val batteryMeanPriceSwap: Int,
+ val batteryMeanPriceJetton: Int,
+ val batteryMeanPriceNft: Int,
+ val batteryMeanPriceTronUsdt: Int? = null,
+ ) : Parcelable
+
@IgnoredOnParcel
val excessesAddress: AddrStd? by lazy {
excessesAccount?.let { AddrStd(it) }
@@ -23,7 +36,10 @@ data class BatteryConfigEntity(
excessesAccount = null,
fundReceiver = null,
rechargeMethods = emptyList(),
- gasProxy = emptyList()
+ gasProxy = emptyList(),
+ meanPrices = MeanPrices(0, 0, 0, null),
+ chargeCost = "0",
+ reservedAmount = "0",
)
}
}
\ No newline at end of file
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 514de4089..4a05cfb8b 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
@@ -26,6 +26,7 @@ data class RechargeMethodEntity(
return when (this) {
RechargeMethodsMethodsInner.Type.jetton -> RechargeMethodType.JETTON
RechargeMethodsMethodsInner.Type.ton -> RechargeMethodType.TON
+ RechargeMethodsMethodsInner.Type.unknown -> RechargeMethodType.TON
}
}
}
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 3ef996e94..91ce2aeed 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
@@ -39,7 +39,15 @@ internal class RemoteDataSource(
excessesAccount = config.excessAccount,
fundReceiver = config.fundReceiver,
rechargeMethods = rechargeMethods.methods.map(::RechargeMethodEntity),
- gasProxy = config.gasProxy.map { it.address }
+ gasProxy = config.gasProxy.map { it.address },
+ meanPrices = BatteryConfigEntity.MeanPrices(
+ batteryMeanPriceSwap = config.meanPrices.batteryMeanPriceSwap,
+ batteryMeanPriceJetton = config.meanPrices.batteryMeanPriceJetton,
+ batteryMeanPriceNft = config.meanPrices.batteryMeanPriceNft,
+ batteryMeanPriceTronUsdt = config.meanPrices.batteryMeanPriceTronUsdt
+ ),
+ 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 403a84b4d..d4430de92 100644
--- a/apps/wallet/data/browser/build.gradle.kts
+++ b/apps/wallet/data/browser/build.gradle.kts
@@ -8,13 +8,13 @@ android {
}
dependencies {
- implementation(Dependence.Squareup.okhttp)
+ implementation(libs.okhttp)
- implementation(project(Dependence.Wallet.api))
- implementation(project(Dependence.Wallet.Data.core))
- implementation(project(Dependence.Wallet.Data.account))
+ implementation(project(ProjectModules.Wallet.api))
+ implementation(project(ProjectModules.Wallet.Data.core))
+ implementation(project(ProjectModules.Wallet.Data.account))
- implementation(project(Dependence.Lib.network))
- implementation(project(Dependence.Lib.extensions))
+ implementation(project(ProjectModules.Lib.network))
+ implementation(project(ProjectModules.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 e191773bd..861c54f8a 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
@@ -41,6 +41,9 @@ class BrowserRepository(context: Context, api: API) {
}
suspend fun isTrustedApp(country: String, testnet: Boolean, 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)
for (app in apps) {
diff --git a/apps/wallet/data/collectibles/build.gradle.kts b/apps/wallet/data/collectibles/build.gradle.kts
index 158b9210b..aa2a5a682 100644
--- a/apps/wallet/data/collectibles/build.gradle.kts
+++ b/apps/wallet/data/collectibles/build.gradle.kts
@@ -8,10 +8,10 @@ android {
}
dependencies {
- implementation(project(Dependence.Module.tonApi))
- implementation(project(Dependence.Wallet.Data.core))
- implementation(project(Dependence.Wallet.api))
- implementation(project(Dependence.Lib.blockchain))
- implementation(project(Dependence.Lib.extensions))
- implementation(project(Dependence.Lib.sqlite))
+ 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))
}
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 ef5be2757..bb9c01368 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
@@ -3,12 +3,19 @@ package com.tonapps.wallet.data.collectibles
import android.content.Context
import android.util.Log
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,
@@ -19,6 +26,24 @@ 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 getDnsSoonExpiring(accountId: String, testnet: Boolean, period: Int = 30) = getDnsExpiring(accountId, testnet, period)
+
+ suspend fun getDnsNftExpiring(
+ accountId: String,
+ testnet: Boolean,
+ nftAddress: String
+ ) = getDnsExpiring(accountId, testnet, 366).firstOrNull {
+ it.dnsItem?.address?.equalsAddress(nftAddress) == true
+ }
+
fun getNft(accountId: String, testnet: Boolean, address: String): NftEntity? {
val nft = localDataSource.getSingle(accountId, testnet, address)
if (nft != null) {
@@ -64,7 +89,7 @@ class CollectiblesRepository(
): List? {
val nftItems = api.getNftItems(address, testnet) ?: return null
val items = nftItems.filter {
- it.trust != "blacklist" && it.metadata["render_type"] != "hidden"
+ it.trust != TrustType.blacklist && it.renderType != "hidden"
}.map { NftEntity(it, testnet) }
localDataSource.save(address, testnet, items.toList())
diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/DnsExpiringEntity.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/DnsExpiringEntity.kt
new file mode 100644
index 000000000..afdc90908
--- /dev/null
+++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/entities/DnsExpiringEntity.kt
@@ -0,0 +1,37 @@
+package com.tonapps.wallet.data.collectibles.entities
+
+import android.os.Parcelable
+import com.tonapps.blockchain.ton.extensions.toRawAddress
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class DnsExpiringEntity(
+ val expiringAt: Long,
+ val name: String,
+ val dnsItem: NftEntity? = null
+): Parcelable {
+
+ @IgnoredOnParcel
+ val addressRaw: String by lazy {
+ dnsItem?.address?.toRawAddress() ?: ""
+ }
+
+ @IgnoredOnParcel
+ val inSale: Boolean by lazy {
+ dnsItem?.inSale ?: false
+ }
+
+ @IgnoredOnParcel
+ val daysUntilExpiration: Int by lazy {
+ val currentTime = System.currentTimeMillis() / 1000
+ val remainingSeconds = expiringAt - currentTime
+
+ if (remainingSeconds <= 0) {
+ 0
+ } else {
+ (remainingSeconds / (24 * 60 * 60)).toInt()
+ }
+ }
+
+}
\ 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 eaeb39552..6e3362af0 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
@@ -119,6 +119,6 @@ data class NftEntity(
verified = item.approvedBy.isNotEmpty(),
inSale = item.sale != null,
dns = item.dns,
- trust = Trust(item.trust),
+ 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 c7e52f642..6e3eaff71 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
@@ -3,6 +3,7 @@ package com.tonapps.wallet.data.collectibles.entities
import android.os.Parcelable
import android.util.Base64
import android.util.Log
+import io.JsonAny
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@@ -43,12 +44,18 @@ data class NftMetadataEntity(
"https://c.tonapi.io/json?url=$encoded"
}
- constructor(map: Map) : this(
- strings = map.filter { it.value is String }.mapValues { it.value as String } as HashMap,
- buttons = map["buttons"]?.let { buttons ->
- (buttons as List |