package com.search2learn.s2l
import com.search2learn.s2l.TokenContract
import com.search2learn.s2l.TokenInfo
import com.search2learn.s2l.TokenType
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 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 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("0x524bC91Dc82d6b90EF29F76A3ECAaBAffFD490Bc",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
)
)
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)
}
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() {
val startIndex = currentPage * pageSize
val endIndex = minOf(startIndex + pageSize, allTokens.size)
if (startIndex >= allTokens.size) {
adapter.updateCoins(emptyList())
return
}
val tokensForPage = allTokens.subList(startIndex, endIndex)
lifecycleScope.launch {
try {
val walletAddress = depositAddressTextView.text.toString()
val prices = getTokenPrices()
val updatedCoins = tokensForPage.map { token ->
val balance = if (token.tokenType == TokenType.SOLANA) {
// جلب رصيد Solana بشكل منفصل لضبط الدقة
getSolanaSpecificBalance(token, walletAddress)
} else {
getUnifiedTokenBalance(token, walletAddress)
}
val price = prices[token.symbol] ?: BigDecimal.ZERO
val formattedBalance = if (token.tokenType == TokenType.SOLANA) {
"${balance.setScale(4, RoundingMode.HALF_UP)} ${token.symbol}"
} else {
"${balance.setScale(4)} ${token.symbol}"
}
Coin(
name = token.symbol,
symbol = token.symbol,
price = "$${"%.6f".format(price)}",
iconRes = token.iconRes,
balance = formattedBalance,
priceDouble = price.toDouble(),
tokenInfo = token
)
}
withContext(Dispatchers.Main) {
adapter.updateCoins(updatedCoins)
btnNext.visibility = if (endIndex < allTokens.size) View.VISIBLE else View.GONE
btnBack.visibility = if (currentPage > 0) View.VISIBLE else View.GONE
}
} catch (e: Exception) {
Log.e("LoadCoins", "Error loading coins", e)
withContext(Dispatchers.Main) {
Toast.makeText(this@NewCoinsActivity, "Error loading coins", Toast.LENGTH_SHORT).show()
}
}
}
}
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 withContext(Dispatchers.IO) {
try {
when (token.tokenType) {
TokenType.SOLANA -> {
if (token.contracts.containsKey("SOLANA")) {
val contract = token.contracts["SOLANA"]!!
if (contract.address == "So11111111111111111111111111111111111111112") {
// رصيد SOL الأساسي
SolanaUtils.getSolanaBalance(walletAddress)
} else {
// رصيد التوكن على Solana
SolanaUtils.getTokenBalance(walletAddress, contract.address)
}
} else {
BigDecimal.ZERO
}
}
TokenType.EVM -> {
// الكود الحالي لجلب أرصدة EVM
var totalBalance = BigDecimal.ZERO
token.contracts.forEach { (network, contract) ->
val balance = when (network) {
"BSC" -> getBscTokenBalance(contract.address, walletAddress, contract.decimals)
"ETH" -> getEthTokenBalance(contract.address, walletAddress, contract.decimals)
"POLYGON" -> getPolygonBalance(contract.address, walletAddress, contract.decimals)
else -> BigDecimal.ZERO
}
totalBalance += balance
}
totalBalance
}
else -> BigDecimal.ZERO
}
} catch (e: Exception) {
Log.e("Balance", "Error getting balance for ${token.symbol}", e)
BigDecimal.ZERO
}
}
}
private suspend fun getBscTokenBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
return Web3Utils.getTokenBalance(
contractAddress = contractAddress,
walletAddress = walletAddress,
decimals = decimals
) ?: 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 {
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()) {
BigDecimal(BigInteger(response.value.substring(2), 16))
.movePointLeft(decimals)
} else {
Log.e("Balance", "Error: ${response.error?.message}")
BigDecimal.ZERO
}
}
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"
else -> "10 $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
}
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 {
val publicKeyObj = PublicKey(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)
// تعديل هنا: استخدام response مباشرة من getTokenAccountBalance
val tokenBalanceResponse = rpcClient.api.getTokenAccountBalance(associatedTokenAccount)
// تأكد من استخراج الكمية والكسور بشكل صحيح
val amount = tokenBalanceResponse.amount ?: "0" // إذا كانت قيمة amount فارغة استخدم "0"
val decimals = tokenBalanceResponse.decimals ?: 0 // إذا كانت decimals فارغة استخدم 0
// تحويل الكمية إلى BigDecimal مع الاحتفاظ بالكسور
BigDecimal(amount).movePointLeft(decimals)
} catch (e: RpcException) {
if (e.message?.contains("Account not found") == true) {
BigDecimal.ZERO
} else {
Log.e(TAG, "RPC Error getting token balance", e)
BigDecimal.ZERO
}
} 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 تعليقات