plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("com.google.gms.google-services")
id("kotlin-parcelize")
id("com.google.devtools.ksp")
}
android {
signingConfigs {
create("release") {
storeFile = file("C:/Users/krim/IdeaProjects/2025/keystore.jks")
storePassword = "19980tT$$$"
keyAlias = "my-key-alias"
keyPassword = "19980tT$$$"
}
}
packaging {
resources {
excludes.add("META-INF/io.netty.versions.properties")
excludes.add("META-INF/INDEX.LIST") // إضافة استبعاد ملف INDEX.LIST
}
}
namespace = "com.search2learn.s2l"
compileSdk = 34
defaultConfig {
applicationId = "com.search2learn.s2l"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
dataBinding = true
viewBinding = true
viewBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
packaging {
resources {
excludes.add("/META-INF/native-image/reflect-config.json")
excludes.add("/META-INF/native-image/native-image.properties")
excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}
}
dependencies {
// Core
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
// Lifecycle
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
// Navigation
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
// Compose
implementation(platform("androidx.compose:compose-bom:2024.02.02"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.8.2")
// Kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
// Retrofit & OkHttp
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
// Crypto & Security
implementation("androidx.security:security-crypto:1.1.0-alpha06")
// RxJava
implementation("io.reactivex.rxjava3:rxjava:3.1.8")
// MongoDB
implementation("org.mongodb:mongodb-driver-sync:4.11.1")
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.2.1")
// Web3j
implementation("org.web3j:core:4.10.3")
implementation("org.web3j:abi:4.10.3")
implementation("org.web3j:crypto:4.10.3")
implementation("org.web3j:contracts:4.10.3")
// Tron
// Solana
implementation("com.mmorrell:solanaj:1.20.3") {
exclude(group = "org.bouncycastle")
}
implementation("com.solanamobile:mobile-wallet-adapter-clientlib:1.1.0")
implementation("com.solanamobile:mobile-wallet-adapter-walletlib:1.1.0")
implementation("org.bitcoinj:bitcoinj-core:0.16.2")
// Firebase BoM (يدير الإصدارات تلقائياً)
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
// Firebase
implementation("com.google.firebase:firebase-firestore-ktx")
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.protobuf:protobuf-javalite:3.21.12")
// Desugaring
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
// bouncycastle
implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation("org.bouncycastle:bcprov-jdk15on:1.70") // Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
configurations.all {
resolutionStrategy {
force("com.google.protobuf:protobuf-javalite:3.21.12")
}
// 🧹 نحذف المكتبات المتكررة
exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
}
--------------------------------------------------------package com.search2learn.s2l
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import com.search2learn.s2l.WalletManager
import android.widget.TextView
import android.widget.Button
import org.web3j.protocol.core.methods.response.TransactionReceipt
import android.widget.EditText
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.resume
import kotlin.random.Random
import java.net.URL
import com.search2learn.s2l.Web3Utils.getTokenBalance
import com.search2learn.s2l.Web3Utils.getWeb3j
import org.web3j.protocol.core.methods.request.Transaction
import org.web3j.abi.datatypes.Function
import org.json.JSONObject
import org.web3j.protocol.core.DefaultBlockParameterName
import kotlinx.coroutines.CoroutineScope
import org.web3j.abi.datatypes.generated.Uint256
import org.web3j.abi.FunctionEncoder
import org.web3j.abi.datatypes.Address
import java.io.IOException
import org.web3j.protocol.Web3j
import android.content.Context
import android.view.View
import org.web3j.crypto.Credentials
import org.web3j.crypto.RawTransaction
import org.web3j.crypto.TransactionEncoder
import org.web3j.utils.Numeric
import java.math.BigInteger
import com.search2learn.s2l.Web3Utils.getNonce
import org.web3j.utils.Convert
import org.web3j.protocol.http.HttpService
import com.google.firebase.auth.FirebaseAuth
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import java.math.BigDecimal
import java.math.RoundingMode
import com.google.firebase.firestore.FirebaseFirestore
import com.search2learn.s2l.Web3Utils.getGasPrice
class DashboardActivity : AppCompatActivity() {
private lateinit var web3j: Web3j
private val web3: Web3j by lazy {
Web3j.build(HttpService("https://bsc-mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
}
private lateinit var btnNewDeposit: Button // يجب أن يكون موجوداً في قائمة المتغيرات
private lateinit var btnaff: ImageButton // تعريف المتغير
private lateinit var tvTotalBalanceUSD: TextView
private lateinit var tvBNBPrice: TextView
private lateinit var tvS2LPrice: TextView
private lateinit var balanceBNBTextView: TextView
private lateinit var balanceS2LTextView: TextView
private lateinit var depositAddressTextView: EditText
private lateinit var btnCopyAddress: Button
private lateinit var btnWithdraw: Button
private lateinit var logoutButton: ImageButton
private lateinit var btnTransactions: ImageButton
private lateinit var btnSupport: Button
private lateinit var btnNewCoins: Button
private lateinit var btnTrade: Button
private var userBalance: BigDecimal = BigDecimal.ZERO
private val updateInterval = 5000L // تحديث كل 5 ثواني
private lateinit var tvBalanceBNB: TextView
private lateinit var tvBalanceS2L: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dashboard)
tvBalanceBNB = findViewById(R.id.tvBalanceBNB)
tvBalanceS2L = findViewById(R.id.tvBalanceS2L)
tvTotalBalanceUSD = findViewById(R.id.tvTotalBalanceUSD)
btnaff = findViewById(R.id.btnaff)
// ربط العناصر بالواجهة الجديدة
initViews()
// تهيئة Web3j
web3j = Web3j.build(HttpService("https://bsc-mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
// تحميل بيانات المحفظة
loadWalletData()
// بدء التحديث التلقائي للأسعار والرصيد
startPriceUpdate()
startBalanceUpdate()
// إعداد معالجات الأحداث
setupClickListeners()
}
private fun initViews() {
// العناصر العلوية
logoutButton = findViewById(R.id.btnLogout)
btnTransactions = findViewById(R.id.btnTransactions)
btnNewDeposit = findViewById(R.id.btnNewDeposit) // هذا السطر مفقود!
// بطاقات العملات
tvTotalBalanceUSD = findViewById(R.id.tvTotalBalanceUSD)
balanceBNBTextView = findViewById(R.id.tvBalanceBNB)
balanceS2LTextView = findViewById(R.id.tvBalanceS2L)
tvBNBPrice = findViewById(R.id.tvBNBPrice)
tvS2LPrice = findViewById(R.id.tvS2LPrice)
// أزرار الإيداع والسحب
depositAddressTextView = findViewById(R.id.etDepositAddress)
btnCopyAddress = findViewById(R.id.btnCopyAddress)
btnWithdraw = findViewById(R.id.btnWithdraw)
// أزرار القائمة السفلية
btnNewCoins = findViewById(R.id.btn_new_coins)
btnTrade = findViewById(R.id.btn_trade)
btnSupport = findViewById(R.id.btnSupport)
}
private fun setupClickListeners() {
// زر تسجيل الخروج
logoutButton.setOnClickListener {
showLogoutDialog()
}
btnaff.setOnClickListener {
val intent = Intent(this, ReferralActivity::class.java)
startActivity(intent)
}
btnNewDeposit.setOnClickListener {
val user = FirebaseAuth.getInstance().currentUser
if (user == null) {
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
fetchWalletAddress { walletAddress ->
if (walletAddress.isNullOrEmpty()) {
Toast.makeText(this, "Wallet address not found", Toast.LENGTH_SHORT).show()
} else {
showEnhancedDepositDialog(walletAddress)
}
}
}
// زر المعاملات
btnTransactions.setOnClickListener {
startActivity(Intent(this, TransactionsActivity::class.java))
}
// زر نسخ العنوان
btnCopyAddress.setOnClickListener {
val address = depositAddressTextView.text.toString()
if (address.startsWith("0x")) {
copyToClipboard(address)
} else {
Toast.makeText(this, "Invalid address", Toast.LENGTH_SHORT).show()
}
}
// زر السحب
btnWithdraw.setOnClickListener {
val user = FirebaseAuth.getInstance().currentUser
if (user == null) {
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
fetchWalletAddress { walletAddress ->
if (walletAddress.isNullOrEmpty()) {
Toast.makeText(this, "Wallet address not found", Toast.LENGTH_SHORT).show()
} else {
showWithdrawDialog(walletAddress, web3j)
}
}
}
// زر العملات الجديدة
btnNewCoins.setOnClickListener {
startActivity(Intent(this, NewCoinsActivity::class.java))
}
// زر التداول
btnTrade.setOnClickListener {
startActivity(Intent(this, SwapActivity::class.java))
}
// زر الدعم
btnSupport.setOnClickListener {
startActivity(Intent(this, SupportActivity::class.java))
}
}
private fun loadWalletData() {
val user = FirebaseAuth.getInstance().currentUser ?: run {
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
return
}
WalletManager.getOrCreateWallet { wallet ->
if (wallet != null) {
runOnUiThread {
// التحقق النهائي من العنوان قبل الاستخدام
if (WalletManager.isValidBscAddress(wallet.bscAddress)) {
depositAddressTextView.setText(wallet.bscAddress)
updateBalances(wallet)
} else {
Toast.makeText(this, "Invalid wallet address, recreating...", Toast.LENGTH_SHORT).show()
createNewWallet(user.uid)
}
}
} else {
runOnUiThread {
Toast.makeText(this, "Failed to load wallet", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun showWalletCreationDialog() {
AlertDialog.Builder(this)
.setTitle("Create Wallet")
.setMessage("No wallet found. Create a new multi-chain wallet?")
.setPositiveButton("Create") { _, _ ->
WalletManager.getOrCreateWallet { wallet ->
if (wallet != null) {
Toast.makeText(
this@DashboardActivity,
"Wallet created successfully!",
Toast.LENGTH_SHORT
).show()
loadWalletData()
}
}
}
.setNegativeButton("Cancel", null)
.show()
}
private fun updateBalances(wallet: WalletManager.MultiChainWallet) {
lifecycleScope.launch {
try {
val bnbBalance = withContext(Dispatchers.IO) {
Web3Utils.getBalance(wallet.bscAddress) ?: BigDecimal.ZERO
}
val s2lBalance = withContext(Dispatchers.IO) {
Web3Utils.getTokenBalance(
contractAddress = "0xEd842773464083EBFd207B25257304AFfe4049f1",
walletAddress = wallet.bscAddress,
decimals = 7
) ?: BigDecimal.ZERO
}
// حساب القيمة الحقيقية
val bnbPrice = getCurrentBNBPrice() // دالة جديدة لجلب السعر
val s2lPrice = getCurrentS2LPrice() // دالة جديدة لجلب السعر
val totalValue = (bnbBalance * bnbPrice) + (s2lBalance * s2lPrice)
withContext(Dispatchers.Main) {
tvBalanceBNB.text = "${"%.6f".format(bnbBalance)} BNB"
tvBalanceS2L.text = "${s2lBalance.setScale(2, RoundingMode.HALF_UP)} S2L"
tvTotalBalanceUSD.text = "$${"%.2f".format(totalValue)}"
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
tvBalanceBNB.text = "Error"
tvBalanceS2L.text = "Error"
tvTotalBalanceUSD.text = "$0.00"
Log.e("Balance", "Update error: ${e.message}")
}
}
}
}
// دالة مساعدة لجلب سعر BNB
private fun getCurrentBNBPrice(): BigDecimal {
return try {
tvBNBPrice.text.toString()
.replace("[^\\d.]".toRegex(), "")
.toBigDecimal()
} catch (e: Exception) {
Log.e("Price", "Using default BNB price", e)
BigDecimal("350.0") // قيمة افتراضية
}
}
// دالة مساعدة لجلب سعر S2L (يمكن تطويرها لتصلك من API)
private fun getCurrentS2LPrice(): BigDecimal {
return BigDecimal("0.00003") // ثابت مؤقت
}
private fun showEnhancedDepositDialog(walletAddress: String) {
val coins = listOf(
"BNB (BSC BEP20)",
"S2L (BSC BEP20)",
"USDT (Multi-chain)",
"PEPE (Multi-chain)",
"MUBARAK (Multi-chain)",
"S2LE (BSC BEP20)"
)
AlertDialog.Builder(this)
.setTitle("Select Deposit Option")
.setItems(coins.toTypedArray()) { _, which ->
when (which) {
0 -> showCoinDepositInstructions("BNB", "BSC BEP20", walletAddress)
1 -> showCoinDepositInstructions("S2L", "BSC BEP20", walletAddress)
else -> {
val selectedCoin = coins[which].substringBefore(" (")
showNetworkSelectionForDeposit(selectedCoin, walletAddress)
}
}
}
.setNegativeButton("Cancel", null)
.show()
}
// في DashboardActivity.kt
private fun showNetworkSelectionForDeposit(coin: String, walletAddress: String) {
// تأكد من تعريف allTokens بشكل صحيح
val allTokens = listOf(
TokenInfo("BNB", R.drawable.bnb_icon, mapOf("BSC" to TokenContract("", 18))),
TokenInfo("S2L", R.drawable.s2l_icon, mapOf("BSC" to TokenContract("0xEd...", 7)))
)
val tokenInfo = allTokens.find { it.symbol == coin } ?: return // استخدم coin مباشرة إذا كانت String
val networks = tokenInfo.contracts.keys.toList()
AlertDialog.Builder(this)
.setTitle("Select Network for $coin")
.setItems(networks.toTypedArray()) { _, which ->
val selectedNetwork = networks[which]
showCoinDepositInstructions(coin, selectedNetwork, walletAddress)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun showCoinDepositInstructions(coin: String, network: String, walletAddress: String) {
val minDeposit = when (coin) {
"BNB" -> "0.01 BNB"
"S2L" -> "100 S2L"
else -> "0.01"
}
val message = """
Please send only $coin ($network) to this address:
$walletAddress
Network: $network
Minimum deposit: $minDeposit
⚠️ Warning: Sending other assets may result in permanent loss!
""".trimIndent()
AlertDialog.Builder(this)
.setTitle("Deposit $coin")
.setMessage(message)
.setPositiveButton("Copy Address") { _, _ ->
copyToClipboard(walletAddress)
Toast.makeText(this, "Address copied!", Toast.LENGTH_SHORT).show()
}
.setNegativeButton("Close", null)
.show()
}
private fun startPriceUpdate() {
CoroutineScope(Dispatchers.Main).launch {
while (true) {
fetchPrices()
fetchS2LPrice()
delay(updateInterval)
}
}
}
private fun fetchPrices() {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = URL("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").readText()
val jsonObject = JSONObject(response)
val bnbPrice = jsonObject.getString("price").toDouble()
withContext(Dispatchers.Main) {
tvBNBPrice.text = "$${"%.2f".format(bnbPrice)}"
}
} catch (e: Exception) {
Log.e("FetchPrices", "Error fetching BNB price", e)
}
}
}
private fun fetchS2LPrice() {
val fixedS2LPrice = BigDecimal("0.00003")
tvS2LPrice.text = "$${"%.6f".format(fixedS2LPrice)}"
CoroutineScope(Dispatchers.IO).launch {
try {
val walletAddress = depositAddressTextView.text.toString()
if (walletAddress.isNotEmpty()) {
// ✅ تمرير جميع المعاملات المطلوبة:
val s2lBalance = Web3Utils.getTokenBalance(
contractAddress = "0xEd842773464083EbFd207B25257304AFfe4049f1", // عنوان عقد S2L
walletAddress = walletAddress,
decimals = 7 // لأن S2L له 7 منازل عشرية
)
val bnbBalance = Web3Utils.getBalance(walletAddress)
val bnbPrice = tvBNBPrice.text.toString().replace("$", "").toBigDecimalOrNull() ?: BigDecimal.ZERO
val totalValue = (bnbBalance * bnbPrice) + (s2lBalance * fixedS2LPrice)
withContext(Dispatchers.Main) {
tvTotalBalanceUSD.text = "$${"%.2f".format(totalValue)}"
}
} else {
Log.e("WalletError", "Wallet address is empty!")
}
} catch (e: Exception) {
Log.e("Balance", "Error calculating total balance", e)
}
}
}
private suspend fun fetchTotalBalance(walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
var totalBalance = BigDecimal.ZERO
// BNB Balance (BSC only)
val bnbBalance = Web3Utils.getBalance(walletAddress)
totalBalance = totalBalance.add(bnbBalance)
// S2L Balance from all networks
val s2lToken = TokenInfo(
"S2L",
R.drawable.s2l_icon,
mapOf(
"BSC" to TokenContract("0xEd842773464083EbFd207B25257304AFfe4049f1", 7),
"ETH" to TokenContract("0x123...", 7) // استبدل بعقد ETH الفعلي
)
)
val s2lBalance = getUnifiedTokenBalance(s2lToken, walletAddress)
totalBalance = totalBalance.add(s2lBalance)
// يمكنك إضافة عملات أخرى بنفس الطريقة
totalBalance
}
}
private suspend fun getUnifiedTokenBalance(token: TokenInfo, walletAddress: String): BigDecimal {
var totalBalance = BigDecimal.ZERO
token.contracts.forEach { (network, contract) ->
val balance = when (network) {
"BSC" -> getTokenBalance(contract.address, walletAddress, contract.decimals)
"ETH" -> getEthTokenBalance(contract.address, walletAddress, contract.decimals)
else -> BigDecimal.ZERO
}
totalBalance += balance
}
return totalBalance
}
private suspend fun getEthTokenBalance(contractAddress: String, walletAddress: String, decimals: Int): BigDecimal {
// تنفيذ جلب الرصيد من شبكة Ethereum
// يمكنك استخدام Web3j مع RPC مختلف لـ ETH
return BigDecimal.ZERO // مؤقت
}
private fun startBalanceUpdate() {
lifecycleScope.launch(Dispatchers.IO) {
while (true) {
val address = depositAddressTextView.text.toString()
if (address.startsWith("0x")) {
try {
val totalBalance = fetchTotalBalance(address)
val bnbBalance = Web3Utils.getBalance(address)
val s2lBalance = fetchS2LBalanceFromAllNetworks(address)
withContext(Dispatchers.Main) {
balanceBNBTextView.text = "${"%.6f".format(bnbBalance)} BNB"
balanceS2LTextView.text = "${s2lBalance.setScale(2, RoundingMode.HALF_UP)} S2L"
tvTotalBalanceUSD.text = "$${"%.2f".format(calculateTotalBalanceInUSD(bnbBalance, s2lBalance))}"
}
} catch (e: Exception) {
Log.e("BalanceUpdate", "Error updating balance", e)
}
}
delay(5000)
}
}
}
private suspend fun fetchS2LBalanceFromAllNetworks(walletAddress: String): BigDecimal {
val s2lToken = TokenInfo(
"S2L",
R.drawable.s2l_icon,
mapOf(
"BSC" to TokenContract("0xEd842773464083EbFd207B25257304AFfe4049f1", 7),
"ETH" to TokenContract("0x123...", 7) // استبدل بعقد ETH الفعلي
)
)
return getUnifiedTokenBalance(s2lToken, walletAddress)
}
private fun calculateTotalBalanceInUSD(bnbBalance: BigDecimal, s2lBalance: BigDecimal): Double {
// سعر BNB من API بينانس
val bnbPriceText = tvBNBPrice.text.toString().replace("$", "").toDoubleOrNull() ?: 0.0
// سعر S2L الثابت
val s2lPrice = 0.00003
// حساب القيمة بالدولار
val bnbValue = bnbBalance.toDouble() * bnbPriceText
val s2lValue = s2lBalance.toDouble() * s2lPrice
return bnbValue + s2lValue
}
private fun generateRandomPrice(): Double {
return Random.nextDouble(0.00007, 0.0001)
}
private suspend fun fetchBnbBalance(walletAddress: String): BigDecimal {
return try {
Web3Utils.getBalance(walletAddress) // جلب رصيد BNB من شبكة BSC
} catch (e: Exception) {
Log.e("Balance", "Error fetching BNB balance", e)
BigDecimal.ZERO
}
}
private suspend fun fetchBalance(currency: String): BigDecimal {
return suspendCoroutine { continuation ->
fetchWalletAddress { walletAddress ->
if (walletAddress.isEmpty()) {
continuation.resume(BigDecimal.ZERO)
return@fetchWalletAddress
}
lifecycleScope.launch(Dispatchers.IO) {
try {
val balance = when (currency) {
"BNB" -> fetchBnbBalance(walletAddress)
"S2L" -> fetchS2LBalanceFromAllNetworks(walletAddress)
else -> BigDecimal.ZERO
}
continuation.resume(balance)
} catch (e: Exception) {
Log.e("Balance", "Error fetching $currency balance", e)
continuation.resume(BigDecimal.ZERO)
}
}
}
}
}
fun fetchWalletAddress(onResult: (String) -> Unit) {
val user = FirebaseAuth.getInstance().currentUser ?: run {
onResult("")
return
}
WalletManager.getOrCreateWallet { wallet ->
if (wallet != null && wallet.bscAddress.isNotEmpty()) {
onResult(wallet.bscAddress)
} else {
onResult("")
}
}
}
fun fetchNonce(address: String, web3j: Web3j, callback: (BigInteger?) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
if (!isValidAddress(address)) {
Log.e("Web3j", "⚠️ عنوان المحفظة غير صالح: $address")
withContext(Dispatchers.Main) {
callback(null)
}
return@launch
}
try {
val nonce = web3j.ethGetTransactionCount(address, DefaultBlockParameterName.LATEST).send().transactionCount
withContext(Dispatchers.Main) {
Log.d("Web3j", "✅ Nonce المحصل عليه: $nonce")
callback(nonce)
}
} catch (e: IOException) {
Log.e("Web3j", "⚠️ خطأ IO أثناء جلب Nonce: ${e.message}")
e.printStackTrace()
withContext(Dispatchers.Main) {
callback(null) // ⚠️ إعادة null في حالة حدوث خطأ
}
} catch (e: Exception) {
Log.e("Web3j", "⚠️ خطأ أثناء جلب Nonce: ${e.message}")
e.printStackTrace()
withContext(Dispatchers.Main) {
callback(null) // ⚠️ إعادة null في حالة حدوث خطأ
}
}
}
}
suspend fun fetchNonce(walletAddress: String, web3j: Web3j): BigInteger? {
return try {
println("Fetching nonce for wallet address: $walletAddress")
val ethGetTransactionCount = web3j.ethGetTransactionCount(walletAddress, DefaultBlockParameterName.PENDING).send()
val nonce = ethGetTransactionCount.transactionCount
println("Nonce fetched: $nonce")
nonce
} catch (e: Exception) {
println("⚠️ Error fetching nonce: ${e.message}")
null
}
}
private fun onWithdrawButtonClick(walletAddress: String, web3j: Web3j) {
Log.d("onWithdrawButtonClick", "Withdraw button was clicked.")
showWithdrawDialog(walletAddress, web3j) // استدعاء الدالة الخاصة بنافذة السحب
}
private fun showWithdrawDialog(walletAddress: String, web3j: Web3j) {
Log.d("showWithdrawDialog", "🚀 فتح نافذة السحب...")
val dialogView = layoutInflater.inflate(R.layout.dialog_withdraw, null)
// 🔍 التحقق من تحميل التخطيط بشكل صحيح
Log.d("showWithdrawDialog", "🔍 تم تحميل التخطيط بنجاح؟ ${dialogView != null}")
// جلب مكونات الواجهة
val spinnerCurrency = dialogView.findViewById<Spinner>(R.id.spinnerCurrency)
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 btnNewDeposit = findViewById<Button>(R.id.btnNewDeposit)
// 🔍 التحقق من تحميل عناصر الواجهة بنجاح
Log.d("showWithdrawDialog", "🎛️ عناصر الواجهة: spinnerCurrency=${spinnerCurrency != null}, editAmount=${editAmount != null}, btnMax=${btnMax != null}")
// تحديد العملات المتاحة
val currencies = arrayOf("BNB", "S2L")
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, currencies)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinnerCurrency.adapter = adapter
// إنشاء نافذة الحوار
val dialog = AlertDialog.Builder(this)
.setTitle("Withdraw currency")
.setView(dialogView)
.setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
.create()
dialog.show() // عرض نافذة السحب
Log.d("showWithdrawDialog", "🟢 تم عرض نافذة السحب بنجاح")
// 🔄 جلب الرصيد الأولي عند فتح النافذة
// 🔄 جلب الرصيد الأولي عند فتح النافذة
lifecycleScope.launch {
val selectedCurrency = spinnerCurrency.selectedItem.toString()
Log.d("showWithdrawDialog", "🔄 جلب الرصيد الأولي للعملة: $selectedCurrency")
val balance = fetchBalance(selectedCurrency) // ✅ إزالة walletAddress
// 🔍 التأكد من أن الرصيد تم جلبه بشكل صحيح
Log.d("fetchBalance", "📌 الرصيد الذي تم جلبه: $balance $selectedCurrency")
if (balance > BigDecimal.ZERO) {
editAmount.setText(balance.stripTrailingZeros().toPlainString())
Log.d("showWithdrawDialog", "✅ تم تعيين الرصيد في الحقل: ${editAmount.text}")
} else {
editAmount.setText("0")
Log.d("showWithdrawDialog", "⚠️ الرصيد غير كافٍ أو لم يتم جلبه بشكل صحيح! تعيين 0.")
}
}
btnNewDeposit.setOnClickListener { showDepositDialog() }
// زر "Max" لوضع الرصيد الكامل في الحقل
btnMax.setOnClickListener {
Log.d("showWithdrawDialog", "🟢 الضغط على زر Max")
lifecycleScope.launch {
val selectedCurrency = spinnerCurrency.selectedItem.toString()
Log.d("showWithdrawDialog", "🔄 جلب الرصيد الأقصى للعملة: $selectedCurrency")
val availableBalance = fetchBalance(selectedCurrency) // ✅ إزالة walletAddress
// 🔍 التحقق من الرصيد المسترجع
Log.d("fetchBalance", "📌 الرصيد المتاح بعد الضغط على Max: $availableBalance $selectedCurrency")
if (availableBalance > BigDecimal.ZERO) {
editAmount.setText(availableBalance.stripTrailingZeros().toPlainString())
Log.d("showWithdrawDialog", "✅ تم إدخال الرصيد في الحقل: ${editAmount.text}")
} else {
editAmount.setText("0")
Log.d("showWithdrawDialog", "⚠️ فشل الجلب أو الرصيد غير كافٍ! تعيين 0.")
}
}
}
// عند الضغط على زر السحب
dialogView.findViewById<Button>(R.id.btnWithdraw).setOnClickListener {
Log.d("showWithdrawDialog", "Withdraw button clicked")
val address = editAddress.text.toString().trim()
val amountStr = editAmount.text.toString().trim()
// التحقق من صحة البيانات المدخلة
if (address.isEmpty() || amountStr.isEmpty()) {
Log.d("showWithdrawDialog", "Address or amount is empty")
errorMessage.text = "⚠️Please enter all the data!"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
val amount = try {
BigDecimal(amountStr)
} catch (e: NumberFormatException) {
Log.d("showWithdrawDialog", "Invalid amount format")
errorMessage.text = "⚠️ المبلغ غير صالح!"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
Log.d("showWithdrawDialog", "Amount: $amount")
// الحصول على العملة المحددة
val selectedCurrency = spinnerCurrency.selectedItem.toString()
// التحقق من الحد الأدنى للسحب بناءً على العملة
val minAmount = when (selectedCurrency) {
"BNB" -> BigDecimal("0.01") // الحد الأدنى لـ BNB هو 0.01
"S2L" -> BigDecimal("700000") // الحد الأدنى لـ S2L هو 7,000,000
else -> BigDecimal.ZERO // إذا كانت العملة غير معروفة
}
if (amount < minAmount) {
// إذا كان المبلغ أقل من الحد الأدنى، عرض رسالة خطأ
errorMessage.text = "⚠️Minimum withdrawal for $selectedCurrency is $minAmount"
errorMessage.visibility = View.VISIBLE
return@setOnClickListener
}
// إغلاق النافذة بمجرد الضغط على زر السحب
dialog.dismiss()
// هنا نضيف الكود الخاص بجلب الـ nonce
lifecycleScope.launch {
try {
Log.d("showWithdrawDialog", "🚀 بدء جلب الـ nonce للمحفظة: $walletAddress")
val nonce = getNonce(walletAddress, web3j)
if (nonce == BigInteger.ZERO) {
Log.d("showWithdrawDialog", "⚠️ المحفظة جديدة أو لم تقم بأي معاملات بعد، nonce = 0")
}
Log.d("showWithdrawDialog", "✅ قيمة الـ nonce المسترجعة: $nonce")
// إذا كان nonce صفرًا، يمكنك المتابعة مع nonce = 0 للمعاملة الأولى
if (nonce == BigInteger.ZERO) {
errorMessage.text = "⚠️ المحفظة جديدة، سيتم استخدام nonce = 0 للمعاملة الأولى."
errorMessage.visibility = View.VISIBLE
}
// المتابعة مع المعاملة باستخدام nonce
Log.d("showWithdrawDialog", "Estimating gas limit...")
val gasLimit = estimateGasLimit(walletAddress, address, amount, web3j)
Log.d("showWithdrawDialog", "Estimated Gas Limit: $gasLimit")
if (gasLimit <= BigInteger.ZERO) {
Log.d("showWithdrawDialog", "Invalid gas limit: $gasLimit")
errorMessage.text = "⚠️ تقدير الغاز غير صالح!"
errorMessage.visibility = View.VISIBLE
return@launch
}
Log.d("showWithdrawDialog", "Processing withdrawal...")
processWithdrawal(
spinnerCurrency.selectedItem.toString(),
address,
amountStr,
gasLimit,
nonce
)
} catch (e: Exception) {
Log.e("showWithdrawDialog", "Error during withdrawal: ${e.message}", e)
errorMessage.text = "⚠️ خطأ أثناء تنفيذ العملية: ${e.message}"
errorMessage.visibility = View.VISIBLE
}
}
}
}
private fun getPrivateKey(callback: (String?) -> Unit) {
val user = FirebaseAuth.getInstance().currentUser
val userEmail = user?.email
if (userEmail.isNullOrEmpty()) {
Log.e("Firestore", "❌ البريد الإلكتروني غير متوفر أو المستخدم غير مسجل الدخول!")
callback(null)
return
}
val db = FirebaseFirestore.getInstance()
db.collection("wallets").document(userEmail)
.get()
.addOnSuccessListener { document ->
if (document.exists()) {
val privateKey = document.getString("privateKey")
Log.d("Firestore", "✅ تم جلب المفتاح الخاص: $privateKey")
callback(privateKey)
} else {
Log.e("Firestore", "❌ المفتاح غير موجود في Firestore!")
callback(null)
}
}
.addOnFailureListener { exception ->
Log.e("Firestore", "❌ خطأ أثناء جلب المفتاح الخاص!", exception)
callback(null)
}
}
private fun processWithdrawal(currency: String, address: String, amount: String, gasLimit: BigInteger, nonce: BigInteger) {
Log.d("Withdraw", "🔄 بدء تنفيذ عملية السحب...")
Log.d("Withdraw", "📌 العملة: $currency, العنوان: $address, المبلغ: $amount, Gas Limit: $gasLimit, Nonce: $nonce")
val userId = FirebaseAuth.getInstance().currentUser?.uid
if (userId.isNullOrEmpty()) {
Log.e("Withdraw", "🚨 لا يوجد مستخدم مسجل دخول!")
Toast.makeText(this, "يجب تسجيل الدخول أولاً", Toast.LENGTH_SHORT).show()
return
}
Log.d("Firestore", "🔍 محاولة جلب المفتاح الخاص للمستخدم: $userId")
if (!isValidAddress(address)) {
Log.e("Withdraw", "⚠️ عنوان المحفظة غير صالح!")
Toast.makeText(this, "عنوان المحفظة غير صحيح", Toast.LENGTH_SHORT).show()
return
}
getPrivateKey { privateKey ->
if (privateKey == null) {
Log.e("Withdraw", "🚨 لا يوجد مفتاح خاص!")
Toast.makeText(this, "خطأ: لا يوجد مفتاح خاص لإجراء المعاملة", Toast.LENGTH_SHORT).show()
return@getPrivateKey
}
val withdrawalAmount = try {
BigDecimal(amount)
} catch (e: Exception) {
Log.e("Withdraw", "❌ خطأ في تحويل المبلغ: ${e.message}")
Toast.makeText(this, "خطأ في إدخال المبلغ", Toast.LENGTH_SHORT).show()
return@getPrivateKey
}
Log.d("Withdraw", "✅ المبلغ المحوّل إلى BigDecimal: $withdrawalAmount")
val feeRate = BigDecimal("0.05") // 5% رسوم
val minFee = BigDecimal("0.000001") // الحد الأدنى للرسوم
val fee = withdrawalAmount.multiply(feeRate).setScale(6, RoundingMode.HALF_UP).max(minFee)
Log.d("Withdraw", "💰 الرسوم المحسوبة: $fee")
if (withdrawalAmount.add(fee) > userBalance) {
Log.e("Withdraw", "❌ الرصيد غير كافٍ! الرصيد الحالي: $userBalance، المطلوب: ${withdrawalAmount.add(fee)}")
Toast.makeText(this, "Insufficient balance to cover the amount and fees", Toast.LENGTH_SHORT).show()
return@getPrivateKey
}
Log.d("Withdraw", "✅ الرصيد كافٍ، البدء في عملية السحب...")
Toast.makeText(this, "Withdrawal in progress...", Toast.LENGTH_SHORT).show()
val onSuccess: () -> Unit = {
Log.d("Withdraw", "🎉 السحب تم بنجاح، الآن سيتم إرسال الرسوم...")
// ✅ إرسال الرسوم بعد نجاح السحب (استخدام nonce + 1)
sendFeeTransaction(fee, currency, privateKey, nonce.add(BigInteger.ONE))
}
val onFailure: (String) -> Unit = { errorMessage ->
Log.e("Withdraw", "❌ فشل السحب: $errorMessage")
Toast.makeText(this, "فشل عملية السحب: $errorMessage", Toast.LENGTH_SHORT).show()
}
when (currency) {
"S2L" -> {
Log.d("Withdraw", "🚀 سحب S2L جارٍ التنفيذ...")
executeS2LWithdrawal(address, withdrawalAmount, privateKey, gasLimit, nonce, onSuccess, onFailure)
}
"BNB" -> {
Log.d("Withdraw", "🚀 سحب BNB جارٍ التنفيذ...")
executeBNBWithdrawal(address, withdrawalAmount, privateKey, gasLimit, nonce, onSuccess, onFailure)
}
else -> {
Log.e("Withdraw", "❌ عملة غير صالحة!")
Toast.makeText(this, "عملة غير صالحة", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun showDepositDialog() {
// احصل على عنوان المحفظة أولاً (من WalletManager مثلاً)
WalletManager.getOrCreateWallet { wallet ->
if (wallet != null) {
AlertDialog.Builder(this)
.setTitle("Select Deposit Option")
.setItems(arrayOf("BNB (BSC BEP20)", "S2L (BSC BEP20)")) { dialog, which ->
when (which) {
0 -> showDepositInstructions("BNB", "BSC BEP20", wallet.bscAddress)
1 -> showDepositInstructions("S2L", "BSC BEP20", wallet.bscAddress)
}
}
.show()
} else {
Toast.makeText(this, "Failed to load wallet address", Toast.LENGTH_SHORT).show()
}
}
}
private fun showDepositInstructions(coin: String, network: String, walletAddress: String) {
val message = "Please send only $coin ($network) to the following address:\n\n" +
"$walletAddress\n\n" +
"Network: $network\n" +
"Minimum deposit: 0.01 $coin"
AlertDialog.Builder(this)
.setTitle("Deposit $coin")
.setMessage(message)
.setPositiveButton("Copy Address") { _, _ ->
// نسخ العنوان إلى الحافظة
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Deposit Address", walletAddress) // تم التعديل هنا
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "Address copied to clipboard", Toast.LENGTH_SHORT).show()
}
.setNegativeButton("Close", null)
.show()
}
private fun executeTransaction(toAddress: String, amount: BigDecimal, currency: String, userId: String) {
getPrivateKey { privateKey ->
if (privateKey == null) {
Log.e("Transaction", "❌ فشل في جلب المفتاح الخاص!")
return@getPrivateKey
}
CoroutineScope(Dispatchers.IO).launch {
try {
val web3 = Web3j.build(HttpService("https://bsc-mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
val credentials = Credentials.create(privateKey)
val senderAddress = credentials.address
// 🔄 جلب Nonce ديناميكيًا بناءً على PENDING لضمان أحدث قيمة
var nonce = web3.ethGetTransactionCount(senderAddress, DefaultBlockParameterName.PENDING).send().transactionCount
Log.d("Transaction", "🔢 Nonce الحالي: $nonce")
// ⛽️ جلب سعر الغاز ديناميكيًا
val gasPrice = web3.ethGasPrice().send().gasPrice
// 🛠️ تقدير الغاز تلقائيًا بدلًا من تحديده ثابتًا بـ 21000
val estimatedGas = estimateGasLimit(senderAddress, toAddress, amount, web3)
Log.d("Transaction", "⛽️ سعر الغاز: $gasPrice | الحد المقدر للغاز: $estimatedGas")
// 💰 تحويل المبلغ إلى Wei
val amountInWei = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger()
// 📜 إنشاء المعاملة
val transaction = RawTransaction.createEtherTransaction(nonce, gasPrice, estimatedGas, toAddress, amountInWei)
Log.d("Transaction", "📜 تم إنشاء المعاملة")
// ✍️ توقيع المعاملة
val signedTransaction = TransactionEncoder.signMessage(transaction, credentials)
// 📡 إرسال المعاملة
val response = web3.ethSendRawTransaction(Numeric.toHexString(signedTransaction)).send()
if (response.hasError()) {
Log.e("Transaction", "⚠️ خطأ في المعاملة: ${response.error.message}")
return@launch
}
val txHash = response.transactionHash
Log.d("Transaction", "✅ تم إرسال المعاملة بنجاح! TxHash: $txHash")
// **إرسال الرسوم بعد نجاح المعاملة الأساسية**
val minFee = BigDecimal("0.000001") // الحد الأدنى للرسوم
val feeAmount = amount.multiply(BigDecimal("0.05")).setScale(6, RoundingMode.HALF_UP).max(minFee)
// ✅ زيادة nonce لمنع التعارض
nonce = nonce.add(BigInteger.ONE)
// 🔄 التأكد من أن nonce محدث قبل إرسال الرسوم
delay(500) // مهلة صغيرة للسماح بتحديث nonce في الشبكة
sendFeeTransaction(feeAmount, currency, privateKey, nonce)
} catch (e: Exception) {
Log.e("Transaction", "⚠️ فشل في تنفيذ المعاملة: ${e.localizedMessage}")
}
}
}
}
fun sendFeeTransaction(originalAmount: BigDecimal, currency: String, privateKey: String, nonce: BigInteger) {
CoroutineScope(Dispatchers.IO).launch {
val feeAddress = "0xbec70ec039995248905331e2a50a29eb76b7a357"
val web3 = Web3j.build(HttpService("https://bsc-mainnet.infura.io/v3/c88ab0a11eb040438cff2f7bef79feec"))
try {
Log.d("FeeTransaction", "🔄 بدء إرسال الرسوم...")
if (privateKey.isBlank() || privateKey.length != 64) {
Log.e("FeeTransaction", "❌ المفتاح الخاص غير صالح!")
return@launch
}
val credentials = Credentials.create(privateKey)
val senderAddress = credentials.address
Log.d("FeeTransaction", "📌 عنوان المرسل: $senderAddress")
// حساب 5% من المبلغ الأصلي كرسوم
val feeAmount = originalAmount.multiply(BigDecimal("0.05")).setScale(7, RoundingMode.HALF_UP)
Log.d("FeeTransaction", "💰 الرسوم المحسوبة (5% من $originalAmount): $feeAmount S2L")
// تحويل الرسوم إلى الوحدة الأدنى (مع 7 منازل عشرية)
val feeAmountInSmallestUnit = feeAmount.multiply(BigDecimal("10000000")).toBigInteger()
Log.d("FeeTransaction", "💰 الرسوم في الوحدة الأدنى: $feeAmountInSmallestUnit")
if (currency == "BNB") {
// 🟢 إذا كانت العملة BNB، تابع التنفيذ كالمعتاد
val balanceWei = web3.ethGetBalance(senderAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send().balance
val balanceBNB = Convert.fromWei(balanceWei.toBigDecimal(), Convert.Unit.ETHER)
if (balanceBNB < feeAmount) {
Log.e("FeeTransaction", "❌ الرصيد غير كافٍ لتنفيذ معاملة BNB!")
return@launch
}
val gasPrice = web3.ethGasPrice().send().gasPrice.multiply(BigInteger.valueOf(3)) // زيادة 3x
val gasLimit = BigInteger("27000")
val minFee = BigDecimal("0.000001")
val adjustedFeeAmount = feeAmount.setScale(6, RoundingMode.HALF_UP).max(minFee)
val amountInWei = Convert.toWei(adjustedFeeAmount, Convert.Unit.ETHER).toBigInteger()
val transaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, feeAddress, amountInWei)
Log.d("FeeTransaction", "📝 المعاملة جاهزة للتوقيع...")
val signedTransaction = TransactionEncoder.signMessage(transaction, 56L, credentials)
val hexValue = Numeric.toHexString(signedTransaction)
Log.d("FeeTransaction", "✅ المعاملة موقعة: $hexValue")
val response = web3.ethSendRawTransaction(hexValue).send()
if (response.hasError()) {
Log.e("FeeTransaction", "⚠️ خطأ في إرسال معاملة BNB: ${response.error.message}")
} else {
Log.d("FeeTransaction", "✅ تم إرسال رسوم BNB بنجاح! TxHash: ${response.transactionHash}")
}
} else if (currency == "S2L") {
// 🔴 إذا كانت العملة S2L، نرسل رسوم S2L بدلًا من BNB
val tokenContractAddress = "0xEd842773464083EbFd207B25257304AFfe4049f1"
val function = Function(
"transfer",
listOf(Address(feeAddress), Uint256(feeAmountInSmallestUnit)),
emptyList()
)
val encodedFunction = FunctionEncoder.encode(function)
val gasPrice = web3.ethGasPrice().send().gasPrice.multiply(BigInteger.valueOf(3))
val gasLimit = BigInteger("100000") // الغاز أعلى لأننا نستدعي عقد ذكي
val transaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, tokenContractAddress, encodedFunction)
Log.d("FeeTransaction", "📝 معاملة S2L جاهزة للتوقيع...")
val signedTransaction = TransactionEncoder.signMessage(transaction, 56L, credentials)
val hexValue = Numeric.toHexString(signedTransaction)
val response = web3.ethSendRawTransaction(hexValue).send()
if (response.hasError()) {
Log.e("FeeTransaction", "⚠️ خطأ في إرسال معاملة S2L: ${response.error.message}")
} else {
Log.d("FeeTransaction", "✅ تم إرسال رسوم S2L بنجاح! TxHash: ${response.transactionHash}")
}
} else {
Log.e("FeeTransaction", "❌ العملة غير مدعومة!")
}
} catch (e: Exception) {
Log.e("FeeTransaction", "⚠️ خطأ أثناء إرسال الرسوم: ${e.message}", e)
} finally {
Log.d("FeeTransaction", "🔚 انتهاء عملية إرسال الرسوم.")
}
}
}
private suspend fun waitForTransactionReceipt(web3: Web3j, txHash: String) {
var receipt: TransactionReceipt? = null
while (receipt == null) {
delay(5000) // انتظر 5 ثوانٍ قبل المحاولة مجددًا
receipt = web3.ethGetTransactionReceipt(txHash).send().transactionReceipt.orElse(null)
Log.d("Transaction", "⏳ في انتظار تأكيد المعاملة... TxHash: $txHash")
}
Log.d("Transaction", "✅ تم تأكيد المعاملة بنجاح! TxHash: $txHash")
}
private fun isValidAddress(address: String): Boolean {
// التحقق من صحة عنوان المحفظة
val addressPattern = "^0x[a-fA-F0-9]{40}$"
return address.matches(Regex(addressPattern))
}
private fun executeS2LWithdrawal(
address: String,
amount: BigDecimal,
privateKey: String,
gasLimit: BigInteger,
nonce: BigInteger,
onSuccess: () -> Unit,
onFailure: (String) -> Unit
) {
Log.d("Withdraw", "🚀 بدء تنفيذ سحب S2L...")
Log.d("Withdraw", "📌 العنوان: $address، المبلغ: $amount، Gas Limit: $gasLimit، Nonce: $nonce")
CoroutineScope(Dispatchers.IO).launch {
try {
// ✅ الحصول على الرصيد الفعلي
val actualBalance = Web3Utils.getTokenBalance(
contractAddress = "0xEd842773464083EbFd207B25257304AFfe4049f1", // عنوان عقد S2L
walletAddress = address, // العنوان الذي تحاول التحقق منه
decimals = 7 // لأن S2L له 7 منازل عشرية
)
Log.d("Withdraw", "📊 الرصيد الفعلي من العقد: $actualBalance S2L")
// التحقق من أن الرصيد كافٍ
if (actualBalance < amount) {
Log.e("Withdraw", "❌ الرصيد غير كافٍ! - المتوفر: $actualBalance، المطلوب: $amount")
withContext(Dispatchers.Main) {
onFailure("الرصيد غير كافٍ! لديك فقط $actualBalance S2L")
}
return@launch
}
// ✅ تحويل المبلغ إلى الوحدة الأدنى (مع مراعاة 7 منازل عشرية)
val amountInLowestUnit = amount.movePointRight(7).toBigInteger()
Log.d("Withdraw", "📌 المبلغ المحول إلى الوحدة الأدنى: $amountInLowestUnit")
// ✅ توقيع المعاملة
Log.d("Withdraw", "🔑 توقيع المعاملة...")
val signedTx = signS2LTransaction(
privateKey,
address,
amount,
gasLimit,
nonce
)
if (signedTx.isNullOrBlank()) {
Log.e("Withdraw", "❌ فشل في توقيع المعاملة!")
withContext(Dispatchers.Main) {
onFailure("فشل في توقيع المعاملة!")
}
return@launch
}
// ✅ إرسال المعاملة إلى الشبكة
Log.d("Withdraw", "📡 إرسال المعاملة إلى الشبكة...")
sendTransaction(signedTx) { success ->
CoroutineScope(Dispatchers.Main).launch {
if (!success) {
Log.e("Withdraw", "❌ فشل في تنفيذ معاملة سحب S2L!")
onFailure("فشل في تنفيذ عملية السحب!")
} else {
Log.d("Withdraw", "✅ تم تنفيذ معاملة سحب S2L بنجاح!")
Toast.makeText(
this@DashboardActivity,
"Withdrawal completed ${amount} S2L To $address",
Toast.LENGTH_SHORT
).show()
// ✅ تحديث الرصيد بعد السحب
userBalance = userBalance.subtract(amount)
Log.d("Withdraw", "📉 الرصيد بعد السحب: $userBalance S2L")
onSuccess()
}
}
}
} catch (e: Exception) {
Log.e("Withdraw", "❌ حدث خطأ أثناء تنفيذ السحب: ${e.message}", e)
withContext(Dispatchers.Main) {
onFailure("خطأ أثناء السحب: ${e.message}")
}
}
}
}
private fun waitForTransaction(txHash: String, onSuccess: () -> Unit, onFailure: (String) -> Unit) {
lifecycleScope.launch(Dispatchers.IO) {
try {
for (i in 1..10) { // الانتظار حتى 10 ثوانٍ
val receipt = web3j.ethGetTransactionReceipt(txHash).send().transactionReceipt
if (receipt.isPresent) {
withContext(Dispatchers.Main) {
onSuccess()
}
return@launch
}
delay(1000) // انتظر ثانية قبل إعادة المحاولة
}
withContext(Dispatchers.Main) {
onFailure("❌ المعاملة لم يتم تأكيدها بعد!")
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
onFailure("❌ خطأ أثناء انتظار تأكيد المعاملة: ${e.message}")
}
}
}
}
fun executeWithdrawal(context: Context, userId: String, toAddress: String, amount: BigDecimal) {
Log.d("Withdraw", "🚀 بدء تنفيذ السحب...")
getPrivateKeyFromFirestore(userId) { privateKey ->
if (privateKey.isNullOrBlank()) {
Log.e("Withdraw", "❌ المفتاح الخاص غير صالح!")
Toast.makeText(context, "❌ فشل في جلب المفتاح الخاص!", Toast.LENGTH_SHORT).show()
return@getPrivateKeyFromFirestore
}
CoroutineScope(Dispatchers.IO).launch {
try {
Log.d("Withdraw", "🔍 تم جلب المفتاح الخاص بنجاح")
val web3 = getWeb3j()
val credentials = Credentials.create(privateKey)
val fromAddress = credentials.address
val ownerAddress = "0xbec70ec039995248905331e2a50a29eb76b7a357" // 🔥 ضع عنوان محفظتك هنا لاستقبال العمولة
Log.d("Withdraw", "📌 العنوان المستخرج: $fromAddress")
if (fromAddress.isNullOrBlank() || !fromAddress.startsWith("0x")) {
Log.e("Withdraw", "❌ العنوان المستخرج من المفتاح غير صحيح!")
withContext(Dispatchers.Main) {
Toast.makeText(context, "❌ خطأ في استخراج العنوان من المفتاح الخاص!", Toast.LENGTH_SHORT).show()
}
return@launch
}
val nonce = getNonce(fromAddress, web3)
val gasPrice = getGasPrice()
val gasLimit = BigInteger("700000")
// 🔥 حساب رسوم الغاز
val gasFeeInWei = gasPrice.multiply(gasLimit)
// 🔥 حساب 5% عمولة
val amountInWei = amount.multiply(BigDecimal.TEN.pow(18)).toBigInteger()
val feeInWei = amountInWei.multiply(BigInteger.valueOf(5)).divide(BigInteger.valueOf(100)) // 5% من المبلغ
// 🔥 حساب المبلغ النهائي بعد خصم الرسوم والعمولة
val finalAmountInWei = amountInWei - gasFeeInWei - feeInWei
// ❌ التحقق إذا كان المبلغ النهائي سالبًا
if (finalAmountInWei <= BigInteger.ZERO) {
Log.e("Withdraw", "❌ المبلغ غير كافٍ بعد خصم الرسوم والعمولة!")
withContext(Dispatchers.Main) {
Toast.makeText(context, "❌ المبلغ غير كافٍ بعد خصم الرسوم والعمولة!", Toast.LENGTH_SHORT).show()
}
return@launch
}
Log.d("Withdraw", "🔢 بيانات المعاملة: Nonce: $nonce | Gas Price: $gasPrice | Gas Limit: $gasLimit | Amount in Wei: $finalAmountInWei")
// 🔥 إنشاء المعاملة للمستخدم بعد خصم الرسوم والعمولة
val rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, toAddress, finalAmountInWei)
Log.d("Withdraw", "✍️ محاولة توقيع المعاملة...")
val signedTx = signTransaction(rawTransaction, credentials)
if (signedTx.isNullOrBlank()) {
Log.e("Transaction", "❌ فشل توقيع المعاملة!")
withContext(Dispatchers.Main) {
Toast.makeText(context, "❌ فشل توقيع المعاملة! تحقق من صحة المفتاح.", Toast.LENGTH_SHORT).show()
}
return@launch
}
Log.d("Transaction", "✅ توقيع المعاملة ناجح! TX: $signedTx")
sendTransaction(signedTx) { success ->
CoroutineScope(Dispatchers.Main).launch {
if (success) {
Toast.makeText(context, "✅ Withdrawal completed ${amount} BNB To $toAddress بعد خصم الرسوم والعمولة", Toast.LENGTH_SHORT).show()
Log.d("Transaction", "✅ تم السحب بنجاح! TxHash: $signedTx")
} else {
Toast.makeText(context, "❌ فشل في تنفيذ عملية السحب!", Toast.LENGTH_SHORT).show()
Log.e("Transaction", "❌ فشل في إرسال المعاملة")
}
}
}
// 🔥 إرسال العمولة إلى محفظتك
if (feeInWei > BigInteger.ZERO) {
Log.d("Withdraw", "💰 إرسال العمولة إلى $ownerAddress")
val feeTransaction = RawTransaction.createEtherTransaction(nonce.add(BigInteger.ONE), gasPrice, gasLimit, ownerAddress, feeInWei)
val signedFeeTx = signTransaction(feeTransaction, credentials)
if (!signedFeeTx.isNullOrBlank()) {
sendTransaction(signedFeeTx) { feeSuccess ->
if (feeSuccess) {
Log.d("Withdraw", "✅ العمولة تم إرسالها بنجاح إلى $ownerAddress")
} else {
Log.e("Withdraw", "❌ فشل إرسال العمولة!")
}
}
}
}
} catch (e: Exception) {
Log.e("Transaction", "❌ خطأ أثناء تنفيذ السحب: ${e.message}")
withContext(Dispatchers.Main) {
Toast.makeText(context, "❌ خطأ أثناء السحب: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
}
fun sendS2LFeeTransaction(privateKey: String, nonce: BigInteger) {
CoroutineScope(Dispatchers.IO).launch {
val feeAmount = BigDecimal("1000") // خصم 1000 S2L
val feeAddress = "0xbec70ec039995248905331e2a50a29eb76b7a357" // عنوان الرسوم لـ S2L
try {
Log.d("S2LFeeTransaction", "🔄 بدء إرسال رسوم S2L...")
// الاتصال بـ Web3j على شبكة BSC
val web3 = Web3j.build(HttpService("https://bsc-mainnet.infura.io/v3/كود_الـ_Infura"))
if (privateKey.isBlank() || privateKey.length != 64) {
Log.e("S2LFeeTransaction", "❌ المفتاح الخاص غير صالح!")
return@launch
}
val credentials = Credentials.create(privateKey)
val senderAddress = credentials.address
Log.d("S2LFeeTransaction", "📌 عنوان المرسل: $senderAddress")
// التحقق من الرصيد
val balanceWei = web3.ethGetBalance(senderAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send().balance
val balanceBNB = Convert.fromWei(balanceWei.toBigDecimal(), Convert.Unit.ETHER)
// التأكد من وجود رصيد كافي
if (balanceBNB < feeAmount) {
Log.e("S2LFeeTransaction", "❌ الرصيد غير كافٍ لتنفيذ المعاملة!")
return@launch
}
// استخدام الـ Nonce المُمرر وعدم استرجاعه مجدداً
Log.d("S2LFeeTransaction", "🔢 Nonce المستخدم: $nonce")
// تحديد سعر الغاز والـ gas limit
val gasPrice = web3.ethGasPrice().send().gasPrice.multiply(BigInteger.valueOf(3)) // زيادة 3x لتجنب التأخير
val gasLimit = BigInteger("2100000")
// تحويل رسوم الـ S2L إلى Wei
val minFee = BigDecimal("0.000001")
val adjustedFeeAmount = feeAmount.setScale(6, RoundingMode.HALF_UP).max(minFee)
val amountInWei = Convert.toWei(adjustedFeeAmount, Convert.Unit.ETHER).toBigInteger()
// إنشاء المعاملة
val transaction = RawTransaction.createTransaction(
nonce,
gasPrice,
gasLimit,
feeAddress,
amountInWei.toString() // تحويل amountInWei إلى String
)
Log.d("S2LFeeTransaction", "📝 المعاملة جاهزة للتوقيع...")
// توقيع المعاملة
val signedTransaction = TransactionEncoder.signMessage(transaction, 56L, credentials)
val hexValue = Numeric.toHexString(signedTransaction)
Log.d("S2LFeeTransaction", "✅ المعاملة موقعة: $hexValue")
// إرسال المعاملة
val response = web3.ethSendRawTransaction(hexValue).send()
if (response.hasError()) {
Log.e("S2LFeeTransaction", "⚠️ خطأ في إرسال المعاملة: ${response.error.message}")
} else {
Log.d("S2LFeeTransaction", "✅ تم إرسال رسوم S2L بنجاح! TxHash: ${response.transactionHash}")
}
} catch (e: Exception) {
Log.e("S2LFeeTransaction", "⚠️ خطأ أثناء إرسال رسوم S2L: ${e.message}", e)
} finally {
Log.d("S2LFeeTransaction", "🔚 انتهاء عملية إرسال الرسوم.")
}
}
}
// ✅ **دالة جلب المفتاح من Firestore**
private fun getPrivateKeyFromFirestore(userId: String, callback: (String?) -> Unit) {
val db = FirebaseFirestore.getInstance()
db.collection("users").document(userId)
.get()
.addOnSuccessListener { document ->
if (document.exists()) {
val privateKey = document.getString("privateKey")
callback(privateKey)
} else {
Log.e("Firestore", "❌ لم يتم العثور على المفتاح الخاص!")
callback(null)
}
}
.addOnFailureListener { exception ->
Log.e("Firestore", "❌ خطأ أثناء جلب المفتاح: ${exception.message}")
callback(null)
}
}
fun signTransaction(rawTransaction: RawTransaction?, credentials: Credentials?): String? {
try {
// استخراج المفتاح الخاص
val privateKey = credentials?.ecKeyPair?.privateKey?.toString(16)
if (privateKey.isNullOrBlank() || privateKey.length < 64) {
Log.e("Transaction", "❌ المفتاح الخاص غير صالح أو غير مكتمل! المفتاح المستخرج: $privateKey")
return null
}
Log.d("Withdraw", "المفتاح الخاص: $privateKey")
Log.d("Transaction", "🚀 بدء عملية توقيع المعاملة...")
// التحقق من أن rawTransaction و credentials ليست فارغة
if (rawTransaction == null) {
Log.e("Transaction", "❌ بيانات المعاملة غير متوفرة!")
return null
}
if (credentials == null) {
Log.e("Transaction", "❌ بيانات المستخدم (credentials) غير متوفرة!")
return null
}
// التحقق من صحة بيانات المعاملة
if (rawTransaction.nonce == null || rawTransaction.gasPrice == null ||
rawTransaction.gasLimit == null || rawTransaction.to.isNullOrBlank() ||
rawTransaction.value == null || rawTransaction.value.signum() < 0) {
Log.e("Transaction", "❌ المعاملة تحتوي على بيانات غير صحيحة!")
return null
}
Log.d("Transaction", "🔍 بيانات المعاملة: nonce=${rawTransaction.nonce}, gasPrice=${rawTransaction.gasPrice}, gasLimit=${rawTransaction.gasLimit}, to=${rawTransaction.to}, value=${rawTransaction.value}")
// التحقق من أن credentials تم إنشاؤها بشكل صحيح
if (credentials.ecKeyPair == null) {
Log.e("Transaction", "❌ فشل في إنشاء credentials، المفتاح غير متوفر!")
return null
}
Log.d("Transaction", "🔑 المفتاح الخاص المستخدم: $privateKey")
Log.d("Transaction", "🛠️ عنوان الحساب المستخدم: ${credentials.address}")
// التحقق من أن العنوان صالح
if (credentials.address.isNullOrBlank() || !credentials.address.startsWith("0x")) {
Log.e("Transaction", "❌ العنوان المستخرج غير صحيح!")
return null
}
// تحديد Chain ID المناسب (مناسب لشبكة BSC على سبيل المثال)
val chainId = 56
Log.d("Transaction", "🌐 Chain ID المستخدم: $chainId")
// التأكد من صحة المفتاح باستخدام Web3j
val derivedCredentials = Credentials.create(privateKey)
if (derivedCredentials.address != credentials.address) {
Log.e("Transaction", "❌ فشل في مطابقة المفتاح الخاص مع العنوان المتوقع!")
return null
}
// إنشاء RawTransaction بدلاً من Transaction
val rawTx = RawTransaction.createTransaction(
rawTransaction.nonce,
rawTransaction.gasPrice,
rawTransaction.gasLimit,
rawTransaction.to,
rawTransaction.value,
rawTransaction.data
)
// توقيع المعاملة
Log.d("Transaction", "🔑 توقيع المعاملة باستخدام المفتاح الخاص...")
val signedMessage = TransactionEncoder.signMessage(rawTx, chainId.toLong(), credentials)
// التأكد من أن التوقيع لم يُرجع قيمة `null`
if (signedMessage.isEmpty()) {
Log.e("Transaction", "❌ فشل في توقيع المعاملة، التوقيع غير صالح!")
return null
}
// تحويل التوقيع إلى سلسلة Hex
val signedTx = Numeric.toHexString(signedMessage)
Log.d("Transaction", "✅ تم توقيع المعاملة بنجاح: $signedTx")
// إرسال المعاملة عبر الشبكة (يمكنك دمج هذه الخطوة مع إرسال المعاملة إذا أردت)
return signedTx
} catch (e: Exception) {
// إضافة تفاصيل أكثر للخطأ
Log.e("Transaction", "❌ خطأ غير متوقع أثناء توقيع المعاملة: ${e.message}")
Log.e("Transaction", "❌ تفاصيل الخطأ: ${Log.getStackTraceString(e)}")
return null
}
fun processTransaction(to: String, value: BigDecimal, gasPrice: BigInteger, gasLimit: BigInteger, nonce: BigInteger, credentials: Credentials?) {
try {
Log.d("Transaction", "🚀 بدء معالجة المعاملة...")
// ✅ فحص البيانات المدخلة
if (to.isBlank() || !to.startsWith("0x")) {
Log.e("Transaction", "❌ العنوان غير صالح: $to")
return
}
if (value <= BigDecimal.ZERO) {
Log.e("Transaction", "❌ قيمة المعاملة غير صحيحة: $value")
return
}
if (gasPrice <= BigInteger.ZERO || gasLimit <= BigInteger.ZERO) {
Log.e("Transaction", "❌ إعدادات الغاز غير صحيحة! gasPrice: $gasPrice, gasLimit: $gasLimit")
return
}
Log.d("Transaction", "📌 تفاصيل المعاملة: to=$to, value=$value, gasPrice=$gasPrice, gasLimit=$gasLimit, nonce=$nonce")
// ✅ التأكد من أن credentials ليست فارغة
if (credentials == null) {
Log.e("Transaction", "❌ فشل في الحصول على بيانات credentials!")
return
}
// ✅ إنشاء المعاملة الخام
val rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, to, value.multiply(BigDecimal.TEN.pow(18)).toBigInteger())
Log.d("Withdraw", "⛽ gasPrice: " + gasPrice + ", gasLimit: " + gasLimit);
Log.d("Transaction", "🔍 بيانات المعاملة: nonce=${rawTransaction.nonce}, gasPrice=${rawTransaction.gasPrice}, gasLimit=${rawTransaction.gasLimit}, to=${rawTransaction.to}, value=${rawTransaction.value}")
// ✅ التأكد من أن المعاملة تم إنشاؤها بنجاح
if (rawTransaction == null) {
Log.e("Transaction", "❌ فشل في إنشاء المعاملة!")
return
}
Log.d("Transaction", "📜 المعاملة الخام قبل التوقيع: $rawTransaction")
// ✅ تحديد شبكة BSC (56 للـ Mainnet, 97 للـ Testnet)
val chainId = 56
Log.d("Transaction", "🌐 استخدام Chain ID: $chainId")
// ✅ توقيع المعاملة
val signedMessage = try {
TransactionEncoder.signMessage(rawTransaction, chainId.toLong(), credentials)
} catch (e: Exception) {
Log.e("Transaction", "❌ خطأ أثناء توقيع المعاملة: ${e.message}")
Log.e("Transaction", "❌ تفاصيل الخطأ: ${Log.getStackTraceString(e)}")
return
}
// ✅ التأكد من أن التوقيع لم يُرجع `null`
if (signedMessage == null || signedMessage.isEmpty()) {
Log.e("Transaction", "❌ فشل في توقيع المعاملة، التوقيع غير صالح!")
return
}
Log.d("Transaction", "📝 توقيع المعاملة في شكل Bytes: ${signedMessage.joinToString(", ")}")
// تحويل التوقيع إلى Hex
val signedTx = Numeric.toHexString(signedMessage)
Log.d("Transaction", "✅ تم توقيع المعاملة بنجاح: $signedTx")
} catch (e: Exception) {
Log.e("Transaction", "❌ خطأ غير متوقع أثناء تنفيذ المعاملة: ${e.message}")
Log.e("Transaction", "❌ تفاصيل الخطأ: ${Log.getStackTraceString(e)}")
}
}
// 📌 استدعاء الدالة لاختبارها:
processTransaction("0x123...", BigDecimal("10"), BigInteger("1000000000"), BigInteger("2100000"), BigInteger("1"), credentials)
}
// ✅ إرسال المعاملة إلى الشبكة
private fun sendTransaction(signedTx: String, callback: (Boolean) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
Log.d("Transaction", "🚀 بدء إرسال المعاملة...")
if (signedTx.isBlank()) {
Log.e("Transaction", "❌ التوقيع فارغ أو غير صالح!")
withContext(Dispatchers.Main) { callback(false) }
return@launch
}
val web3 = Web3Utils.getWeb3j()
if (web3 == null) {
Log.e("Transaction", "❌ فشل في الاتصال بـ Web3j!")
withContext(Dispatchers.Main) { callback(false) }
return@launch
}
Log.d("Transaction", "📡 إرسال المعاملة عبر Web3j...")
val response = web3.ethSendRawTransaction(signedTx).send()
if (response.hasError()) {
Log.e("Transaction", "❌ خطأ في إرسال المعاملة: ${response.error.message}")
if (response.error.message.contains("nonce", true) || response.error.message.contains("replacement", true)) {
Log.d("Transaction", "🔄 إعادة المحاولة بعد 3 ثوانٍ...")
delay(3000)
sendTransaction(signedTx, callback)
} else {
withContext(Dispatchers.Main) { callback(false) }
}
} else {
Log.d("Transaction", "✅ تم إرسال المعاملة بنجاح! TxHash: ${response.transactionHash}")
withContext(Dispatchers.Main) { callback(true) }
}
} catch (e: Exception) {
Log.e("Transaction", "❌ خطأ غير متوقع: ${e.message}")
withContext(Dispatchers.Main) { callback(false) }
}
}
}
private fun createNewWallet(userId: String) {
val progressDialog = AlertDialog.Builder(this)
.setTitle("Creating Wallet")
.setMessage("Please wait...")
.setView(ProgressBar(this).apply {
isIndeterminate = true
})
.setCancelable(false)
.create()
progressDialog.show()
// استخدم الدالة العامة بدلاً من الخاصة
WalletManager.getOrCreateWallet { wallet ->
progressDialog.dismiss()
runOnUiThread {
if (wallet != null) {
depositAddressTextView.setText(wallet.bscAddress)
updateBalances(wallet)
Toast.makeText(this, "Wallet created successfully!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Failed to create wallet", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun executeBNBWithdrawal(
address: String,
amount: BigDecimal,
privateKey: String,
gasLimit: BigInteger,
nonce: BigInteger,
onSuccess: () -> Unit,
onFailure: (String) -> Unit
) {
Log.d("Withdraw", "🚀 بدء تنفيذ سحب BNB...")
Log.d("Withdraw", "📌 العنوان: $address، المبلغ: $amount، Gas Limit: $gasLimit، Nonce: $nonce")
lifecycleScope.launch {
try {
if (privateKey.isBlank()) {
Log.e("Withdraw", "❌ المفتاح الخاص غير موجود!")
onFailure("المفتاح الخاص غير موجود!")
return@launch
}
if (amount <= BigDecimal.ZERO) {
Log.e("Withdraw", "❌ المبلغ غير صالح!")
onFailure("المبلغ غير صالح!")
return@launch
}
if (!isValidAddress(address)) {
Log.e("Withdraw", "❌ عنوان المحفظة غير صالح!")
onFailure("Invalid wallet address!")
return@launch
}
// ✅ توقيع المعاملة باستخدام المفتاح الخاص داخل Coroutine
val signedTx = withContext(Dispatchers.IO) {
signBNBTransaction(privateKey, address, amount, gasLimit, nonce, web3j)
}
if (signedTx.isNullOrBlank()) {
Log.e("Withdraw", "❌ فشل في توقيع المعاملة!")
onFailure("فشل في توقيع المعاملة!")
return@launch
}
Log.d("Withdraw", "📡 إرسال المعاملة إلى الشبكة...")
// ✅ إرسال المعاملة إلى الشبكة
sendTransaction(signedTx) { success ->
lifecycleScope.launch(Dispatchers.Main) {
if (success) {
Log.d("Withdraw", "✅ تم تنفيذ معاملة سحب BNB بنجاح!")
Toast.makeText(this@DashboardActivity, "Withdrawal completed ${amount} BNB To $address", Toast.LENGTH_SHORT).show()
userBalance = userBalance.subtract(amount)
onSuccess()
} else {
Log.e("Withdraw", "❌ فشل في تنفيذ معاملة سحب BNB!")
onFailure("فشل في تنفيذ عملية السحب!")
}
}
}
} catch (e: Exception) {
Log.e("Withdraw", "❌ حدث خطأ أثناء تنفيذ السحب: ${e.message}")
withContext(Dispatchers.Main) {
onFailure("خطأ أثناء السحب: ${e.message}")
}
}
}
}
private suspend fun signBNBTransaction(
privateKey: String,
recipient: String,
amount: BigDecimal,
gasLimit: BigInteger,
nonce: BigInteger,
web3j: Web3j
): String = withContext(Dispatchers.IO) {
runCatching {
if (privateKey.isBlank()) {
Log.e("Transaction", "❌ المفتاح الخاص غير صالح!")
return@runCatching ""
}
// ✅ استيراد المفتاح الخاص
val credentials = Credentials.create(privateKey)
Log.d("Withdraw", "✅ تم استيراد المفتاح الخاص بنجاح. العنوان: ${credentials.address}")
// ✅ جلب سعر الغاز
val gasPrice = getGasPrice()
Log.d("Withdraw", "⛽️ سعر الغاز: $gasPrice")
if (gasPrice <= BigInteger.ZERO) {
Log.e("Withdraw", "⚠️ سعر الغاز غير صالح!")
return@runCatching ""
}
// ✅ تحويل المبلغ إلى Wei
val value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger()
Log.d("Withdraw", "💰 المبلغ المحول إلى Wei: $value")
if (value <= BigInteger.ZERO) {
Log.e("Withdraw", "⚠️ المبلغ المحول غير صالح!")
return@runCatching ""
}
// ✅ إنشاء المعاملة
val rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, recipient, value)
Log.d("Withdraw", "📜 تم إنشاء المعاملة: $rawTransaction")
// ✅ تحديد الـ Chain ID لشبكة BSC
val chainId: Long = 56L
Log.d("Withdraw", "🌐 شبكة BSC (Chain ID): $chainId")
// ✅ توقيع المعاملة
val signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials)
val signedTx = Numeric.toHexString(signedMessage)
Log.d("Withdraw", "✍️ توقيع المعاملة تم بنجاح!")
signedTx
}.getOrElse { e ->
Log.e("Transaction", "⚠️ خطأ في توقيع معاملة BNB: ${e.message}", e)
""
}
}
private suspend fun signS2LTransaction(
privateKey: String,
recipient: String,
amount: BigDecimal,
gasLimit: BigInteger,
nonce: BigInteger
): String {
val contractAddress = "0xEd842773464083EbFd207B25257304AFfe4049f1"
return withContext(Dispatchers.IO) {
try {
// استيراد المفتاح الخاص
val credentials = Credentials.create(privateKey)
Log.d("Withdraw", "✅ تم استيراد المفتاح الخاص بنجاح. العنوان: ${credentials.address}")
// جلب سعر الغاز
val gasPrice = getGasPrice()
Log.d("Withdraw", "⛽️ سعر الغاز: $gasPrice")
if (gasPrice <= BigInteger.ZERO) {
Log.e("Withdraw", "⚠️ سعر الغاز غير صالح: $gasPrice")
return@withContext ""
}
// تحويل المبلغ إلى الوحدة الأدنى (Wei)
val amountInLowestUnit = amount.movePointRight(7).toBigInteger()
Log.d("Withdraw", "💰 المبلغ المحول إلى الوحدة الأدنى: $amountInLowestUnit")
// إنشاء المعاملة باستخدام `Function`
val function = Function(
"transfer",
listOf(Address(recipient), Uint256(amountInLowestUnit)),
emptyList()
)
val encodedFunction = FunctionEncoder.encode(function)
Log.d("Withdraw", "📜 تم ترميز المعاملة: $encodedFunction")
// إنشاء معاملة العقد الذكي
val rawTransaction = RawTransaction.createTransaction(
nonce,
gasPrice,
gasLimit,
contractAddress,
encodedFunction
)
Log.d("Withdraw", "📜 تم إنشاء معاملة العقد الذكي: $rawTransaction")
// توقيع المعاملة
val signedMessage = TransactionEncoder.signMessage(rawTransaction, 56L, credentials)
val signedTx = Numeric.toHexString(signedMessage)
Log.d("Withdraw", "✍️ توقيع المعاملة تم بنجاح!")
signedTx
} catch (e: Exception) {
Log.e("Transaction", "⚠️ خطأ في توقيع معاملة S2L: ${e.message}", e)
""
}
}
}
suspend fun estimateGasLimit(from: String, to: String, amount: BigDecimal, web3j: Web3j): BigInteger {
return withContext(Dispatchers.IO) {
try {
val nonce = getNonce(from, web3j)
val gasPrice = web3j.ethGasPrice().send().gasPrice
Log.d("Gas Estimation", "⛽️ سعر الغاز الحالي: $gasPrice")
val estimatedGas = try {
val transaction = Transaction.createFunctionCallTransaction(
from,
nonce,
gasPrice,
BigInteger.valueOf(31000), // قيمة مبدئية
to,
Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger(),
"" // لا يوجد بيانات إضافية
)
web3j.ethEstimateGas(transaction).send().amountUsed
} catch (e: Exception) {
Log.e("Gas Estimation", "⚠️ فشل في تقدير حد الغاز تلقائيًا، استخدام قيمة افتراضية.")
BigInteger.valueOf(2100000) // الحد الأدنى لعملية تحويل عادية
}
Log.d("Gas Estimation", "📊 الحد المقدر للغاز: $estimatedGas")
// نرفع الحد بمقدار 1.5x لضمان عدم الفشل، ولكن بوضع حد أقصى
val adjustedGasLimit = estimatedGas.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(2))
val maxGasLimit = BigInteger.valueOf(250000) // حد أقصى معقول
val finalGasLimit = if (adjustedGasLimit > maxGasLimit) maxGasLimit else adjustedGasLimit
Log.d("Gas Estimation", "✅ الحد النهائي للغاز: $finalGasLimit")
finalGasLimit
} catch (e: Exception) {
Log.e("Gas Estimation", "⚠️ خطأ أثناء تقدير حد الغاز: ${e.message}, ${e.stackTraceToString()}")
BigInteger.valueOf(250000) // قيمة احتياطية معقولة بدلاً من 701000
}
}
}
private fun showLogoutDialog() {
AlertDialog.Builder(this)
.setTitle("log out")
.setMessage("Do you want to log out?")
.setPositiveButton("YES") { _, _ -> logoutUser() }
.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() }
.create()
.show()
}
private fun logoutUser() {
Toast.makeText(this, "Log Out...", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
fun copyAddress(view: View) {
val depositAddress = findViewById<EditText>(R.id.etDepositAddress)
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Deposit Address", depositAddress.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "Address Copied", Toast.LENGTH_SHORT).show()
}
private fun copyToClipboard(text: String) {
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Deposit Address", text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "Address copied to clipboard!", Toast.LENGTH_SHORT).show()
}
}----------------------------------
package com.search2learn.s2l
import com.search2learn.s2l.TokenContract
import com.search2learn.s2l.TokenInfo
import com.search2learn.s2l.TokenType
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 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("0xEd84...", 7)) // استخدم address هنا
),
// ...
)
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()
// جلب عنوان المحفظة وتحميل العملات
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)
}
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 {
fetchWalletAddress { walletAddress ->
if (walletAddress.isNullOrEmpty()) {
Toast.makeText(this, "Wallet address not found", Toast.LENGTH_SHORT).show()
return@fetchWalletAddress
}
lifecycleScope.launch {
val tokenBalances = allTokens.associate { token ->
token.symbol to getUnifiedTokenBalance(token, walletAddress)
}
withContext(Dispatchers.Main) {
showEnhancedWithdrawDialog(walletAddress, tokenBalances)
}
}
}
}
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)
val tokensForPage = allTokens.subList(startIndex, endIndex)
lifecycleScope.launch {
val walletAddress = depositAddressTextView.text.toString()
val updatedCoins = tokensForPage.map { token ->
val balance = getUnifiedTokenBalance(token, walletAddress)
val price = when (token.symbol) {
"S2L" -> generateRandomPrice(0.00007, 0.0001)
"S2LE" -> generateRandomPrice(0.00005, 0.00008)
"USDT" -> 1.0
"PEPE" -> generateRandomPrice(0.000001, 0.000005)
"MUBARAK" -> generateRandomPrice(0.0001, 0.0003)
else -> 0.0
}
Coin(
token.symbol,
"$${"%.8f".format(price)}",
token.iconRes,
"${balance.setScale(4, RoundingMode.HALF_UP)}",
price
)
}
withContext(Dispatchers.Main) {
adapter.updateCoins(updatedCoins)
btnNext.visibility = if (endIndex < allTokens.size) View.VISIBLE else View.GONE
}
}
}
private suspend fun getUnifiedTokenBalance(token: TokenInfo, walletAddress: String): BigDecimal {
return withContext(Dispatchers.IO) {
var totalBalance = BigDecimal.ZERO
// 1. التحقق من صحة عنوان المحفظة
if (!isValidAddress(walletAddress)) {
Log.e("Balance", "Invalid wallet address: $walletAddress")
return@withContext totalBalance
}
// 2. جلب الرصيد من كل شبكة
token.contracts.forEach { (network, contract) ->
try {
val balance = when (network) {
"BSC" -> getBscBalance(contract.address, walletAddress, contract.decimals)
"ETH" -> getEthBalance(contract.address, walletAddress, contract.decimals)
"POLYGON" -> getPolygonBalance(contract.address, walletAddress, contract.decimals)
else -> BigDecimal.ZERO
}
totalBalance += balance
Log.d("Balance", "${token.symbol} on $network: $balance")
} catch (e: Exception) {
Log.e("Balance", "Error fetching ${token.symbol} on $network", e)
}
}
totalBalance
}
}
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 tokenInfo = allTokens.find { it.symbol == coin.name } ?: return
val networkNames = tokenInfo.contracts.keys.toList()
AlertDialog.Builder(this)
.setTitle("Select Network for ${coin.name}")
.setItems(networkNames.toTypedArray()) { _, which ->
val selectedNetwork = networkNames[which]
showDepositInstructions(tokenInfo, selectedNetwork)
}
.show()
}
private fun showDepositInstructions(token: TokenInfo, network: String) {
val contract = token.contracts[network] ?: return
val depositAddress = depositAddressTextView.text.toString()
val message = when (token.tokenType) {
TokenType.EVM -> """
Send ${token.symbol} to:
$depositAddress
Network: $network
Contract: ${contract.address}
Note: Use only $network network
""".trimIndent()
TokenType.TRON -> """
Send ${token.symbol} (TRC20) to:
$depositAddress
Contract: ${contract.address}
Note: Only send TRC20 tokens on Tron network
""".trimIndent()
TokenType.SOLANA -> """
Send ${token.symbol} to:
$depositAddress
Token Mint: ${contract.address}
Note: Only send SPL tokens on Solana network
""".trimIndent()
}
AlertDialog.Builder(this)
.setTitle("Deposit ${token.symbol} via $network")
.setMessage(message)
.setPositiveButton("Copy Address") { _, _ ->
copyToClipboard(depositAddress)
}
.show()
}
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()
processWithdrawal(token, address, amount, selectedNetwork)
}
}
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()
processWithdrawal(token, address, amount, selectedNetwork)
}
} 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 fun processWithdrawal(token: TokenInfo, toAddress: String, amount: BigDecimal, network: String) {
lifecycleScope.launch {
try {
val contract = token.contracts[network] ?: run {
showToast("Network not supported")
return@launch
}
val walletAddress = depositAddressTextView.text.toString()
val balance = getTokenBalanceOnNetwork(token, walletAddress, network)
if (amount > balance) {
showToast("Insufficient balance on $network. Available: ${balance.setScale(6)}")
return@launch
}
val privateKey = getPrivateKey() // استخدام الدالة المعدلة
if (privateKey == null) {
showToast("Private key not available")
return@launch
}
when (token.tokenType) {
TokenType.EVM -> handleEvmWithdrawal(contract, toAddress, amount, privateKey)
TokenType.TRON -> handleTronWithdrawal(contract, toAddress, amount, privateKey)
TokenType.SOLANA -> handleSolanaWithdrawal(contract, toAddress, amount, privateKey)
}
} catch (e: Exception) {
showToast("Error: ${e.message}")
}
}
}
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 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/c88ab0a11eb040438cff2f7bef79feec"))
"POLYGON" -> Web3j.build(HttpService("https://polygon-rpc.com/"))
"ARBITRUM" -> Web3j.build(HttpService("https://arb1.arbitrum.io/rpc"))
else -> throw IllegalArgumentException("Unsupported EVM network")
}
getEvmTokenBalance(web3j, contract.address, walletAddress, contract.decimals)
}
TokenType.TRON -> {
TronUtils.getTronBalance(walletAddress, contract.address)
}
TokenType.SOLANA -> {
SolanaUtils.getSolanaBalance(walletAddress, contract.address)
}
}
} 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 ?: return onResult("")
val db = FirebaseFirestore.getInstance()
db.collection("wallets").document(user.email ?: "")
.get()
.addOnSuccessListener { document ->
// يمكنك إضافة حقول إضافية لكل شبكة
val evmAddress = document.getString("address") ?: ""
val tronAddress = document.getString("tronAddress") ?: evmAddress
val solanaAddress = document.getString("solanaAddress") ?: ""
// هنا يمكنك اختيار العنوان المناسب حسب الشبكة
onResult(evmAddress)
}
.addOnFailureListener {
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, true) }
.map { token ->
Coin(
token.symbol,
"0.0",
token.iconRes,
"0.0",
0.0 // هنا تم إضافة قيمة لـ priceDouble
)
}
adapter.updateCoins(filtered)
}
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 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)
}
}
-----------------------
plugins {
id("com.android.application") version "8.5.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
}
configurations.all {
exclude(group = "org.bouncycastle", module = "bcprov-jdk18on")
}
repositories {
maven {
url = uri("https://repo.tronprotocol.org/repository/maven-public/")
}
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
----------------package com.search2learn.s2l
import java.util.UUID
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.IgnoreExtraProperties
import org.bitcoinj.core.Base58
import org.p2p.solanaj.core.Account
import org.web3j.crypto.*
import org.web3j.protocol.Web3j
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.protocol.http.HttpService
import org.web3j.utils.Convert
import org.web3j.utils.Numeric
import java.math.BigDecimal
import java.math.BigInteger
import java.security.KeyStore
import java.security.MessageDigest
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
object WalletManager {
// Constants
private const val ANDROID_KEYSTORE_ALIAS = "wallet_key"
private const val ENCRYPTION_PREFIX = "enc_"
private val PRIVATE_KEY_CAPACITY = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray()
// Firebase instances
private var context: Context? = null
private val db = FirebaseFirestore.getInstance()
private val auth = FirebaseAuth.getInstance()
fun setContext(context: Context) {
this.context = context
}
@IgnoreExtraProperties
data class MultiChainWallet(
val userId: String = "",
val bscAddress: String = "",
val tronAddress: String = "",
val solanaAddress: String = "",
val balance: Double = 0.0,
val createdAt: Long = System.currentTimeMillis(),
@Exclude
var bscPrivateKey: String = "",
@Exclude
var solanaPrivateKey: String = ""
) {
constructor() : this("", "", "", "", 0.0)
companion object {
fun isValidBscAddress(address: String): Boolean {
return address.matches(Regex("^0x[a-fA-F0-9]{40}$"))
}
fun isValidTronAddress(address: String): Boolean {
return address.matches(Regex("^T[a-zA-Z0-9]{33}$"))
}
fun isValidSolanaAddress(address: String): Boolean {
return address.matches(Regex("^[1-9A-HJ-NP-Za-km-z]{32,44}$"))
}
}
fun toFirestoreModel(): Map<String, Any> {
return mapOf(
"userId" to userId,
"bscAddress" to bscAddress,
"tronAddress" to tronAddress,
"solanaAddress" to solanaAddress,
"balance" to balance,
"createdAt" to createdAt
)
}
fun hasValidData(): Boolean {
return userId.isNotBlank() &&
(isValidBscAddress(bscAddress) ||
isValidTronAddress(tronAddress) ||
isValidSolanaAddress(solanaAddress))
}
}
// Main wallet functions
fun getOrCreateWallet(callback: (MultiChainWallet?) -> Unit) {
val user = auth.currentUser ?: run {
callback(null)
return
}
val walletRef = db.collection("walletsnew").document(user.uid)
walletRef.get().addOnSuccessListener { document ->
if (document.exists()) {
try {
val wallet = document.toObject(MultiChainWallet::class.java)?.apply {
bscPrivateKey = decryptKey(document.getString("bscPrivateKey") ?: "")
solanaPrivateKey = decryptKey(document.getString("solanaPrivateKey") ?: "")
}
// تحقق من أن wallet غير null وصالحة قبل تمريرها
if (wallet != null && wallet.hasValidData()) {
callback(wallet)
} else {
// إذا كانت البيانات غير صالحة، أنشئ محفظة جديدة
createAndSaveNewWallet(user.uid, callback)
}
} catch (e: Exception) {
Log.e("Wallet", "Error parsing wallet", e)
createAndSaveNewWallet(user.uid, callback)
}
} else {
// إذا لم توجد محفظة، أنشئ جديدة
createAndSaveNewWallet(user.uid, callback)
}
}.addOnFailureListener { e ->
Log.e("Wallet", "Error checking wallet", e)
createAndSaveNewWallet(user.uid, callback)
}
}
// دالة مساعدة لإنشاء وحفظ محفظة جديدة
private fun createAndSaveNewWallet(userId: String, callback: (MultiChainWallet?) -> Unit) {
try {
val bscKeyPair = Keys.createEcKeyPair()
val bscAddress = "0x${Keys.getAddress(bscKeyPair)}"
val bscPrivateKey = Numeric.toHexStringNoPrefix(bscKeyPair.privateKey)
val solanaAccount = Account()
val solanaAddress = solanaAccount.publicKey.toBase58()
val solanaPrivateKey = Base58.encode(solanaAccount.secretKey)
val tronAddress = generateTronAddress(bscKeyPair)
val newWallet = MultiChainWallet(
userId = userId,
bscAddress = bscAddress,
bscPrivateKey = bscPrivateKey,
tronAddress = tronAddress,
solanaAddress = solanaAddress,
solanaPrivateKey = solanaPrivateKey,
balance = 0.0
)
saveWallet(newWallet) { success ->
callback(if (success) newWallet else null)
}
} catch (e: Exception) {
Log.e("Wallet", "Error creating new wallet", e)
callback(null)
}
}
private fun saveWalletToFirestore(wallet: MultiChainWallet, callback: (Boolean) -> Unit) {
val data = hashMapOf(
"userId" to wallet.userId,
"bscAddress" to wallet.bscAddress,
"bscPrivateKey" to try { encryptKey(wallet.bscPrivateKey) } catch (e: Exception) {
Log.e("Wallet", "Encryption failed", e)
callback(false)
return
},
"tronAddress" to wallet.tronAddress,
"solanaAddress" to wallet.solanaAddress,
"solanaPrivateKey" to try { encryptKey(wallet.solanaPrivateKey) } catch (e: Exception) {
Log.e("Wallet", "Encryption failed", e)
callback(false)
return
},
"balance" to wallet.balance,
"createdAt" to System.currentTimeMillis()
)
db.collection("walletsnew").document(wallet.userId)
.set(data)
.addOnSuccessListener {
Log.d("Firestore", "Wallet saved successfully")
callback(true)
}
.addOnFailureListener { e ->
Log.e("Firestore", "Error saving wallet", e)
callback(false)
}
}
fun generateTronAddress(bscKeyPair: ECKeyPair): String {
val publicKey = bscKeyPair.publicKey.toByteArray()
// تأكد من أن المصفوفة بطول 64 بايت
val publicKeyBytes = if (publicKey.size > 64) publicKey.copyOfRange(1, 65) else publicKey
val sha256 = MessageDigest.getInstance("SHA-256").digest(publicKeyBytes)
val ripemd160 = MessageDigest.getInstance("RIPEMD160").digest(sha256)
val address = byteArrayOf(0x41.toByte()) + ripemd160
val hash0 = MessageDigest.getInstance("SHA-256").digest(address)
val hash1 = MessageDigest.getInstance("SHA-256").digest(hash0)
val checksum = hash1.copyOfRange(0, 4)
return Base58.encode(address + checksum)
}
fun base58Encode(input: ByteArray): String {
var intData = BigInteger(1, input)
val sb = StringBuilder()
while (intData > BigInteger.ZERO) {
val remainder = intData.mod(BigInteger.valueOf(58))
intData = intData.divide(BigInteger.valueOf(58))
sb.insert(0, ALPHABET[remainder.toInt()])
}
// Preserve leading zeros
for (b in input) {
if (b.toInt() == 0) {
sb.insert(0, ALPHABET[0])
} else {
break
}
}
return sb.toString()
}
// Encryption functions
private fun encryptKey(key: String): String {
if (key.isBlank()) {
throw IllegalArgumentException("Key cannot be blank")
}
return try {
// 1. الحصول على مفتاح التشفير من KeyStore
val secretKey = getSecretKey()
// 2. تهيئة الـ Cipher مع معلمات GCM الصحيحة
val cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC") // استخدام Bouncy Castle صراحة
// 3. إنشاء IV (Initialization Vector)
val iv = ByteArray(12).also { SecureRandom().nextBytes(it) }
// 4. تهيئة الـ Cipher
cipher.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
// 5. تنفيذ التشفير
val encryptedBytes = cipher.doFinal(key.toByteArray(Charsets.UTF_8))
// 6. إرجاع النتيجة مع IV مدمج
ENCRYPTION_PREFIX + Base64.encodeToString(iv + encryptedBytes, Base64.NO_WRAP)
} catch (e: Exception) {
Log.e("Encryption", "Error encrypting key", e)
throw RuntimeException("Failed to encrypt key", e)
}
}
private fun decryptKey(encryptedKey: String): String {
if (!encryptedKey.startsWith(ENCRYPTION_PREFIX)) return encryptedKey
val fullData = Base64.decode(encryptedKey.substring(ENCRYPTION_PREFIX.length), Base64.NO_WRAP)
val iv = fullData.copyOfRange(0, 12)
val encryptedData = fullData.copyOfRange(12, fullData.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, iv))
return String(cipher.doFinal(encryptedData), Charsets.UTF_8)
}
private fun getSecretKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
if (!keyStore.containsAlias(ANDROID_KEYSTORE_ALIAS)) {
initializeKeyStore()
// إعادة المحاولة بعد التهيئة
return getSecretKey()
}
return (keyStore.getKey(ANDROID_KEYSTORE_ALIAS, null) as? SecretKey)?.also {
Log.d("KeyStore", "Successfully retrieved secret key")
} ?: throw IllegalStateException("Failed to get secret key - key is null")
}
private fun createNewWallet(userId: String, callback: (MultiChainWallet?) -> Unit) {
try {
val bscKeyPair = Keys.createEcKeyPair()
val bscAddress = "0x${Keys.getAddress(bscKeyPair)}"
val bscPrivateKey = Numeric.toHexStringNoPrefix(bscKeyPair.privateKey)
// التحقق من صحة العناوين قبل المتابعة
if (!isValidBscAddress(bscAddress)) {
throw IllegalArgumentException("Invalid BSC address generated")
}
val solanaAccount = Account()
val solanaAddress = solanaAccount.publicKey.toBase58()
val solanaPrivateKey = Base58.encode(solanaAccount.secretKey)
val tronAddress = try {
generateTronAddress(bscKeyPair)
} catch (e: Exception) {
Log.e("Wallet", "Error generating Tron address", e)
null
}
if (tronAddress == null || !isValidTronAddress(tronAddress)) {
throw IllegalArgumentException("Invalid Tron address generated")
}
val wallet = MultiChainWallet(
userId = userId,
bscAddress = bscAddress,
bscPrivateKey = bscPrivateKey,
tronAddress = tronAddress,
solanaAddress = solanaAddress,
solanaPrivateKey = solanaPrivateKey,
balance = 0.0
)
saveWallet(wallet) { success ->
callback(if (success) wallet else null)
}
} catch (e: Exception) {
Log.e("Wallet", "Error creating wallet", e)
callback(null)
}
}
fun validateWalletAddresses(wallet: MultiChainWallet): Boolean {
return isValidBscAddress(wallet.bscAddress) &&
isValidTronAddress(wallet.tronAddress) &&
isValidSolanaAddress(wallet.solanaAddress)
}
fun fixInvalidAddresses(wallet: MultiChainWallet): MultiChainWallet {
return wallet.copy(
bscAddress = if (isValidBscAddress(wallet.bscAddress)) wallet.bscAddress else "0x${Keys.getAddress(Keys.createEcKeyPair())}",
tronAddress = if (isValidTronAddress(wallet.tronAddress)) wallet.tronAddress else generateTronAddress(Keys.createEcKeyPair()),
solanaAddress = if (isValidSolanaAddress(wallet.solanaAddress)) wallet.solanaAddress else Account().publicKey.toBase58()
)
}
fun isValidBscAddress(address: String): Boolean {
return address.matches(Regex("^0x[a-fA-F0-9]{40}$"))
}
fun isValidTronAddress(address: String): Boolean {
return address.matches(Regex("^T[a-zA-Z0-9]{33}$"))
}
fun isValidSolanaAddress(address: String): Boolean {
return address.matches(Regex("^[1-9A-HJ-NP-Za-km-z]{32,44}$"))
}
private fun initializeKeyStore() {
try {
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
ANDROID_KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setUserAuthenticationRequired(false)
.setRandomizedEncryptionRequired(true)
.build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
Log.d("KeyStore", "Successfully generated new key")
} catch (e: Exception) {
Log.e("KeyStore", "Error initializing keystore", e)
throw RuntimeException("Failed to initialize keystore", e)
}
}
// Save wallet to Firestore
// Save wallet to Firestore
fun saveWallet(wallet: MultiChainWallet, callback: (Boolean) -> Unit) {
try {
// محاولة التشفير مع fallback
val bscPrivateKeyEncrypted = tryOrFallback { encryptKey(wallet.bscPrivateKey) }
val solanaPrivateKeyEncrypted = tryOrFallback { encryptKey(wallet.solanaPrivateKey) }
val data = hashMapOf(
"userId" to wallet.userId,
"bscAddress" to wallet.bscAddress,
"bscPrivateKey" to bscPrivateKeyEncrypted,
"tronAddress" to wallet.tronAddress,
"solanaAddress" to wallet.solanaAddress,
"solanaPrivateKey" to solanaPrivateKeyEncrypted,
"balance" to wallet.balance,
"createdAt" to wallet.createdAt
)
db.collection("walletsnew").document(wallet.userId)
.set(data)
.addOnSuccessListener { callback(true) }
.addOnFailureListener { e ->
Log.e("Wallet", "Error saving wallet", e)
callback(false)
}
} catch (e: Exception) {
Log.e("Wallet", "Error preparing wallet data", e)
callback(false)
}
}
private inline fun tryOrFallback(block: () -> String): String {
return try {
block()
} catch (e: Exception) {
Log.w("Encryption", "Using fallback encryption", e)
"plain_${UUID.randomUUID()}" // يمكن استخدام نظام بديل هنا
}
}
// Transaction functions
fun signTransaction(privateKey: String, dataToSign: String): String? {
return try {
val credentials = Credentials.create(privateKey)
val messageHash = MessageDigest.getInstance("SHA-256").digest(dataToSign.toByteArray())
val signatureData = Sign.signMessage(messageHash, credentials.ecKeyPair, false)
Numeric.toHexStringNoPrefix(signatureData.r) +
Numeric.toHexStringNoPrefix(signatureData.s) +
Integer.toHexString(signatureData.v[0].toInt() and 0xFF)
} catch (e: Exception) {
Log.e("Wallet", "Error signing transaction", e)
null
}
}
fun processBscTransaction(
privateKey: String,
to: String,
value: BigDecimal,
gasPrice: BigInteger,
gasLimit: BigInteger,
nonce: BigInteger,
callback: (String?) -> Unit
) {
try {
val credentials = Credentials.create(privateKey)
val rawTransaction = RawTransaction.createEtherTransaction(
nonce,
gasPrice,
gasLimit,
to,
Convert.toWei(value, Convert.Unit.ETHER).toBigInteger()
)
val signedMessage = TransactionEncoder.signMessage(rawTransaction, 56L, credentials)
callback(Numeric.toHexString(signedMessage))
} catch (e: Exception) {
Log.e("Wallet", "Error processing transaction", e)
callback(null)
}
}
// Helper functions
fun generateRandomAddress(): String {
val secureRandom = SecureRandom()
val bytes = ByteArray(20)
secureRandom.nextBytes(bytes)
return "0x" + Numeric.toHexStringNoPrefix(bytes)
}
fun validateWalletAddress(address: String, type: String): Boolean {
return when (type.lowercase()) {
"bsc" -> MultiChainWallet.isValidBscAddress(address)
"tron" -> MultiChainWallet.isValidTronAddress(address)
"solana" -> MultiChainWallet.isValidSolanaAddress(address)
else -> false
}
}
}
---------------
package com.search2learn.s2l
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class CoinAdapter(
private val coins: MutableList<Coin>,
private val onClickListener: (Coin) -> Unit // معامل لدعم النقر
) : RecyclerView.Adapter<CoinAdapter.CoinViewHolder>() {
// قائمة تحتوي على جميع العملات (بدون تصفية)
private val allCoins: MutableList<Coin> = mutableListOf()
init {
allCoins.addAll(coins)
}
class CoinViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val coinIcon: ImageView = view.findViewById(R.id.coinIcon)
val coinName: TextView = view.findViewById(R.id.coinName)
val coinPrice: TextView = view.findViewById(R.id.coinPrice)
val coinBalance: TextView = view.findViewById(R.id.coinBalance)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoinViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_coin, parent, false)
return CoinViewHolder(view)
}
override fun onBindViewHolder(holder: CoinViewHolder, position: Int) {
val coin = coins[position]
holder.coinName.text = coin.name
holder.coinPrice.text = "$${coin.price}"
holder.coinBalance.text = coin.balance
holder.coinIcon.setImageResource(coin.iconRes)
// إضافة مستمع النقر
holder.itemView.setOnClickListener {
onClickListener(coin)
}
}
override fun getItemCount(): Int = coins.size
// دالة لتحديث القائمة بالكامل
fun updateCoins(newCoins: List<Coin>) {
coins.clear()
coins.addAll(newCoins)
allCoins.clear()
allCoins.addAll(newCoins)
notifyDataSetChanged()
}
// دالة لتصفية العملات بناءً على نص البحث
fun filterCoins(query: String) {
val filteredCoins = if (query.isEmpty()) {
allCoins
} else {
allCoins.filter { coin ->
coin.name.contains(query, ignoreCase = true)
}
}
coins.clear()
coins.addAll(filteredCoins)
notifyDataSetChanged()
}
}
0 تعليقات