package com.search2learn.s2l
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import com.search2learn.s2l.TokenContract
import com.search2learn.s2l.TokenInfo
import com.search2learn.s2l.TokenType
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.withContext
import org.json.JSONObject
import javax.crypto.SecretKey
import com.search2learn.s2l.WalletManager
import kotlinx.coroutines.launch
import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.net.URL
import kotlinx.coroutines.tasks.await
import com.search2learn.s2l.Web3Utils
import com.search2learn.s2l.SolanaUtils
import com.search2learn.s2l.TronUtils
import com.search2learn.s2l.Web3TokenUtils
import com.search2learn.s2l.WalletManager.MultiChainWallet
import android.text.TextWatcher
import android.text.Editable
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import android.content.ClipData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import android.content.ClipboardManager
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.*
import org.web3j.abi.FunctionEncoder
import org.web3j.abi.TypeReference
import org.web3j.abi.datatypes.Address
import org.web3j.abi.datatypes.Function
import org.web3j.abi.datatypes.generated.Uint256
import org.web3j.protocol.Web3j
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.protocol.core.methods.request.Transaction
import org.web3j.protocol.http.HttpService
import org.web3j.utils.Convert
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import kotlin.random.Random
class NewCoinsActivity : AppCompatActivity() {
// أضف هذا قبل تعريف الكلاس NewCoinsActivity
// أضف هذا قبل تعريف الكلاس NewCoinsActivity
private fun Any?.shorten(maxLength: Int = 10): String {
return when (this) {
null -> "unknown"
is String -> this.take(maxLength)
else -> this.toString().take(maxLength)
}
}
private lateinit var web3j: Web3j
private lateinit var depositAddressTextView: TextView
private lateinit var btnCopyAddress: Button
private lateinit var btnWithdraw: Button
private lateinit var btnNext: Button
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: CoinAdapter
private lateinit var etSearch: EditText
private lateinit var btnSearch: Button
private lateinit var btnBack: Button
private lateinit var tvBscAddress: TextView
private lateinit var tvSolanaAddress: TextView
private lateinit var tvTronAddress: TextView
private val web3jClients = mutableMapOf<String, Web3j>()
private lateinit var progressBar: ProgressBar
private var loadJob: Job? = null
private suspend fun getFormattedBnbBalance(walletAddress: String): String {
val balance = getBnbBalance(walletAddress)
return balance.setScale(6, RoundingMode.HALF_UP).toString()
}
private fun calculateWithdrawalFee(token: TokenInfo, amount: BigDecimal): BigDecimal {
return when {
token.symbol == "S2L" || token.symbol == "S2LE" -> amount.multiply(BigDecimal("0.02")) // 2% fee
else -> amount.multiply(BigDecimal("0.05")) // 5% fee for other tokens
}
}
// هيكل البيانات المعدل لدعم متعدد الشبكات
private val allTokens = listOf(
TokenInfo(
"S2L",
R.drawable.s2l_icon,
mapOf(
"BSC" to TokenContract("0xEd842773464083EbFd207B25257304AFfe4049f1", 7)
),
TokenType.EVM
),
TokenInfo(
"USDT",
R.drawable.usdt_icon,
mapOf(
"BSC" to TokenContract("0x55d398326f99059fF775485246999027B3197955",18),
"ETH" to TokenContract("0xdAC17F958D2ee523a2206206994597C13D831ec7", 6),
"POLY" to TokenContract("0x3813e82e6f7098b9583FC0F33a962D02018B6803", 6),
"Arb" to TokenContract("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", 6),
),
TokenType.EVM
),
TokenInfo(
"SOL",
R.drawable.sol_icon,
mapOf(
"SOLANA" to TokenContract("So11111111111111111111111111111111111111112", 9)
),
TokenType.SOLANA
),
TokenInfo(
"TRUMP",
R.drawable.trump_icon,
mapOf(
"SOLANA" to TokenContract("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN", 9)
),
TokenType.SOLANA
)
)
private val pageSize = 10
private var currentPage = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_coins)
// تهيئة Web3j لشبكة BSC (افتراضي)
web3j = Web3j.build(HttpService("https://bsc-dataseed.binance.org/"))
// ربط العناصر
initViews()
setupRecyclerView()
setupButtons()
fetchMultiChainWallet { wallet ->
wallet?.let {
tvBscAddress.text = "BSC: ${it.bscAddress.shorten()}"
tvSolanaAddress.text = "Solana: ${it.solanaAddress.shorten()}"
tvTronAddress.text = "Tron: ${it.tronAddress.shorten()}"
// تحديد العنوان المناسب للعرض بناءً على نوع العملة
depositAddressTextView.text = when {
it.solanaAddress.isNotEmpty() -> it.solanaAddress
it.bscAddress.isNotEmpty() -> it.bscAddress
else -> it.tronAddress
}
loadCoinsForCurrentPage()
} ?: run {
Toast.makeText(this, "Failed to load wallet", Toast.LENGTH_SHORT).show()
}
}
// جلب عنوان المحفظة وتحميل العملات
fetchWalletAddress { address ->
if (address.isNotEmpty()) {
depositAddressTextView.text = address
loadCoinsForCurrentPage()
}
}
}
private fun initViews() {
depositAddressTextView = findViewById(R.id.tvDepositAddress)
btnCopyAddress = findViewById(R.id.btnCopyAddress)
btnWithdraw = findViewById(R.id.btnWithdraw)
btnNext = findViewById(R.id.btnNext)
recyclerView = findViewById(R.id.recyclerViewNewCoins)
etSearch = findViewById(R.id.etSearch)
btnSearch = findViewById(R.id.btnSearch)
btnBack = findViewById(R.id.btnBack)
tvBscAddress = findViewById(R.id.tvBscAddress)
tvSolanaAddress = findViewById(R.id.tvSolanaAddress)
tvTronAddress = findViewById(R.id.tvTronAddress)
progressBar = findViewById(R.id.progressBar)
}
private fun setupRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = CoinAdapter(mutableListOf()) { coin -> showNetworkSelectionDialog(coin) }
recyclerView.adapter = adapter
}
private fun setupButtons() {
btnCopyAddress.setOnClickListener {
copyToClipboard(depositAddressTextView.text.toString())
}
btnWithdraw.setOnClickListener {
fetchMultiChainWallet { wallet ->
if (wallet != null) {
// استخدم النسخة التي تأخذ MultiChainWallet
showEnhancedWithdrawDialog(wallet)
} else {
Toast.makeText(this@NewCoinsActivity,
"Wallet not available",
Toast.LENGTH_SHORT).show()
}
}
}
btnBack.setOnClickListener {
if (currentPage > 0) {
currentPage--
loadCoinsForCurrentPage()
} else {
onBackPressedDispatcher.onBackPressed()
}
}
// الطريقة الصحيحة للاستدعاء:
btnNext.setOnClickListener {
currentPage++
loadCoinsForCurrentPage()
}
btnSearch.setOnClickListener {
val query = etSearch.text.toString().trim()
if (query.isNotEmpty()) searchCoins(query) else loadCoinsForCurrentPage()
}
}
private fun checkBnbBalanceAndShowWithdrawDialog() {
fetchWalletAddress { walletAddress ->
if (walletAddress.isNullOrEmpty()) {
Toast.makeText(this, "لم يتم العثور على عنوان المحفظة!", Toast.LENGTH_SHORT).show()
return@fetchWalletAddress
}
lifecycleScope.launch {
try {
// 1. التحقق من رصيد BNB للرسوم
val bnbBalance = getBnbBalance(walletAddress)
val minRequiredBNB = BigDecimal("0.0005") // الحد الأدنى المطلوب للرسوم
// 2. جلب أرصدة جميع العملات مرة واحدة
val tokenBalances = allTokens.associate { token ->
token.symbol to getUnifiedTokenBalance(token, walletAddress)
}
withContext(Dispatchers.Main) {
if (bnbBalance < minRequiredBNB) {
showInsufficientBNBBalanceMessage()
} else {
// 3. عرض واجهة السحب مع تمرير أرصدة العملات
showEnhancedWithdrawDialog(walletAddress, tokenBalances)
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
this@NewCoinsActivity,
"Error checking balances: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
private suspend fun getBnbBalance(walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val balance = web3j.ethGetBalance(walletAddress, DefaultBlockParameterName.LATEST).send()
Convert.fromWei(balance.balance.toString(), Convert.Unit.ETHER)
} catch (e: Exception) {
Log.e("Balance", "Error fetching BNB balance", e)
BigDecimal.ZERO
}
}
}
private fun loadCoinsForCurrentPage() {
loadJob?.cancel()
showLoading(true)
loadJob = lifecycleScope.launch {
try {
val wallet = fetchMultiChainWalletSuspended() ?: run {
showError("Wallet not available")
return@launch
}
val coins = withTimeoutOrNull(15000) {
allTokens.subList(currentPage * pageSize, minOf((currentPage + 1) * pageSize, allTokens.size)).map { token ->
async {
try {
val balance = when (token.tokenType) {
TokenType.SOLANA -> {
if (isValidSolanaAddress(wallet.solanaAddress)) {
getSolanaBalanceSafely(token, wallet.solanaAddress)
} else BigDecimal.ZERO
}
TokenType.EVM -> {
if (isValidEvmAddress(wallet.bscAddress)) {
getEvmBalanceSafely(token, wallet.bscAddress)
} else BigDecimal.ZERO
}
else -> BigDecimal.ZERO
}
val price = getTokenPrice(token.symbol)
createCoinItem(token, balance, price)
} catch (e: Exception) {
createErrorCoin(token, "Tap to retry")
}
}
}.awaitAll()
} ?: allTokens.subList(currentPage * pageSize, minOf((currentPage + 1) * pageSize, allTokens.size)).map {
createErrorCoin(it, "Timeout")
}
updateUI(coins)
} catch (e: Exception) {
showError("Connection error. Tap to retry")
} finally {
showLoading(false)
}
}
}
private suspend fun fetchMultiChainWalletSuspended(): MultiChainWallet? = withContext(Dispatchers.IO) {
try {
val user = FirebaseAuth.getInstance().currentUser ?: return@withContext null
val document = FirebaseFirestore.getInstance()
.collection("walletsnew")
.document(user.uid)
.get()
.await()
document.toObject(MultiChainWallet::class.java)?.apply {
bscPrivateKey = decryptKey(document.getString("bscPrivateKey") ?: "")
solanaPrivateKey = decryptKey(document.getString("solanaPrivateKey") ?: "")
}
} catch (e: Exception) {
null
}
}
private suspend fun loadCoinData(token: TokenInfo, walletAddress: String): Coin {
return try {
val balance = when (token.tokenType) {
TokenType.SOLANA -> getSolanaBalanceSafely(token, walletAddress)
TokenType.EVM -> getEvmBalanceSafely(token, walletAddress)
else -> BigDecimal.ZERO
}
val price = getTokenPriceSafely(token.symbol)
createCoinItem(
token = token,
balance = balance,
price = price
)
} catch (e: Exception) {
Log.e("LoadCoin", "Error loading ${token.symbol}", e)
createErrorCoin(token, e.message ?: "Error")
}
}
private fun showError(message: String) {
runOnUiThread {
Toast.makeText(this@NewCoinsActivity, message, Toast.LENGTH_LONG).show()
}
}
private fun updateUI(coins: List<Coin>) {
runOnUiThread {
try {
adapter.updateCoins(coins)
btnNext.isEnabled = (currentPage + 1) * pageSize < allTokens.size
btnBack.isEnabled = currentPage > 0
} catch (e: Exception) {
Log.e("UpdateUI", "Error updating UI: ${e.message}")
}
}
}
// ------ الدوال المساعدة ------
private suspend fun getTokenBalance(token: TokenInfo, walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
when (token.tokenType) {
TokenType.SOLANA -> {
if (!isValidSolanaAddress(walletAddress)) {
return@withContext BigDecimal.ZERO
}
val contract = token.contracts["SOLANA"] ?: return@withContext BigDecimal.ZERO
if (contract.address == "So11111111111111111111111111111111111111112") {
SolanaUtils.getSolanaBalance(walletAddress)
} else {
SolanaUtils.getTokenBalance(walletAddress, contract.address)
}
}
TokenType.EVM -> {
if (!isValidEvmAddress(walletAddress)) {
return@withContext BigDecimal.ZERO
}
val contract = token.contracts.values.firstOrNull() ?: return@withContext BigDecimal.ZERO
Web3TokenUtils.getTokenBalance(
contract.address,
walletAddress,
contract.decimals
)
}
else -> BigDecimal.ZERO
}
} catch (e: Exception) {
Log.e("Balance", "Error getting ${token.symbol} balance", e)
BigDecimal.ZERO
}
}
}
private suspend fun getTokenPriceSafely(symbol: String): BigDecimal {
return try {
withTimeout(5000) {
getTokenPrice(symbol)
}
} catch (e: Exception) {
Log.e("Price", "Error for $symbol", e)
BigDecimal.ZERO
}
}
private fun isValidEvmAddress(address: String): Boolean {
return address.startsWith("0x") && address.length == 42
}
private fun createCoinItem(token: TokenInfo, balance: BigDecimal, price: BigDecimal): Coin {
val formattedBalance = if (balance > BigDecimal.ZERO) {
"${balance.setScale(4, RoundingMode.HALF_UP)} ${token.symbol}"
} else {
"0.0 ${token.symbol}"
}
return Coin(
name = token.symbol,
symbol = token.symbol,
price = "$${"%.6f".format(price)}",
iconRes = token.iconRes,
balance = formattedBalance,
priceDouble = price.toDouble(),
tokenInfo = token
)
}
private fun getCurrentWalletAddress(): String? {
val address = depositAddressTextView.text.toString()
return if (address.isNotEmpty() && isValidAddress(address)) address else null
}
private fun createErrorCoin(token: TokenInfo, message: String): Coin {
return Coin(
name = token.symbol,
symbol = token.symbol,
price = "N/A",
iconRes = token.iconRes,
balance = message,
priceDouble = 0.0,
tokenInfo = token
)
}
private fun showLoading(show: Boolean) {
runOnUiThread {
progressBar.visibility = if (show) View.VISIBLE else View.GONE
recyclerView.visibility = if (show) View.INVISIBLE else View.VISIBLE
}
}
private fun createErrorCoinItem(token: TokenInfo): Coin {
return Coin(
name = token.symbol,
symbol = token.symbol,
price = "$0.0",
iconRes = token.iconRes,
balance = "Error loading",
priceDouble = 0.0,
tokenInfo = token,
isLoading = false
)
}
private suspend fun updateUI(coins: List<Coin>, endIndex: Int) {
withContext(Dispatchers.Main) {
adapter.updateCoins(coins)
btnNext.visibility = if (endIndex < allTokens.size) View.VISIBLE else View.GONE
btnBack.visibility = if (currentPage > 0) View.VISIBLE else View.GONE
}
}
private fun showLoadingState(isLoading: Boolean) {
lifecycleScope.launch(Dispatchers.Main) {
progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
// يمكنك إضافة المزيد من عناصر واجهة المستخدم التي تريد التحكم بها أثناء التحميل
}
}
private suspend fun getSolanaSpecificBalance(token: TokenInfo, walletAddress: String): BigDecimal {
return try {
val contract = token.contracts["SOLANA"]!!
if (contract.address == "So11111111111111111111111111111111111111112") {
SolanaUtils.getSolanaBalance(walletAddress)
} else {
SolanaUtils.getTokenBalance(walletAddress, contract.address)
}
} catch (e: Exception) {
Log.e("SolanaBalance", "Error getting Solana balance", e)
BigDecimal.ZERO
}
}
private suspend fun getTokenPrices(): Map<String, BigDecimal> = withContext(Dispatchers.IO) {
val prices = mutableMapOf<String, BigDecimal>()
try {
// جلب سعر BNB من Binance API
val bnbResponse = URL("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").readText()
prices["BNB"] = JSONObject(bnbResponse).getString("price").toBigDecimal()
// أسعار ثابتة للعملات الأخرى
prices["S2L"] = BigDecimal("0.00003")
prices["USDT"] = BigDecimal.ONE
} catch (e: Exception) {
Log.e("Prices", "Error fetching prices", e)
// قيم افتراضية في حالة الخطأ
prices["BNB"] = BigDecimal("300.0")
prices["S2L"] = BigDecimal("0.00003")
prices["USDT"] = BigDecimal.ONE
}
return@withContext prices
}
private suspend fun getUnifiedTokenBalance(token: TokenInfo, walletAddress: String): BigDecimal {
return try {
when (token.tokenType) {
TokenType.SOLANA -> getSolanaBalanceSafely(token, walletAddress)
TokenType.EVM -> getEvmBalanceSafely(token, walletAddress)
else -> BigDecimal.ZERO
}
} catch (e: Exception) {
Log.e("Balance", "Error for ${token.symbol}", e)
BigDecimal.ZERO
}
}
private suspend fun getSolanaBalanceSafely(token: TokenInfo, walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
if (!isValidSolanaAddress(walletAddress)) {
Log.e("Solana", "Invalid Solana address: $walletAddress")
return@withContext BigDecimal.ZERO
}
val contract = token.contracts["SOLANA"] ?: return@withContext BigDecimal.ZERO
if (contract.address == "So11111111111111111111111111111111111111112") {
SolanaUtils.getSolanaBalance(walletAddress)
} else {
if (!isValidSolanaAddress(contract.address)) {
Log.e("Solana", "Invalid token contract: ${contract.address}")
return@withContext BigDecimal.ZERO
}
SolanaUtils.getTokenBalance(walletAddress, contract.address)
}
} catch (e: Exception) {
Log.e("Solana", "Error getting ${token.symbol}", e)
BigDecimal.ZERO
}
}
}
private suspend fun getEvmBalanceSafely(token: TokenInfo, walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
if (!isValidEvmAddress(walletAddress)) {
Log.e("EVM", "Invalid wallet address: $walletAddress")
return@withContext BigDecimal.ZERO
}
// الحصول على الرصيد من جميع الشبكات المدعومة لهذه العملة
val balances = token.contracts.map { (network, contract) ->
async {
try {
when (network) {
"BSC" -> getBscTokenBalance(contract.address, walletAddress, contract.decimals)
"ETH" -> getEthTokenBalance(contract.address, walletAddress, contract.decimals)
"POLY" -> getPolygonBalance(contract.address, walletAddress, contract.decimals)
"Arb" -> getArbitrumBalance(contract.address, walletAddress, contract.decimals)
else -> BigDecimal.ZERO
}
} catch (e: Exception) {
Log.e("EVM", "Error getting $network balance", e)
BigDecimal.ZERO
}
}
}
balances.awaitAll().sumOf { it }
} catch (e: Exception) {
Log.e("EVM", "General error for ${token.symbol}", e)
BigDecimal.ZERO
}
}
}
private suspend fun getBscTokenBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val web3j = getWeb3jClient("BSC")
val function = Function(
"balanceOf",
listOf(Address(walletAddress)),
listOf(object : TypeReference<Uint256>() {})
)
val response = web3j.ethCall(
Transaction.createEthCallTransaction(null, contractAddress, FunctionEncoder.encode(function)),
DefaultBlockParameterName.LATEST
).send()
if (response.hasError() || response.value == null || response.value.isEmpty()) {
Log.e("BSC", "Invalid response: ${response.error?.message}")
return@withContext BigDecimal.ZERO
}
// معالجة الاستجابة بشكل آمن
val hexValue = if (response.value.startsWith("0x")) response.value.substring(2) else response.value
if (hexValue.isEmpty() || !hexValue.matches("[0-9a-fA-F]+".toRegex())) {
Log.e("BSC", "Invalid hex value: ${response.value}")
return@withContext BigDecimal.ZERO
}
BigDecimal(BigInteger(hexValue, 16)).movePointLeft(decimals)
} catch (e: Exception) {
Log.e("BSC", "Error getting balance", e)
BigDecimal.ZERO
}
}
}
private suspend fun getEthTokenBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
val web3j = Web3j.build(HttpService("https://mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
return try {
getTokenBalance(web3j, contractAddress, walletAddress, decimals)
} finally {
web3j.shutdown()
}
}
private suspend fun getEvmTokenBalance(
network: String,
contractAddress: String,
walletAddress: String,
decimals: Int
): BigDecimal {
val web3j = when (network) {
"BSC" -> Web3j.build(HttpService("https://bsc-dataseed.binance.org/"))
"ETH" -> Web3j.build(HttpService("https://mainnet.infura.io/v3/YOUR_INFURA_KEY"))
else -> return BigDecimal.ZERO
}
return try {
val function = Function(
"balanceOf",
listOf(Address(walletAddress)),
listOf(object : TypeReference<Uint256>() {})
)
val response = web3j.ethCall(
Transaction.createEthCallTransaction(null, contractAddress, FunctionEncoder.encode(function)),
DefaultBlockParameterName.LATEST
).send()
if (response.hasError()) {
Log.e("Balance", "Error for $network: ${response.error?.message}")
BigDecimal.ZERO
} else {
val balanceWei = BigInteger(response.value.substring(2), 16)
BigDecimal(balanceWei).movePointLeft(decimals)
}
} catch (e: Exception) {
Log.e("Balance", "Network error for $network", e)
BigDecimal.ZERO
} finally {
web3j.shutdown()
}
}
private suspend fun getPolygonBalance(
contractAddress: String,
walletAddress: String,
decimals: Int
): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val web3j = Web3j.build(HttpService("https://polygon-rpc.com/"))
getTokenBalance(web3j, contractAddress, walletAddress, decimals)
} catch (e: Exception) {
Log.e("PolygonBalance", "Error fetching Polygon balance", e)
BigDecimal.ZERO
}
}
}
private suspend fun getBscBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
val web3j = Web3j.build(HttpService("https://bsc-dataseed.binance.org/"))
return getTokenBalance(web3j, contractAddress, walletAddress, decimals)
}
private suspend fun getEthBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
val web3j = Web3j.build(HttpService("https://mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
return getTokenBalance(web3j, contractAddress, walletAddress, decimals)
}
private suspend fun getTokenBalance(web3j: Web3j, contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
return try {
val function = Function(
"balanceOf",
listOf(Address(walletAddress)),
listOf(object : TypeReference<Uint256>() {})
)
val response = web3j.ethCall(
Transaction.createEthCallTransaction(null, contractAddress, FunctionEncoder.encode(function)),
DefaultBlockParameterName.LATEST
).send()
if (response.hasError() || response.value == null || response.value.isEmpty()) {
Log.e("Balance", "Error: ${response.error?.message ?: "Empty response"}")
BigDecimal.ZERO
} else {
try {
// التحقق من أن القيمة تحتوي على 0x وأنها صالحة
val hexValue = if (response.value.startsWith("0x")) response.value else "0x${response.value}"
BigDecimal(BigInteger(hexValue.substring(2), 16))
.movePointLeft(decimals)
} catch (e: NumberFormatException) {
Log.e("Balance", "Invalid hex value: ${response.value}")
BigDecimal.ZERO
}
}
} catch (e: Exception) {
Log.e("Balance", "Network error: ${e.message}")
BigDecimal.ZERO
}
}
private fun getWeb3jClient(network: String): Web3j {
return web3jClients.getOrPut(network) {
val rpcUrl = when (network) {
"BSC" -> "https://bsc-dataseed.binance.org/"
"ETH" -> "https://mainnet.infura.io/v3/YOUR_INFURA_KEY"
"POLYGON" -> "https://polygon-rpc.com/"
else -> throw IllegalArgumentException("Unsupported network")
}
// إنشاء OkHttpClient مخصص مع timeout
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS) // مهلة الاتصال
.readTimeout(5, TimeUnit.SECONDS) // مهلة القراءة
.writeTimeout(5, TimeUnit.SECONDS) // مهلة الكتابة
.build()
Web3j.build(HttpService(rpcUrl, okHttpClient))
}
}
private fun isValidAddress(address: String): Boolean {
return address.startsWith("0x") && address.length == 42
}
private fun showNetworkSelectionDialog(coin: Coin) {
val token = coin.tokenInfo
val supportedNetworks = token.contracts.keys.toList()
AlertDialog.Builder(this)
.setTitle("Select Network for ${token.symbol}")
.setItems(supportedNetworks.toTypedArray()) { _, which ->
val selectedNetwork = supportedNetworks[which]
showDepositInstructions(token, selectedNetwork)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun showDepositInstructions(token: TokenInfo, network: String) {
fetchMultiChainWallet { wallet ->
wallet?.let {
val depositAddress = when {
network.equals("SOLANA", ignoreCase = true) -> it.solanaAddress
network.equals("TRON", ignoreCase = true) -> it.tronAddress
else -> it.bscAddress
}
val contract = token.contracts[network] ?: return@let
val message = """
To deposit ${token.symbol}:
🔹 Network: $network
🔹 Address: $depositAddress
${if (contract.address.isNotEmpty()) "🔹 Contract: ${contract.address}" else ""}
⚠️ Important:
- Only send ${token.symbol} via $network
- Minimum deposit: ${getMinDeposit(token.symbol)}
""".trimIndent()
AlertDialog.Builder(this@NewCoinsActivity)
.setTitle("Deposit ${token.symbol} ($network)")
.setMessage(message)
.setPositiveButton("Copy Address") { _, _ ->
copyToClipboard(depositAddress)
}
.apply {
if (contract.address.isNotEmpty()) {
setNeutralButton("Copy Contract") { _, _ ->
copyToClipboard(contract.address)
}
}
}
.show()
} ?: run {
Toast.makeText(this@NewCoinsActivity, "Wallet not available", Toast.LENGTH_SHORT).show()
}
}
}
private fun getMinDeposit(symbol: String): String {
return when (symbol) {
"S2L" -> "100 S2L"
"BNB" -> "0.01 BNB"
"SOL" -> "0.01 SOL"
"TRUMP" -> "1 TRUMP"
"USDT" -> "7 USDT"
else -> "0.01 $symbol"
}
}
private fun showWithdrawDialog(walletAddress: String) {
val dialogView = layoutInflater.inflate(R.layout.dialog_withdraw, null)
val spinnerCurrency = dialogView.findViewById<Spinner>(R.id.spinnerCurrency)
val spinnerNetwork = dialogView.findViewById<Spinner>(R.id.spinnerNetwork)
val editAmount = dialogView.findViewById<EditText>(R.id.etAmount)
val editAddress = dialogView.findViewById<EditText>(R.id.etAddress)
val errorMessage = dialogView.findViewById<TextView>(R.id.errorMessage)
val btnMax = dialogView.findViewById<Button>(R.id.btnMax)
val progressBar = dialogView.findViewById<ProgressBar>(R.id.progressBar)
val networkLayout = dialogView.findViewById<View>(R.id.networkLayout) // LinearLayout يحتوي على spinnerNetwork
// إعداد قائمة العملات
val currencies = allTokens.map { it.symbol }
val currencyAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, currencies)
spinnerCurrency.adapter = currencyAdapter
val dialog = AlertDialog.Builder(this)
.setTitle("Withdraw")
.setView(dialogView)
.setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
.create()
dialog.show()
// تحديث قائمة الشبكات عند تغيير العملة
spinnerNetwork.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
val selectedNetwork = parent.getItemAtPosition(position).toString().substringBefore(" (")
val selectedSymbol = spinnerCurrency.selectedItem?.toString()?.substringBefore(" (") ?: return
val token = allTokens.find { it.symbol == selectedSymbol } ?: return
progressBar.visibility = View.VISIBLE
// استخدم lifecycleScope.launch هنا
lifecycleScope.launch {
try {
val balance = getTokenBalanceOnNetwork(token, walletAddress, selectedNetwork)
withContext(Dispatchers.Main) {
val newItems = token.contracts.keys.map { network ->
if (network == selectedNetwork) {
"$network (${balance.setScale(4)})"
} else {
parent.getItemAtPosition(position).toString()
}
}
spinnerNetwork.adapter = ArrayAdapter(
this@NewCoinsActivity,
android.R.layout.simple_spinner_item,
newItems
).also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
spinnerNetwork.setSelection(position)
}
} catch (e: Exception) {
Log.e("Network", "Error updating balance", e)
} finally {
withContext(Dispatchers.Main) {
progressBar.visibility = View.GONE
}
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
btnMax.setOnClickListener {
progressBar.visibility = View.VISIBLE
btnMax.isEnabled = false
// 🔥 التصحيح: استخدم lifecycleScope.launch لاستدعاء الدوال المعلقة
lifecycleScope.launch {
try {
val selectedCurrency = spinnerCurrency.selectedItem?.toString()?.substringBefore(" (") ?: return@launch
val token = allTokens.find { it.symbol == selectedCurrency } ?: return@launch
val selectedNetwork = if (networkLayout.visibility == View.VISIBLE) {
spinnerNetwork.selectedItem?.toString()?.substringBefore(" (") ?: "BSC"
} else {
token.contracts.keys.firstOrNull() ?: "BSC"
}
val balance = getTokenBalanceOnNetwork(token, walletAddress, selectedNetwork)
// عدل UI داخل withContext(Dispatchers.Main)
withContext(Dispatchers.Main) {
editAmount.setText(balance.setScale(6, RoundingMode.HALF_UP).toString())
progressBar.visibility = View.GONE
btnMax.isEnabled = true
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
this@NewCoinsActivity,
"Error: ${e.message}",
Toast.LENGTH_SHORT
).show()
progressBar.visibility = View.GONE
btnMax.isEnabled = true
}
}
}
}
dialogView.findViewById<Button>(R.id.btnWithdraw).setOnClickListener {
val address = editAddress.text.toString().trim()
val amountStr = editAmount.text.toString().trim()
if (address.isEmpty() || amountStr.isEmpty()) {
errorMessage.text = "Please enter all fields"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
if (!isValidAddress(address)) {
errorMessage.text = "Invalid wallet address"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val amount = try {
BigDecimal(amountStr)
} catch (e: Exception) {
errorMessage.text = "Invalid amount"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
if (amount <= BigDecimal.ZERO) {
errorMessage.text = "Amount must be greater than zero"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val selectedCurrency = spinnerCurrency.selectedItem.toString()
val token = allTokens.find { it.symbol == selectedCurrency }
if (token == null) {
errorMessage.text = "Token not supported"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val selectedNetwork = if (networkLayout.visibility == View.VISIBLE) {
spinner(spinnerNetwork)?.selectedItem?.toString()?.substringBefore(" (") ?: ""
} else {
token.contracts.keys.firstOrNull() ?: "BSC"
}
errorMessage.visibility = View.GONE
dialog.dismiss()
}
}
private fun updateFeeNotice(
token: TokenInfo,
editAmount: EditText,
feeNotice: TextView,
receivableAmount: TextView
) {
try {
val amountStr = editAmount.text.toString()
if (amountStr.isNotEmpty()) {
val amount = BigDecimal(amountStr)
val fee = calculateWithdrawalFee(token, amount)
val receivable = amount - fee
feeNotice.text = when {
token.symbol == "S2L" || token.symbol == "S2LE" ->
"Fee: ${fee.setScale(4)} ${token.symbol} (2%)"
else ->
"Fee: ~${fee.setScale(4)} USD (5%)"
}
receivableAmount.text = "You'll receive: ${receivable.setScale(4)} ${token.symbol}"
receivableAmount.visibility = View.VISIBLE
} else {
receivableAmount.visibility = View.GONE
}
} catch (e: Exception) {
feeNotice.text = "Enter valid amount to calculate fee"
receivableAmount.visibility = View.GONE
}
}
private fun getNetworkBalance(token: TokenInfo?, walletAddress: String, network: String): String {
return if (token != null) {
runBlocking {
try {
val balance = getTokenBalanceOnNetwork(token, walletAddress, network)
balance.setScale(4, RoundingMode.HALF_UP).toString()
} catch (e: Exception) {
"0.0"
}
}
} else {
"0.0"
}
}
private fun showEnhancedWithdrawDialog(
walletAddress: String,
tokenBalances: Map<String, BigDecimal>
) {
val dialogView = layoutInflater.inflate(R.layout.dialog_withdraw, null)
val spinnerCurrency = dialogView.findViewById<Spinner>(R.id.spinnerCurrency)
val spinnerNetwork = dialogView.findViewById<Spinner>(R.id.spinnerNetwork)
val editAmount = dialogView.findViewById<EditText>(R.id.etAmount)
val editAddress = dialogView.findViewById<EditText>(R.id.etAddress)
val errorMessage = dialogView.findViewById<TextView>(R.id.errorMessage)
val btnMax = dialogView.findViewById<Button>(R.id.btnMax)
val progressBar = dialogView.findViewById<ProgressBar>(R.id.progressBar)
val networkLayout = dialogView.findViewById<View>(R.id.networkLayout)
val feeNotice = dialogView.findViewById<TextView>(R.id.feeNotice)
val tvBnbBalance = dialogView.findViewById<TextView>(R.id.tvBnbBalance)
val tvReceivableAmount = dialogView.findViewById<TextView>(R.id.tvReceivableAmount)
// إعداد قائمة العملات مع عرض الرصيد
val currencyItems = allTokens.map { token ->
val balance = tokenBalances[token.symbol] ?: BigDecimal.ZERO
"${token.symbol} (${balance.setScale(4, RoundingMode.HALF_UP)})"
}
val currencyAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, currencyItems)
spinnerCurrency.adapter = currencyAdapter
// جلب وعرض رصيد BNB
lifecycleScope.launch {
val bnbBalance = getFormattedBnbBalance(walletAddress)
withContext(Dispatchers.Main) {
tvBnbBalance.text = "BNB Balance: $bnbBalance (for gas fees)"
}
}
val dialog = AlertDialog.Builder(this)
.setTitle("Withdraw Funds")
.setView(dialogView)
.setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
.create()
dialog.show()
// تحديث قائمة الشبكات وعرض الرسوم عند تغيير العملة
spinnerNetwork.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
val selectedNetwork = parent.getItemAtPosition(position).toString().substringBefore(" (")
val selectedSymbol = spinnerCurrency.selectedItem?.toString()?.substringBefore(" (") ?: return
val token = allTokens.find { it.symbol == selectedSymbol } ?: return
progressBar.visibility = View.VISIBLE
lifecycleScope.launch {
try {
val balance = getTokenBalanceOnNetwork(token, walletAddress, selectedNetwork)
withContext(Dispatchers.Main) {
val newItems = token.contracts.keys.map { network ->
if (network == selectedNetwork) {
"$network (${balance.setScale(4)})"
} else {
parent.getItemAtPosition(position).toString()
}
}
spinnerNetwork.adapter = ArrayAdapter(
this@NewCoinsActivity,
android.R.layout.simple_spinner_item,
newItems
).also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
spinnerNetwork.setSelection(position)
}
} catch (e: Exception) {
Log.e("Network", "Error updating balance", e)
} finally {
progressBar.visibility = View.GONE
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
// تحديث الرسوم عند تغيير المبلغ
editAmount.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val selectedSymbol = spinnerCurrency.selectedItem?.toString()?.substringBefore(" (") ?: return
val token = allTokens.find { it.symbol == selectedSymbol } ?: return
updateFeeNotice(token, editAmount, feeNotice, tvReceivableAmount)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// لا شيء مطلوب هنا
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// لا شيء مطلوب هنا
}
})
btnMax.setOnClickListener {
val selectedSymbol = spinnerCurrency.selectedItem.toString().substringBefore(" (")
val balance = tokenBalances[selectedSymbol] ?: BigDecimal.ZERO
editAmount.setText(balance.setScale(6, RoundingMode.HALF_UP).toString())
}
dialogView.findViewById<Button>(R.id.btnWithdraw).setOnClickListener {
val address = editAddress.text.toString().trim()
val amountStr = editAmount.text.toString().trim()
if (address.isEmpty() || amountStr.isEmpty()) {
errorMessage.text = "Please enter all fields"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
if (!isValidAddress(address)) {
errorMessage.text = "Invalid wallet address"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val amount = try {
BigDecimal(amountStr)
} catch (e: Exception) {
errorMessage.text = "Invalid amount"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
if (amount <= BigDecimal.ZERO) {
errorMessage.text = "Amount must be greater than zero"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val selectedSymbol = spinnerCurrency.selectedItem.toString().substringBefore(" (")
val token = allTokens.find { it.symbol == selectedSymbol }
if (token == null) {
errorMessage.text = "Token not supported"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val selectedNetwork = if (networkLayout.visibility == View.VISIBLE) {
spinnerNetwork.selectedItem.toString().substringBefore(" (")
} else {
token.contracts.keys.firstOrNull() ?: "BSC"
}
// التحقق من الرصيد على الشبكة المحددة
lifecycleScope.launch {
try {
val networkBalance = getTokenBalanceOnNetwork(token, walletAddress, selectedNetwork)
if (amount > networkBalance) {
withContext(Dispatchers.Main) {
errorMessage.text = "Insufficient balance on $selectedNetwork. Available: ${networkBalance.setScale(4)}"
errorMessage.visibility = View.VISIBLE
}
return@launch
}
withContext(Dispatchers.Main) {
errorMessage.visibility = View.GONE
dialog.dismiss()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
errorMessage.text = "Error checking balance: ${e.message}"
errorMessage.visibility = View.VISIBLE
}
}
}
}
}
private fun spinner(spinnerNetwork: Spinner?): Spinner? = spinnerNetwork
private suspend fun handleEvmWithdrawal(contract: TokenContract, toAddress: String, amount: BigDecimal, privateKey: String) {
try {
val txHash = Web3TokenUtils.withdrawToken(
context = this@NewCoinsActivity,
tokenContractAddress = contract.address,
toAddress = toAddress,
amount = amount,
privateKey = privateKey,
decimals = contract.decimals
)
showToast("Transaction sent: ${txHash.shorten()}...")
} catch (e: Exception) {
showToast("EVM Error: ${(e.message ?: "Unknown error").take(20)}")
}
}
private suspend fun handleTronWithdrawal(
contract: TokenContract,
toAddress: String,
amount: BigDecimal,
privateKey: String
) {
withContext(Dispatchers.Main) {
Toast.makeText(
this@NewCoinsActivity,
"TRON withdrawals are currently under maintenance. Please try again later.",
Toast.LENGTH_LONG
).show()
}
}
private fun decryptKey(encryptedKey: String): String {
return WalletManager.decryptKeySafely(encryptedKey) // بدون أقواس بعد WalletManager
}
fun isValidSolanaAddress(address: String): Boolean {
return address.matches("^[1-9A-HJ-NP-Za-km-z]{32,44}$".toRegex())
}
fun isValidBscAddress(address: String): Boolean {
return address.startsWith("0x") && address.length == 42
}
private fun fetchMultiChainWallet(callback: (MultiChainWallet?) -> Unit) {
val user = FirebaseAuth.getInstance().currentUser ?: run {
callback(null)
return
}
FirebaseFirestore.getInstance()
.collection("walletsnew")
.document(user.uid)
.get()
.addOnSuccessListener { document ->
try {
val wallet = document.toObject(MultiChainWallet::class.java)?.apply {
bscPrivateKey = decryptKey(document.getString("bscPrivateKey") ?: "")
solanaPrivateKey = decryptKey(document.getString("solanaPrivateKey") ?: "")
}
callback(wallet)
} catch (e: Exception) {
Log.e("Wallet", "Error parsing wallet", e)
callback(null)
}
}
.addOnFailureListener {
Log.e("Wallet", "Error fetching wallet", it)
callback(null)
}
}
private suspend fun getSolanaBalance(walletAddress: String): BigDecimal {
return SolanaUtils.getSolanaBalance(walletAddress)
}
private suspend fun getSolanaTokenBalance(walletAddress: String, tokenMint: String, decimals: Int): BigDecimal {
return SolanaUtils.getTokenBalance(walletAddress, tokenMint)
}
private suspend fun handleSolanaWithdrawal(contract: TokenContract, toAddress: String, amount: BigDecimal, privateKey: String) {
try {
val privateKeyBytes = privateKey.hexStringToByteArray()
val signature = SolanaUtils.withdrawSolanaToken(
context = this@NewCoinsActivity,
privateKey = privateKeyBytes,
toAddress = toAddress,
tokenMint = contract.address,
amount = amount,
decimals = contract.decimals
)
showToast("Solana Transaction sent: ${signature.shorten()}...")
} catch (e: Exception) {
showToast("Solana Error: ${(e.message ?: "Unknown error").take(20)}")
}
}
private suspend fun showToast(message: String) {
withContext(Dispatchers.Main) {
Toast.makeText(this@NewCoinsActivity, message, Toast.LENGTH_SHORT).show()
}
}
// دالة مساعدة لتحويل Hex String إلى ByteArray
fun String.hexStringToByteArray(): ByteArray {
val len = this.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = ((Character.digit(this[i], 16) shl 4) +
Character.digit(this[i + 1], 16)).toByte()
i += 2
}
return data
}
private fun getRpcUrl(network: String): String {
return when (network.uppercase()) {
"BSC" -> "https://bsc-dataseed.binance.org/"
"ETH" -> "https://mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec" // استبدل YOUR_INFURA_KEY بمفتاحك
"POLYGON" -> "https://polygon-rpc.com/"
"ARBITRUM" -> "https://arb1.arbitrum.io/rpc"
else -> throw IllegalArgumentException("Unsupported network")
}
}
private suspend fun getTokenBalanceOnNetwork(token: TokenInfo, walletAddress: String, network: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val contract = token.contracts[network] ?: return@withContext BigDecimal.ZERO
when (token.tokenType) {
TokenType.EVM -> {
val web3j = when (network) {
"BSC" -> Web3j.build(HttpService("https://bsc-dataseed.binance.org/"))
"ETH" -> Web3j.build(HttpService("https://mainnet.infura.io/v3/YOUR_KEY"))
else -> throw IllegalArgumentException("Unsupported EVM network")
}
getEvmTokenBalance(web3j, contract.address, walletAddress, contract.decimals)
}
TokenType.TRON -> {
TronUtils.getTronBalance(walletAddress, contract.address)
}
TokenType.SOLANA -> {
if (contract.address == "So11111111111111111111111111111111111111112") {
// SOL native balance
SolanaUtils.getSolanaBalance(walletAddress)
} else {
// Token balance
SolanaUtils.getTokenBalance(walletAddress, contract.address)
}
}
TokenType.NATIVE -> {
// معالجة العملات الأصلية
when (network) {
"BSC" -> getBnbBalance(walletAddress)
else -> BigDecimal.ZERO
}
}
}
} catch (e: Exception) {
Log.e("Balance", "Error fetching ${token.symbol} on $network", e)
BigDecimal.ZERO
}
}
}
private suspend fun getEvmTokenBalance(web3j: Web3j, contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
val function = Function(
"balanceOf",
listOf(Address(walletAddress)),
listOf(object : TypeReference<Uint256>() {})
)
val response = web3j.ethCall(
Transaction.createEthCallTransaction(null, contractAddress, FunctionEncoder.encode(function)),
DefaultBlockParameterName.LATEST
).send()
return if (response.hasError()) {
Log.e("Balance", "Error: ${response.error?.message}")
BigDecimal.ZERO
} else {
val balanceWei = BigInteger(response.value.substring(2), 16)
BigDecimal(balanceWei).movePointLeft(decimals)
}
}
private suspend fun getArbitrumBalance(
contractAddress: String,
walletAddress: String,
decimals: Int
): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val web3j = Web3j.build(HttpService("https://arb1.arbitrum.io/rpc"))
getTokenBalance(web3j, contractAddress, walletAddress, decimals)
} catch (e: Exception) {
Log.e("ArbitrumBalance", "Error", e)
BigDecimal.ZERO
}
}
}
private fun fetchWalletAddress(onResult: (String) -> Unit) {
val user = FirebaseAuth.getInstance().currentUser ?: run {
onResult("")
return
}
FirebaseFirestore.getInstance()
.collection("walletsnew")
.document(user.uid)
.get()
.addOnSuccessListener { document ->
if (document.exists()) {
val wallet = document.toObject(MultiChainWallet::class.java)
val address = when {
wallet?.bscAddress?.isNotEmpty() == true -> wallet.bscAddress
wallet?.tronAddress?.isNotEmpty() == true -> wallet.tronAddress
wallet?.solanaAddress?.isNotEmpty() == true -> wallet.solanaAddress
else -> ""
}
onResult(address)
} else {
onResult("")
}
}
.addOnFailureListener {
Log.e("Firestore", "Error fetching wallet", it)
onResult("")
}
}
private suspend fun getPrivateKey(): String? = withContext(Dispatchers.IO) {
val user = FirebaseAuth.getInstance().currentUser ?: return@withContext null
val email = user.email ?: return@withContext null
suspendCancellableCoroutine<String?> { continuation ->
FirebaseFirestore.getInstance()
.collection("wallets")
.document(email)
.get()
.addOnSuccessListener { document ->
continuation.resume(document.getString("privateKey"))
}
.addOnFailureListener {
continuation.resume(null)
}
}
}
private fun searchCoins(query: String) {
val filtered = allTokens.filter {
it.symbol.contains(query, ignoreCase = true)
}.map { token ->
val randomPrice = generateRandomPrice(0.00001, 0.1)
Coin(
name = token.symbol, // أو أي اسم تريده
symbol = token.symbol,
price = "$${"%.6f".format(randomPrice)}",
iconRes = token.iconRes,
balance = "0.0 ${token.symbol}",
priceDouble = randomPrice,
tokenInfo = token
)
}
adapter.updateCoins(filtered)
}
private suspend fun getTokenPrice(symbol: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
when (symbol.uppercase()) {
"S2L" -> BigDecimal("0.00003")
"BNB" -> {
val response = URL("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").readText()
JSONObject(response).getString("price").toBigDecimal()
}
"USDT" -> BigDecimal.ONE
else -> BigDecimal.ZERO
}
} catch (e: Exception) {
Log.e("Price", "Error fetching price for $symbol", e)
BigDecimal.ZERO
}
}
}
private fun copyToClipboard(text: String) {
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Address", text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "Address copied", Toast.LENGTH_SHORT).show()
}
private fun showEnhancedWithdrawDialog(wallet: MultiChainWallet) {
// احصل على العنوان المناسب من المحفظة
val walletAddress = wallet.bscAddress.ifEmpty {
wallet.solanaAddress.ifEmpty {
wallet.tronAddress
}
}
if (walletAddress.isEmpty()) {
Toast.makeText(this, "No valid wallet address found", Toast.LENGTH_SHORT).show()
return
}
lifecycleScope.launch {
try {
val tokenBalances = allTokens.associate { token ->
token.symbol to getUnifiedTokenBalance(token, walletAddress)
}
// استدعاء النسخة الأصلية مع البيانات المطلوبة
showEnhancedWithdrawDialog(walletAddress, tokenBalances)
} catch (e: Exception) {
Toast.makeText(
this@NewCoinsActivity,
"Error fetching balances: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
private fun showInsufficientBNBBalanceMessage() {
Toast.makeText(
this,
"Insufficient BNB for gas fees. Please deposit BNB first.",
Toast.LENGTH_LONG
).show()
}
private fun generateRandomPrice(min: Double, max: Double): Double {
return Random.nextDouble(min, max)
}
}
-----------------------------
package com.search2learn.s2l
import kotlinx.coroutines.withContext
import org.p2p.solanaj.core.*
import org.p2p.solanaj.programs.*
import org.p2p.solanaj.rpc.*
import android.content.Context
import android.util.Log
import android.widget.Toast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import java.math.RoundingMode
import org.p2p.solanaj.programs.SystemProgram
object SolanaUtils {
private const val TAG = "SolanaUtils"
private val rpcClient = RpcClient("https://api.mainnet-beta.solana.com")
private const val SOL_MINT_ADDRESS = "So11111111111111111111111111111111111111112"
private const val LAMPORTS_PER_SOL = 1_000_000_000L
suspend fun getSolanaBalance(publicKey: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
// تحقق متقدم من صحة العنوان
if (!publicKey.matches("^[1-9A-HJ-NP-Za-km-z]{32,44}$".toRegex())) {
throw IllegalArgumentException("Invalid Solana address: $publicKey")
}
val publicKeyObj = try {
PublicKey(publicKey)
} catch (e: Exception) {
throw IllegalArgumentException("Failed to create PublicKey from: $publicKey")
}
val balance = rpcClient.api.getBalance(publicKeyObj)
BigDecimal(balance).divide(BigDecimal(LAMPORTS_PER_SOL), 9, RoundingMode.HALF_UP)
} catch (e: Exception) {
Log.e(TAG, "Error getting SOL balance", e)
BigDecimal.ZERO
}
}
}
// دالة لحساب العنوان المرتبط للتوكن
private fun findAssociatedTokenAddress(owner: PublicKey, mint: PublicKey): PublicKey {
val seeds = listOf(
owner.toByteArray(),
TokenProgram.PROGRAM_ID.toByteArray(),
mint.toByteArray()
)
val programId = PublicKey("ATokenGPvbdGVxr1j6N1YMoWz7sEriwMpXGzVgjjz4z")
return PublicKey.findProgramAddress(seeds, programId).address
}
suspend fun getTokenBalance(publicKey: String, tokenMint: String): BigDecimal {
return withContext(Dispatchers.IO) {
try {
val publicKeyObj = PublicKey(publicKey)
val mintPublicKey = PublicKey(tokenMint)
val associatedTokenAccount = findAssociatedTokenAddress(publicKeyObj, mintPublicKey)
// محاولة جلب الرصيد أولاً
try {
val tokenBalanceResponse = rpcClient.api.getTokenAccountBalance(associatedTokenAccount)
val amount = tokenBalanceResponse.amount ?: "0"
val decimals = tokenBalanceResponse.decimals ?: 0
BigDecimal(amount).movePointLeft(decimals)
} catch (e: RpcException) {
if (e.message?.contains("Account not found") == true) {
// إنشاء الحساب إذا لم يوجد
createAssociatedTokenAccount(publicKeyObj, mintPublicKey)
BigDecimal.ZERO // الرصيد صفر بعد الإنشاء
} else {
throw e
}
}
} catch (e: Exception) {
Log.e(TAG, "Error getting token balance", e)
BigDecimal.ZERO
}
}
}
suspend fun withdrawSolanaToken(
context: Context,
privateKey: ByteArray,
toAddress: String,
tokenMint: String,
amount: BigDecimal,
decimals: Int
): String? {
return withContext(Dispatchers.IO) {
try {
if (!isValidSolanaAddress(toAddress)) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "عنوان Solana غير صالح", Toast.LENGTH_SHORT).show()
}
return@withContext null
}
val fromAccount = Account(privateKey)
val toPublicKey = PublicKey(toAddress)
val mintPublicKey = PublicKey(tokenMint)
val amountInUnits = amount.movePointRight(decimals).toLong()
// التحقق من الرصيد
val balance = if (tokenMint == SOL_MINT_ADDRESS) {
getSolanaBalance(fromAccount.publicKey.toString())
} else {
getTokenBalance(fromAccount.publicKey.toString(), tokenMint)
}
if (amount > balance) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "الرصيد غير كافي", Toast.LENGTH_SHORT).show()
}
return@withContext null
}
val transaction = Transaction()
if (tokenMint == SOL_MINT_ADDRESS) {
// تحويل SOL الأصلي
transaction.addInstruction(
SystemProgram.transfer(
fromAccount.publicKey,
toPublicKey,
amountInUnits
)
)
} else {
// تحويل التوكن
val fromTokenAccount = findAssociatedTokenAddress(
owner = fromAccount.publicKey,
mint = mintPublicKey
)
val toTokenAccount = findAssociatedTokenAddress(
owner = toPublicKey,
mint = mintPublicKey
)
transaction.addInstruction(
TokenProgram.transferChecked(
fromTokenAccount, // p0: مصدر التوكن
mintPublicKey, // p1: عنوان التوكن
amountInUnits, // p2: الكمية
decimals.toByte(), // p3: الكسور العشرية (كم Byte)
fromAccount.publicKey, // p4: المالك
toTokenAccount // p5: الوجهة
)
)
}
// إرسال المعاملة
rpcClient.api.sendTransaction(transaction, fromAccount)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"خطأ في الإرسال: ${e.localizedMessage?.take(50)}",
Toast.LENGTH_LONG
).show()
Log.e(TAG, "Withdrawal error", e)
}
null
}
}
}
private fun isValidSolanaAddress(address: String): Boolean {
return try {
PublicKey(address)
true
} catch (e: Exception) {
false
}
}
}





0 تعليقات