diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt index 09c6746fc..7877e302f 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt @@ -239,7 +239,9 @@ class API( token = TokenEntity.TON, value = Coins.of(account.balance), walletAddress = accountId, - initializedAccount = initializedAccount + initializedAccount = initializedAccount, + isCompressed = false, + isTransferable = true ) } @@ -254,15 +256,29 @@ class API( return TokenEntity(jetton) } + fun getJettonCustomPayload( + accountId: String, + testnet: Boolean, + jettonId: String + ): TokenEntity.TransferPayload? { + val jettonsAPI = jettons(testnet) + val payload = withRetry { + jettonsAPI.getJettonTransferPayload(accountId, jettonId) + } ?: return null + return TokenEntity.TransferPayload(tokenAddress = jettonId, payload) + } + fun getJettonsBalances( accountId: String, testnet: Boolean, - currency: String? = null + currency: String? = null, + extensions: List? = null ): List? { val jettonsBalances = withRetry { accounts(testnet).getAccountJettonsBalances( accountId = accountId, - currencies = currency?.let { listOf(it) } + currencies = currency?.let { listOf(it) }, + extensions = extensions, ).balances } ?: return null return jettonsBalances.map { BalanceEntity(it) }.filter { it.value.isPositive } 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 e48d7088e..c3b5f9e23 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,6 +1,7 @@ package com.tonapps.wallet.api.entity import android.os.Parcelable +import android.util.Log import com.tonapps.icu.Coins import io.tonapi.models.JettonBalance import io.tonapi.models.TokenRates @@ -13,18 +14,31 @@ data class BalanceEntity( val token: TokenEntity, val value: Coins, val walletAddress: String, - val initializedAccount: Boolean + val initializedAccount: Boolean, + val isCompressed: Boolean, + val isTransferable: Boolean, ): Parcelable { companion object { - fun empty(accountId: String) = create(accountId, Coins.ZERO) + fun empty( + accountId: String, + isCompressed: Boolean, + isTransferable: Boolean + ) = create(accountId, Coins.ZERO, isCompressed, isTransferable) - fun create(accountId: String, value: Coins) = BalanceEntity( + fun create( + accountId: String, + value: Coins, + isCompressed: Boolean, + isTransferable: Boolean + ) = BalanceEntity( token = TokenEntity.TON, value = value, walletAddress = accountId, initializedAccount = false, + isCompressed = isCompressed, + isTransferable = isTransferable ) } @@ -38,10 +52,12 @@ data class BalanceEntity( get() = token.decimals constructor(jettonBalance: JettonBalance) : this( - token = TokenEntity(jettonBalance.jetton), + token = TokenEntity(jettonBalance.jetton, jettonBalance.extensions, jettonBalance.lock), value = Coins.of(BigDecimal(jettonBalance.balance).movePointLeft(jettonBalance.jetton.decimals), jettonBalance.jetton.decimals), walletAddress = jettonBalance.walletAddress.address, initializedAccount = true, + isCompressed = jettonBalance.extensions?.contains(TokenEntity.Extension.CustomPayload.value) == true, + isTransferable = jettonBalance.extensions?.contains(TokenEntity.Extension.NonTransferable.value) != true ) { rates = jettonBalance.price } 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 0cbef2867..68cb7dc15 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 @@ -3,12 +3,17 @@ package com.tonapps.wallet.api.entity import android.net.Uri import android.os.Parcelable import android.util.Log +import com.tonapps.blockchain.ton.extensions.safeParseCell import com.tonapps.blockchain.ton.extensions.toRawAddress import com.tonapps.wallet.api.R +import io.tonapi.models.JettonBalanceLock import io.tonapi.models.JettonInfo import io.tonapi.models.JettonPreview +import io.tonapi.models.JettonTransferPayload import io.tonapi.models.JettonVerificationType import kotlinx.parcelize.Parcelize +import org.ton.block.StateInit +import org.ton.cell.Cell @Parcelize data class TokenEntity( @@ -17,13 +22,58 @@ data class TokenEntity( val symbol: String, val imageUri: Uri, val decimals: Int, - val verification: Verification + val verification: Verification, + val isCompressed: Boolean, + val isTransferable: Boolean, + val lock: Lock? = null ): Parcelable { enum class Verification { whitelist, blacklist, none } + @Parcelize + data class Lock( + val amount: String, + val till: Long + ): Parcelable { + + constructor(lock: JettonBalanceLock) : this( + amount = lock.amount, + till = lock.till + ) + } + + data class TransferPayload( + val tokenAddress: String, + val customPayload: Cell? = null, + val stateInit: StateInit? = null + ) { + + companion object { + + fun empty(tokenAddress: String): TransferPayload { + return TransferPayload(tokenAddress) + } + } + + val isEmpty: Boolean + get() = customPayload == null && stateInit == null + + constructor(tokenAddress: String, model: JettonTransferPayload) : this( + tokenAddress = tokenAddress, + customPayload = model.customPayload?.safeParseCell(), + stateInit = model.stateInit?.safeParseCell()?.let { + StateInit.Companion.loadTlb(it) + }, + ) + } + + enum class Extension(val value: String) { + NonTransferable("non_transferable"), + CustomPayload("custom_payload") + } + companion object { val TON_ICON_URI = Uri.Builder().scheme("res").path(R.drawable.ic_ton_with_bg.toString()).build() @@ -35,7 +85,9 @@ data class TokenEntity( symbol = "TON", imageUri = TON_ICON_URI, decimals = 9, - verification = Verification.whitelist + verification = Verification.whitelist, + isCompressed = false, + isTransferable = true ) val USDT = TokenEntity( @@ -44,7 +96,9 @@ data class TokenEntity( symbol = "USDâ‚®", imageUri = USDT_ICON_URI, decimals = 6, - verification = Verification.whitelist + verification = Verification.whitelist, + isCompressed = false, + isTransferable = true ) private fun convertVerification(verification: JettonVerificationType): Verification { @@ -68,21 +122,35 @@ data class TokenEntity( val blacklist: Boolean get() = verification == TokenEntity.Verification.blacklist - constructor(jetton: JettonPreview) : this( + constructor( + jetton: JettonPreview, + extensions: List? = null, + lock: JettonBalanceLock? = null + ) : this( address = jetton.address.toRawAddress(), name = jetton.name, symbol = jetton.symbol, imageUri = Uri.parse(jetton.image), decimals = jetton.decimals, - verification = convertVerification(jetton.verification) + verification = convertVerification(jetton.verification), + isCompressed = extensions?.contains(Extension.CustomPayload.value) == true, + isTransferable = extensions?.contains(Extension.NonTransferable.value) != true, + lock = lock?.let { Lock(it) } ) - constructor(jetton: JettonInfo) : this( + constructor( + jetton: JettonInfo, + extensions: List? = null, + lock: JettonBalanceLock? = null + ) : this( address = jetton.metadata.address.toRawAddress(), name = jetton.metadata.name, symbol = jetton.metadata.symbol, imageUri = Uri.parse(jetton.metadata.image), decimals = jetton.metadata.decimals.toInt(), - verification = convertVerification(jetton.verification) + verification = convertVerification(jetton.verification), + isCompressed = extensions?.contains(Extension.CustomPayload.value) == true, + isTransferable = extensions?.contains(Extension.NonTransferable.value) != true, + lock = lock?.let { Lock(it) } ) } \ No newline at end of file diff --git a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/RawMessageEntity.kt b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/RawMessageEntity.kt index 60022fda6..c156ba4b1 100644 --- a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/RawMessageEntity.kt +++ b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/entity/RawMessageEntity.kt @@ -40,7 +40,9 @@ data class RawMessageEntity( val payload: Cell get() = payloadValue.safeParseCell() ?: Cell() - fun getWalletTransfer(excessesAddress: AddrStd? = null): WalletTransfer { + fun getWalletTransfer( + excessesAddress: AddrStd? = null + ): WalletTransfer { val builder = WalletTransferBuilder() builder.stateInit = stateInit builder.destination = address diff --git a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt index d185ce767..1891bb32c 100644 --- a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt +++ b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/entities/AccountTokenEntity.kt @@ -18,11 +18,13 @@ data class AccountTokenEntity( companion object { val EMPTY = AccountTokenEntity( - BalanceEntity( - TokenEntity.TON, - Coins.ZERO, - "", - false + balance = BalanceEntity( + token = TokenEntity.TON, + value = Coins.ZERO, + walletAddress = "", + initializedAccount = false, + isCompressed = false, + isTransferable = true, ) ) } @@ -62,4 +64,7 @@ data class AccountTokenEntity( val blacklist: Boolean get() = balance.token.blacklist + + val isCompressed: Boolean + get() = balance.isCompressed } \ No newline at end of file diff --git a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt index 8ee3dcdcb..312aa1697 100644 --- a/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt +++ b/apps/wallet/data/tokens/src/main/java/com/tonapps/wallet/data/token/source/RemoteDataSource.kt @@ -21,7 +21,15 @@ internal class RemoteDataSource( testnet: Boolean ): List? = withContext(Dispatchers.IO) { val tonBalanceDeferred = async { api.getTonBalance(accountId, testnet) } - val jettonBalancesDeferred = async { api.getJettonsBalances(accountId, testnet, currency.code) } + val jettonBalancesDeferred = async { api.getJettonsBalances( + accountId = accountId, + testnet = testnet, + currency = currency.code, + extensions = listOf( + TokenEntity.Extension.CustomPayload.value, + TokenEntity.Extension.NonTransferable.value + )) + } val tonBalance = tonBalanceDeferred.await() ?: return@withContext null val jettons = jettonBalancesDeferred.await()?.toMutableList() ?: mutableListOf() @@ -36,7 +44,9 @@ internal class RemoteDataSource( token = TokenEntity.USDT, value = Coins.ZERO, walletAddress = accountId, - initializedAccount = tonBalance.initializedAccount + initializedAccount = tonBalance.initializedAccount, + isCompressed = false, + isTransferable = true )) } else if (usdtIndex >= 0) { jettons[usdtIndex] = jettons[usdtIndex].copy( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt index 807df3fb5..9f384134d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt @@ -28,7 +28,12 @@ data class AssetsExtendedEntity( get() = when (raw) { is AssetsEntity.Token -> raw.token is AssetsEntity.Staked -> AccountTokenEntity( - balance = BalanceEntity.create(accountId, raw.staked.balance), + balance = BalanceEntity.create( + accountId = accountId, + value = raw.staked.balance, + isCompressed = false, + isTransferable = true + ), ) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt index 49c0fd80c..bef9771f9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt @@ -17,6 +17,7 @@ import com.tonapps.security.Security import com.tonapps.security.hex import com.tonapps.tonkeeper.extensions.toGrams import com.tonapps.wallet.api.entity.BalanceEntity +import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.events.CommentEncryption import org.ton.api.pk.PrivateKeyEd25519 @@ -45,6 +46,7 @@ data class TransferEntity( val nftAddress: String? = null, val commentEncrypted: Boolean, val queryId: BigInteger = newWalletQueryId(), + val tokenPayload: TokenEntity.TransferPayload? ) { val contract: BaseWalletContract @@ -57,7 +59,13 @@ data class TransferEntity( get() = nftAddress != null val stateInit: StateInit? - get() = if (seqno == 0) contract.stateInit else null + get() { + return if (seqno == 0) { + tokenPayload?.stateInit ?: contract.stateInit + } else { + tokenPayload?.stateInit + } + } val testnet: Boolean get() = wallet.testnet @@ -229,13 +237,17 @@ data class TransferEntity( return getCommentForwardPayload(privateKey) } - private fun jettonBody(privateKey: PrivateKeyEd25519?, excessesAddress: AddrStd): Cell { + private fun jettonBody( + privateKey: PrivateKeyEd25519?, + excessesAddress: AddrStd + ): Cell { return TonTransferHelper.jetton( coins = coins, toAddress = destination, responseAddress = excessesAddress, queryId = queryId, - body = getCommentForwardPayload(privateKey), + forwardPayload = getCommentForwardPayload(privateKey), + customPayload = tokenPayload?.customPayload, ) } @@ -282,7 +294,8 @@ data class TransferEntity( toAddress = batteryAddress, responseAddress = batteryAddress, queryId = queryId, - body = CellBuilder.beginCell().storeOpCode(TONOpCode.GASLESS).endCell(), + forwardPayload = beginCell().storeOpCode(TONOpCode.GASLESS).endCell(), + customPayload = tokenPayload?.customPayload, ) builder.coins = TRANSFER_PRICE builder.destination = AddrStd.parse(token.walletAddress) @@ -303,6 +316,13 @@ data class TransferEntity( private var commentEncrypted: Boolean = false private var nftAddress: String? = null private var queryId: BigInteger? = null + private var tokenPayload: TokenEntity.TransferPayload? = null + + fun setTokenPayload(tokenPayload: TokenEntity.TransferPayload) = apply { + if (!tokenPayload.isEmpty) { + this.tokenPayload = tokenPayload + } + } fun setQueryId(queryId: BigInteger) = apply { this.queryId = queryId } @@ -352,7 +372,8 @@ data class TransferEntity( comment = comment, nftAddress = nftAddress, commentEncrypted = commentEncrypted, - queryId = queryId ?: newWalletQueryId() + queryId = queryId ?: newWalletQueryId(), + tokenPayload = tokenPayload ) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/BalancesManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/BalancesManager.kt index 1578b97c0..75a127d63 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/BalancesManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/BalancesManager.kt @@ -18,6 +18,7 @@ class BalancesManager( settingsRepository.currencyFlow.onEach { clear() }.launchIn(scope) + settingsRepository.walletPrefsChangedFlow.onEach { clear() }.launchIn(scope) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt index 53820be95..97665975e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/battery/recharge/BatteryRechargeViewModel.kt @@ -15,6 +15,7 @@ import com.tonapps.extensions.state import com.tonapps.icu.Coins import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.core.entities.TransferEntity +import com.tonapps.tonkeeper.extensions.toGrams import com.tonapps.tonkeeper.ui.base.BaseWalletVM import com.tonapps.tonkeeper.ui.screen.battery.recharge.entity.BatteryRechargeEvent import com.tonapps.tonkeeper.ui.screen.battery.recharge.entity.RechargePackEntity @@ -326,12 +327,19 @@ class BatteryRechargeViewModel( _eventFlow.tryEmit(BatteryRechargeEvent.Sign(request)) } else { val queryId = TransferEntity.newWalletQueryId() + val customPayload = if (token.isCompressed) { + api.getJettonCustomPayload(wallet.accountId, wallet.testnet, token.address) + } else { + null + } + val jettonPayload = TonTransferHelper.jetton( - coins = org.ton.block.Coins.ofNano(amount.toLong()), + coins = amount.toGrams(), toAddress = AddrStd.parse(fundReceiver), responseAddress = wallet.contract.address, queryId = queryId, - body = payload, + forwardPayload = payload, + customPayload = customPayload?.customPayload ) val request = SignRequestEntity( fromValue = wallet.contract.address.toAccountId(), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt index b91efb304..8e1f8e87b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt @@ -2,6 +2,7 @@ package com.tonapps.tonkeeper.ui.screen.send.main import android.app.Application import android.content.Context +import android.util.Log import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.contract.WalletFeature import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 @@ -114,6 +115,7 @@ class SendViewModel( private var lastTransferEntity: TransferEntity? = null private var sendTransferType: SendTransferType = SendTransferType.Default + private var tokenCustomPayload: TokenEntity.TransferPayload? = null private val userInputAddressFlow = userInputFlow .map { it.address } @@ -309,8 +311,12 @@ class SendViewModel( userInputFlow.map { Pair(it.comment, it.encryptedComment) }.distinctUntilChanged(), selectedTokenFlow, ) { transaction, (comment, encryptedComment), token -> + val customPayload = getTokenCustomPayload(token.balance.token) val sendMetadata = getSendParams(wallet) val builder = TransferEntity.Builder(wallet) + if (!customPayload.isEmpty) { + builder.setTokenPayload(customPayload) + } builder.setToken(transaction.token) builder.setDestination(transaction.destination.address, transaction.destination.publicKey) builder.setSeqno(sendMetadata.seqno) @@ -760,4 +766,20 @@ class SendViewModel( _uiEventFlow.tryEmit(SendEvent.Success) }.launchIn(viewModelScope) } + + private fun getTokenCustomPayload(token: TokenEntity): TokenEntity.TransferPayload { + if (token.isTon) { + return TokenEntity.TransferPayload.empty("TON") + } else if (!token.isCompressed) { + return TokenEntity.TransferPayload.empty(token.address) + } + if (tokenCustomPayload != null && tokenCustomPayload!!.tokenAddress.equalsAddress(token.address)) { + return tokenCustomPayload!! + } + + if (tokenCustomPayload == null) { + tokenCustomPayload = api.getJettonCustomPayload(wallet.accountId, wallet.testnet, token.address) + } + return tokenCustomPayload ?: TokenEntity.TransferPayload.empty(token.address) + } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt index 0fb578852..e8e3d7893 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/picker/TokenPickerViewModel.kt @@ -40,7 +40,10 @@ class TokenPickerViewModel( private val queryFlow = _queryFlow.asSharedFlow() private val tokensFlow = settingsRepository.currencyFlow.map { currency -> - val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet) ?: emptyList() + val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet)?.filter { + it.balance.isTransferable + } ?: emptyList() + if (allowedTokens.isNotEmpty()) { tokens.filter { allowedTokens.contains(it.address) } } else { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt index 6052eb814..0890b6fb2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/Item.kt @@ -40,7 +40,7 @@ sealed class Item(type: Int): BaseListItem(type) { get() = wallet.type val send: Boolean - get() = !wallet.isWatchOnly + get() = !wallet.isWatchOnly && token.isTransferable val swap: Boolean get() = token.verified && !wallet.isWatchOnly diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TonTransferHelper.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TonTransferHelper.kt index a517358a2..2e05a584b 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TonTransferHelper.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/TonTransferHelper.kt @@ -1,12 +1,16 @@ package com.tonapps.blockchain.ton +import com.tonapps.blockchain.ton.extensions.storeMaybeRef import com.tonapps.blockchain.ton.extensions.storeOpCode +import org.ton.block.AddrStd import org.ton.block.Coins import org.ton.block.MsgAddressInt +import org.ton.block.StateInit import org.ton.cell.Cell import org.ton.cell.buildCell import org.ton.tlb.CellRef import org.ton.tlb.constructor.AnyTlbConstructor +import org.ton.tlb.loadTlb import org.ton.tlb.storeRef import org.ton.tlb.storeTlb import java.math.BigInteger @@ -42,9 +46,10 @@ object TonTransferHelper { responseAddress: MsgAddressInt, queryId: BigInteger = BigInteger.ZERO, forwardAmount: Long = 1L, - body: Any? = null, + forwardPayload: Any? = null, + customPayload: Cell? = null ): Cell { - val payload = body(body) + val payload = body(forwardPayload) return buildCell { storeOpCode(TONOpCode.JETTON_TRANSFER) @@ -52,14 +57,9 @@ object TonTransferHelper { storeTlb(Coins, coins) storeTlb(MsgAddressInt, toAddress) storeTlb(MsgAddressInt, responseAddress) - storeBit(false) + storeMaybeRef(customPayload) storeTlb(Coins, Coins.ofNano(forwardAmount)) - if (payload == null) { - storeBit(false) - } else { - storeBit(true) - storeRef(AnyTlbConstructor, CellRef(payload)) - } + storeMaybeRef(payload) } } diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt index ce0b25a9f..8f43fa2cc 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/CellBuilder.kt @@ -1,8 +1,13 @@ package com.tonapps.blockchain.ton.extensions import com.tonapps.blockchain.ton.TONOpCode +import org.ton.cell.Cell import org.ton.cell.CellBuilder import org.ton.cell.CellBuilder.Companion.beginCell +import org.ton.tlb.CellRef +import org.ton.tlb.TlbCodec +import org.ton.tlb.constructor.AnyTlbConstructor +import org.ton.tlb.storeRef import kotlin.math.floor val CellBuilder.availableBits: Int @@ -50,3 +55,21 @@ private fun writeBytes(src: ByteArray, builder: CellBuilder) { } } } + +fun CellBuilder.storeMaybeRef(codec: TlbCodec, value: CellRef?) = apply { + if (value == null) { + storeBit(false) + } else { + storeBit(true) + storeRef(codec, value) + } +} + +fun CellBuilder.storeMaybeRef(value: Cell?) = apply { + if (value == null) { + storeBit(false) + } else { + storeBit(true) + storeRef(value) + } +} diff --git a/tonapi/src/main/kotlin/io/tonapi/apis/AccountsApi.kt b/tonapi/src/main/kotlin/io/tonapi/apis/AccountsApi.kt index d4a21b944..68d204ca1 100644 --- a/tonapi/src/main/kotlin/io/tonapi/apis/AccountsApi.kt +++ b/tonapi/src/main/kotlin/io/tonapi/apis/AccountsApi.kt @@ -830,8 +830,8 @@ class AccountsApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClien */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) - fun getAccountJettonsBalances(accountId: kotlin.String, currencies: kotlin.collections.List? = null) : JettonsBalances { - val localVarResponse = getAccountJettonsBalancesWithHttpInfo(accountId = accountId, currencies = currencies) + fun getAccountJettonsBalances(accountId: kotlin.String, currencies: kotlin.collections.List? = null, extensions: List? = null) : JettonsBalances { + val localVarResponse = getAccountJettonsBalancesWithHttpInfo(accountId = accountId, currencies = currencies, extensions = extensions) return when (localVarResponse.responseType) { ResponseType.Success -> (localVarResponse as Success<*>).data as JettonsBalances @@ -859,8 +859,8 @@ class AccountsApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClien */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class) - fun getAccountJettonsBalancesWithHttpInfo(accountId: kotlin.String, currencies: kotlin.collections.List?) : ApiResponse { - val localVariableConfig = getAccountJettonsBalancesRequestConfig(accountId = accountId, currencies = currencies) + fun getAccountJettonsBalancesWithHttpInfo(accountId: kotlin.String, currencies: kotlin.collections.List?, extensions: List?) : ApiResponse { + val localVariableConfig = getAccountJettonsBalancesRequestConfig(accountId = accountId, currencies = currencies, extensions = extensions) return request( localVariableConfig @@ -874,13 +874,16 @@ class AccountsApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClien * @param currencies accept ton and all possible fiat currencies, separated by commas (optional) * @return RequestConfig */ - fun getAccountJettonsBalancesRequestConfig(accountId: kotlin.String, currencies: kotlin.collections.List?) : RequestConfig { + fun getAccountJettonsBalancesRequestConfig(accountId: kotlin.String, currencies: kotlin.collections.List?, extensions: List?) : RequestConfig { val localVariableBody = null val localVariableQuery: MultiValueMap = mutableMapOf>() .apply { if (currencies != null) { put("currencies", toMultiValue(currencies.toList(), "csv")) } + if (extensions != null) { + put("supported_extensions", toMultiValue(extensions.toList(), "csv")) + } } val localVariableHeaders: MutableMap = mutableMapOf() localVariableHeaders["Accept"] = "application/json" diff --git a/tonapi/src/main/kotlin/io/tonapi/apis/UtilitiesApi.kt b/tonapi/src/main/kotlin/io/tonapi/apis/UtilitiesApi.kt new file mode 100644 index 000000000..15d72ee9f --- /dev/null +++ b/tonapi/src/main/kotlin/io/tonapi/apis/UtilitiesApi.kt @@ -0,0 +1,192 @@ +/** + * + * Please note: + * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit this file manually. + * + */ + +@file:Suppress( + "ArrayInDataClass", + "EnumEntryName", + "RemoveRedundantQualifierName", + "UnusedImport" +) + +package io.tonapi.apis + +import java.io.IOException +import okhttp3.OkHttpClient +import okhttp3.HttpUrl + +import io.tonapi.models.AddressParse200Response +import io.tonapi.models.ServiceStatus +import io.tonapi.models.StatusDefaultResponse + +import com.squareup.moshi.Json + +import io.tonapi.infrastructure.ApiClient +import io.tonapi.infrastructure.ApiResponse +import io.tonapi.infrastructure.ClientException +import io.tonapi.infrastructure.ClientError +import io.tonapi.infrastructure.ServerException +import io.tonapi.infrastructure.ServerError +import io.tonapi.infrastructure.MultiValueMap +import io.tonapi.infrastructure.PartConfig +import io.tonapi.infrastructure.RequestConfig +import io.tonapi.infrastructure.RequestMethod +import io.tonapi.infrastructure.ResponseType +import io.tonapi.infrastructure.Success +import io.tonapi.infrastructure.toMultiValue + +class UtilitiesApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient = ApiClient.defaultClient) : ApiClient(basePath, client) { + companion object { + @JvmStatic + val defaultBasePath: String by lazy { + System.getProperties().getProperty(ApiClient.baseUrlKey, "https://tonapi.io") + } + } + + /** + * + * parse address and display in all formats + * @param accountId account ID + * @return AddressParse200Response + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun addressParse(accountId: kotlin.String) : AddressParse200Response { + val localVarResponse = addressParseWithHttpInfo(accountId = accountId) + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as AddressParse200Response + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()} ${localVarError.body}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * + * parse address and display in all formats + * @param accountId account ID + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun addressParseWithHttpInfo(accountId: kotlin.String) : ApiResponse { + val localVariableConfig = addressParseRequestConfig(accountId = accountId) + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation addressParse + * + * @param accountId account ID + * @return RequestConfig + */ + fun addressParseRequestConfig(accountId: kotlin.String) : RequestConfig { + val localVariableBody = null + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.GET, + path = "/v2/address/{account_id}/parse".replace("{"+"account_id"+"}", encodeURIComponent(accountId.toString())), + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + /** + * + * Status + * @return ServiceStatus + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + * @throws UnsupportedOperationException If the API returns an informational or redirection response + * @throws ClientException If the API returns a client error response + * @throws ServerException If the API returns a server error response + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) + fun status() : ServiceStatus { + val localVarResponse = statusWithHttpInfo() + + return when (localVarResponse.responseType) { + ResponseType.Success -> (localVarResponse as Success<*>).data as ServiceStatus + ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.") + ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.") + ResponseType.ClientError -> { + val localVarError = localVarResponse as ClientError<*> + throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse) + } + ResponseType.ServerError -> { + val localVarError = localVarResponse as ServerError<*> + throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()} ${localVarError.body}", localVarError.statusCode, localVarResponse) + } + } + } + + /** + * + * Status + * @return ApiResponse + * @throws IllegalStateException If the request is not correctly configured + * @throws IOException Rethrows the OkHttp execute method exception + */ + @Suppress("UNCHECKED_CAST") + @Throws(IllegalStateException::class, IOException::class) + fun statusWithHttpInfo() : ApiResponse { + val localVariableConfig = statusRequestConfig() + + return request( + localVariableConfig + ) + } + + /** + * To obtain the request config of the operation status + * + * @return RequestConfig + */ + fun statusRequestConfig() : RequestConfig { + val localVariableBody = null + val localVariableQuery: MultiValueMap = mutableMapOf() + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Accept"] = "application/json" + + return RequestConfig( + method = RequestMethod.GET, + path = "/v2/status", + query = localVariableQuery, + headers = localVariableHeaders, + requiresAuthentication = false, + body = localVariableBody + ) + } + + + private fun encodeURIComponent(uriComponent: kotlin.String): kotlin.String = + HttpUrl.Builder().scheme("http").host("localhost").addPathSegment(uriComponent).build().encodedPathSegments[0] +}